📊 第4章 Pandas 进阶
📄 一句话概括

str.split 拆一列,关键是 expand=True——不给它拿到的是列表,给了才拆成多列。 pivot 把长表拧成宽表,unstack 做同一件事,区别只在数据入口。 长表适合存和算,宽表适合看。melt 是 pivot 的逆操作。

📝 本章和前面几章的关系

真实数据里「姓名-电话」挤在一列、「一月二月三月」摊成三列,都是常事。 这一篇讲两个方向:一列拆多列(拆字符串)、多列拧成两列(行列转换)。

一、按分隔符拆一列

造一列「张伟-13800000001」,想拆成姓名和电话两列。 df['联系方式'].str.split('-', expand=True) 直接给出两列。

联系方式 联系人 电话
张伟-13800000001 张伟 13800000001
王芳-13800000002 王芳 13800000002

expand=True 是关键。不给它,拿到的是「每行一个列表」。 给了它,才直接拆成多列。这一步忘了,后面所有列操作都会失败。 拆列 90% 的错都在这个参数上,报错信息还常常看不出是它。

二、拆完原列要不要删

拆完原列还在,要删就 drop(columns=['联系方式'])。 不删也不影响计算,但导出去的时候会多一列中间产物。 拆列通常顺手删原列,除非原列还有别的解析方式要用。

拆出来的列名默认是 0、1、2。想直接命名,就整体赋给一个列名列表。

三、长表转宽表:pivot

长表是「姓名、科目、分数」三列 30 行;宽表是「姓名」一行、三个科目三列。 long.pivot(index='姓名', columns='科目', values='分数'),30 行变 10 行 3 列。

姓名 数学 英语 语文
刘洋 88 79 85
吴霞 90 87 79
周涛 86 84 83

三个参数缺一不可:index 是行、columns 是新列名、values 是填进去的数。 新列的顺序按科目名排序,不是原表出现的顺序。上面是数学、英语、语文。 想要指定顺序,转完再按列表挑列,或者用 reindex(columns=[...])。

四、unstack:把行标签的一层搬到列上

先 set_index(['姓名','科目']) 造两级行标签,再 .unstack()。 结果形状 (10, 3),和 pivot 一模一样。 区别在入口:pivot 吃三列,unstack 只吃「多级行标签」。

手上是原始三列数据就用 pivot;已经有两列当索引就用 unstack。

五、反方向:melt 拧回长表

wide.reset_index().melt(id_vars='姓名', var_name='科目', value_name='分数')。 拧回来是 30 行——和原来的长表一样长,一行不多一行不少。 宽长互转是数据整理的日常:拿到的是宽表,画图要长表,存库又要长表。

pivot 和 melt 是一对,一个拧紧一个拧松,来回两遍应该回到原样。

六、动手 Demo:一列拆多列与行列转换

先造一列「姓名-电话」拆成两列,再用 30 行长表演 pivot 和 unstack。 最后 melt 拧回来,核对行数是否复原。

import pandas as pd

DF = pd.DataFrame({
    "姓名": ["张伟", "王芳", "李娜"],
    "联系方式": ["张伟-13800000001", "王芳-13800000002", "李娜-13800000003"],
})
DF[ ["联系人", "电话"]] = DF["联系方式"].str.split("-", expand=True)
print(DF.to_string(index=False))

N = ["张伟", "王芳", "李娜", "刘洋"]
LONG = pd.DataFrame({
    "姓名": N * 3,
    "科目": ["语文"] * 4 + ["数学"] * 4 + ["英语"] * 4,
    "分数": [88, 92, 75, 85, 92, 95, 68, 88, 85, 90, 80, 79],
})

W = LONG.pivot(index="姓名", columns="科目", values="分数")
print("pivot 后 %s" % (W.shape,))

S = LONG.set_index(["姓名", "科目"])["分数"]
print("unstack 后 %s" % (S.unstack().shape,))

M = W.reset_index().melt(id_vars="姓名", var_name="科目", value_name="分数")
print("melt 回来 %d 行" % len(M))

实跑输出:

--------------------------------------------------------------------
一列拆多列与行列转换
--------------------------------------------------------------------

  ① 按分隔符拆一列
  --------------------------------------------------------------------

| 行 | 姓名 | 联系方式 |
|---|---:|---:|
| 0 | 张伟 | 张伟-13800000001 |
| 1 | 王芳 | 王芳-13800000002 |
| 2 | 李娜 | 李娜-13800000003 |

  造了一列「姓名-电话」,用 str.split 拆成两列。

| 行 | 联系方式 | 联系人 | 电话 |
|---|---:|---:|---:|
| 0 | 张伟-13800000001 | 张伟 | 13800000001 |
| 1 | 王芳-13800000002 | 王芳 | 13800000002 |
| 2 | 李娜-13800000003 | 李娜 | 13800000003 |

  expand=True 是关键:不给它,拿到的是列表;给了它,直接拆成多列。
  ! expand=True 这一步忘了,后面所有列操作都会失败。拆列 90% 的错在这。

  ② 拆分之后原列要不要删
  --------------------------------------------------------------------
  拆完原列还在,要删就 drop(columns=['联系方式'])。
  不删也不影响计算,但导出去的时候会多一列中间产物。
  ! 拆列通常顺手删原列,除非原列还有别的解析方式要用。

  ③ 长表转宽表:pivot
  --------------------------------------------------------------------

| 行 | 姓名 | 科目 | 分数 |
|---|---:|---:|---:|
| 0 | 张伟 | 语文 | 88 |
| 1 | 王芳 | 语文 | 92 |
| 2 | 李娜 | 语文 | 75 |
| 3 | 刘洋 | 语文 | 85 |


| 姓名 | 数学 | 英语 | 语文 |
|---|---:|---:|---:|
| 刘洋 | 88 | 79 | 85 |
| 吴霞 | 90 | 87 | 79 |
| 周涛 | 86 | 84 | 83 |

  pivot 把「科目」这一列的值变成新列,行是姓名。30 行的长表变成 10 行 3 列。
  三个参数缺一不可:index 是行、columns 是新列名、values 是填进去的数。
  ! 长表适合存,宽表适合看。数据库里存长表,报表上给人看宽表。

  ④ unstack:把行标签的一层搬到列上
  --------------------------------------------------------------------
  unstack 之后形状是 (10, 3),和 pivot 的结果一样。
  区别在入口:pivot 吃三列,unstack 只吃「多级行标签」。
  ! 已有两列当索引就用 unstack,手上有三列原始数据就用 pivot。

  ⑤ 反方向:宽表拧回长表
  --------------------------------------------------------------------
  melt 把宽表拧回长表,pivot 的逆操作。
  拧回来是 30 行——和原来的长表一样长。
  ! 宽长互转是数据整理的日常:拿到的是宽表,画图要长表,存库又要长表。

str.split('-', expand=True) 少了 expand 就返回列表,赋值给两列会直接报错。 pivot 要求「行标签 + 列名」的组合唯一。同一人同一科目有两行会报 ValueError。

30 秒实验

  1. 把 expand=True 去掉,看报什么错。
  2. 给 LONG 加一行重复的「张伟、语文」,再 pivot 一次。
  3. 想一想:为什么画图前要把宽表拧成长表?

常见误区

  1. split 忘了 expand=True:拿到列表,后面全崩。
  2. pivot 遇到重复组合:报 ValueError,得先 groupby 或去重。
  3. 以为 pivot 的列按原顺序:它按名字排序,要指定得 reindex。
  4. 拆完不删原列:导出的表里多一列没人看得懂的中间产物。
自测三问(点开对答案) 1. str.split 的 expand=True 在做什么? —— 把「每行一个列表」展开成「多列」。不加它结果是一列列表,没法当列用。 2. pivot 和 unstack 有什么区别? —— 结果一样,入口不同:pivot 吃三列原始数据,unstack 吃已经设好的两级行标签。 3. pivot 报 ValueError 通常是什么原因? —— 「行标签 + 列名」的组合有重复,pandas 不知道往格子里填哪个值。

表整理成想要的样子了。下一篇反过来:把表交出去——转字典、转列表、转网页。

相关笔记