σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademySkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
Skills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python skill›Module 2 · NumPy & pandas for market data›Topic 2.6·~45 minBeginner

pandas DataFrames

Skill positionModule 2 of 11 · Topic 6 of 20
2.6 pandas DataFrames
—Lab score— / 100Total XP—Streak—

A table of labelled columns

A DataFrame is a two-dimensional table: a set of columns, each a Series, that share one index of row labels. It is the workhorse of market-data analysis: one row per symbol or per day, one column per field (price, volume, return). Build one from a dictionary of columns, pd.DataFrame({"price": [...], "volume": [...]}, index=symbols), or from a list of row dictionaries. Each column has its own dtype (prices are float64, volumes int64), unlike a NumPy array that has a single dtype for everything.

import pandas as pd

df = pd.DataFrame(
    {"price": [184.25, 410.2, 171.0], "volume": [200, 50, 300]},
    index=["AAPL", "MSFT", "GOOG"],
)
print(df)
#        price  volume
# AAPL  184.25     200
# MSFT  410.20      50
# GOOG  171.00     300

print(df.shape, df.columns.tolist())              # (3, 2) ['price', 'volume']
print(df.dtypes.astype(str).to_dict())            # {'price': 'float64', 'volume': 'int64'}

rows = [{"symbol": "AAPL", "price": 184.25}, {"symbol": "MSFT", "price": 410.2}]
print(pd.DataFrame(rows))                         # from a list of row dictionaries
#   symbol   price
# 0   AAPL  184.25
# 1   MSFT  410.20

Columns, new columns and filtered rows

df["price"] is one column, a Series; df[["price"]] with a list is a one-column DataFrame. A new column comes from arithmetic on columns: df.assign(notional=df["price"] * df["volume"]) returns a new frame with the extra column and leaves df untouched (writing df["notional"] = ... modifies df itself). A Boolean condition on a column filters rows, df[df["volume"] >= 100], and df.sort_values("volume", ascending=False) orders them, with .head(n) keeping the first n. All of these work on whole columns at once: never loop over rows with iterrows() when a column operation exists.

import pandas as pd

df = pd.DataFrame(
    {"price": [184.25, 410.2, 171.0], "volume": [200, 50, 300]},
    index=["AAPL", "MSFT", "GOOG"],
)

bigger = df.assign(notional=df["price"] * df["volume"])   # a new frame
print(bigger)
#        price  volume  notional
# AAPL  184.25     200   36850.0
# MSFT  410.20      50   20510.0
# GOOG  171.00     300   51300.0
print(df.columns.tolist())                                # ['price', 'volume']  df is unchanged

print(df[df["volume"] >= 100])                            # rows that pass the test
#        price  volume
# AAPL  184.25     200
# GOOG  171.00     300

print(df.sort_values("volume", ascending=False).head(2))  # the two biggest volumes
#        price  volume
# GOOG  171.00     300
# AAPL  184.25     200

Summaries and a look at the data

Always look at a table before trusting it. df.head() shows the first rows, df.shape gives (rows, columns), df.dtypes lists the type of each column, df.isna().sum() counts missing values per column and df.describe() summarises the numeric columns (count, mean, standard deviation, minimum, quartiles, maximum). Column methods such as df.mean() give one value per column; df.sum(axis=1) gives one per row. A dtype of object or str where you expected numbers usually means text slipped into the column.

import pandas as pd

df = pd.DataFrame(
    {"price": [184.25, 410.2, 171.0], "volume": [200, 50, 300]},
    index=["AAPL", "MSFT", "GOOG"],
)

print(df.describe().loc[["count", "mean", "max"]])
#         price      volume
# count    3.00    3.000000
# mean   255.15  183.333333
# max    410.20  300.000000

print(df.mean().to_dict())                 # {'price': 255.15, 'volume': 183.33333333333334}
print(df.sum(axis=1).tolist())             # [384.25, 460.2, 471.0]   one value per row
print(df.isna().sum().to_dict())           # {'price': 0, 'volume': 0}

# Changing a copy never changes the original (pandas 3 copies on write)
cheap = df[df["price"] < 200].copy()
cheap.loc["AAPL", "price"] = 0.0
print(df.loc["AAPL", "price"])             # 184.25
import pandas as pd

rows = [
    {"symbol": "AAPL", "price": 184.25, "volume": 200},
    {"symbol": "MSFT", "price": 410.2, "volume": 50},
]
df = pd.DataFrame(rows).set_index("symbol")      # make a column the row labels
print(df)
#          price  volume
# symbol
# AAPL    184.25     200
# MSFT    410.20      50

print(df.rename(columns={"price": "px"}).columns.tolist())   # ['px', 'volume']
print(df.drop(columns="volume").columns.tolist())             # ['price']
print(df.reset_index().columns.tolist())                      # ['symbol', 'price', 'volume']
Return new frames, do not edit the caller's tableA function that receives a DataFrame should treat it as read-only and return a new one: use assign, boolean filtering and sort_values, which all return new frames. The classic bug is writing a new column into the argument, which silently adds it to the caller's table. The lab tests compare the argument before and after for exactly that reason.
  • pd.DataFrame({column: values}, index=labels); df["c"] is a Series, df[["c"]] a DataFrame; each column has its own dtype.
  • assign adds a column to a new frame; df[mask] filters rows; sort_values(...).head(n) picks the top n.
  • Look first: shape, dtypes, head(), describe(), isna().sum(); never edit the caller's frame.

Next up: selecting data with loc and iloc, the two precise ways to pick rows and columns by label or by position.

Lab · what's graded

  • Build a table of prices and volumes with make_frame25 pts
  • Add a traded-value column with add_notional25 pts
  • Keep only the liquid rows with filter_liquid25 pts
  • Rank the rows by any column with top_by25 pts
Explorer · lab-39-1
▾ lab-39-1
…
bash · in-browser
learner@quantcraft:~/lab-39-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-39-1 goes back to the starter files and the terminal is cleared. Your current solution will be lost — this can't be undone.

Will be reset
  • main.py → starter code
  • Terminal history and test output
  • Unsubmitted changes
Will be kept
  • Best lab score
  • Your XP and streak
  • Lecture progress and notes

Log in to run your code.

Every lecture is free to read. Create an account or log in to run code, run tests and submit this lab.

  • In-browser IDE with a bash terminal — nothing to install
  • Instant grading, XP and your place on the skill
Create an accountLog in
OR
Continue with GitHub