7.9 组合数据集:连接和附加
原文:Combining Datasets: Concat and Append
译者:飞龙
本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。
一些最有趣的数据研究来自于不同的数据源的组合。这些操作可能涉及,从两个不同数据集的非常简单的连接,到更复杂的数据库风格的连接和合并,来正确处理数据集之间的任何重叠。Series
和DataFrame
是考虑到这类的操作而构建的,而 Pandas 包含的函数和方法使得这种数据整理变得快速而直接。
在这里,我们将使用pd.concat
函数的,看一下Series
和DataFrame
的简单连接;稍后我们将深入研究 Pandas 中实现的内存中的更复杂的合并和连接。
我们从标准导入开始:
import pandas as pd
import numpy as np
为方便起见,我们将定义这个函数,该函数创建一个特定形式的DataFrame
,它将在下面有用:
def make_df(cols, ind):
"""Quickly make a DataFrame"""
data = {c: [str(c) + str(i) for i in ind]
for c in cols}
return pd.DataFrame(data, ind)
# 示例数据帧
make_df('ABC', range(3))
A | B | C | |
---|---|---|---|
0 | A0 | B0 | C0 |
1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
另外,我们将创建一个简单的类,允许我们并排显示多个DataFrame
。代码使用了特殊的_repr_html_
方法,IPython 使用该方法来实现其丰富的对象显示:
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 数组的基础知识”中所述。回想一下,使用它,你可以将两个或多个数组的内容组合到一个数组中:
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
关键字,允许你指定沿着它连接结果的轴:
x = [[1, 2],
[3, 4]]
np.concatenate([x, x], axis=1)
'''
array([[1, 2, 1, 2],
[3, 4, 3, 4]])
'''
使用pd.concat
的简单连接
Pandas 拥有函数pd.concat()
,它的语法与np.concatenate
类似,但是包含了一些我们将要讨论的选项:
# Pandas v0.18 中的签名
pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False,
keys=None, levels=None, names=None, verify_integrity=False,
copy=True)
pd.concat()
可以用于Series
或DataFrame
对象的简单连接,就像np.concatenate()
可以用于简单的数组连接:
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
:
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
允许指定一个轴,沿着该轴进行连接。请考虑以下示例:
df3 = make_df('AB', [0, 1])
df4 = make_df('CD', [0, 1])
display('df3', 'df4', "pd.concat([df3, df4], axis='col')")
df3
:
A | B | |
---|---|---|
0 | A0 | B0 |
1 | A1 | B1 |
df4
:
C | D | |
---|---|---|
0 | C0 | D0 |
1 | C1 | D1 |
pd.concat([df3, df4], axis='col')
:
A | B | C | D | |
---|---|---|---|---|
0 | A0 | B0 | C0 | D0 |
1 | A1 | B1 | C1 | D1 |
与之等价,我们可以指定axis = 1
;在这里,我们使用了更直观的axis ='col'
。
重复的索引
np.concatenate
和pd.concat
之间的一个重要区别是,Pandas 的连接保留了索引,即使结果会有重复的索引!考虑这个简单的例子:
x = make_df('AB', [0, 1])
y = make_df('AB', [2, 3])
y.index = x.index # 复制索引
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
,如果存在重复索引,则连接将引发异常。这是一个示例,为清楚起见,我们将捕获并打印错误消息:
try:
pd.concat([x, y], verify_integrity=True)
except ValueError as e:
print("ValueError:", e)
'''
ValueError: Indexes have overlapping values: [0, 1]
'''
忽略索引
有时索引本身无关紧要,你宁愿忽略它。可以使用ignore_index
标志指定此选项。将此设置为True
,连接将为生成的Series
创建一个新的整数索引:
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
选项为数据源指定标签;结果将是包含数据的分层索引的序列:
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 |
1 | A1 | B1 | |
y | 0 | A2 | B2 |
1 | A3 | B3 |
结果是一个多重索引的DataFrame
,我们可以使用“分层索引”中讨论的工具,将这些数据转换成我们感兴趣的表示。
使用join
的连接
在我们刚看到的简单示例中,我们主要使用共享列名来连接DataFrame
。实际上,来自不同来源的数据可能具有不同的列名称集,而pd.concat
在这种情况下提供了几个选项。
考虑以下两个`DataFrame
的连接,它们有一些共同的列(但不是全部!):
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 值。要改变它,我们可以为concatenate
函数的join
和join_axes
参数指定几个选项之一。默认情况下,连接是输入列的并集(join ='outer'
),但我们可以使用join ='inner'
将其更改为列的交集:
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 |
另一种选择是,使用join_axes
参数直接指定保留的列的索引,该参数接受索引对象列表。这里我们指定,返回的列应该与第一个输入的列相同:
display('df5', 'df6',
"pd.concat([df5, df6], join_axes=[df5.columns])")
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_axes=[df5.columns])
:
A | B | C | |
---|---|---|---|
1 | A1 | B1 | C1 |
2 | A2 | B2 | C2 |
3 | NaN | B3 | C3 |
4 | NaN | B4 | C4 |
在连接两个数据集时,pd.concat
函数的选项组合,允许各种可能的行为;将这些工具用于你自己的数据时,请记住这些。
append()
方法
因为直接的数组连接是如此常见,Series
和DataFrame
对象有append
方法,可以用更少的打字完成同样的事情。例如,不是调用pd.concat([df1, df2])
,而是简单地调用df1.append(df2)
:
display('df1', 'df2', 'df1.append(df2)')
df1
:
A | B | |
---|---|---|
1 | A1 | B1 |
2 | A2 | B2 |
df2
:
A | B | |
---|---|---|
3 | A3 | B3 |
4 | A4 | B4 |
df1.append(df2)
:
A | B | |
---|---|---|
1 | A1 | B1 |
2 | A2 | B2 |
3 | A3 | B3 |
4 | A4 | B4 |
请记住,与Python列表的append()
和extend()
方法不同,Pandas 中的append()
方法不会修改原始对象 - 而是创建一个新对象,带有组合的数据。
它也不是一种非常有效的方法,因为它涉及创建新的索引和数据缓冲区。因此,如果你计划进行多次append
操作,通常最好建立一个DataFrame
列表并将它们全部传递给concat()
函数。
在下一节中,我们将介绍另一种更强大的方法,来组合来自多个源的数据,即pd.merge
中实现的数据库风格的合并/连接。对于concat()
,append()
及相关函数的更多信息,请参阅“合并,连接(Join)和连接(Concatenate)”一节。