7.9 组合数据集:连接和附加

原文:Combining Datasets: Concat and Append

译者:飞龙

协议:CC BY-NC-SA 4.0

本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。

一些最有趣的数据研究来自于不同的数据源的组合。这些操作可能涉及,从两个不同数据集的非常简单的连接,到更复杂的数据库风格的连接和合并,来正确处理数据集之间的任何重叠。SeriesDataFrame是考虑到这类的操作而构建的,而 Pandas 包含的函数和方法使得这种数据整理变得快速而直接。

在这里,我们将使用pd.concat函数的,看一下SeriesDataFrame的简单连接;稍后我们将深入研究 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 数组的连接

SeriesDataFrame对象的连接非常类似于 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()可以用于SeriesDataFrame对象的简单连接,就像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.concatenatepd.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函数的joinjoin_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()方法

因为直接的数组连接是如此常见,SeriesDataFrame对象有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)”一节。

results matching ""

    No results matching ""