Pandas Concat:合并与拼接DataFrame指南 – wiki基地


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)。它将 df1df2df3 沿着行轴连接起来。结果 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_integritysort

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' 时,结果包含 df1df5 的所有列 (A, B, C, D, G)。对于 df1 中没有 G 列的位置,填充 NaN;对于 df5 中没有 C, D 列的位置,填充 NaN
  • join='inner' 时,结果只包含 df1df5 共有的列 (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' 时,结果包含 df1df6 的所有行索引 (0, 1, 2, 3)。对于某个行索引,如果某个 DataFrame 中没有数据,则填充 NaN
  • join='inner' 时,结果只包含 df1df6 共有的行索引 (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=0ignore_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

虽然 concatmerge 和 DataFrame 的 .join() 方法都可以用来组合 DataFrame,但它们的设计目的和使用场景有所不同:

  • concat: 专注于沿着特定轴将多个 Pandas 对象简单地堆叠或并排拼接。它主要关注索引的对齐(当 axis=1join 不是 ‘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=1ignore_index=False时,要清楚结果 DataFrame 索引的含义。它们是原始 DataFrame 索引的组合。如果原始索引不唯一或没有逻辑关系,考虑使用ignore_index=True或通过keys` 参数创建有意义的多级索引。

6. 总结

pandas.concat() 是 Pandas 中一个强大且灵活的数据组合函数。它提供了沿着指定轴(行或列)拼接多个 Pandas 对象的能力,并提供了 axis, join, ignore_index, keys, names, sort, verify_integrity 等丰富的参数来精细控制连接的行为。

通过本文的详细介绍和示例,您应该对 concat 的基本用法、核心参数及其在不同场景下的应用有了深入的理解。掌握 concatmerge.join() 之间的区别,并根据实际需求选择最合适的工具,将极大地提升您在 Pandas 中的数据处理效率和能力。

记住,数据处理的艺术在于理解工具的原理,并在实践中不断尝试和优化。勤加练习,您就能在数据合并与拼接的任务中游刃有余。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部