文章目录
- [Pandas 合并数据集:concat 和 append](#Pandas 合并数据集:concat 和 append)
-
- [回顾:NumPy 数组的拼接](#回顾:NumPy 数组的拼接)
- [使用 pd.concat 进行简单拼接](#使用 pd.concat 进行简单拼接)
-
- 重复索引
- 使用连接(Join)方式拼接
- [append 方法](#append 方法)
Pandas 合并数据集:concat 和 append

一些最有趣的数据研究来自于合并不同的数据源。
这些操作可以包括从非常简单的两个数据集的拼接,到更复杂的数据库式连接和合并,这些操作能够正确处理数据集之间的重叠部分。
Series
和 DataFrame
都是为这种操作设计的,Pandas 提供了函数和方法,使得这种数据整理变得快速且简单。
在这里,我们将首先介绍如何使用 pd.concat
函数对 Series
和 DataFrame
进行简单拼接;稍后我们会深入讲解 Pandas 中更复杂的内存合并和连接操作。
我们从标准的导入开始:
python
import pandas as pd
import numpy as np
为了方便起见,我们将定义一个函数,用于创建特定格式的 DataFrame
,这将在接下来的示例中非常有用。
python
# 快速创建DataFrame
def make_df(cols, ind):
"""快速创建一个DataFrame"""
data = {c: [str(c) + str(i) for i in ind]
for c in cols}
return pd.DataFrame(data, ind)
# 创建一个示例DataFrame
df1 = make_df(['A', 'B', 'C'], [1, 2])
df2 = make_df(['A', 'B', 'C'], [3, 4])
python
df1
| | A | B | C |
| 1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
---|
python
df2
| | A | B | C |
| 3 | A3 | B3 | C3 |
4 | A4 | B4 | C4 |
---|
python
make_df('ABC', range(3))
| | A | B | C |
| 0 | A0 | B0 | C0 |
| 1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
---|
此外,我们还将创建一个快速类,用于让我们能够并排显示多个 DataFrame
。该代码利用了特殊的 _repr_html_
方法,这是 IPython/Jupyter 用于实现其丰富对象显示的机制:
python
class display(object):
"""Display HTML representation of multiple objects"""
template = """<div style="float: left; padding: 10px;">
<p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}
</div>"""
def __init__(self, *args):
self.args = args
def _repr_html_(self):
return '\n'.join(self.template.format(a, eval(a)._repr_html_())
for a in self.args)
def __repr__(self):
return '\n\n'.join(a + '\n' + repr(eval(a))
for a in self.args)
随着我们在接下来的章节中继续讨论,这个类的用途将会变得更加清晰。
回顾:NumPy 数组的拼接
Series
和 DataFrame
对象的拼接行为类似于 NumPy 数组的拼接,可以通过 np.concatenate
函数实现,相关内容可参考 NumPy 数组基础。
回顾一下,使用该函数可以将两个或多个数组的内容合并为一个数组:
python
x = [1, 2, 3]
y = [4, 5, 6]
z = [7, 8, 9]
np.concatenate([x, y, z])
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
第一个参数是要连接的数组的列表或元组。
此外,对于多维数组,还可以使用 axis
关键字参数,指定沿哪个轴进行拼接:
python
x = [[1, 2],
[3, 4]]
np.concatenate([x, x], axis=1)
array([[1, 2, 1, 2],
[3, 4, 3, 4]])
使用 pd.concat 进行简单拼接
pd.concat
函数提供了类似于 np.concatenate
的语法,但包含了许多我们稍后会讨论的选项:
python
# Pandas v1.3.5 中的函数签名
pd.concat(objs, axis=0, join='outer', ignore_index=False, keys=None,
levels=None, names=None, verify_integrity=False,
sort=False, copy=True)
pd.concat
可以用于简单地拼接 Series
或 DataFrame
对象,就像 np.concatenate
可以用于简单地拼接数组一样:
python
ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
pd.concat([ser1, ser2])
1 A
2 B
3 C
4 D
5 E
6 F
dtype: object
它同样适用于拼接更高维度的对象,比如 DataFrame
:
python
df1 = make_df('AB', [1, 2])
df2 = make_df('AB', [3, 4])
display('df1', 'df2', 'pd.concat([df1, df2])')
df1
| | A | B |
| 1 | A1 | B1 |
2 | A2 | B2 |
---|
df2
| | A | B |
| 3 | A3 | B3 |
4 | A4 | B4 |
---|
pd.concat([df1, df2])
| | A | B |
| 1 | A1 | B1 |
| 2 | A2 | B2 |
| 3 | A3 | B3 |
4 | A4 | B4 |
---|
它的默认行为是在 DataFrame
内按行拼接(即 axis=0
)。
与 np.concatenate
类似,pd.concat
允许指定拼接所沿的轴。
请看下面的例子:
python
df3 = make_df('AB', [0, 1])
df4 = make_df('CD', [0, 1])
display('df3', 'df4', "pd.concat([df3, df4], axis='columns')")
df3
| | A | B |
| 0 | A0 | B0 |
1 | A1 | B1 |
---|
df4
| | C | D |
| 0 | C0 | D0 |
1 | C1 | D1 |
---|
pd.concat([df3, df4], axis='columns')
| | A | B | C | D |
| 0 | A0 | B0 | C0 | D0 |
1 | A1 | B1 | C1 | D1 |
---|
我们同样可以指定 axis=1
;这里我们使用了更直观的 axis='columns'
。
重复索引
np.concatenate
和 pd.concat
之间的一个重要区别是,Pandas 的拼接操作会保留索引 ,即使结果中会出现重复的索引!
请看下面这个简短的例子:
python
x = make_df('AB', [0, 1])
y = make_df('AB', [2, 3])
y.index = x.index # make indices match
display('x', 'y', 'pd.concat([x, y])')
x
| | A | B |
| 0 | A0 | B0 |
1 | A1 | B1 |
---|
y
| | A | B |
| 0 | A2 | B2 |
1 | A3 | B3 |
---|
pd.concat([x, y])
| | A | B |
| 0 | A0 | B0 |
| 1 | A1 | B1 |
| 0 | A2 | B2 |
1 | A3 | B3 |
---|
注意结果中重复的索引。
虽然在 DataFrame
中这是允许的,但这种结果通常并不是我们想要的。
pd.concat
为我们提供了几种处理这种情况的方法。
将重复索引视为错误
如果你希望在 pd.concat
的结果中索引没有重叠,可以设置 verify_integrity
参数为 True
。
这样拼接时如果出现重复索引,就会抛出异常。
下面是一个示例,为了更清晰,我们会捕获并打印错误信息:
python
try:
pd.concat([x, y], verify_integrity=True)
except ValueError as e:
print("ValueError:", e)
ValueError: Indexes have overlapping values: Index([0, 1], dtype='int64')
忽略索引
有时候索引本身并不重要,你可能更希望直接忽略它。
可以通过设置 ignore_index
参数来实现这一点。
当该参数设为 True
时,拼接后的结果会为新的 DataFrame
创建一个新的整数索引:
python
display('x', 'y', 'pd.concat([x, y], ignore_index=True)')
x
| | A | B |
| 0 | A0 | B0 |
1 | A1 | B1 |
---|
y
| | A | B |
| 0 | A2 | B2 |
1 | A3 | B3 |
---|
pd.concat([x, y], ignore_index=True)
| | A | B |
| 0 | A0 | B0 |
| 1 | A1 | B1 |
| 2 | A2 | B2 |
3 | A3 | B3 |
---|
添加多级索引(MultiIndex)键
另一种选择是使用 keys
选项为数据源指定标签;结果将是一个包含这些数据的分层索引(MultiIndex)序列:
python
display('x', 'y', "pd.concat([x, y], keys=['x', 'y'])")
x
| | A | B |
| 0 | A0 | B0 |
1 | A1 | B1 |
---|
y
| | A | B |
| 0 | A2 | B2 |
1 | A3 | B3 |
---|
pd.concat([x, y], keys=['x', 'y'])
| | | A | B |
| x | 0 | A0 | B0 |
| x | 1 | A1 | B1 |
| y | 0 | A2 | B2 |
y | 1 | A3 | B3 |
---|
我们可以使用分层索引中讨论的工具,将这个多重索引的 DataFrame
转换为我们感兴趣的表示形式。
使用连接(Join)方式拼接
在前面的简短示例中,我们主要拼接的是具有相同列名的 DataFrame
。
实际上,来自不同数据源的数据可能具有不同的列集合,此时 pd.concat
提供了多种选项。
请看下面两个 DataFrame
的拼接示例,它们只有部分(而不是全部)列名相同:
python
df5 = make_df('ABC', [1, 2])
df6 = make_df('BCD', [3, 4])
display('df5', 'df6', 'pd.concat([df5, df6])')
df5
| | A | B | C |
| 1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
---|
df6
| | B | C | D |
| 3 | B3 | C3 | D3 |
4 | B4 | C4 | D4 |
---|
pd.concat([df5, df6])
| | A | B | C | D |
| 1 | A1 | B1 | C1 | NaN |
| 2 | A2 | B2 | C2 | NaN |
| 3 | NaN | B3 | C3 | D3 |
4 | NaN | B4 | C4 | D4 |
---|
默认行为是用 NA
值填充没有数据的位置。
要更改这一点,我们可以调整 concat
函数的 join
参数。
默认情况下,join
是输入列的并集(join='outer'
),但我们也可以通过设置 join='inner'
,将其改为只保留列的交集:
python
display('df5', 'df6',
"pd.concat([df5, df6], join='inner')")
df5
| | A | B | C |
| 1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
---|
df6
| | B | C | D |
| 3 | B3 | C3 | D3 |
4 | B4 | C4 | D4 |
---|
pd.concat([df5, df6], join='inner')
| | B | C |
| 1 | B1 | C1 |
| 2 | B2 | C2 |
| 3 | B3 | C3 |
4 | B4 | C4 |
---|
另一种有用的模式是在拼接前使用 reindex
方法,以更精细地控制哪些列被舍弃:
python
pd.concat([df5, df6.reindex(df5.columns, axis=1)])
| | A | B | C |
| 1 | A1 | B1 | C1 |
| 2 | A2 | B2 | C2 |
| 3 | NaN | B3 | C3 |
4 | NaN | B4 | C4 |
---|
append 方法
由于直接拼接数组非常常见,在Pandas 2.0.0版本之前,Series
和 DataFrame
对象都提供了 append
方法,可以用更少的代码实现同样的功能。
但值得注意的是,自 Pandas 2.0.0 开始,不再支持append
方法,故以下的示例将报 AttributeError。
python
display('df1', 'df2', 'df1.append(df2)')
请注意,与 Python 列表的 append
和 extend
方法不同,Pandas 中的 append
方法不会修改原始对象;它会创建一个包含合并数据的新对象。
此外,这种方法的效率并不高,因为它涉及新索引和数据缓冲区的创建。
因此,如果你打算进行多次 append
操作,通常更好的做法是先构建一个 DataFrame 对象的列表,然后一次性传递给 concat
函数。
Pandas有更强大的多数据源合并方法:pd.merge
实现的数据库式合并/连接。
关于 concat
、append
及相关功能的更多信息,请参阅 Pandas 文档的"合并、连接、拼接与比较"部分。