Skip to content

DataFrame

A labelled table that keeps observations and fields together. See how pandas aligns instrument prices with holdings and why a missing quote can invalidate a portfolio total.

A dataframe is a two-dimensional table whose columns represent fields and whose rows represent observations. In pandas, a DataFrame has row labels, called its index, and column labels; different columns can hold different data types. This makes it useful for a position table containing instrument identifiers, share quantities and prices. Its labels help keep the right price attached to the right holding. The pandas reference defines the structure and its labelled arithmetic.

Two holdings, reversed quotes

Suppose a fictional portfolio holds two shares of A and three shares of B. At one common valuation time, A costs 100 USD per share and B costs 50 USD per share. The quote input happens to list B before A.

A pandas Series is a one-dimensional labelled object. Constructing a DataFrame from labelled Series performs index alignment: values match by label, rather than by their incoming position. Here the explicit index fixes the output row order as A, B. The checks reject duplicated identifiers, missing identifiers and missing values before valuation.
import pandas as pd quantity = pd.Series([2, 3], index=["A", "B"], dtype="int64") quotes = pd.Series([50, 100], index=["B", "A"], dtype="int64") assert quantity.index.is_unique and quotes.index.is_unique assert set(quantity.index) == set(quotes.index) holdings = pd.DataFrame( {"quantity": quantity, "price_usd": quotes}, index=["A", "B"] ) assert not holdings[["quantity", "price_usd"]].isna().any().any() holdings["value_usd"] = holdings["quantity"] * holdings["price_usd"] print(holdings["value_usd"].to_dict()) # {'A': 200, 'B': 150} print(int(holdings["value_usd"].sum())) # 350 print(int(holdings.loc["A", "price_usd"]), int(holdings.iloc[0, 0])) # 100 2
The position values are 200 USD and 150 USD, giving 350 USD in total. Stripping off the labels and pairing the input sequences by position would instead calculate 2 * 50 + 3 * 100 = 400: the arithmetic runs, but both asset-price matches are wrong. The data-structures guide explains construction from labelled Series and column calculations.
Fictional holdings A two shares and B three shares match reversed quotes B 50 and A 100 USD per share by label. Values are A 200 and B 150 USD, total 350 USD; pairing by input position would incorrectly give 400 USD.
Fictional holdings A two shares and B three shares match reversed quotes B 50 and A 100 USD per share by label. Values are A 200 and B 150 USD, total 350 USD; pairing by input position would incorrectly give 400 USD.
Original fictional valuation at one common time. Quantities are shares, prices are USD per share and values are USD. Matching instrument labels preserves the economic pairing even when quote order changes.

A label and a position answer different questions

In the example, holdings.loc["A", "price_usd"] asks for A's price; holdings.iloc[0, 0] asks for the first row's first field, its quantity. After sorting, “first row” may mean a different instrument. Use the selection rule that matches the question. Selecting holdings["quantity"] returns a Series; selecting holdings[["quantity"]] keeps a one-column DataFrame. These distinctions are documented in the indexing guide.

Labels still need an economic contract. Two rows labelled A are not interchangeable if one belongs to a different date, venue or currency. For a multi-date table, define the row identity using all the necessary fields. A numeric dtype does not enforce “USD per share”, and a table does not automatically reject duplicated instrument labels.

A partial sum can look complete

If B's quote is absent, assigning the remaining labelled price leaves B missing. It does not create a zero-priced holding. This separate example exposes the danger:

import pandas as pd holdings = pd.DataFrame({"quantity": [2, 3]}, index=["A", "B"]) holdings["price_usd"] = pd.Series([100], index=["A"], dtype="int64") holdings["value_usd"] = holdings["quantity"] * holdings["price_usd"] print(holdings["value_usd"].isna().to_dict()) # {'A': False, 'B': True} print(float(holdings["value_usd"].sum())) # 200.0
The 200 USD is only A's value. pandas skips missing values in this default sum, so the output is not a valid valuation of both holdings. Diagnose the missing data before reporting a total. The first example's explicit check rejects this case; sum(skipna=False) would also propagate the missing value. Missing-value representation depends on dtype, so use isna() rather than testing equality with a missing-value marker. See the missing-data guide.
For edits, assign directly to the intended DataFrame using a single .loc[row, column] = value operation. pandas 3.0 made Copy-on-Write the default; code that relies on changing a selected object to mutate its parent is unsafe to carry across versions. The migration guide explains this change.
Both examples were checked with Python 3.12.14 and pandas 2.2.3. Continue with the pandas lesson for labelled price tables, returns and data checks. Its preview is public; the full lesson requires membership.

Sources

Last updated
  • python
  • pandas
  • tabular data
  • index alignment
  • missing data
← Back to the glossaryBack to top ↑