ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python进行数据拆分和合并的超详细指南

Python进行数据拆分和合并的超详细指南

前言


"拆分"和"合并"这两个词在数据处理的语境里其实很含糊。标题把它们并列, 容易让人以为只有一种操作——实际在 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结果



innerINNER JOIN两边键都存在的行(默认)

leftLEFT OUTER JOIN左表全保留

rightRIGHT OUTER JOIN右表全保留

outerFULL OUTER JOIN两边并集

crossCROSS 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换成真实数据即可。


常见坑点



  1. str.split忘了expand=True


❌df["a"].str.split(",")得到"每格是列表"的Series,再取列会报错 ✅df["a"].str.split(",", expand=True)得到可用的多列DataFrame



  1. 拿split去拆行


❌ 想"把一格列表拆成多行"却用str.split,结果每格还是一个列表 ✅ 用df.explode("items")



  1. concat后索引重复


❌pd.concat([df1, df2])得到 0,1,2,0,1,2 的重复索引,后续.loc[0]取到多行 ✅pd.concat([df1, df2], ignore_index=True)



  1. 把merge和join的默认连接方向记混


❌ 以为df.join(other)是内连接 ✅merge默认how="inner",join默认how="left",别混



  1. 连接键有重复导致行数暴涨


❌ 以为一对一,结果一边键重复,合并后行数翻几倍还不知道为什么 ✅ 显式传validate="many_to_one"等,让 pandas 帮你查基数关系



  1. concat(axis=1)不按列名对齐


❌ 以为横向拼接会自动按列名对齐再并入,实际是按索引对齐 ✅ 横向拼接靠索引;要按键对齐请用merge



  1. 分组时丢掉了键里的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基准方向记反



拆分与合并的关键不是记函数名,而是先判断"拆的是列、行还是组"、 "合的是纵向、按键还是按索引"。方向定下来,函数自然就选对了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进