拆分、应用、合并
很多问题要求每组给出一个数:每个行业的敞口、每个策略的平均收益、每天的成交笔数。模式始终是同一个,即拆分-应用-合并(split-apply-combine)。df.groupby("sector") 按某列的值把行拆分成组,对每组应用 .sum() 这样的聚合,pandas 再把结果合并成以分组键为索引的一张表。没有它,你得写一个带累计字典的循环;有了它,整个问题只需一行:df.groupby("sector")["value"].sum()。
import pandas as pd
df = pd.DataFrame({
"symbol": ["AAPL", "MSFT", "GOOG", "TSLA", "F"],
"sector": ["tech", "tech", "tech", "auto", "auto"],
"value": [36850.0, 20510.0, 51300.0, 30120.0, 9000.0],
"ret": [0.012, -0.004, 0.021, 0.03, -0.01],
})
print(df.groupby("sector")["value"].sum())
# sector
# auto 39120.0
# tech 108660.0
# Name: value, dtype: float64
print(df.groupby("sector").size().to_dict()) # {'auto': 2, 'tech': 3} rows per group
print(df.groupby("sector")["ret"].mean().round(4).to_dict()) # {'auto': 0.01, 'tech': 0.0097}用 agg 一次得到多个汇总
.agg() 一次就算出多个汇总,并允许给结果列命名:df.groupby("sector").agg(count=("value", "size"), mean_return=("ret", "mean"), max_value=("value", "max"))。每个关键字是新列的名字,元组说明读取哪一列、应用哪个函数("sum"、"mean"、"min"、"max"、"std"、"size"、"first" 等)。也可以同时按多列分组,groupby(["sector", "day"]),得到两级索引的结果。分组键默认会排序,键缺失(NaN)的行会被丢弃。
import pandas as pd
df = pd.DataFrame({
"symbol": ["AAPL", "MSFT", "GOOG", "TSLA", "F"],
"sector": ["tech", "tech", "tech", "auto", "auto"],
"value": [36850.0, 20510.0, 51300.0, 30120.0, 9000.0],
"ret": [0.012, -0.004, 0.021, 0.03, -0.01],
})
summary = df.groupby("sector").agg(
count=("value", "size"),
mean_return=("ret", "mean"),
max_value=("value", "max"),
)
print(summary)
# count mean_return max_value
# sector
# auto 2 0.010000 30120.0
# tech 3 0.009667 51300.0
print(df.groupby("sector")["ret"].agg(["min", "max"])) # a list of function names
# min max
# sector
# auto -0.010 0.030
# tech -0.004 0.021transform:把组统计量放到每一行
聚合会把每组缩成一行。transform 则保持原来的形状:它计算组统计量,并在该组的每一行上重复,与原索引对齐。因此它适合表达“某一行的值与其所在组的比较”:持仓在其行业中的占比是 df["value"] / df.groupby("sector")["value"].transform("sum"),组内去均值是 x - x.groupby(g).transform("mean")。要为每组选出整行,比如每个行业表现最好的一只,先用 idxmax() 得到每组最大值的索引标签,再用 .loc 选出这些行。
import pandas as pd
df = pd.DataFrame({
"symbol": ["AAPL", "MSFT", "GOOG", "TSLA", "F"],
"sector": ["tech", "tech", "tech", "auto", "auto"],
"value": [36850.0, 20510.0, 51300.0, 30120.0, 9000.0],
"ret": [0.012, -0.004, 0.021, 0.03, -0.01],
})
totals = df.groupby("sector")["value"].transform("sum") # one value per original row
print(totals.tolist()) # [108660.0, 108660.0, 108660.0, 39120.0, 39120.0]
print((df["value"] / totals).round(3).tolist()) # [0.339, 0.189, 0.472, 0.77, 0.23]
best = df.loc[df.groupby("sector")["ret"].idxmax()].set_index("sector")[["symbol", "ret"]]
print(best)
# symbol ret
# sector
# auto TSLA 0.030
# tech GOOG 0.021import pandas as pd
t = pd.DataFrame({
"day": ["mon", "mon", "tue", "tue"],
"sector": ["tech", "auto", "tech", "tech"],
"value": [1.0, 2.0, 3.0, 4.0],
})
# Two keys give a two-level index
print(t.groupby(["day", "sector"])["value"].sum())
# day sector
# mon auto 2.0
# tech 1.0
# tue tech 7.0
# Name: value, dtype: float64
# reset_index() turns the keys back into ordinary columns
print(t.groupby(["day", "sector"])["value"].sum().reset_index())
# day sector value
# 0 mon auto 2.0
# 1 mon tech 1.0
# 2 tue tech 7.0for name, group in df.groupby("sector"): 循环,这适合打印或绘制少数几组;但对于数值计算,内置聚合和 transform 比 Python 循环或带函数的 apply 更短也快得多。要检查结果的索引(分组键,通常已排序)和 dtype,并记住 size 统计行数,而 count 统计非缺失值的个数。df.groupby(键)[col].sum()(或mean、max、size等):每组一个值,以键为索引。.agg(名称=(列, 函数), ...)一次生成多个命名汇总。transform保持行数(占比、去均值);idxmax+.loc为每组选出整行。
下一节:合并与连接表格,组合共享键的表,例如价格与参考数据。
实验:评分内容
- 用
total_by_sector汇总每个行业的敞口25 分 - 用
summary_by_sector在一张表中汇总每个行业25 分 - 用
share_of_sector衡量每个持仓在其行业中的占比25 分 - 用
best_per_sector选出每个行业表现最好的一只25 分