Pandas Concat:合并与拼接 DataFrame 指南
在数据分析和处理的广阔天地中,将多个数据集有效地整合到一起是一项基础且至关重要的任务。无论是将来自不同文件的数据堆叠在一起,还是将具有相同索引但不同列的数据并排拼接,Pandas 库都提供了强大的工具来应对这些挑战。其中,pandas.concat()
函数便是实现这一目标的核心利器之一。
相较于 merge
或 DataFrame 的 .join()
方法侧重于基于共同列或索引的“合并”(类似于数据库的 Join 操作),concat
函数更倾向于沿着某个轴向对数据对象进行“拼接”(Concatenation)。它可以将 Series 或 DataFrame 对象沿着行轴(axis=0)或列轴(axis=1)简单地堆叠或并排连接。理解并熟练运用 concat
函数及其各种参数,对于高效地组织和处理复杂数据流至关重要。
本文将深入探讨 Pandas concat
函数的用法、核心参数以及常见应用场景,助您全面掌握这一强大的数据拼接工具。
1. 理解 Concat 的基本原理
pandas.concat()
函数用于将 Pandas 对象(Series 或 DataFrame)沿着特定轴进行连接。它接受一个由 Pandas 对象组成的列表或字典作为输入。默认情况下,它沿着行轴(axis=0)进行连接,这意味着它将一个对象的行直接堆叠到另一个对象的行的下方。
核心概念:
- 轴 (Axis):
concat
操作是沿着某个轴进行的。axis=0
表示沿着行轴操作(结果的行数增加),axis=1
表示沿着列轴操作(结果的列数增加)。 - 对齐 (Alignment): 当沿着某个轴进行连接时,
concat
会尝试根据另一个轴上的索引进行对齐。例如,当沿着行轴(axis=0)连接时,它会根据列索引进行对齐;当沿着列轴(axis=1)连接时,它会根据行索引进行对齐。对齐方式可以通过join
参数控制。 - 输入:
concat
接受一个 Pandas 对象的列表或字典。字典的键可以作为结果中的多级索引的键(通过keys
参数实现)。
2. Concat 的基本用法和默认行为
让我们从最简单的例子开始。
“`python
import pandas as pd
import numpy as np
创建示例 DataFrame
df1 = pd.DataFrame({
‘A’: [‘A0’, ‘A1’, ‘A2’, ‘A3’],
‘B’: [‘B0’, ‘B1’, ‘B2’, ‘B3’],
‘C’: [‘C0’, ‘C1’, ‘C2’, ‘C3’],
‘D’: [‘D0’, ‘D1’, ‘D2’, ‘D3’]
}, index=[0, 1, 2, 3])
df2 = pd.DataFrame({
‘A’: [‘A4’, ‘A5’, ‘A6’, ‘A7’],
‘B’: [‘B4’, ‘B5’, ‘B6’, ‘B7’],
‘C’: [‘C4’, ‘C5’, ‘C6’, ‘C7’],
‘D’: [‘D4’, ‘D5’, ‘D6’, ‘D7’]
}, index=[4, 5, 6, 7])
df3 = pd.DataFrame({
‘A’: [‘A8’, ‘A9’, ‘A10’, ‘A11’],
‘B’: [‘B8’, ‘B9’, ‘B10’, ‘B11’],
‘C’: [‘C8’, ‘C9’, ‘C10’, ‘C11’],
‘D’: [‘D8’, ‘D9’, ‘D10’, ‘D11’]
}, index=[8, 9, 10, 11])
frames = [df1, df2, df3]
基本连接 (默认 axis=0)
result_default = pd.concat(frames)
print(“— Default Concat (axis=0) —“)
print(result_default)
“`
输出解释:
默认情况下,pd.concat(frames)
等价于 pd.concat(frames, axis=0)
。它将 df1
、df2
和 df3
沿着行轴连接起来。结果 DataFrame 包含了所有三个输入 DataFrame 的行。列会根据列名进行对齐。原始的索引被保留下来,这可能导致结果中出现重复的索引值。
3. 核心参数详解
concat
函数提供了多个参数来控制连接的行为:
python
pd.concat(objs, axis=0, join='outer', ignore_index=False, keys=None,
levels=None, names=None, verify_integrity=False, sort=False, copy=True)
其中,最常用且重要的参数包括 axis
, join
, ignore_index
, keys
, names
, verify_integrity
和 sort
。
3.1 axis
: 控制连接方向
这是 concat
中最基本的参数,决定了是沿着行轴还是列轴进行连接。
axis=0
(默认值): 沿着行轴连接。将 DataFrame 一个接一个地纵向堆叠。列会根据列名进行对齐。axis=1
: 沿着列轴连接。将 DataFrame 一个接一个地横向拼接。行会根据行索引进行对齐。
示例 (axis=1
):
“`python
df4 = pd.DataFrame({
‘B’: [‘B2’, ‘B3’, ‘B6’, ‘B7’],
‘D’: [‘D2’, ‘D3’, ‘D6’, ‘D7’],
‘F’: [‘F2’, ‘F3’, ‘F6’, ‘F7’]},
index=[2, 3, 6, 7])
沿着列轴连接 df1 和 df4
result_axis1 = pd.concat([df1, df4], axis=1)
print(“\n— Concat with axis=1 —“)
print(result_axis1)
“`
输出解释:
当 axis=1
时,concat
会根据行索引进行对齐。df1
的索引是 [0, 1, 2, 3],df4
的索引是 [2, 3, 6, 7]。结果 DataFrame 的索引是两个 DataFrame 索引的并集 [0, 1, 2, 3, 6, 7](默认 join='outer'
)。对于某个行索引,如果某个 DataFrame 中存在对应的行,则填充其值;如果不存在,则填充 NaN
。
3.2 join
: 控制对齐方式
当沿着一个轴进行连接时,concat
会根据另一个轴上的索引进行对齐。join
参数控制这种对齐的方式。
join='outer'
(默认值): 对齐方式是索引的“并集”(Union)。结果 DataFrame 会包含所有输入 DataFrame 在该轴上的所有索引值。如果某个 DataFrame 在某个索引位置没有数据,则填充NaN
。join='inner'
: 对齐方式是索引的“交集”(Intersection)。结果 DataFrame 只会包含所有输入 DataFrame 在该轴上都存在的索引值。
示例 (join
):
沿行轴(axis=0
)连接时,join
控制列的对齐。
“`python
df5 = pd.DataFrame({
‘A’: [‘A8’, ‘A9’],
‘B’: [‘B8’, ‘B9’],
‘G’: [‘G8’, ‘G9’]},
index=[8, 9])
axis=0, join=’outer’ (default)
result_outer_cols = pd.concat([df1, df5], axis=0, join=’outer’)
print(“\n— Concat with axis=0, join=’outer’ —“)
print(result_outer_cols)
axis=0, join=’inner’
result_inner_cols = pd.concat([df1, df5], axis=0, join=’inner’)
print(“\n— Concat with axis=0, join=’inner’ —“)
print(result_inner_cols)
“`
输出解释 (axis=0, join):
join='outer'
时,结果包含df1
和df5
的所有列 (A
,B
,C
,D
,G
)。对于df1
中没有G
列的位置,填充NaN
;对于df5
中没有C
,D
列的位置,填充NaN
。join='inner'
时,结果只包含df1
和df5
共有的列 (A
,B
)。其他列被丢弃。
沿列轴(axis=1
)连接时,join
控制行的对齐。
“`python
df6 = pd.DataFrame({
‘H’: [‘H0’, ‘H2’],
‘I’: [‘I0’, ‘I2’]},
index=[0, 2]) # 部分与 df1 索引重叠
axis=1, join=’outer’ (default)
result_outer_rows = pd.concat([df1, df6], axis=1, join=’outer’)
print(“\n— Concat with axis=1, join=’outer’ —“)
print(result_outer_rows)
axis=1, join=’inner’
result_inner_rows = pd.concat([df1, df6], axis=1, join=’inner’)
print(“\n— Concat with axis=1, join=’inner’ —“)
print(result_inner_rows)
“`
输出解释 (axis=1, join):
join='outer'
时,结果包含df1
和df6
的所有行索引 (0
,1
,2
,3
)。对于某个行索引,如果某个 DataFrame 中没有数据,则填充NaN
。join='inner'
时,结果只包含df1
和df6
共有的行索引 (0
,2
)。其他行被丢弃。
3.3 ignore_index
: 忽略原始索引
默认情况下,concat
会保留原始 DataFrame 的索引。如果原始索引没有实际意义,或者您希望结果 DataFrame 有一个新的、连续的 0 到 n-1 的整数索引,可以使用 ignore_index=True
。
示例 (ignore_index
):
“`python
原始索引会被忽略,生成新的 0-based 索引
result_ignore_index = pd.concat([df1, df2, df3], ignore_index=True)
print(“\n— Concat with ignore_index=True —“)
print(result_ignore_index)
与默认行为对比 (原始索引保留,可能重复)
result_default = pd.concat(frames) # 见上面示例
“`
输出解释:
使用了 ignore_index=True
后,结果 DataFrame 的索引不再是原始的 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],而是新的、从 0 开始的连续整数索引 [0, 1, …, 11]。这在合并来自不同来源、索引可能重复或不具有全局唯一性的数据时非常有用。
3.4 keys
: 创建多级索引
当合并多个 DataFrame 时,有时需要标记结果中的每一行(或列,如果 axis=1
)来自哪个原始 DataFrame。keys
参数可以帮助实现这一点,它会在结果中创建一个多级索引 (MultiIndex)。
keys
参数接受一个与输入对象列表等长的列表。这个列表中的每个元素将成为结果中对应输入对象的顶级索引。
示例 (keys
):
“`python
axis=0 时,创建行多级索引
result_keys_axis0 = pd.concat(frames, keys=[‘df1_key’, ‘df2_key’, ‘df3_key’])
print(“\n— Concat with keys (axis=0) —“)
print(result_keys_axis0)
可以方便地通过多级索引选择数据
print(“\n— Select data from ‘df2_key’ —“)
print(result_keys_axis0.loc[‘df2_key’])
axis=1 时,创建列多级索引
创建一些具有重叠行索引的 DataFrame
df_part1 = pd.DataFrame({‘A’: [1, 2], ‘B’: [3, 4]}, index=[‘x’, ‘y’])
df_part2 = pd.DataFrame({‘C’: [5, 6], ‘D’: [7, 8]}, index=[‘x’, ‘z’])
result_keys_axis1 = pd.concat([df_part1, df_part2], axis=1, keys=[‘Part1’, ‘Part2’])
print(“\n— Concat with keys (axis=1) —“)
print(result_keys_axis1)
可以方便地通过多级索引选择列
print(“\n— Select columns from ‘Part1’ —“)
print(result_keys_axis1[‘Part1’])
“`
输出解释:
- 当
axis=0
并使用keys
时,结果 DataFrame 的行索引变成了多级索引。第一级索引是keys
参数中对应 DataFrame 的键,第二级索引是原始 DataFrame 的索引。这使得您可以轻松地按原始 DataFrame 来源对结果进行分组或选择。 - 当
axis=1
并使用keys
时,结果 DataFrame 的列索引变成了多级索引。第一级索引是keys
参数中对应 DataFrame 的键,第二级索引是原始 DataFrame 的列名。
3.5 names
: 为多级索引命名
如果使用了 keys
创建了多级索引,可以使用 names
参数为多级索引的每个级别指定名称。这提高了结果的可读性。
names
参数接受一个列表,列表的长度应与多级索引的级别数相同。
示例 (names
):
“`python
result_names = pd.concat(frames, keys=[‘File1’, ‘File2’, ‘File3’],
names=[‘Source File’, ‘Original Index’])
print(“\n— Concat with keys and names (axis=0) —“)
print(result_names)
result_names_axis1 = pd.concat([df_part1, df_part2], axis=1, keys=[‘Set1’, ‘Set2’],
names=[‘Data Source’, ‘Column Name’])
print(“\n— Concat with keys and names (axis=1) —“)
print(result_names_axis1)
“`
输出解释:
在打印的结果中,可以看到多级索引的每个级别都有了指定的名称(例如 ‘Source File’ 和 ‘Original Index’),这让索引的含义更加清晰。
3.6 sort
: 对非连接轴进行排序
当 axis=1
且输入 DataFrames 的列不完全相同时(即 join='outer'
),或者当 axis=0
且输入 DataFrames 的列不完全相同时,sort
参数控制结果中非连接轴(对于 axis=1
是列,对于 axis=0
是列)的排序方式。
sort=False
(默认值): 不对非连接轴进行排序。列/行 的顺序通常取决于输入 DataFrames 的顺序和发现新列/行索引的顺序。sort=True
: 对非连接轴进行排序。对于axis=0
,列会按字母顺序排序;对于axis=1
,列会按字母顺序排序。
示例 (sort
):
“`python
df_a = pd.DataFrame({‘C’: [1, 2], ‘A’: [3, 4]})
df_b = pd.DataFrame({‘B’: [5, 6], ‘A’: [7, 8]})
axis=0, join=’outer’ (default). 列的顺序?
result_sort_false = pd.concat([df_a, df_b], axis=0, join=’outer’, sort=False)
print(“\n— Concat with sort=False (axis=0) —“)
print(result_sort_false)
axis=0, join=’outer’, sort=True. 列按字母排序
result_sort_true = pd.concat([df_a, df_b], axis=0, join=’outer’, sort=True)
print(“\n— Concat with sort=True (axis=0) —“)
print(result_sort_true)
axis=1. 列的顺序?
df_c = pd.DataFrame({‘X’: [9, 10]}, index=[0, 1])
df_d = pd.DataFrame({‘Y’: [11, 12]}, index=[0, 1])
result_sort_false_axis1 = pd.concat([df_c, df_d], axis=1, sort=False)
print(“\n— Concat with sort=False (axis=1) —“)
print(result_sort_false_axis1)
result_sort_true_axis1 = pd.concat([df_c, df_d], axis=1, sort=True)
print(“\n— Concat with sort=True (axis=1) —“)
print(result_sort_true_axis1)
“`
输出解释:
- 当
sort=False
时,列的顺序可能不是字母顺序,而是按照它们在输入 DataFrame 中出现的顺序以及首次被发现的顺序。 - 当
sort=True
时,无论是axis=0
还是axis=1
,结果 DataFrame 的列都会按照字母顺序排列。在旧版本的 Pandas 中,sort
默认为True
并会在没有排序时发出警告;在新版本中默认为False
以提升性能。
3.7 verify_integrity
: 验证索引完整性
如果担心连接操作会产生重复的索引(当 axis=0
且 ignore_index=False
时,或者当 axis=1
且输入 DataFrame 的索引有重叠时),可以使用 verify_integrity=True
。如果结果中存在重复索引,concat
将抛出 ValueError
。
示例 (verify_integrity
):
“`python
df_dup_index1 = pd.DataFrame({‘A’: [1, 2]}, index=[0, 1])
df_dup_index2 = pd.DataFrame({‘B’: [3, 4]}, index=[0, 1]) # 与 df_dup_index1 索引重复
默认情况下,会创建重复索引,不会报错
result_dup = pd.concat([df_dup_index1, df_dup_index2])
print(“\n— Concat with duplicate index (verify_integrity=False) —“)
print(result_dup)
使用 verify_integrity=True 会报错
try:
pd.concat([df_dup_index1, df_dup_index2], verify_integrity=True)
except ValueError as e:
print(“\n— Concat with verify_integrity=True and duplicate index —“)
print(f”Caught expected error: {e}”)
axis=1 时,如果行索引重复,也会报错
df_dup_cols1 = pd.DataFrame({‘A’: [1, 2]}, index=[0, 1])
df_dup_cols2 = pd.DataFrame({‘B’: [3, 4]}, index=[0, 1]) # 与 df_dup_cols1 索引重复
try:
pd.concat([df_dup_cols1, df_dup_cols2], axis=1, verify_integrity=True)
except ValueError as e:
print(“\n— Concat with verify_integrity=True and duplicate row index (axis=1) —“)
print(f”Caught expected error: {e}”)
“`
输出解释:
当 verify_integrity=True
且连接操作导致索引重复时,Pandas 会抛出 ValueError
。这是一种非常有用的调试工具,可以帮助您在早期阶段发现潜在的数据问题(例如,意外的重复记录或不唯一的标识符)。
4. Concat vs Merge vs Join
虽然 concat
、merge
和 DataFrame 的 .join()
方法都可以用来组合 DataFrame,但它们的设计目的和使用场景有所不同:
concat
: 专注于沿着特定轴将多个 Pandas 对象简单地堆叠或并排拼接。它主要关注索引的对齐(当axis=1
或join
不是 ‘outer’ 时),或者仅仅是追加行/列(当axis=0, join='outer', ignore_index=True
时)。适用于合并具有相同列结构但不同行(例如,来自不同文件或时间段的数据)的 DataFrame,或者具有相同行索引但不同列的 DataFrame。merge
: 专注于基于一个或多个关键列(或索引)的值进行合并。它类似于 SQL 的 JOIN 操作(INNER JOIN, LEFT JOIN, RIGHT JOIN, OUTER JOIN)。适用于组合来自不同来源、但通过某个共同标识符关联的数据集(例如,订单数据和客户数据通过客户ID关联)。.join()
: 是 DataFrame 的一个方法,通常是merge
的一个便捷版本,默认为基于索引进行合并。适用于将一个 DataFrame 与另一个 DataFrame 基于它们的索引进行合并。
总结:
- 如果你想堆叠或并排粘贴数据,使用
concat
。 - 如果你想基于列值匹配来组合数据,使用
merge
。 - 如果你想基于索引匹配来组合数据(通常用于添加列),使用 DataFrame 的
.join()
方法,或者使用pd.merge(left, right, left_index=True, right_index=True, ...)
,或者使用pd.concat([left, right], axis=1, join=...)
(取决于对齐和连接需求)。
理解这三者之间的区别,是选择正确工具、高效进行数据组合的关键。
5. Concat 的进阶应用与注意事项
-
连接 Series:
concat
也可以用于连接 Series。当连接 Series 时,它们会被视为只有一列的 DataFrame(如果axis=0
),或者一行(如果axis=1
,但这种情况较少见)。
“`python
s1 = pd.Series([0, 1, 2, 3], index=[‘A’, ‘B’, ‘C’, ‘D’])
s2 = pd.Series([4, 5, 6], index=[‘B’, ‘C’, ‘E’])result_concat_series = pd.concat([s1, s2])
print(“\n— Concat Series (axis=0) —“)
print(result_concat_series)沿着列轴连接 Series (结果是一个 DataFrame)
result_concat_series_axis1 = pd.concat([s1, s2], axis=1, sort=False)
print(“\n— Concat Series (axis=1) —“)
print(result_concat_series_axis1)
* **性能考虑:** 当需要连接大量(例如几千个)DataFrame 时,将它们收集到一个列表中,然后一次性调用 `pd.concat()` 比在循环中反复调用 `concat`(或 `.append()`, `.append()` 方法效率很低且已被标记为弃用)要高效得多。
python避免这样做 (效率低)
final_df = pd.DataFrame()
for chunk in read_data_in_chunks():
final_df = pd.concat([final_df, chunk]) # 非常慢
推荐这样做 (效率高)
list_of_dfs = []
for chunk in read_data_in_chunks():
list_of_dfs.append(chunk)
final_df = pd.concat(list_of_dfs)
``
concat
* **数据类型:**会尝试保留原始数据类型。如果合并的 DataFrame 在同一列中包含不同的数据类型(例如,一个包含整数,另一个包含字符串),Pandas 会向上转型以找到一个兼容的数据类型,这可能导致结果列的数据类型变成
object,影响后续的数值计算。在使用
concat后,检查结果的数据类型是一个好习惯。
concat
* **索引的含义:** 在使用时,特别是
axis=1或
ignore_index=False时,要清楚结果 DataFrame 索引的含义。它们是原始 DataFrame 索引的组合。如果原始索引不唯一或没有逻辑关系,考虑使用
ignore_index=True或通过
keys` 参数创建有意义的多级索引。
6. 总结
pandas.concat()
是 Pandas 中一个强大且灵活的数据组合函数。它提供了沿着指定轴(行或列)拼接多个 Pandas 对象的能力,并提供了 axis
, join
, ignore_index
, keys
, names
, sort
, verify_integrity
等丰富的参数来精细控制连接的行为。
通过本文的详细介绍和示例,您应该对 concat
的基本用法、核心参数及其在不同场景下的应用有了深入的理解。掌握 concat
与 merge
和 .join()
之间的区别,并根据实际需求选择最合适的工具,将极大地提升您在 Pandas 中的数据处理效率和能力。
记住,数据处理的艺术在于理解工具的原理,并在实践中不断尝试和优化。勤加练习,您就能在数据合并与拼接的任务中游刃有余。