前提¶
安装必要的包:pip install pyarrow
pandas版本要求:Pandas 2.0及以上
准备数据结构,示例如下:
import pandas as pd
df = pd.DataFrame({
"team": ["A", "A", "B", "B"],
"urn": ["u1", "u2", "u3", "u4"],
"val": [10, 20, 30, 40],
"tags": ["x■y", "z", "x", "y■z"]
})
kf = pd.DataFrame({
"team": ["A","A","B","B","B"],
"done": [10,20,5,15,8],
"total": [20,25,10,20,16]
})
特性¶
1转换字符串数据类型¶
Arrow dtype(2.0 重磅)
作用:string[pyarrow] / int64[pyarrow] / bool[pyarrow],原生支持可空类型、更快、内存更小,不再是 object 类型。
# 指定arrow字符串类型(替代object字符串)
df = df.astype({"tags": "string[pyarrow]"})
# 自动识别并转为 pandas 可空 Dtype;配合 Arrow 后端更好
df = df.convert_dtypes(dtype_backend="pyarrow")
✅ 好处:NaN不会混进字符串列、str系列方法更快、导出 parquet 友好
⚠️ 坑:老版本不识别,和部分第三方库兼容性一般;str.split行为大体一致。
2计数和比例¶
# 统计team占比,保留空值
df["team"].value_counts(normalize=True, dropna=False)
3分组统计¶
pd.NamedAgg 详细讲解:
pd.NamedAgg = Named Aggregation,具名聚合,是 pandas 用来在 groupby.agg() 里,给每一次聚合单独指定【输出列名 + 聚合方式】 的工具。
语法:pd.NamedAgg(column="待聚合的列", aggfunc="聚合函数")
解释:
- column:要拿哪一列来计算
- aggfunc:聚合方式,可以是字符串、函数、自定义函数
- 字符串:"sum", "mean", "count", "max", "min", "size"
- 函数:np.sum、自定义函数 lambda x: ...
res = kf.groupby("team").agg(
sum_done = pd.NamedAgg(column="done", aggfunc="sum"),
sum_total = pd.NamedAgg("total", "sum"),
cnt = pd.NamedAgg("done", "count")
)
print(res)
# 输出如下:
sum_done sum_total cnt
team
A 30 45 2
B 28 46 3
3.1支持同一个列,多个不同聚合¶
同一个字段,可以做多指标:
res = kf.groupby("team").agg(
done_sum = pd.NamedAgg("done", "sum"),
done_mean = pd.NamedAgg("done", "mean"),
done_max = pd.NamedAgg("done", "max")
)
# 简写写法如下:
res = kf.groupby("team").agg(
done_sum = ("done", "sum"),
done_mean = ("done", "mean"),
done_max = ("done", "max")
)
# res内容如下:
done_sum done_mean done_max
team
A 30 15.000000 20
B 28 9.333333 15
3.2aggfunc 可以传自定义函数¶
比如分组内算比率:
def rate_func(s):
return s.sum()
# 注意:自定义函数接收的是**分组后的 Series**,不是单行。
res = kf.groupby("team").agg(
done_total = pd.NamedAgg("done", rate_func)
)
# 或者写lambada函数:
res = df.groupby("team").agg(
sum_val=pd.NamedAgg("val", "sum"),
avg_val=("val", lambda s: s.mean())
)
3.3综合应用¶
链式计算分组内的比率:
res = kf.groupby("team").agg(
已完成 = pd.NamedAgg("done", "sum"),
总共 = pd.NamedAgg("total", "sum")
).assign(
制度化率 = lambda x: x["已完成"] / x["总共"]
)
# res内容如下:
已完成 总共 制度化率
team
A 30 45 0.666667
B 28 46 0.608696
示例2:
res = (
df
# 新增拆分列表
.assign(tag_list = lambda x:x["tags"].str.split("■"))
# 炸开多行
.explode("tag_list", ignore_index=True)
# 过滤
.query("val > 5")
# 分组聚合,命名聚合
.groupby(["team","tag_list"]).agg(
total_val = pd.NamedAgg("val", "sum"),
distinct_urn = pd.NamedAgg("urn", "nunique")
)
# 组内汇总,用来算占比
.assign(
team_total = lambda x: x.groupby("team")["total_val"].transform("sum"),
pct = lambda x: x["total_val"] / x["team_total"]
)
.reset_index()
)
# 结果如下:
team tag_list total_val distinct_urn team_total pct
0 A x 10 1 40 0.250000
1 A y 10 1 40 0.250000
2 A z 20 1 40 0.500000
3 B x 30 1 110 0.272727
4 B y 40 1 110 0.363636
5 B z 40 1 110 0.363636
4字符串处理¶
df.str.extractall 性能大幅提升(2.0)
df["tags"].str.extractall(r"(\w+)")
# 如果拆分后想变成多行,且索引重置,写法如下:
res = (
df["tags"].str.extractall(r"([^ ]+)")
.reset_index(names=["orig_idx", "match_idx"])
.merge(df.drop("tags", axis=1), left_on="orig_idx", right_index=True)
.rename(columns={0: "tags"})
.drop(columns=["orig_idx", "match_idx"])
.reset_index(drop=True)
)
print(res)
字符串分割:
df.assign(tag_list = lambda x:x["tags"].str.split("■")).explode("tag_list")
# 限制分割次数
# 只分割第一次■
df["tags"].str.split("■", n=1)
5数据过滤、映射、输入输出¶
5.1数据过滤¶
df.query("team.isin(['A'])")
# 或者布尔索引
df[df["team"].isin(["A"])]
5.2数据映射¶
区分(很多人混淆)
Series.map:值映射(一对一)Series.apply:通用函数Series.transform:分组广播
mapping = {"A":"一组","B":"二组"}
# 2.0 修复:可空类型 map 不会把 None 乱转成 np.nan。
df.assign(team_cn = lambda x:x["team"].map(mapping))
5.3输入输出¶
- parquet格式
# pd.read_csv 新增 `dtype_backend="pyarrow"`(2.0)
df.to_parquet("data.parquet")
df = pd.read_parquet("data.parquet")
- 读取csv
# 读 csv 直接上 Arrow 类型,省去后续 astype
df = pd.read_csv("xxx.csv", dtype_backend="pyarrow")
- 填充、替换空值
df["val"].fillna(0)
df.dropna(subset=["tags"])