第8章封面

真实项目的数据从来不是一张表:用户信息在一张表、订单在另一张、月份拆在十二个文件里。第 8 章教你三件事:把多张表拼起来(join/concat)、把一张表变个形状(reshape/pivot)、用层次索引优雅驾驭嵌套结构

🏢 层次索引:一列顶两列

MultiIndex 让一个索引里带多层,高维数据优雅「住」进二维表:

data = pd.Series(rng.standard_normal(6),
                 index=[["a","a","b","b","c","c"],
                        [1,2,1,2,1,2]])
data["b"]              # 外层取:b 层的全部
data["b", "1"]         # 内层精确定位
data.unstack()         # 内层索引「弹」成列 → 变成 3×2 表
data.unstack().stack() # 再「压」回去,完美互逆

层次索引的实用三招:swaplevel() 交换层、sort_index(level=0) 分层排序(先排序再定位是性能铁律)、df.set_index(["key1","key2"]) 把普通列升格为索引。

🔗 merge:数据库风格的连接

pandas 的 merge 就是 SQL 的 JOIN。以「多对一」「多对多」最常见:

df1 = pd.DataFrame({"key": ["b","b","a","c","a"], "data1": range(5)})
df2 = pd.DataFrame({"key": ["a","b","d"], "data2": range(3)})

pd.merge(df1, df2)                     # 默认 inner,按同名列 key 连接
pd.merge(df1, df2, on="key", how="left")   # 左连接:保留 df1 全部行
pd.merge(df1, df2, how="outer")        # 外连接:并集
pd.merge(df1, df2, how="inner")        # 内连接:交集(默认)

四种连接

how 参数的四张脸

  • inner:两边 key 都有的行(默认);
  • left:以左表为准,右边没有就 NaN;
  • right:镜像版 left;
  • outer:两边全保留。

两边的 key 列名不同用 left_on/right_on;列名撞车用 suffixes=("_left","_right") 区分。多对多时行数会按笛卡尔积膨胀——merge 之后 len() 一下是好习惯。

按索引合并

一边的连接键在索引里时,用 left_index=True / right_index=True

pd.merge(left, right, left_on="key", right_index=True)  # 混合式
left.join(right)      # 简便写法:按索引连接,还能一次连多张表

🧲 concat:沿轴堆叠

merge 处理「按 key 找对象」,concat 处理「直接摞/拼」:

s1, s2, s3 = pd.Series(...), pd.Series(...), pd.Series(...)
pd.concat([s1, s2, s3])               # 纵向摞成一根长 Series
pd.concat([s1, s2], axis=1)           # 横向拼成表(按索引对齐!)
pd.concat([s1, s2], axis=1, join="inner")  # 只留共享索引
pd.concat([s1, s2], keys=["one","two"])    # 加外层标签记录来源
df = df[~df.index.duplicated()]       # 拼完去重的常规操作

join_axes(第3版中被 reindex 替代)与 ignore_index=True(重排 0..n 索引)是另两个常用参数。

🩹 combine_first:逐格补洞

两张表都有部分数据、各有缺口时:

a.combine_first(b)   # a 的值优先,a 缺的位置用 b 的补上

相当于「逐元素版的 df.update + 按索引对齐」,合并多个来源的脏数据极好用。

🏗️ 重塑:stack / pivot / melt

stack 与 unstack:在「列」和「内层行索引」之间旋转数据。stack 把最内列压进行索引(宽变窄),unstack 反之——第 5 章见过,这里是完整用法。

长表 → 宽表(pivot):这是「一行一个观测」的长格式数据最经典的变换:

ldata  # columns: date, item, value(长格式:每个 (date,item) 一行)
pivoted = ldata.pivot(index="date", columns="item", values="value")
# 每个 item 变成一列!

宽表 → 长表(melt):pivot 的逆操作,统计软件和绘图(seaborn 的 tidy data)都偏爱长格式:

df = pd.DataFrame({"key": ["foo","bar"],
                   "A": [1,2], "B": [3,4]})
melted = pd.melt(df, id_vars="key")
#    key variable  value
# 0  foo        A      1
# 1  bar        A      2
# 2  foo        B      3
# 3  bar        B      4

📌 本章小结:merge 四连接 inner/left/right/outer,key 不同用 left_on/right_on,撞名用 suffixes;concat 按 axis 摞或拼、keys 标来源;combine_first 逐格补洞;pivot 长转宽、melt 宽转长、stack/unstack 在列与索引间旋转——长宽表变换想清楚「哪一列该变成行标签、哪个维度该展开成列」就永远不会晕。