Pandas concat 入门:轻松合并多个 DataFrame – wiki基地


Pandas concat 入门:轻松合并多个 DataFrame

在数据处理的世界里,整合来自不同源的数据是一项核心任务。想象一下,你需要分析一年的销售数据,而这些数据分散在每个月的单独文件中;或者你需要合并两个不同的数据集,一个包含客户的基本信息,另一个包含他们的购买记录。在这些场景中,将多个数据集有效地组合成一个统一的视图是进行后续分析的基础。

Pandas 库是 Python 中进行数据分析和处理的瑞士军刀,它提供了多种强大的工具来应对这类合并任务。其中,pandas.concat() 函数是用于沿轴(axis)方向连接或“堆叠” Pandas 对象的灵活工具,无论是将多个 DataFrame 首尾相连(按行合并),还是将它们并排放置(按列合并),concat 都能胜任。

本文将带你深入了解 pandas.concat() 函数,从最基本的用法到高级参数,通过丰富的代码示例,帮助你轻松掌握如何利用它来合并多个 DataFrame,让你的数据整合工作变得更加简单高效。

为什么需要 concat

在深入细节之前,让我们先明确 concat 的作用和它解决的问题。

数据通常不是以一个完美的、单一的大文件形式出现的。你可能会遇到以下情况:

  1. 数据分块存储: 数据因为量太大或获取方式的原因,被分成多个文件(例如,每月的数据一个文件,每天的数据一个文件)。
  2. 不同来源的数据: 你从不同的数据库、API 或 Excel 表格中获取了相关但不完全相同的数据集。
  3. 特征分散: 数据的特征(列)被分散存储在不同的 DataFrame 中,但它们描述的是同一批实体(行)。

concat 就是用来处理这些场景的利器。它可以将这些分散的 Pandas Series 或 DataFrame 对象沿着指定的轴(行或列)简单地堆叠起来,形成一个更大的对象。

与 Pandas 中另一个常用的合并函数 merge/join 不同,concat 主要基于 索引位置 进行合并,而 merge/join 是基于 列的关键值 进行合并(类似于 SQL 中的 JOIN 操作)。理解这两者的区别至关重要,这将帮助你在不同的合并需求中选择最合适的工具。本文专注于 concat

pandas.concat() 的基本用法

pandas.concat() 函数的基本语法如下:

python
pandas.concat(objs, axis=0, join='outer', ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False, sort=False, copy=True)

看起来参数很多?别担心,对于入门而言,最核心的参数只有几个:objsaxisjoinignore_index。我们将逐一详细讲解。

1. objs 参数:要合并的对象

这是唯一一个必须提供的参数。objs 参数接收一个 Pandas Series 或 DataFrame 对象的列表(list)或字典(dict)。通常,你会传入一个包含多个 DataFrame 的列表。

“`python
import pandas as pd

创建两个简单的 DataFrame

df1 = pd.DataFrame({
‘A’: [‘A0’, ‘A1’],
‘B’: [‘B0’, ‘B1’]},
index=[0, 1])

df2 = pd.DataFrame({
‘A’: [‘A2’, ‘A3’],
‘B’: [‘B2’, ‘B3’]},
index=[2, 3])

print(“df1:”)
print(df1)
print(“\ndf2:”)
print(df2)

使用 concat 合并

result = pd.concat([df1, df2])

print(“\n合并结果 (默认参数):”)
print(result)
“`

输出:

“`
df1:
A B
0 A0 B0
1 A1 B1

df2:
A B
2 A2 B2
3 A3 B3

合并结果 (默认参数):
A B
0 A0 B0
1 A1 B1
2 A2 B2
3 A3 B3
“`

可以看到,默认情况下,concatdf2 的行添加到了 df1 的末尾。这就是沿着行方向(垂直方向)的合并。

2. axis 参数:合并方向

axis 参数决定了沿着哪个轴进行合并。它是理解 concat 功能的关键。

  • axis=0 (默认值): 沿着行方向合并。这意味着将第二个 DataFrame 的行添加到第一个 DataFrame 的末尾,依此类推。列会根据列名进行对齐。如果某些列只存在于部分 DataFrame 中,缺失的值将用 NaN(Not a Number)填充。
  • axis=1: 沿着列方向合并。这意味着将第二个 DataFrame 的列添加到第一个 DataFrame 的右侧,依此类推。行会根据索引进行对齐。如果某些行索引只存在于部分 DataFrame 中,缺失的值将用 NaN 填充。

让我们看一个 axis=1 的例子:

“`python

创建另外两个 DataFrame,注意它们的索引

df3 = pd.DataFrame({
‘C’: [‘C0’, ‘C1’],
‘D’: [‘D0’, ‘D1’]},
index=[0, 1]) # 索引与 df1 相同

df4 = pd.DataFrame({
‘E’: [‘E2’, ‘E3’],
‘F’: [‘F2’, ‘F3’]},
index=[2, 3]) # 索引与 df2 相同

print(“df3:”)
print(df3)
print(“\ndf4:”)
print(df4)

沿着列方向合并 df1 和 df3 (它们有相同的索引)

result_axis1_1 = pd.concat([df1, df3], axis=1)

print(“\n合并 df1 和 df3 (axis=1):”)
print(result_axis1_1)

沿着列方向合并 df1 和 df4 (它们的索引不同)

result_axis1_2 = pd.concat([df1, df4], axis=1)

print(“\n合并 df1 和 df4 (axis=1):”)
print(result_axis1_2)
“`

输出:

“`
df3:
C D
0 C0 D0
1 C1 D1

df4:
E F
2 E2 F2
3 E3 F3

合并 df1 和 df3 (axis=1):
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1

合并 df1 和 df4 (axis=1):
A B E F
0 A0 B0 NaN NaN
1 A1 B1 NaN NaN
2 NaN NaN E2 F2
3 NaN NaN E3 F3
“`

axis=1 时,concat 会尝试根据 行索引 来对齐数据。在 result_axis1_1 中,df1df3 的索引都是 [0, 1],所以它们完美地对齐并排在一起。在 result_axis1_2 中,df1 的索引是 [0, 1]df4 的索引是 [2, 3]concat 包含了所有出现的索引 [0, 1, 2, 3],并在没有对应数据的地方用 NaN 填充。这就是 axis=1 的默认行为,它依赖于索引。

3. join 参数:处理不完全匹配的索引/列

当沿着某个轴合并时,如果参与合并的 DataFrame 在另一个轴上的索引(axis=0 时看列索引,axis=1 时看行索引)不完全一致,join 参数就起作用了。它决定了如何处理这些不匹配的情况。

  • join='outer' (默认值): 这是“并集”操作。合并结果的索引(axis=0 时是列索引,axis=1 时是行索引)将包含所有参与合并的 DataFrame 中出现过的所有索引值。缺失的数据位置将用 NaN 填充。
  • join='inner': 这是“交集”操作。合并结果的索引将只包含所有参与合并的 DataFrame 中都存在的索引值。

让我们看几个例子:

axis=0 结合 join (处理列不匹配):

“`python
df5 = pd.DataFrame({
‘A’: [‘A4’, ‘A5’],
‘B’: [‘B4’, ‘B5’],
‘C’: [‘C4’, ‘C5’]}, # df5 多了一列 ‘C’
index=[4, 5])

df6 = pd.DataFrame({
‘B’: [‘B6’, ‘B7’], # df6 只有列 ‘B’
‘D’: [‘D6’, ‘D7’]}, # df6 多了一列 ‘D’
index=[6, 7])

print(“df5:”)
print(df5)
print(“\ndf6:”)
print(df6)

axis=0, join=’outer’ (默认): 合并所有列

result_outer_cols = pd.concat([df5, df6], axis=0, join=’outer’)
print(“\n合并 df5 和 df6 (axis=0, join=’outer’):”)
print(result_outer_cols)

axis=0, join=’inner’: 只保留共同的列

result_inner_cols = pd.concat([df5, df6], axis=0, join=’inner’)
print(“\n合并 df5 和 df6 (axis=0, join=’inner’):”)
print(result_inner_cols)
“`

输出:

“`
df5:
A B C
4 A4 B4 C4
5 A5 B5 C5

df6:
B D
6 B6 D6
7 B7 D7

合并 df5 和 df6 (axis=0, join=’outer’):
A B C D
4 A4 B4 C4 NaN
5 A5 B5 C5 NaN
6 NaN B6 NaN D6
7 NaN B7 NaN D7

合并 df5 和 df6 (axis=0, join=’inner’):
B
4 B4
5 B5
6 B6
7 B7
“`

axis=0 时,join 参数控制合并结果的outer 保留所有 DataFrame 中出现过的列([‘A’, ‘B’, ‘C’, ‘D’]),并在缺失处填充 NaNinner 只保留所有 DataFrame 都拥有的列(只有 ‘B’)。

axis=1 结合 join (处理行不匹配):

我们回顾 df1 (索引 [0, 1]) 和 df4 (索引 [2, 3]) 的例子。默认是 join='outer'

“`python
print(“df1:”)
print(df1)
print(“\ndf4:”)
print(df4)

axis=1, join=’outer’ (默认): 合并所有行索引

result_outer_rows = pd.concat([df1, df4], axis=1, join=’outer’)
print(“\n合并 df1 和 df4 (axis=1, join=’outer’):”)
print(result_outer_rows)

axis=1, join=’inner’: 只保留共同的行索引

result_inner_rows = pd.concat([df1, df4], axis=1, join=’inner’)
print(“\n合并 df1 和 df4 (axis=1, join=’inner’):”)
print(result_inner_rows)
“`

输出:

“`
df1:
A B
0 A0 B0
1 A1 B1

df4:
E F
2 E2 F2
3 E3 F3

合并 df1 和 df4 (axis=1, join=’outer’):
A B E F
0 A0 B0 NaN NaN
1 A1 B1 NaN NaN
2 NaN NaN E2 F2
3 NaN NaN E3 F3

合并 df1 和 df4 (axis=1, join=’inner’):
Empty DataFrame
Columns: [A, B, E, F]
Index: []
“`

axis=1 时,join 参数控制合并结果的行索引outer 保留所有 DataFrame 中出现过的行索引([0, 1, 2, 3])。inner 只保留所有 DataFrame 都拥有的行索引。由于 df1df4 没有共同的行索引,inner 合并的结果是一个空的 DataFrame。

理解 axisjoin 如何相互作用是掌握 concat 的关键。记住:axis 决定了堆叠的方向,join 决定了如何在另一个轴上处理不匹配的索引/列。

4. ignore_index 参数:重置索引

axis=0 的合并中,默认行为是保留原始 DataFrame 的索引。如果原始索引不唯一或你不想保留它们,可以使用 ignore_index=True 参数。这会创建一个新的、从 0 开始的顺序整数索引,完全忽略原始索引。

“`python
df1_same_index = pd.DataFrame({
‘A’: [‘A0’, ‘A1’],
‘B’: [‘B0’, ‘B1’]},
index=[‘a’, ‘b’]) # 使用字母索引

df2_same_index = pd.DataFrame({
‘A’: [‘A2’, ‘A3’],
‘B’: [‘B2’, ‘B3’]},
index=[‘a’, ‘c’]) # 使用部分相同、部分不同的字母索引

print(“df1_same_index:”)
print(df1_same_index)
print(“\ndf2_same_index:”)
print(df2_same_index)

默认合并 (保留索引)

result_default_index = pd.concat([df1_same_index, df2_same_index])
print(“\n合并结果 (默认保留索引):”)
print(result_default_index)

使用 ignore_index=True

result_ignore_index = pd.concat([df1_same_index, df2_same_index], ignore_index=True)
print(“\n合并结果 (ignore_index=True):”)
print(result_ignore_index)
“`

输出:

“`
df1_same_index:
A B
a A0 B0
b A1 B1

df2_same_index:
A B
a A2 B2
c A3 B3

合并结果 (默认保留索引):
A B
a A0 B0
b A1 B1
a A2 B2 # 索引 ‘a’ 重复了
c A3 B3

合并结果 (ignore_index=True):
A B
0 A0 B0
1 A1 B1
2 A2 B2
3 A3 B3
“`

默认情况下,索引 a 在合并结果中出现了两次。如果你的分析依赖于唯一的索引,这可能会导致问题。使用 ignore_index=True 生成了一个全新的、唯一的整数索引,这是处理按行合并多个数据文件时非常常见的需求。

5. keys 参数:创建分层索引

当合并多个 DataFrame 时,有时你需要知道每一行或每一列是来自哪个原始 DataFrame。keys 参数允许你在合并结果中创建一个额外的索引层(MultiIndex),用来标识数据的来源。这个参数在你合并大量来自不同文件或分类的数据时特别有用。

keys 参数接受一个列表,列表中的每个元素将作为对应的 DataFrame 在新的 MultiIndex 中的标签。

“`python
df_north = pd.DataFrame({
‘Sales’: [100, 150],
‘Profit’: [20, 30]},
index=[‘Jan’, ‘Feb’])

df_south = pd.DataFrame({
‘Sales’: [120, 160, 180],
‘Profit’: [25, 35, 40]},
index=[‘Jan’, ‘Feb’, ‘Mar’])

print(“df_north:”)
print(df_north)
print(“\ndf_south:”)
print(df_south)

使用 keys 参数合并,并用区域名称作为 key

result_with_keys = pd.concat([df_north, df_south], keys=[‘North’, ‘South’])

print(“\n合并结果 (使用 keys 创建分层索引):”)
print(result_with_keys)
print(“\n合并结果的索引类型:”, type(result_with_keys.index))
“`

输出:

“`
df_north:
Sales Profit
Jan 100 20
Feb 150 30

df_south:
Sales Profit
Jan 120 25
Feb 160 35
Mar 180 40

合并结果 (使用 keys 创建分层索引):
Sales Profit
North Jan 100 20
Feb 150 30
South Jan 120 25
Feb 160 35
Mar 180 40

合并结果的索引类型:
“`

结果的索引现在有两层:第一层是我们在 keys 中提供的标签 (‘North’, ‘South’),第二层是原始 DataFrame 的索引 (‘Jan’, ‘Feb’, ‘Mar’)。这形成了一个分层索引(MultiIndex),你可以方便地通过这些标签来选择数据,例如 result_with_keys.loc['North'] 将会返回来自 ‘North’ 的所有数据。

如果沿着 axis=1 合并并使用 keys,将会在列方向上创建分层索引:

“`python
df_info = pd.DataFrame({
‘Name’: [‘Alice’, ‘Bob’],
‘Age’: [25, 30]},
index=[101, 102])

df_contact = pd.DataFrame({
‘Email’: [‘[email protected]’, ‘[email protected]’],
‘Phone’: [‘111-111’, ‘222-222’]},
index=[101, 102])

沿着列方向合并,并使用 keys

result_keys_axis1 = pd.concat([df_info, df_contact], axis=1, keys=[‘Info’, ‘Contact’])

print(“\n合并 df_info 和 df_contact (axis=1, 使用 keys):”)
print(result_keys_axis1)
print(“\n合并结果的列索引类型:”, type(result_keys_axis1.columns))
“`

输出:

“`
df_info:
Name Age
101 Alice 25
102 Bob 30

df_contact:
Email Phone
101 [email protected] 111-111
102 [email protected] 222-222

合并 df_info 和 df_contact (axis=1, 使用 keys):
Info Contact
Name Age Email Phone
101 Alice 25 [email protected] 111-111
102 Bob 30 [email protected] 222-222

合并结果的列索引类型:
“`

在这种情况下,列索引变成了分层索引,第一层是 ‘Info’ 和 ‘Contact’,第二层是原始列名。你可以使用 result_keys_axis1['Info'] 来访问 ‘Info’ 下的所有列。

6. names 参数:命名 MultiIndex 的层级

当使用 keys 创建分层索引(MultiIndex)时,你可以使用 names 参数为 MultiIndex 的不同层级指定名称。names 参数接收一个列表,其长度应与 MultiIndex 的层级数相同。

“`python

沿用上面的 result_with_keys 结果

result_with_names = pd.concat([df_north, df_south], keys=[‘North’, ‘South’], names=[‘Region’, ‘Month’])

print(“\n合并结果 (使用 keys 和 names):”)
print(result_with_names)
print(“\nMultiIndex 的层级名称:”, result_with_names.index.names)
“`

输出:

“`
合并结果 (使用 keys 和 names):
Sales Profit
Region Month
North Jan 100 20
Feb 150 30
South Jan 120 25
Feb 160 35
Mar 180 40

MultiIndex 的层级名称: [‘Region’, ‘Month’]
“`

现在 MultiIndex 的层级有了名字 (‘Region’, ‘Month’),这提高了代码的可读性,并且在某些操作中(如 groupby(level='Region'))非常有用。

7. verify_integrity 参数:检查索引重复

默认情况下,concat 会保留原始索引,即使这导致结果中出现重复的索引值。如果你希望确保合并后的索引是唯一的,可以使用 verify_integrity=True。如果存在重复的索引,Pandas 将会抛出一个 ValueError

“`python
df_a = pd.DataFrame({‘data’: [1, 2]}, index=[‘x’, ‘y’])
df_b = pd.DataFrame({‘data’: [3, 4]}, index=[‘y’, ‘z’]) # 索引 ‘y’ 重复

print(“df_a:”)
print(df_a)
print(“\ndf_b:”)
print(df_b)

默认合并 (允许重复索引)

result_repeat_index = pd.concat([df_a, df_b])
print(“\n合并结果 (允许重复索引):”)
print(result_repeat_index)

使用 verify_integrity=True (会报错)

try:
pd.concat([df_a, df_b], verify_integrity=True)
except ValueError as e:
print(f”\n使用 verify_integrity=True 发生错误: {e}”)

如果你想合并但同时确保索引唯一,并且不希望报错,可以先 ignore_index

result_ignore_and_unique = pd.concat([df_a, df_b], ignore_index=True)
print(“\n合并结果 (ignore_index=True 总是唯一索引):”)
print(result_ignore_and_unique)
“`

输出:

“`
df_a:
data
x 1
y 2

df_b:
data
y 3
z 4

合并结果 (允许重复索引):
data
x 1
y 2
y 3
z 4

使用 verify_integrity=True 发生错误: Indexes have overlapping values: [‘y’]
“`

verify_integrity=True 是一个非常有用的调试工具,可以帮助你在合并过程中发现潜在的索引问题。

实际应用场景示例

掌握了 concat 的基本参数后,让我们看一些更贴近实际的应用场景。

场景 1: 合并多个文件中的同构数据

假设你每个月都有一个 CSV 文件记录销售数据,它们的列结构完全相同。你需要将它们合并成一个年度 DataFrame。

“`python
import os

模拟创建几个 CSV 文件

如果这些文件不存在,需要先创建它们

例如,创建 ‘sales_2023_01.csv’ 和 ‘sales_2023_02.csv’

data_jan = {‘Date’: [‘2023-01-01’, ‘2023-01-02’], ‘Amount’: [100, 150], ‘Region’: [‘North’, ‘South’]}
df_jan_save = pd.DataFrame(data_jan)
df_jan_save.to_csv(‘sales_2023_01.csv’, index=False)

data_feb = {‘Date’: [‘2023-02-01’, ‘2023-02-03’], ‘Amount’: [120, 180], ‘Region’: [‘South’, ‘North’]}
df_feb_save = pd.DataFrame(data_feb)
df_feb_save.to_csv(‘sales_2023_02.csv’, index=False)

读取所有 sales_2023_*.csv 文件并合并

file_list = [‘sales_2023_01.csv’, ‘sales_2023_02.csv’] # 假设文件列表已知
all_dataframes = []

for file in file_list:
if os.path.exists(file): # 检查文件是否存在
df = pd.read_csv(file)
all_dataframes.append(df)
print(f”已读取文件: {file}”)

if all_dataframes:
# 使用 concat 沿着行方向合并所有 DataFrame
# ignore_index=True 以便获得一个从0开始的连续新索引
annual_sales_df = pd.concat(all_dataframes, ignore_index=True)

print("\n年度销售数据合并结果:")
print(annual_sales_df)

# 清理创建的模拟文件
# os.remove('sales_2023_01.csv')
# os.remove('sales_2023_02.csv')

else:
print(“\n没有找到任何文件进行合并。”)
“`

这个例子展示了如何循环读取文件并将每个 DataFrame 添加到一个列表中,然后一次性使用 concat 合并列表中的所有 DataFrame。ignore_index=True 在这里通常是必要的,因为你可能不关心原始文件中的行号索引,而需要一个反映年度总数据量的新索引。

场景 2: 合并不同来源但描述同一对象的列

假设你有两个 DataFrame,一个包含用户 ID 和姓名,另一个包含相同的用户 ID 和他们的电子邮件地址。你需要将这些信息合并到同一个 DataFrame 中。

“`python
df_users = pd.DataFrame({
‘UserID’: [101, 102, 103],
‘Name’: [‘Alice’, ‘Bob’, ‘Charlie’]
})

df_emails = pd.DataFrame({
‘UserID’: [101, 103, 104], # 注意有一个用户 104 只在邮箱数据中出现
‘Email’: [‘[email protected]’, ‘[email protected]’, ‘[email protected]’]
})

print(“df_users:”)
print(df_users)
print(“\ndf_emails:”)
print(df_emails)

注意:这里如果直接使用 concat(axis=1),它会根据默认的整数索引对齐,这不是我们想要的。

对于这种基于某个 key (UserID) 的合并,更推荐使用 merge。

但是,如果我们先将 UserID 设置为索引,就可以使用 concat(axis=1)

df_users_indexed = df_users.set_index(‘UserID’)
df_emails_indexed = df_emails.set_index(‘UserID’)

print(“\ndf_users (以 UserID 为索引):”)
print(df_users_indexed)
print(“\ndf_emails (以 UserID 为索引):”)
print(df_emails_indexed)

使用 concat(axis=1) 基于索引合并

join=’outer’ 保留所有 UserID,join=’inner’ 只保留共同的 UserID

result_concat_indexed = pd.concat([df_users_indexed, df_emails_indexed], axis=1, join=’outer’)

print(“\n合并结果 (以 UserID 为索引, axis=1, join=’outer’):”)
print(result_concat_indexed)

我们可以选择重置索引,将 UserID 变回列

result_concat_indexed_reset = result_concat_indexed.reset_index()
print(“\n合并结果 (重置索引):”)
print(result_concat_indexed_reset)

再次强调:对于这种基于 Key 的合并,merge 通常更直观和强大

result_merge = pd.merge(df_users, df_emails, on=’UserID’, how=’outer’)

print(“\n使用 merge 合并结果 (更常用):”)

print(result_merge)

“`

输出:

“`
df_users:
UserID Name
0 101 Alice
1 102 Bob
2 103 Charlie

df_emails:
UserID Email
0 101 [email protected]
1 103 [email protected]
2 104 [email protected]

df_users (以 UserID 为索引):
Name
UserID
101 Alice
102 Bob
103 Charlie

df_emails (以 UserID 为索引):
Email
UserID
101 [email protected]
103 [email protected]
104 [email protected]

合并结果 (以 UserID 为索引, axis=1, join=’outer’):
Name Email
UserID
101 Alice [email protected]
102 Bob NaN
103 Charlie [email protected]
104 NaN [email protected]

合并结果 (重置索引):
UserID Name Email
0 101 Alice [email protected]
1 102 Bob NaN
2 103 Charlie [email protected]
3 104 NaN [email protected]
“`

这个例子展示了 concat(axis=1) 是如何依赖于索引来对齐数据的。因此,如果你想根据某个 ID 列进行列合并,你需要先将该 ID 列设置为索引,然后使用 concat(axis=1),最后可以选择重置索引。

重要提示: 尽管可以使用 concat(axis=1) 结合设置索引来实现基于 key 的合并,但 Pandas 提供了专门用于此类任务的 merge 函数,它通常更灵活和直观(例如,你可以指定左右 DataFrame 的不同 key 列)。concat 更擅长的是简单地堆叠数据,无论是在行方向(即使列不同)还是列方向(即使行索引不同),尤其适合于那些在另一个轴上没有明确 key 进行关联的场景,或者当索引本身就是关联的关键时。

场景 3: 使用字典进行合并

objs 参数也可以接受一个字典。当使用字典时,字典的键会自动用作 keys 参数的值,在结果中创建分层索引。

“`python
data_2022 = {‘Sales’: [200, 250], ‘Profit’: [50, 60]}
df_2022 = pd.DataFrame(data_2022, index=[‘Q3’, ‘Q4’])

data_2023 = {‘Sales’: [220, 280, 300], ‘Profit’: [55, 70, 75]}
df_2023 = pd.DataFrame(data_2023, index=[‘Q1’, ‘Q2’, ‘Q3’])

print(“df_2022:”)
print(df_2022)
print(“\ndf_2023:”)
print(df_2023)

使用字典作为 objs 参数合并

result_from_dict = pd.concat({‘Year_2022’: df_2022, ‘Year_2023’: df_2023})

print(“\n合并结果 (使用字典 objs):”)
print(result_from_dict)
“`

输出:

“`
df_2022:
Sales Profit
Q3 200 50
Q4 250 60

df_2023:
Sales Profit
Q1 220 55
Q2 280 70
Q3 300 75

合并结果 (使用字典 objs):
Sales Profit
Year_2022 Q3 200 50
Q4 250 60
Year_2023 Q1 220 55
Q2 280 70
Q3 300 75
“`

字典的键 (‘Year_2022’, ‘Year_2023’) 自动成为了结果 MultiIndex 的第一层。这是一种方便的方式,可以在合并的同时为数据添加来源标签。

concat vs merge/join vs append

在 Pandas 中,有几种合并 DataFrame 的方法,这可能会让初学者感到困惑。理解 concatmerge/joinappend 的区别非常重要。

  • concat(): 侧重于简单地将多个对象堆叠在一起。它沿着某个轴进行连接,主要基于索引(对于 axis=1)或位置(对于 axis=0,如果 ignore_index=True)对齐数据。它不执行关系型数据库中的连接操作(如 JOIN on key)。它可以处理 Series 和 DataFrame。
  • merge(): 侧重于基于一个或多个列的关键值来组合 DataFrame,类似于数据库的 JOIN 操作。它会查找两个 DataFrame 中指定键列的匹配值,并根据匹配结果组合行。这是处理具有共同标识符(如用户 ID, 订单号等)的数据集时最常用的方法。
  • join(): 是 merge() 的一个方便方法,用于通过索引连接 DataFrame。df1.join(df2) 类似于 pd.merge(df1, df2, left_index=True, right_index=True, how='left')。它更简洁,但功能不如 merge() 强大。
  • append(): df1.append(df2)pd.concat([df1, df2], ignore_index=False) 的一个更简洁的别名。然而,append() 方法自 Pandas 1.4.0 版本起已被弃用,并将在未来版本中移除。 官方推荐使用 pd.concat() 代替 append()

总结:

  • 当你需要简单地将 DataFrame 垂直堆叠(行)或水平堆叠(列),并且对齐主要依赖于索引或只是简单追加时,使用 concat
  • 当你需要根据两个 DataFrame 中的共同列值进行关联和组合时,使用 merge
  • 当你需要根据 DataFrame 的索引进行关联时,可以考虑使用 join,但理解 merge 更好。

对于初学者,专注于掌握 concataxisjoin 参数,以及 mergeonhow 参数,就能应对绝大多数的合并场景。

concat 的一些注意事项和最佳实践

  1. 数据类型: 合并不同数据类型的列时,结果列的数据类型可能会发生变化(例如,整数和浮点数合并为浮点数,数字和字符串合并为对象)。合并前检查和统一数据类型可以避免意外。
  2. 性能: 当合并大量(例如几百或几千个)小型 DataFrame 时,每次 concat 都会涉及一些开销。将所有 DataFrame 收集到一个列表或字典中,然后一次性调用 pd.concat() 通常比循环调用 concat 将新的 DataFrame 添加到现有 DataFrame 中效率更高。
  3. 索引管理: 仔细考虑是否需要保留原始索引。如果原始索引不具有唯一性或意义,使用 ignore_index=True 是一个好选择,可以生成干净的从 0 开始的索引。
  4. 使用 keys 追溯来源: 如果合并的数据来自不同的源(文件、数据库表等),使用 keys 参数可以帮助你轻松地追溯每一行或每一列数据的来源,这对于数据清洗和验证非常有帮助。
  5. 内存使用: 合并大型 DataFrame 可能会占用大量内存。在处理非常大的数据集时,考虑使用 Dask 或 PySpark 等并行计算框架,或者在 Pandas 中分块处理。

常见问题和故障排除

  • 合并后出现大量 NaN 这通常是由于 axis=1 时行索引不匹配,或者 axis=0 时列名不匹配,并且使用了默认的 join='outer'。检查你的 DataFrame 的索引和列名是否符合预期。如果需要只保留共同的部分,考虑使用 join='inner'
  • 索引重复: 如果在 axis=0 合并后发现索引重复,并且你不希望这样,可能是因为你没有使用 ignore_index=True。如果你需要确保索引唯一且不想忽略原始索引,你需要在使用 concat 之前或之后处理索引,例如使用 drop_duplicates()groupby()verify_integrity=True 可以帮助你诊断是否存在重复索引。
  • 列顺序不一致: concat(axis=0) 会根据列名对齐。如果 DataFrame A 有列 [‘A’, ‘B’],DataFrame B 有列 [‘B’, ‘A’],它们合并后仍然会是 [‘A’, ‘B’] 的顺序(默认会按字母顺序排,或者保持第一个 DataFrame 的列顺序,但最终是按列名对齐)。如果你需要特定的列顺序,可以在合并后重新选择和排序列。
  • 数据类型错误或意外转换: 合并包含不同数据类型的列(例如,一列是整数,另一列包含字符串)会导致该列的数据类型变为更通用的类型(通常是 object)。在合并前确保需要合并的列具有兼容的数据类型,或者在合并后进行类型转换。

总结

pandas.concat() 是 Pandas 中一个功能强大且灵活的工具,用于沿着某个轴方向连接多个 Series 或 DataFrame 对象。通过掌握 objs(要合并的对象列表/字典)、axis(合并方向)、join(处理不匹配的索引/列)和 ignore_index(重置索引)等核心参数,你可以轻松地完成各种数据整合任务,无论是堆叠多个文件的数据,还是将不同特征集组合在一起。

虽然 concatmerge/join 都能用于组合数据,但它们解决的问题和工作方式不同:concat 基于索引或位置堆叠,而 merge 基于关键列值关联。理解它们各自的适用场景是高效数据处理的关键。

通过本文的详细讲解和示例,希望你已经对 pandas.concat() 有了深入的了解,并能够自信地运用它来处理你的数据合并需求。记住,多实践是掌握任何工具的最好方法。现在,就开始使用 concat 来组织和整合你的数据集吧!


发表评论

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

滚动至顶部