Skip to content

Series (data structure)

A pandas Series pairs one dimension of values with index labels. Learn how label alignment changes arithmetic, selection and missing-data checks.

A Series is a one-dimensional labelled data structure in pandas: each value has an index label, and the Series has a data type (dtype). In quant work, it can hold prices indexed by instrument or returns indexed by date. Labels let calculations match the intended observations even after their row order changes.
This is different from a mathematical series, which sums terms. A pandas Series is also not automatically a time series: its index might contain tickers, scenarios or ordinary integers rather than dates.

Labels carry meaning; positions carry order

Think of a Series as one column of values together with an index. A DataFrame has two labelled axes and can hold several columns with different dtypes. A Series has one value axis and one dtype; an object dtype can nevertheless contain mixed Python objects. Its index labels are separate from that value dtype.
Use .loc[label] to select by label and .iloc[position] to select by zero-based position. Integer labels are still labels to .loc; do not infer that label 0 means the first observation. These explicit accessors also avoid the changing historical meaning of bare integer indexing with s[0].

Reordering should not change the pairing

Suppose fictional instruments A and B have prices of 100 and 50 under a common USD-per-unit convention. We want a weighted price summary with dimensionless coefficients 0.6 for A and 0.4 for B. The weights arrive in the opposite row order:
import pandas as pd prices = pd.Series({"A": 100.0, "B": 50.0}, name="price_usd") weights = pd.Series({"B": 0.4, "A": 0.6}, name="weight") assert prices.index.is_unique and weights.index.is_unique assert set(prices.index) == set(weights.index) assert prices.notna().all() and weights.notna().all() print(prices.ndim, prices.shape, str(prices.dtype)) # 1 (2,) float64 print(prices.loc["A"], prices.iloc[0]) # 100.0 100.0 print(f"Label matched: {prices.dot(weights):.1f}") # Label matched: 80.0 print(f"Position matched: {prices.to_numpy() @ weights.to_numpy():.1f}") # Position matched: 70.0
For these unique matching indexes, Series.dot aligns the labels before taking the dot product:
100(0.6)+50(0.4)=80.100(0.6)+50(0.4)=80.
Converting both Series to NumPy arrays removes their labels. The second product instead computes 100(0.4)+50(0.6)=70100(0.4)+50(0.6)=70. It is valid positional arithmetic with the wrong pairing for this task. Before crossing into an array-based model, establish and check a common observation order.
Fictional prices A 100 and B 50 USD per unit match weights B 0.4 and A 0.6 by label, giving contributions A 60 and B 20 and a weighted summary of 80. Discarding labels pairs by position and gives 70 instead.
Fictional prices A 100 and B 50 USD per unit match weights B 0.4 and A 0.6 by label, giving contributions A 60 and B 20 and a weighted summary of 80. Discarding labels pairs by position and gives 70 instead.
Labels preserve the intended pairing even when the two Series use different row orders. All prices use a common USD-per-unit convention; the weights are dimensionless.
The result is a weighted price summary in the stated convention. It is not a portfolio return: the inputs are prices. Nor does it establish portfolio market value: that requires quantities held and instrument-specific valuation conventions. Labels protect identity, but cannot supply economic meaning or validate units.

A successful calculation can still be incomplete

Elementwise arithmetic between differently indexed Series normally retains the union of labels and introduces missing values where a partner is absent. A reduction can then hide the problem because .sum() skips missing values by default:
import pandas as pd prices = pd.Series({"A": 100.0, "B": 50.0}) weights = pd.Series({"A": 0.6, "C": 0.4}) contributions = prices * weights print(contributions.isna().to_dict()) # {'A': False, 'B': True, 'C': True} print(f"Default sum: {contributions.sum():.1f}") # Default sum: 60.0 print(f"No skipping: {contributions.sum(skipna=False):.1f}") # No skipping: nan

The 60 is only A's contribution. It is not a complete summary of the requested universe. Choose a missing-data policy deliberately; do not fill absent prices or weights with zero just to obtain a number. The first example checks matching labels, uniqueness and nonmissing inputs before calculation.

Index labels can repeat. With duplicates, a scalar label selection may return several observations, and some operations such as reindexing can fail. If one row must represent one instrument, enforce uniqueness rather than treating the index as an automatically validated key.

The examples were executed with Python 3.12.14 and pandas 2.2.3. Current pandas 3.x documentation describes Copy-on-Write semantics that differ from older defaults. A Series is mutable; assigning another variable to it does not copy it. For edits, target the intended object directly with .loc, and do not rely on changing a selected Series to mutate a parent DataFrame.

Continue with financial data

The pandas lesson develops labelled tables, return calculations and data checks. Tabular and Time-Indexed Data extends alignment to calendars and resampling. Both have public previews; full lessons require membership.

Sources

Last updated
  • python
  • pandas
  • data structures
  • label alignment
← Back to the glossaryBack to top ↑