An array with labels
A pandas Series is a one-dimensional array in which every value has a label, kept in the Series' index. Instead of remembering that position 1 is Microsoft, you ask for prices["MSFT"]. A Series has a name, a dtype like a NumPy array, and it supports the same whole-array arithmetic, so prices * 2, prices.mean() and prices > 180 all work. Import pandas by convention as import pandas as pd; it is built on NumPy, which you met in the previous topics.
import pandas as pd
prices = pd.Series([184.25, 410.2, 171.0], index=["AAPL", "MSFT", "GOOG"], name="price")
print(prices)
# AAPL 184.25
# MSFT 410.20
# GOOG 171.00
# Name: price, dtype: float64
print(prices["AAPL"], prices.iloc[1]) # 184.25 410.2 by label, by position
print(prices.index.tolist()) # ['AAPL', 'MSFT', 'GOOG']
print(prices[prices > 180]) # keeps AAPL and MSFT, labels intact
print(prices.mean(), prices.idxmax()) # 255.15 MSFT
# A dict also builds a Series: keys become the index
print(pd.Series({"AAPL": 1, "MSFT": 2}))Alignment: operations match labels, not positions
When you combine two Series, pandas aligns them by label first. Labels present in only one of them give NaN (not a number, the marker for a missing value) in the result, and the result's index is the union of both indexes. This is exactly what you want for positions held in different accounts. Use a.add(b, fill_value=0) to treat a missing label as zero instead of producing NaN. Reading by label also differs from NumPy: s["MSFT"] is by label, while s.iloc[1] is by position, and a label that does not exist raises KeyError.
import pandas as pd
a = pd.Series([100, 200], index=["AAPL", "MSFT"])
b = pd.Series([50, 10], index=["MSFT", "GOOG"])
print(a + b)
# AAPL NaN
# GOOG NaN
# MSFT 250.0
print(a.add(b, fill_value=0))
# AAPL 100.0
# GOOG 10.0
# MSFT 250.0
# Selecting by a list of labels, and asking for one that is missing
prices = pd.Series([184.25, 410.2], index=["AAPL", "MSFT"])
print(prices.reindex(["MSFT", "TSLA"]).tolist()) # [410.2, nan]Sorting, ranking and rebasing
s.sort_values(ascending=False) orders by value and carries the labels with it, s.sort_index() orders by label, and s.abs() takes absolute values. Ordering plus slicing finds the biggest movers: r.abs().sort_values(ascending=False).index[:3] gives the three labels with the largest absolute return. To compare assets that trade at different price levels, rebase each to 100 at the start: s / s.iloc[0] * 100, which keeps the index and name. Missing values (NaN) are skipped by sum, mean and friends; s.isna() marks them.
import pandas as pd
r = pd.Series([0.01, -0.03, 0.02], index=["x", "y", "z"])
print(r.abs().sort_values(ascending=False))
# y 0.03
# z 0.02
# x 0.01
# dtype: float64
prices = pd.Series([184.25, 410.2, 171.0], index=["AAPL", "MSFT", "GOOG"], name="price")
print(prices / prices.iloc[0] * 100) # rebased to 100 at the first value
# AAPL 100.000000
# MSFT 222.632293
# GOOG 92.808684
# Name: price, dtype: float64
gappy = pd.Series([1.0, None, 3.0])
print(gappy.sum(), gappy.mean(), gappy.isna().tolist()) # 4.0 2.0 [False, True, False]import pandas as pd
# A stray space and a lower-case letter make "the same" symbol two labels
raw = pd.Series([1, 2], index=["AAPL", "aapl "])
print(raw.index.is_unique) # True: they look different
raw.index = raw.index.str.strip().str.upper() # clean the labels
print(raw.index.is_unique) # False: now both are "AAPL"
print(raw["AAPL"].tolist()) # [1, 2] a duplicate label returns a Series
merged = raw.groupby(level=0).sum() # combine duplicates (groupby comes soon)
print(merged.to_dict()) # {'AAPL': 3}"AAPL" and "aapl " are different labels, so a typo silently produces NaN rows; clean labels (strip, upper) before combining. An index can contain duplicates, and then s["AAPL"] returns a Series instead of a value, so check s.index.is_unique for data that should have one value per label. Operations return new Series, leaving the original untouched, which is the behaviour the lab tests check.pd.Series(values, index=labels, name=...); read by labels["A"]or positions.iloc[0].- Operations align on labels (union index,
NaNfor missing);a.add(b, fill_value=0)treats a gap as 0. sort_values,abs,idxmax,s / s.iloc[0] * 100; results are new Series and the original stays unchanged.
Next up: pandas DataFrames, tables made of Series that share one index.
Lab · what's graded
- Label prices by symbol with
price_series25 pts - Add two accounts by label with
total_exposure25 pts - Find the biggest movers with
top_movers25 pts - Put every price on a base of 100 with
rebase_to_10025 pts