精通 Pandas concat
:数据合并的艺术与实践
在数据分析和处理的浩瀚海洋中,数据往往不是以单一、完美的表格形式呈现的。我们经常需要从多个来源收集数据,或者将经过分割处理的数据重新整合在一起。这时,Pandas 库中强大的数据合并功能就显得尤为重要。Pandas 提供了多种合并数据的函数,其中 pandas.concat()
是一个基石,它允许我们在指定的轴(行或列)上简单地堆叠(stack)或连接(concatenate)多个 Series 或 DataFrame 对象。
与侧重于数据库风格的、基于列值进行匹配连接的 merge()
和 join()
不同,concat()
的核心是沿着轴方向进行数据的“拼接”。理解并熟练掌握 concat()
是高效处理复杂数据集的关键一步。
本文将深入探讨 pandas.concat()
的各种用法、参数及其背后的逻辑,通过丰富的代码示例,帮助您全面掌握这一强大的工具。
1. 理解 pandas.concat()
的基本原理
pandas.concat()
函数用于在某个轴向上连接 Pandas 对象(Series 或 DataFrame)。它的基本思想是将多个对象“粘合”在一起,就像拼接乐高积木一样。
核心参数:
objs
: 这是唯一必须指定的参数,它是一个包含要连接的 Pandas 对象的序列(如列表或元组)。这些对象通常是 DataFrame,也可以是 Series。axis
: 指定在哪一个轴上进行连接。axis=0
(默认值): 沿着行方向进行连接,将对象垂直堆叠。axis=1
: 沿着列方向进行连接,将对象水平拼接。
join
: 指定如何处理其他轴上的索引(当axis=0
时处理列索引,当axis=1
时处理行索引)。'outer'
(默认值): 取所有对象的索引的并集,缺失的地方填充NaN
。'inner'
: 取所有对象的索引的交集,只保留所有对象中都存在的索引。
ignore_index
: 如果设置为True
,连接后的结果将生成一个新的从 0 到 n-1 的整数索引,而忽略原始对象的索引。这在原始索引没有意义或包含重复项时非常有用。如果设置为False
(默认值),则保留原始索引。keys
: 如果指定,它将为连接后的结果生成一个分层索引(MultiIndex)。当axis=0
时,keys
会成为新的行索引的最外层;当axis=1
时,keys
会成为新的列索引的最外层。这对于标记每个数据块的来源非常有用。names
: 与keys
结合使用,为生成的 MultiIndex 的层级指定名称。verify_integrity
: 如果设置为True
,在连接完成后检查新轴上的索引是否有重复项。如果有,将抛出ValueError
。默认是False
,允许重复索引。sort
: 在非连接轴上按字典顺序对连接结果进行排序。默认是False
,保留原始对象的列/行顺序(取决于axis
)。
2. 垂直堆叠:沿着行轴连接 (axis=0
)
这是 concat()
最常见的用法,用于将具有相似结构(列名)的数据集堆叠起来,形成一个更长的 DataFrame。
示例 1: 简单的垂直堆叠 (列名完全相同)
假设我们有两个 DataFrame,代表两个不同时间段的销售数据,它们的列结构是相同的。
“`python
import pandas as pd
import numpy as np
数据框 1:上半年的销售数据
data1 = {‘商品ID’: [‘A001’, ‘A002’, ‘A003’, ‘A004’],
‘销售额’: [1500, 2000, 1200, 1800],
‘城市’: [‘北京’, ‘上海’, ‘广州’, ‘深圳’]}
df1 = pd.DataFrame(data1, index=[‘Jan’, ‘Feb’, ‘Mar’, ‘Apr’])
print(“DataFrame 1 (df1):”)
print(df1)
print(“-” * 30)
数据框 2:下半年的销售数据
data2 = {‘商品ID’: [‘A005’, ‘A006’, ‘A007’, ‘A008’],
‘销售额’: [2500, 3000, 1600, 2200],
‘城市’: [‘北京’, ‘上海’, ‘深圳’, ‘广州’]}
df2 = pd.DataFrame(data2, index=[‘May’, ‘Jun’, ‘Jul’, ‘Aug’])
print(“DataFrame 2 (df2):”)
print(df2)
print(“-” * 30)
使用 concat 垂直堆叠
result_v_simple = pd.concat([df1, df2])
print(“垂直堆叠结果 (result_v_simple):”)
print(result_v_simple)
print(“-” * 30)
“`
解释:
- 我们将
df1
和df2
放在一个列表中[df1, df2]
作为objs
参数传递给pd.concat()
。 - 由于没有指定
axis
参数,它默认使用axis=0
,即沿着行方向进行连接。 - 两个 DataFrame 的列名完全相同,因此
concat
直接将df2
的行附加到df1
的下方。 - 原始的行索引(’Jan’, ‘Feb’, …, ‘Aug’)被保留下来。注意到结果中可能存在重复的索引,这在
concat
的默认行为中是允许的。
示例 2: 垂直堆叠 (列名不完全相同) – join='outer'
(默认)
现在假设两个 DataFrame 有一些不同的列。
“`python
数据框 3:包含额外信息的上半年的销售数据
data3 = {‘商品ID’: [‘A001’, ‘A002’, ‘A003’, ‘A004’],
‘销售额’: [1500, 2000, 1200, 1800],
‘城市’: [‘北京’, ‘上海’, ‘广州’, ‘深圳’],
‘类别’: [‘电子’, ‘服装’, ‘家居’, ‘电子’]} # 新增列 ‘类别’
df3 = pd.DataFrame(data3, index=[‘Jan’, ‘Feb’, ‘Mar’, ‘Apr’])
print(“DataFrame 3 (df3):”)
print(df3)
print(“-” * 30)
数据框 4:包含不同额外信息的下半年的销售数据
data4 = {‘商品ID’: [‘A005’, ‘A006’, ‘A007’, ‘A008’],
‘销售额’: [2500, 3000, 1600, 2200],
‘国家’: [‘中国’, ‘中国’, ‘中国’, ‘中国’]} # 新增列 ‘国家’,缺少 ‘城市’ 和 ‘类别’
df4 = pd.DataFrame(data4, index=[‘May’, ‘Jun’, ‘Jul’, ‘Aug’])
print(“DataFrame 4 (df4):”)
print(df4)
print(“-” * 30)
使用 concat 垂直堆叠,默认 join=’outer’
result_v_outer = pd.concat([df3, df4])
print(“垂直堆叠结果 (result_v_outer, join=’outer’):”)
print(result_v_outer)
print(“-” * 30)
“`
解释:
df3
有列商品ID
,销售额
,城市
,类别
。df4
有列商品ID
,销售额
,国家
。concat
默认join='outer'
,这意味着它会取所有输入 DataFrame 的列名的并集作为结果 DataFrame 的列。这里的列并集是['商品ID', '销售额', '城市', '类别', '国家']
。- 对于每个原始 DataFrame,如果在并集中存在的某个列在它自身中不存在,那么该列的对应位置将填充
NaN
(Not a Number),表示缺失值。例如,df3
没有国家
列,所以在结果中df3
对应的行,国家
列的值就是NaN
。同理,df4
没有城市
和类别
列,对应的值就是NaN
。
示例 3: 垂直堆叠 (列名不完全相同) – join='inner'
如果我们只关心所有 DataFrame 都共同拥有的列,可以使用 join='inner'
。
“`python
使用 concat 垂直堆叠,指定 join=’inner’
result_v_inner = pd.concat([df3, df4], join=’inner’)
print(“垂直堆叠结果 (result_v_inner, join=’inner’):”)
print(result_v_inner)
print(“-” * 30)
“`
解释:
concat
使用join='inner'
,这意味着它会取所有输入 DataFrame 的列名的交集作为结果 DataFrame 的列。df3
和df4
共同拥有的列是['商品ID', '销售额']
。- 结果 DataFrame 只包含这些共同列,并丢弃了在某些输入 DataFrame 中不存在的列 (
城市
,类别
,国家
)。
示例 4: 忽略原始索引 (ignore_index=True
)
当原始 DataFrame 的索引没有特定意义(例如,它们只是默认的整数索引,或者您不关心它们)时,可以使用 ignore_index=True
来生成一个新的从 0 开始的整数索引。
“`python
数据框 5:使用默认整数索引
data5 = {‘名称’: [‘张三’, ‘李四’], ‘年龄’: [25, 30]}
df5 = pd.DataFrame(data5) # 默认索引 0, 1
print(“DataFrame 5 (df5):”)
print(df5)
print(“-” * 30)
数据框 6:使用默认整数索引
data6 = {‘名称’: [‘王五’, ‘赵六’], ‘年龄’: [22, 28]}
df6 = pd.DataFrame(data6) # 默认索引 0, 1
print(“DataFrame 6 (df6):”)
print(df6)
print(“-” * 30)
使用 concat 垂直堆叠,忽略原始索引
result_v_ignore_index = pd.concat([df5, df6], ignore_index=True)
print(“垂直堆叠结果 (result_v_ignore_index, ignore_index=True):”)
print(result_v_ignore_index)
print(“-” * 30)
对比不忽略索引的情况 (会有重复索引)
result_v_keep_index = pd.concat([df5, df6], ignore_index=False) # 或者不指定 ignore_index
print(“垂直堆叠结果 (result_v_keep_index, ignore_index=False):”)
print(result_v_keep_index)
print(“-” * 30)
“`
解释:
- 当
ignore_index=True
时,原始的索引(df5
和df6
中的 0, 1)被完全丢弃。 - 结果 DataFrame 获得一个新的、连续的整数索引 (0, 1, 2, 3)。
- 当
ignore_index=False
(默认) 时,原始索引被保留,导致结果 DataFrame 中出现了重复的索引值 (0 和 1 各出现两次)。虽然 Pandas 允许重复索引,但在某些操作中可能会引起混淆或错误。
示例 5: 使用 keys
添加分层索引
当您从多个来源连接数据时,保留每个数据块的来源信息是很重要的。keys
参数可以帮助您做到这一点,它会创建一个 MultiIndex (分层索引)来标记数据来自哪个原始 DataFrame。
“`python
沿用 df1 和 df2
print(“DataFrame 1 (df1):”)
print(df1)
print(“-” * 30)
print(“DataFrame 2 (df2):”)
print(df2)
print(“-” * 30)
使用 keys 进行垂直堆叠,添加分层索引
result_v_keys = pd.concat([df1, df2], keys=[‘上半年’, ‘下半年’])
print(“垂直堆叠结果 (result_v_keys, 使用 keys):”)
print(result_v_keys)
print(“-” * 30)
print(“结果索引类型:”, type(result_v_keys.index))
print(“结果索引层级:”, result_v_keys.index.names)
“`
解释:
- 我们将一个列表
['上半年', '下半年']
作为keys
参数传递。列表中的每个元素对应于objs
列表中的一个 DataFrame。 - 结果 DataFrame 的行索引变为了一个 MultiIndex。最外层索引 (
上半年
,下半年
) 来源于keys
参数,用于标识数据来源。内层索引 (Jan
,Feb
, etc.) 保留了原始 DataFrame 的索引。 - 这个分层索引使得按来源进行数据选择变得非常方便,例如
result_v_keys.loc['上半年']
将会选择所有来源于df1
的行。 - 可以使用
names
参数为 MultiIndex 的各个层级指定名称,例如pd.concat([df1, df2], keys=['上半年', '下半年'], names=['时期', '月份'])
。
3. 水平拼接:沿着列轴连接 (axis=1
)
当您想将具有相同行索引的不同数据集水平拼接在一起时,可以使用 axis=1
。这类似于 SQL 中的 JOIN 操作,但 concat
是基于索引进行对齐,而不是基于某个特定的列值。
示例 6: 简单的水平拼接 (行索引完全相同)
假设我们有两个 DataFrame,一个包含用户基本信息,另一个包含用户的联系方式,它们的用户ID(作为索引)是完全匹配的。
“`python
数据框 7:用户基本信息
data7 = {‘姓名’: [‘张三’, ‘李四’, ‘王五’],
‘年龄’: [25, 30, 22]}
df7 = pd.DataFrame(data7, index=[‘user_001’, ‘user_002’, ‘user_003’])
print(“DataFrame 7 (df7):”)
print(df7)
print(“-” * 30)
数据框 8:用户联系方式
data8 = {‘邮箱’: [‘[email protected]’, ‘[email protected]’, ‘[email protected]’],
‘电话’: [‘13800001111’, ‘13900002222’, ‘13000003333’]}
df8 = pd.DataFrame(data8, index=[‘user_001’, ‘user_002’, ‘user_003’])
print(“DataFrame 8 (df8):”)
print(df8)
print(“-” * 30)
使用 concat 水平拼接 (行索引完全匹配)
result_h_simple = pd.concat([df7, df8], axis=1)
print(“水平拼接结果 (result_h_simple):”)
print(result_h_simple)
print(“-” * 30)
“`
解释:
- 指定
axis=1
告诉concat
沿着列方向进行连接。 df7
和df8
的行索引是完全相同的 (user_001
,user_002
,user_003
)。concat
按照行索引进行对齐,并将df8
的列附加到df7
的右侧。结果 DataFrame 的行索引保留了原始索引。
示例 7: 水平拼接 (行索引不完全相同) – join='outer'
(默认)
如果两个 DataFrame 的行索引不完全匹配,concat
默认使用 join='outer'
。
“`python
数据框 9:用户基本信息 (包含一些新增和缺失用户)
data9 = {‘姓名’: [‘张三’, ‘李四’, ‘赵六’, ‘钱七’], # 新增赵六,钱七;缺失王五
‘年龄’: [25, 30, 28, 35]}
df9 = pd.DataFrame(data9, index=[‘user_001’, ‘user_002’, ‘user_004’, ‘user_005’]) # 索引不同
print(“DataFrame 9 (df9):”)
print(df9)
print(“-” * 30)
数据框 10:用户联系方式 (沿用 df8)
print(“DataFrame 10 (df10, 沿用 df8):”)
print(df8)
print(“-” * 30)
使用 concat 水平拼接,默认 join=’outer’
result_h_outer = pd.concat([df9, df8], axis=1)
print(“水平拼接结果 (result_h_outer, axis=1, join=’outer’):”)
print(result_h_outer)
print(“-” * 30)
“`
解释:
df9
的索引是['user_001', 'user_002', 'user_004', 'user_005']
。df8
的索引是['user_001', 'user_002', 'user_003']
。concat
使用axis=1
和默认的join='outer'
。它会取df9
和df8
行索引的并集作为结果 DataFrame 的行索引。这里的并集是['user_001', 'user_002', 'user_003', 'user_004', 'user_005']
。- 对于结果 DataFrame 中的每一行索引,如果在某个原始 DataFrame 中不存在该索引,那么该原始 DataFrame 对应的所有列的值都将填充
NaN
。例如,user_003
只存在于df8
的索引中,所以结果中user_003
这一行的姓名
和年龄
列为NaN
。user_004
和user_005
只存在于df9
的索引中,所以结果中这两行的邮箱
和电话
列为NaN
。
示例 8: 水平拼接 (行索引不完全相同) – join='inner'
如果我们只关心所有 DataFrame 都共同拥有的行索引,可以使用 join='inner'
。
“`python
使用 concat 水平拼接,指定 join=’inner’
result_h_inner = pd.concat([df9, df8], axis=1, join=’inner’)
print(“水平拼接结果 (result_h_inner, axis=1, join=’inner’):”)
print(result_h_inner)
print(“-” * 30)
“`
解释:
concat
使用axis=1
和join='inner'
。它会取df9
和df8
行索引的交集作为结果 DataFrame 的行索引。df9
和df8
共同拥有的行索引是['user_001', 'user_002']
。- 结果 DataFrame 只包含这两行索引的数据,并丢弃了在某些输入 DataFrame 中不存在的行 (
user_003
,user_004
,user_005
)。
示例 9: 使用 keys
添加分层列索引 (axis=1
)
与 axis=0
类似,当沿着列方向拼接多个 DataFrame 时,也可以使用 keys
参数为结果 DataFrame 的列创建一个 MultiIndex,以标识每列的来源。
“`python
沿用 df7 和 df8
print(“DataFrame 7 (df7):”)
print(df7)
print(“-” * 30)
print(“DataFrame 8 (df8):”)
print(df8)
print(“-” * 30)
使用 keys 进行水平拼接,添加分层列索引
result_h_keys = pd.concat([df7, df8], axis=1, keys=[‘基本信息’, ‘联系方式’])
print(“水平拼接结果 (result_h_keys, axis=1, 使用 keys):”)
print(result_h_keys)
print(“-” * 30)
print(“结果列索引类型:”, type(result_h_keys.columns))
print(“结果列索引层级:”, result_h_keys.columns.names)
如何访问特定来源的列
print(“\n访问来自’基本信息’的列:”)
print(result_h_keys[‘基本信息’])
“`
解释:
- 指定
axis=1
和keys=['基本信息', '联系方式']
。 - 结果 DataFrame 的列索引变为了一个 MultiIndex。最外层索引 (
基本信息
,联系方式
) 来源于keys
参数,用于标识列的来源。内层索引 (姓名
,年龄
,邮箱
,电话
) 保留了原始 DataFrame 的列名。 - 可以使用 MultiIndex 的切片或标签访问特定来源的所有列。
4. ignore_index
的深度理解
ignore_index=True
是一个非常实用的参数,尤其是在进行垂直堆叠时,如果您不关心原始行的标识,只想简单地将数据堆叠起来并获得一个新的、干净的序列索引。
何时使用 ignore_index=True
?
- 当原始 DataFrame 的索引是默认的整数索引 (0, 1, 2…),且这些数字本身不代表任何含义。
- 当原始 DataFrame 的索引有意义,但您希望结果 DataFrame 有一个全新的、从 0 开始的连续索引,例如在进行批量数据加载后重置索引。
- 当垂直连接多个 DataFrame,且原始索引存在重复项,您希望避免结果中出现重复索引并获得一个唯一的序列索引。
需要注意什么?
- 使用
ignore_index=True
会完全丢弃原始的行索引信息。如果您需要保留原始行的来源信息,应该考虑使用keys
参数来创建 MultiIndex,而不是丢弃索引。 - 这个参数主要用于
axis=0
的垂直连接。虽然也可以用于axis=1
,但在水平连接中行索引通常用于对齐,忽略行索引会导致结果失去原有的行对应关系,因此在axis=1
中较少使用ignore_index=True
。
5. concat
与 append
(已弃用)
在旧版本的 Pandas 中,DataFrame 和 Series 有一个 append()
方法,用于方便地在末尾添加另一个 DataFrame 或 Series。例如 df1.append(df2)
。
然而,append()
方法已经被标记为弃用,并在未来的版本中会被移除。官方推荐使用 pandas.concat()
代替 append()
。
如何使用 concat
替代 append
?
df1.append(df2)
等价于 pd.concat([df1, df2])
。
df1.append(df2, ignore_index=True)
等价于 pd.concat([df1, df2], ignore_index=True)
。
为什么推荐使用 concat
?
concat
更加通用和灵活,它可以连接任意数量的对象,控制连接轴 (axis
),以及更精细地控制索引 (join
,ignore_index
,keys
)。append
在底层实际上就是调用concat
,但接口功能较少。- 使用
concat
有助于编写更具前瞻性和兼容性的代码,避免在 Pandas 版本升级时遇到问题。
因此,在编写新的代码时,应该始终优先使用 pd.concat()
而不是已弃用的 append()
方法。
6. concat
vs. merge
vs. join
这是 Pandas 中经常令人困惑的地方。理解 concat
与 merge
/join
的区别至关重要。
特性 | pandas.concat() |
pandas.merge() & DataFrame.join() |
---|---|---|
主要功能 | 沿着指定轴(行或列)简单地拼接对象。 | 基于一个或多个列或索引的共同值进行数据库风格的合并。 |
对齐方式 | 主要基于索引或直接按位置(当 ignore_index=True 或列名/索引完全匹配时)。 |
基于指定的列值或索引值进行匹配。 |
输入对象 | 一个 Pandas 对象的列表或字典。 | 通常是两个 DataFrame。 |
连接轴 | 可以在 axis=0 (垂直) 或 axis=1 (水平) 上进行。 |
默认是水平连接,生成新的列。 |
列处理 | axis=0 : 根据 join 参数处理列的交集或并集。 axis=1 : 直接拼接列。 |
根据指定的键列进行匹配,合并源 DataFrame 的列。 |
索引处理 | axis=0 : 根据 join 参数处理索引的交集或并集,保留或忽略原始索引。 axis=1 : 根据 join 参数处理行索引的交集或并集。 |
合并操作的结果索引取决于合并类型 (inner, outer, left, right) 和指定的键。 |
常见用途 | – 堆叠相同结构的数据集 (例如,来自不同文件或批次的数据)。 – 将多个 Series 合并为 DataFrame。 – 将通过切片或筛选分割的数据块重新组合。 |
– 将来自不同表(DataFrame)的相关信息关联起来 (例如,订单表与客户信息表)。 – 添加基于共同标识符的额外列。 |
总结来说:
concat
适用于:当您想将数据简单地堆叠起来(增加行数,如果列不同则会产生 NaNs)或者将具有相同行索引的数据水平并排(增加列数,如果索引不同则产生 NaNs)。它不查找值进行匹配,而是直接沿着轴进行拼接。merge
/join
适用于:当您想通过查找一个或多个共同的标识符(如用户ID、订单号等)来组合数据。它们是基于值匹配的查找和合并操作。
示例对比:
使用 concat(axis=1)
拼接 df7
和 df8
:
“`python
基于行索引对齐拼接
result_concat_h = pd.concat([df7, df8], axis=1)
print(“Concat (axis=1) 结果:”)
print(result_concat_h)
“`
使用 merge
基于共同列 (商品ID
) 合并数据(假设需要先将索引变成列):
“`python
创建两个需要基于共同列合并的 DataFrame
df_sales = pd.DataFrame({‘商品ID’: [‘A001’, ‘A002’, ‘A003’],
‘销售额’: [100, 200, 150]})
df_product_info = pd.DataFrame({‘商品ID’: [‘A001’, ‘A002’, ‘A004’],
‘商品名称’: [‘电脑’, ‘手机’, ‘耳机’],
‘价格’: [5000, 3000, 500]})
print(“\nDataFrame Sales:”)
print(df_sales)
print(“\nDataFrame Product Info:”)
print(df_product_info)
使用 merge 基于 ‘商品ID’ 列合并
result_merge = pd.merge(df_sales, df_product_info, on=’商品ID’, how=’inner’)
print(“\nMerge 结果 (基于’商品ID’列):”)
print(result_merge)
“`
在这个 merge
示例中,合并是基于 '商品ID'
列的值进行的,只有 'A001'
和 'A002'
这两个共同的商品ID被保留下来(因为使用了 how='inner'
)。concat
则不会执行这种基于值的匹配查找,它只关心索引或位置。
7. 使用 Series 进行 concat
concat
函数不仅可以用于 DataFrame,也可以用于 Series,或者 DataFrame 和 Series 的混合连接。
示例 10: 连接 Series
“`python
s1 = pd.Series([1, 2, 3], index=[‘a’, ‘b’, ‘c’])
s2 = pd.Series([4, 5, 6], index=[‘d’, ‘e’, ‘f’])
s3 = pd.Series([7, 8, 9], index=[‘a’, ‘g’, ‘h’]) # 包含重复索引
print(“Series 1 (s1):”)
print(s1)
print(“\nSeries 2 (s2):”)
print(s2)
print(“\nSeries 3 (s3):”)
print(s3)
垂直连接 Series (默认 axis=0)
result_s_v = pd.concat([s1, s2, s3])
print(“\n垂直连接 Series 结果 (result_s_v):”)
print(result_s_v)
print(“-” * 30)
垂直连接 Series,忽略索引
result_s_v_ignore = pd.concat([s1, s2, s3], ignore_index=True)
print(“\n垂直连接 Series 结果 (result_s_v_ignore, ignore_index=True):”)
print(result_s_v_ignore)
print(“-” * 30)
垂直连接 Series,使用 keys
result_s_v_keys = pd.concat([s1, s2, s3], keys=[‘S1’, ‘S2’, ‘S3’])
print(“\n垂直连接 Series 结果 (result_s_v_keys, keys):”)
print(result_s_v_keys)
print(“-” * 30)
“`
解释:
- 垂直连接 Series 与垂直连接 DataFrame 类似,只是结果是一个 Series(如果只有一个列)或 DataFrame(如果 Series 之间有索引差异或使用 keys)。
- 默认
axis=0
,直接堆叠 Series 的值,索引被保留(可能重复)。 ignore_index=True
丢弃原始索引,生成新的整数索引。keys
参数为结果 Series (如果是一个 Series) 或 DataFrame (如果创建了 MultiIndex) 添加分层索引。
示例 11: 连接 DataFrame 和 Series
当 DataFrame 与 Series 进行 concat(axis=1)
操作时,Series 会被 treated as a single column DataFrame。
“`python
沿用 df7
print(“DataFrame 7 (df7):”)
print(df7)
print(“-” * 30)
Series:用户等级信息,索引与 df7 相同
s4 = pd.Series([‘Gold’, ‘Silver’, ‘Bronze’], index=[‘user_001’, ‘user_002’, ‘user_003′], name=’用户等级’) # 指定 Series 名称
print(“Series 4 (s4):”)
print(s4)
print(“-” * 30)
将 df7 和 s4 水平拼接
result_df_s_h = pd.concat([df7, s4], axis=1)
print(“水平拼接 DataFrame 和 Series 结果:”)
print(result_df_s_h)
print(“-” * 30)
“`
解释:
concat([df7, s4], axis=1)
将s4
视为一个单列的 DataFrame,并根据行索引与df7
进行对齐。- 由于行索引完全匹配,
s4
的值作为新的一列被添加到df7
的右侧。新列的名称是s4
的name
属性 (用户等级
)。 - 如果 Series 没有指定
name
属性,它在结果中可能获得一个默认的数字列名。
8. 性能考虑
对于小型到中型的数据集,concat
的性能通常不是问题。然而,当处理非常大的数据集时,连接操作可能会消耗较多内存和时间。
pd.concat()
通常比在循环中反复使用append()
更高效,因为concat
可以一次性处理所有对象,而append
会在每次迭代中创建新的 DataFrame。- 预估结果 DataFrame 的总大小有助于避免内存溢出。
- 如果连接多个具有相同列(或索引)但顺序不同的 DataFrame,设置
sort=False
(默认值) 可以避免不必要的排序开销,但如果需要特定顺序的列/索引,则可能需要排序。 - 在某些情况下,如果只是添加少量行到现有 DataFrame,预分配空间或使用列表存储数据然后一次性创建 DataFrame 可能比多次
concat
更高效。但对于通用合并任务,concat
是标准且优化的方法。
9. 常见陷阱与故障排除
- 遗忘
axis
参数:concat
默认axis=0
。如果您想要水平连接,必须明确指定axis=1
。这是新手常犯的错误。结果通常是垂直堆叠而不是预期的水平拼接。 - 索引不对齐导致
NaN
过多: 在使用axis=1
(水平拼接) 时,如果两个 DataFrame 的行索引不完全匹配,默认的join='outer'
会引入大量NaN
。请检查您的索引是否正确,或者考虑使用join='inner'
如果您只关心共同的行。 - 列不对齐导致
NaN
过多 (axis=0): 在使用axis=0
(垂直堆叠) 时,如果两个 DataFrame 的列不完全匹配,默认的join='outer'
会引入大量NaN
。请检查您的列名是否一致,或者考虑使用join='inner'
如果您只关心共同的列。 - 重复索引: 默认情况下
concat
允许重复索引。如果您的下游操作对索引唯一性有要求,可以考虑在concat
之前或之后重置索引 (reset_index()
),或者使用ignore_index=True
,或者在concat
时设置verify_integrity=True
来捕获重复索引错误。 - 数据类型 (dtypes) 改变: 当连接包含不同数据类型的列时,Pandas 会尝试找到一个兼容的数据类型。这可能导致整数列变成浮点列(因为
NaN
是浮点类型)或对象列。在连接后检查dtypes
是个好习惯,必要时进行类型转换。 - 连接 Series 和 DataFrame: 当使用
axis=1
连接 Series 和 DataFrame 时,确保 Series 有一个name
属性,这样在结果 DataFrame 中它会有一个有意义的列名。
10. 总结
pandas.concat()
是 Pandas 库中一个功能强大且灵活的数据合并工具。它允许您在指定的轴上简单地拼接多个 DataFrame 或 Series。
- 使用
axis=0
进行垂直堆叠,增加行数。 - 使用
axis=1
进行水平拼接,增加列数。 join
参数控制如何处理非连接轴上的索引/列 (outer
或inner
)。ignore_index=True
用于生成新的连续整数索引,丢弃原始索引。keys
参数用于创建 MultiIndex,方便标记数据来源。
熟练掌握 concat()
及其参数,能够帮助您高效地整合分散的数据,为后续的数据清洗、分析和建模奠定坚实基础。请务必结合 merge()
和 join()
的使用场景,选择最适合您数据合并需求的工具。通过实践和尝试不同的参数组合,您将能够更自信地处理各种复杂的数据整合任务。
希望本文的详细讲解和丰富的示例能够帮助您全面掌握 pandas.concat()
的用法。祝您在数据探索的旅程中一帆风顺!