前提

安装必要的包: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输入输出

  1. parquet格式
# pd.read_csv 新增 `dtype_backend="pyarrow"`(2.0)
df.to_parquet("data.parquet")
df = pd.read_parquet("data.parquet")
  1. 读取csv
# 读 csv 直接上 Arrow 类型,省去后续 astype
df = pd.read_csv("xxx.csv", dtype_backend="pyarrow")
  1. 填充、替换空值
df["val"].fillna(0)
df.dropna(subset=["tags"])