Pandas concat
入门:轻松合并多个 DataFrame
在数据处理的世界里,整合来自不同源的数据是一项核心任务。想象一下,你需要分析一年的销售数据,而这些数据分散在每个月的单独文件中;或者你需要合并两个不同的数据集,一个包含客户的基本信息,另一个包含他们的购买记录。在这些场景中,将多个数据集有效地组合成一个统一的视图是进行后续分析的基础。
Pandas 库是 Python 中进行数据分析和处理的瑞士军刀,它提供了多种强大的工具来应对这类合并任务。其中,pandas.concat()
函数是用于沿轴(axis)方向连接或“堆叠” Pandas 对象的灵活工具,无论是将多个 DataFrame 首尾相连(按行合并),还是将它们并排放置(按列合并),concat
都能胜任。
本文将带你深入了解 pandas.concat()
函数,从最基本的用法到高级参数,通过丰富的代码示例,帮助你轻松掌握如何利用它来合并多个 DataFrame,让你的数据整合工作变得更加简单高效。
为什么需要 concat
?
在深入细节之前,让我们先明确 concat
的作用和它解决的问题。
数据通常不是以一个完美的、单一的大文件形式出现的。你可能会遇到以下情况:
- 数据分块存储: 数据因为量太大或获取方式的原因,被分成多个文件(例如,每月的数据一个文件,每天的数据一个文件)。
- 不同来源的数据: 你从不同的数据库、API 或 Excel 表格中获取了相关但不完全相同的数据集。
- 特征分散: 数据的特征(列)被分散存储在不同的 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)
看起来参数很多?别担心,对于入门而言,最核心的参数只有几个:objs
、axis
、join
和 ignore_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
“`
可以看到,默认情况下,concat
将 df2
的行添加到了 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
中,df1
和 df3
的索引都是 [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’]),并在缺失处填充 NaN
。inner
只保留所有 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 都拥有的行索引。由于 df1
和 df4
没有共同的行索引,inner
合并的结果是一个空的 DataFrame。
理解 axis
和 join
如何相互作用是掌握 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 的方法,这可能会让初学者感到困惑。理解 concat
与 merge
/join
和 append
的区别非常重要。
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
更好。
对于初学者,专注于掌握 concat
的 axis
和 join
参数,以及 merge
的 on
和 how
参数,就能应对绝大多数的合并场景。
concat
的一些注意事项和最佳实践
- 数据类型: 合并不同数据类型的列时,结果列的数据类型可能会发生变化(例如,整数和浮点数合并为浮点数,数字和字符串合并为对象)。合并前检查和统一数据类型可以避免意外。
- 性能: 当合并大量(例如几百或几千个)小型 DataFrame 时,每次
concat
都会涉及一些开销。将所有 DataFrame 收集到一个列表或字典中,然后一次性调用pd.concat()
通常比循环调用concat
将新的 DataFrame 添加到现有 DataFrame 中效率更高。 - 索引管理: 仔细考虑是否需要保留原始索引。如果原始索引不具有唯一性或意义,使用
ignore_index=True
是一个好选择,可以生成干净的从 0 开始的索引。 - 使用
keys
追溯来源: 如果合并的数据来自不同的源(文件、数据库表等),使用keys
参数可以帮助你轻松地追溯每一行或每一列数据的来源,这对于数据清洗和验证非常有帮助。 - 内存使用: 合并大型 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
(重置索引)等核心参数,你可以轻松地完成各种数据整合任务,无论是堆叠多个文件的数据,还是将不同特征集组合在一起。
虽然 concat
和 merge
/join
都能用于组合数据,但它们解决的问题和工作方式不同:concat
基于索引或位置堆叠,而 merge
基于关键列值关联。理解它们各自的适用场景是高效数据处理的关键。
通过本文的详细讲解和示例,希望你已经对 pandas.concat()
有了深入的了解,并能够自信地运用它来处理你的数据合并需求。记住,多实践是掌握任何工具的最好方法。现在,就开始使用 concat
来组织和整合你的数据集吧!