σμ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 Academy学习路线
Ctrl K
AO
+1.2k 位学习者
登录加入 ↗
学习路线
AO
+1.2k 位学习者
登录
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python 学习路线›模块 2 · 用于市场数据的 NumPy 与 pandas›主题 2.9·~40分钟入门

合并与连接表格

路线进度模块 2/11 · 主题 9/20
2.9 合并与连接表格
—实验得分— / 100总 XP—连续天数—

共享键的表

真实数据很少只在一张表里。持仓在一张表,每个代码所属的行业在另一张表,价格在第三张表。把它们连起来的是键,这里是代码。left.merge(right, on="键") 会为 left 的每一行找到 right 中键相同的行并并排放在一起。这与 SQL 的 join 是同一个思路。

import pandas as pd

positions = pd.DataFrame({"symbol": ["AAPL", "MSFT", "TSLA", "GOOG"], "qty": [100, 50, 20, 10]})
reference = pd.DataFrame({"symbol": ["AAPL", "MSFT", "GOOG", "F"], "sector": ["tech", "tech", "tech", "auto"]})

merged = positions.merge(reference, on="symbol")
print(merged)
#   symbol  qty sector
# 0   AAPL  100   tech
# 1   MSFT   50   tech
# 2   GOOG   10   tech

inner、left、right 与 outer

上面的结果里 TSLA 消失了,F 也是。默认的 how="inner" 只保留两张表都有的键。请明确说出你要什么:how="left" 保留左表所有行,右侧缺失的值填 NaN;how="right" 相反;how="outer" 保留两边的键。为一张表补充信息时几乎总是用 left,因为它不会悄悄丢行。

# how="left" keeps every position; TSLA has no sector, so it gets NaN
left = positions.merge(reference, on="symbol", how="left")
print(left)
#   symbol  qty sector
# 0   AAPL  100   tech
# 1   MSFT   50   tech
# 2   TSLA   20    NaN
# 3   GOOG   10   tech

# how="outer" keeps the keys of both tables (sorted); quantities become floats because of the NaN
print(positions.merge(reference, on="symbol", how="outer"))
#   symbol    qty sector
# 0   AAPL  100.0   tech
# 1      F    NaN   auto
# 2   GOOG   10.0   tech
# 3   MSFT   50.0   tech
# 4   TSLA   20.0    NaN

找出没有匹配上的行

悄悄出现的 NaN 可能掩盖数据问题,所以要检查。indicator=True 会增加一列 _merge,说明每一行的键是在 left_only、right_only 还是 both 中找到的。按它筛选就能回答「哪些持仓没有价格?」。另一个陷阱是重复的键:右表中同一个键出现两次,左表每个匹配的行就会被重复。传入 validate="many_to_one"(或 "one_to_one"),pandas 会报错,而不是悄悄改变行数。

# Which positions have no sector?
check = positions.merge(reference, on="symbol", how="left", indicator=True)
print(check)
#   symbol  qty sector     _merge
# 0   AAPL  100   tech       both
# 1   MSFT   50   tech       both
# 2   TSLA   20    NaN  left_only
# 3   GOOG   10   tech       both

print(check.loc[check["_merge"] == "left_only", "symbol"].tolist())   # ['TSLA']
# Duplicate keys repeat rows: AAPL has two quotes, so the AAPL position appears twice
quotes = pd.DataFrame({"symbol": ["AAPL", "AAPL", "MSFT"], "price": [184.25, 184.5, 410.1]})
print(len(positions.merge(quotes, on="symbol")))   # 3

# validate turns the surprise into an error
try:
    positions.merge(quotes, on="symbol", validate="one_to_one")
except Exception as error:
    print(type(error).__name__)   # MergeError

键名不同与列名冲突

当两张表里键的名字不同,用 left_on="symbol", right_on="ticker",两列都会保留。当两张表有同名但不是键的列(这里是 qty),pandas 会都保留并加上后缀 _x 和 _y。用 suffixes=("_a", "_b") 换成更清楚的后缀。外连接之后缺失的数量是 NaN,列变成浮点型:先 fillna(0) 再 astype("int64") 即可恢复整数。

# Different key names: both columns are kept
book_a = pd.DataFrame({"symbol": ["AAPL", "MSFT"], "qty": [100, 50]})
book_b = pd.DataFrame({"ticker": ["MSFT", "TSLA"], "qty": [-20, 30]})
print(book_a.merge(book_b, left_on="symbol", right_on="ticker", how="outer", suffixes=("_a", "_b")))
#   symbol  qty_a ticker  qty_b
# 0   AAPL  100.0    NaN    NaN
# 1   MSFT   50.0   MSFT  -20.0
# 2    NaN    NaN   TSLA   30.0

# Same key name, outer join, suffixes, then back to whole numbers
both = book_a.merge(book_b.rename(columns={"ticker": "symbol"}), on="symbol", how="outer", suffixes=("_a", "_b"))
both[["qty_a", "qty_b"]] = both[["qty_a", "qty_b"]].fillna(0).astype("int64")
print(both)
#   symbol  qty_a  qty_b
# 0   AAPL    100      0
# 1   MSFT     50    -20
# 2   TSLA      0     30
每次合并后都数一数行数悄悄丢行或重复行的合并会让之后的一切出错,而且没有任何报错。合并前后比较 len();想保留所有行时用 how="left";认为键唯一时加上 validate=。
  • left.merge(right, on="键") 匹配两张表中共享键的行;left_on / right_on 处理键名不同的情况。
  • how="inner"(默认)保留两边都有的键,"left" 保留左表所有行,"outer" 保留所有键。
  • indicator=True 显示没匹配上的行;validate= 与 suffixes= 防范重复键和列名冲突。

下一节:简单收益率,把价格序列转换为相邻两期之间的百分比变化。

实验:评分内容

  • 用 attach_sector 为每笔成交添加行业25 分
  • 用 value_positions 为有价格的持仓估值25 分
  • 用 unpriced_symbols 找出没有价格的持仓25 分
  • 用 combine_books 把两个账簿合并为一张表25 分
资源管理器 · lab-42-1
▾ lab-42-1
…
bash · 浏览器内
learner@quantcraft:~/lab-42-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

lab-42-1 中的所有内容将恢复为初始文件,终端也会被清空。你当前的解答将丢失,且无法撤销。

将被重置
  • main.py → 初始代码
  • 终端历史与测试输出
  • 未提交的更改
将被保留
  • 实验最高得分
  • 你的 XP 与连续天数
  • 讲解进度与笔记

登录后即可运行你的代码。

所有讲解均可免费阅读。创建账户或登录后,即可运行代码、运行测试并提交此实验。

  • 浏览器内 IDE 与 bash 终端,无需安装
  • 即时评分、XP 以及你在学习路线中的位置
创建账户登录
或
使用 GitHub 继续