共享键的表
真实数据很少只在一张表里。持仓在一张表,每个代码所属的行业在另一张表,价格在第三张表。把它们连起来的是键,这里是代码。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 techinner、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 分