前言
"拆分"和"合并"这两个词在数据处理的语境里其实很含糊。标题把它们并列, 容易让人以为只有一种操作——实际在 pandas 里,这两个方向各自都至少有三类含义:
- 拆分:把一列字符串拆成多列、把一张表按某个键拆成若干张子表、
把一格里的列表拆成多行;
- 合并:纵向堆叠(
concat)、横向按键对齐(merge)、
按索引拼接(join)、缺失值互补(combine_first)。
如果分不清是哪一类,就会写出"用split却想拆行"这种对不上的代码。 本文按这几种真实场景逐一说清,并给出可直接照着推演的示例。
再次提醒:pandas 是第三方库,本机没有安装、也没有 Python 解释器, 示例无法运行验证,只能逐行人工推演。具体参数以 Pandas 官方文档为准。
一、拆分之一:把字符串列拆成多列
Series.str.split是向量化的字符串切分,配合expand=True直接得到多列。
# pandas 1.0+
import pandas as pd
df = pd.DataFrame({"full": ["Alice,25,Beijing", "Bob,30,Shanghai"]})
parts = df["full"].str.split(",", expand=True)
parts.columns = ["name", "age", "city"]
print(parts)关键点:
expand=True才返回DataFrame;默认False返回"每格一个列表"的Series。- 拆出来的列是
object类型,数字列要再astype(int)转换。 - 元素个数不一致时,短的那侧会补
NaN,要留意。
注意str.split与 Python 内置的str.split语义相近,但pandas版本多了一个expand/n的开关。内置str.split(sep=None, maxsplit=-1)保持原样。
二、拆分之二:把一格列表拆成多行
如果某一列的每个单元格本身就是列表,要把它"炸开"成多行,用explode。
# DataFrame.explode 需要 pandas 0.25+;ignore_index 参数需要 pandas 1.1+
df = pd.DataFrame(
{"order": ["A", "B"], "items": [["apple", "pear"], ["banana"]]}
)
long = df.explode("items", ignore_index=True)
print(long)explode(column, ignore_index=False)会把 list-like 的每个元素变成一行, 并复制同一行的其他列值。它是官方 API,不是自己写的循环。若列表为空, 结果里会保留一行NaN,这一点常被忽略。
三、拆分之三:按分组键拆成多张子表
groupby之后可以迭代,每个分组是一个(键, 子 DataFrame)对:
# pandas 1.0+
groups = {city: sub for city, sub in df.groupby("city")}这样得到的是一个字典,key 是分组键、value 是对应的子表。要点:
- 默认
as_index=True;这里迭代的是分组,与as_index无关。 - 分组键里如果有
NaN,默认dropna=True会把这个组丢掉,
想保留分组键里的缺失值需要显式设dropna=False。
四、合并之一:纵向堆叠用 concat
# pandas 1.0+
stacked = pd.concat([df1, df2], ignore_index=True)concat(objs, axis=0, join='outer', ignore_index=False, keys=None, ...):
axis=0纵向、axis=1横向;join='outer'时列取并集,缺的补NaN;join='inner'取交集;ignore_index=True重建从 0 开始的整数索引,避免重复索引;keys=[...]会给每段数据加一层外层索引(得到MultiIndex)。
五、合并之二:按键对齐用 merge
how取值 | 对应 SQL | 结果 |
|---|
inner | INNER JOIN | 两边键都存在的行(默认) |
left | LEFT OUTER JOIN | 左表全保留 |
right | RIGHT OUTER JOIN | 右表全保留 |
outer | FULL OUTER JOIN | 两边并集 |
cross | CROSS JOIN | 笛卡尔积 |
# pandas 1.0+
merged = pd.merge(left, right, how="left", on="id", suffixes=("_l", "_r"))要点:
- 左右键名不同时用
left_on/right_on;要用索引对齐就设
left_index=True或right_index=True。
- 两表有同名列但不是连接键时,
suffixes决定后缀,默认('_x', '_y')。 - 连接键在一边不唯一时会产生笛卡尔扩张,行数暴涨。
可用validate="one_to_one"等取值让 pandas 主动检查并报错。
六、合并之三:按索引 join 与缺失互补
# pandas 1.0+
a = df1.join(df2, how="left") # 按索引对齐,底层仍是 merge
b = df1.combine_first(df2) # df1 缺失处用 df2 补DataFrame.join默认是左连接(和merge默认内连接不同,别混)。combine_first则用于"两张表互补":以调用者为基准,它缺的地方用另一张补上。 它按标签对齐,只有双方索引/列对不上的位置才会用对方的值填充,因此 最适合"两份来源不同、覆盖范围互相错开"的数据。
如果你只想标记每行来自哪张表,而不是真的拼接,可以在merge里传indicator=True,结果会多出一列_merge,取值是left_only、right_only或both。排查"为什么合并后少了行"时,这一列非常有用:
# pandas 1.0+
probe = pd.merge(left, right, on="id", how="outer", indicator=True)
print(probe["_merge"].value_counts())先看value_counts()的输出,就能立刻知道是左表独有的键多、还是右表独有的键多。
七、完整示例
# pandas 1.0+
import pandas as pd
def build(orders: pd.DataFrame, users: pd.DataFrame) -> pd.DataFrame:
# 拆分:把 "user_id|sku" 拆成两列
split = orders["key"].str.split("|", expand=True)
split.columns = ["user_id", "sku"]
orders = pd.concat([orders.drop(columns="key"), split], axis=1)
# 合并:补上用户城市
merged = orders.merge(users, how="left", on="user_id",
validate="many_to_one")
return merged
if __name__ == "__main__":
print(build(pd.DataFrame(), pd.DataFrame()).columns)这段示例把str.split、concat(axis=1)、merge串了起来; 实际使用时把空DataFrame换成真实数据即可。
常见坑点
str.split忘了expand=True
❌df["a"].str.split(",")得到"每格是列表"的Series,再取列会报错 ✅df["a"].str.split(",", expand=True)得到可用的多列DataFrame
- 拿
split去拆行
❌ 想"把一格列表拆成多行"却用str.split,结果每格还是一个列表 ✅ 用df.explode("items")
concat后索引重复
❌pd.concat([df1, df2])得到 0,1,2,0,1,2 的重复索引,后续.loc[0]取到多行 ✅pd.concat([df1, df2], ignore_index=True)
- 把
merge和join的默认连接方向记混
❌ 以为df.join(other)是内连接 ✅merge默认how="inner",join默认how="left",别混
- 连接键有重复导致行数暴涨
❌ 以为一对一,结果一边键重复,合并后行数翻几倍还不知道为什么 ✅ 显式传validate="many_to_one"等,让 pandas 帮你查基数关系
concat(axis=1)不按列名对齐
❌ 以为横向拼接会自动按列名对齐再并入,实际是按索引对齐 ✅ 横向拼接靠索引;要按键对齐请用merge
- 分组时丢掉了键里的
NaN
❌df.groupby("city")后城市为空的组凭空消失 ✅ 需要保留就写df.groupby("city", dropna=False)
总结
| 需求 | 该用的方法 | 易错点 |
|---|
| 字符串拆多列 | str.split(expand=True) | 漏expand |
| 列表拆多行 | explode | 误用split |
| 按组拆子表 | groupby迭代 | dropna丢组 |
| 纵向堆叠 | concat(axis=0) | 索引重复 |
| 按键连接 | merge(how=...) | 默认内连接、基数 |
| 按索引对齐 | join | 默认左连接 |
| 缺失互补 | combine_first | 基准方向记反 |
拆分与合并的关键不是记函数名,而是先判断"拆的是列、行还是组"、 "合的是纵向、按键还是按索引"。方向定下来,函数自然就选对了。