pandas Series
NumPy arrays are fast, but their values are only identified by position: temps[3] tells you nothing about which day or city that is. pandas adds labels. Its two main objects are the Series, a single labelled column of data, and the DataFrame, a table made of several Series.
This lesson covers the Series. Almost everything you learn here, such as selecting by label, vectorised maths and summary methods, works the same way on DataFrame columns.
Creating a Series
A Series has two parts: the values (a NumPy-style array) and the index (the labels). If you do not give an index, pandas numbers the rows from 0.
import pandas as pd
rain = pd.Series([3.2, 0.0, 12.5, 41.0, 7.8])
print(rain)
monthly_rain = pd.Series(
[15, 25, 45, 120, 260, 650],
index=["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
name="Kochi rainfall (mm)",
)
print(monthly_rain)0 3.2 1 0.0 2 12.5 3 41.0 4 7.8 dtype: float64 Jan 15 Feb 25 Mar 45 Apr 120 May 260 Jun 650 Name: Kochi rainfall (mm), dtype: int64
The left column is the index and the right column holds the values. Under the values pandas shows the name (if you set one) and the dtype. You can also build a Series from a dictionary, where the keys become the index:
import pandas as pd
population_lakh = pd.Series({"Rohtak": 3.7, "Jaipur": 30.5, "Pune": 31.2, "Kochi": 6.0})
print(population_lakh)
print(population_lakh.index)
print(population_lakh.values)
cities = pd.Series(["Rohtak", "Jaipur", None])
print(cities)Rohtak 3.7 Jaipur 30.5 Pune 31.2 Kochi 6.0 dtype: float64 Index(['Rohtak', 'Jaipur', 'Pune', 'Kochi'], dtype='str') [ 3.7 30.5 31.2 6. ] 0 Rohtak 1 Jaipur 2 NaN dtype: str
The last Series holds text, and its dtype is str. In pandas 3.0 text columns get this dedicated string type; older versions showed object. The missing value None is stored as NaN, pandas' usual marker for "no value". You will learn more about both in Handling Missing Values and What's New in pandas 3.0.
Selecting values: labels and positions
Because a Series has labels and positions, pandas gives you two explicit selectors:
.loc[...]selects by label..iloc[...]selects by integer position, starting at 0.
import pandas as pd
marks = pd.Series([78, 91, 55, 84, 69], index=["Aarav", "Priya", "Kabir", "Meera", "Rohan"])
print(marks.loc["Priya"])
print(marks.iloc[0])
print(marks.loc["Kabir":"Rohan"]) # label slices INCLUDE the end
print(marks.iloc[1:3]) # position slices exclude the end
print(marks.loc[["Aarav", "Meera"]])
print(marks[marks >= 80]) # boolean filter, like NumPy91 78 Kabir 55 Meera 84 Rohan 69 dtype: int64 Priya 91 Kabir 55 dtype: int64 Aarav 78 Meera 84 dtype: int64 Priya 91 Meera 84 dtype: int64
One surprise: a label slice such as "Kabir":"Rohan" includes Rohan, while a position slice 1:3 stops before position 3. Labels have no natural "next one", so pandas includes both ends.
Maths aligns on labels
Arithmetic between two Series works element by element, like NumPy, with one big difference: pandas matches values by label, not by position. This is called alignment.
import pandas as pd
jan = pd.Series({"Delhi": 120, "Pune": 85, "Kochi": 40})
feb = pd.Series({"Pune": 95, "Kochi": 55, "Jaipur": 70})
print(jan + feb)
print(jan.add(feb, fill_value=0))
print((jan * 1.18).round(1)) # with a single number, like NumPyDelhi NaN Jaipur NaN Kochi 95.0 Pune 180.0 dtype: float64 Delhi 120.0 Jaipur 70.0 Kochi 95.0 Pune 180.0 dtype: float64 Delhi 141.6 Pune 100.3 Kochi 47.2 dtype: float64
Delhi only appears in January and Jaipur only in February, so for those labels one side is missing and the result is NaN. Alignment protects you from silently adding Pune's sales to Kochi's just because they happened to be in the same position. When missing should mean zero, use the method form add() with fill_value=0. Notice also that the result is sorted by label and its dtype became float64, because NaN is a decimal value.
Summary methods
A Series has dozens of methods. These are the ones you will use every day:
| Method | Returns |
|---|---|
sum(), mean(), median(), min(), max(), std() | One summary number (missing values are skipped) |
idxmax(), idxmin() | The label of the largest or smallest value |
count() | Number of non-missing values |
sort_values(), sort_index() | A sorted copy |
value_counts() | How often each distinct value appears |
unique(), nunique() | Distinct values, and how many there are |
describe() | Count, mean, std, min, quartiles and max in one go |
import pandas as pd
temps = pd.Series([31.5, 33.0, 29.8, 35.2, None, 34.1, 32.7],
index=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
print("Mean:", round(temps.mean(), 2), " Count:", temps.count())
print("Hottest day:", temps.idxmax())
print(temps.sort_values(ascending=False).head(3))
print(temps.describe().round(2))
modes = pd.Series(["UPI", "Card", "UPI", "Cash", "UPI", "Card"])
print(modes.value_counts())Mean: 32.72 Count: 6 Hottest day: Thu Thu 35.2 Sat 34.1 Tue 33.0 dtype: float64 count 6.00 mean 32.72 std 1.91 min 29.80 25% 31.80 50% 32.85 75% 33.83 max 35.20 dtype: float64 UPI 3 Card 2 Cash 1 Name: count, dtype: int64
Unlike NumPy's plain mean(), pandas skips missing values by default, so the Friday gap does not ruin the average. count() confirms that only 6 of 7 days have a reading. value_counts() returns a new Series whose index holds the distinct values, sorted from most to least common.
Common mistakes
- Expecting position-based maths. Two Series are added by matching labels. If labels differ, you get
NaN. - Forgetting that label slices include the end.
s.loc["a":"c"]includes"c";s.iloc[0:2]excludes position 2. - Using
s[0]on a labelled Series. Writes.iloc[0]for the first value. - Checking
dtype == objectfor text. In pandas 3.0 text columns have dtypestr.
Key points
- A Series is a 1-D array of values with an index of labels and an optional name.
- Create it from a list (default 0, 1, 2 index), a list plus
index=, or a dictionary. - Use
.locfor labels (slices include the end) and.ilocfor positions. - Arithmetic aligns on labels; unmatched labels give
NaNunless you usefill_value. - Summary methods such as
mean(),idxmax()andvalue_counts()skip missing values.
Exercise
Create a Series of five students' science marks with their names as the index (choose your own). Print the name of the top scorer, the average rounded to one decimal, and only the students who scored below the average. Then add 5 grace marks to everyone and print the new Series.
Show solution
import pandas as pd
science = pd.Series({"Ananya": 74, "Farhan": 58, "Isha": 89, "Rohan": 63, "Meera": 81})
print("Top:", science.idxmax())
avg = science.mean()
print("Average:", round(avg, 1))
print(science[science < avg])
print(science + 5)Top: Isha Average: 73.0 Farhan 58 Rohan 63 dtype: int64 Ananya 79 Farhan 63 Isha 94 Rohan 68 Meera 86 dtype: int64
science < avg creates a boolean Series that filters the rows. Adding a single number is applied to every value, just like with NumPy.
Quick Quiz
1. What does pd.Series({"a": 1, "b": 2}) + pd.Series({"b": 10, "c": 20}) give for label "a"?
Label a exists only on the left, so alignment produces a missing value.
2. Which selector picks values by integer position?
.iloc is position-based; .loc is label-based.
3. What does s.idxmax() return?
idxmax() returns the index label where the maximum occurs.
4. What dtype does pandas 3.0 give pd.Series(["Pune", "Kochi"])?
pandas 3.0 uses the dedicated string dtype, shown as str, for text by default.
Learn Data Science with a mentor
Free lessons get you started. Our Data Science & Machine Learning training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
