前言
pandas 提供了各种工具,可以轻松地将不同的 Series 或 DataFrame 连接、合并在一起
此外,pandas 还提供了比较两个 Series 或 DataFrame 对象差异的实用工具
连接对象
concat() 函数能够沿指定轴执行连接操作,同时对其他轴上的索引(如果有的话,Series 只有一个轴)执行可选的集合运算(并集或交集)
下面是一个简单的示例
In [1]: df1 = pd.DataFrame(
...: {
...: "A": ["A0", "A1", "A2", "A3"],
...: "B": ["B0", "B1", "B2", "B3"],
...: "C": ["C0", "C1", "C2", "C3"],
...: "D": ["D0", "D1", "D2", "D3"],
...: },
...: index=[0, 1, 2, 3],
...: )
...:
In [2]: df2 = pd.DataFrame(
...: {
...: "A": ["A4", "A5", "A6", "A7"],
...: "B": ["B4", "B5", "B6", "B7"],
...: "C": ["C4", "C5", "C6", "C7"],
...: "D": ["D4", "D5", "D6", "D7"],
...: },
...: index=[4, 5, 6, 7],
...: )
...:
In [3]: df3 = pd.DataFrame(
...: {
...: "A": ["A8", "A9", "A10", "A11"],
...: "B": ["B8", "B9", "B10", "B11"],
...: "C": ["C8", "C9", "C10", "C11"],
...: "D": ["D8", "D9", "D10", "D11"],
...: },
...: index=[8, 9, 10, 11],
...: )
...:
In [4]: frames = [df1, df2, df3]
In [5]: result = pd.concat(frames)
就像 ndarrays 上的函数 numpy.concatenate 一样,pandas.concat 接收一个相同类型的对象列表或字典,并通过一些配置来设置处理方式并将它们连接起来
pd.concat(
objs,
axis=0,
join="outer",
ignore_index=False,
keys=None,
levels=None,
names=None,
verify_integrity=False,
copy=True,
)
让我们再来看看上面的例子。假设我们想将特定的键与被切割的 DataFrame 的每一个片段关联起来。我们可以使用 keys 参数来实现。
In [6]: result = pd.concat(frames, keys=["x", "y", "z"])
从图中可以看出,在结果对象的索引的最外层添加了相应的索引(变为层次索引)。这意味着我们现在可以按键选择每个块
In [7]: result.loc["y"]
Out[7]:
A B C D
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7
下面将详细介绍此功能
注意:
concat()、append() 都会对数据进行完整的复制,不断重复使用这个函数会造成显著的性能下降。
如果你想在多个数据集上执行该操作,可以使用列表推导式。
frames = [ process_your_file(f) for f in files ]
result = pd.concat(frames)
同时,当在指定轴上连接 DataFrames 时,pandas 将尽可能尝试保留这些索引或列名。
1 在其他轴上的设置逻辑
当将多个 DataFrame 连接在一起时,您可以通过以下两种方式来选择如何处理其他轴上的数据(即除被连接的轴外)。
并集 join='outer',这是默认选项,因为它不会丢失信息
交集 join='inner'
下面是这些方法的一个示例。首先,对于默认的 join='outer'
In [8]: df4 = pd.DataFrame(
...: {
...: "B": ["B2", "B3", "B6", "B7"],
...: "D": ["D2", "D3", "D6", "D7"],
...: "F": ["F2", "F3", "F6", "F7"],
...: },
...: index=[2, 3, 6, 7],
...: )
...:
In [9]: result = pd.concat([df1, df4], axis=1)
对于 join='inner' 也是一样的
In [10]: result = pd.concat([df1, df4], axis=1, join="inner")
最后,假设我们只是想重用原始 DataFrame 中的确切索引
In [11]: result = pd.concat([df1, df4], axis=1).reindex(df1.index)
类似地,我们可以在连接之前进行索引
In [12]: pd.concat([df1, df4.reindex(df1.index)], axis=1)
Out[12]:
A B C D B D F
0 A0 B0 C0 D0 NaN NaN NaN
1 A1 B1 C1 D1 NaN NaN NaN
2 A2 B2 C2 D2 B2 D2 F2
3 A3 B3 C3 D3 B3 D3 F3
2 使用 append 连接
Series 和 DataFrame 上的 append() 实例方法是 concat() 的一个简单的快捷方式。
该方法实际上早于 concat。它沿着 axis=0 即索引方向进行连接
In [13]: result = df1.append(df2)
在 DataFrame 中,索引必须是不相交的,但列不需要
In [14]: result = df1.append(df4, sort=False)
append 可以接受多个对象来进行连接
In [15]: result = df1.append([df2, df3])
注意:
这里的 append() 方法不同于 list,该方法不会修改 df1 的值,同时返回是添加 df2 后的拷贝
3 忽略连接轴上的索引
对于某些 DataFrame 对象,其上的索引并没有任何意义,同时你可能希望在连接的时候,忽略这些对象的索引可能存在重叠的情况。
要做到这一点,可以使用 ignore_index 参数
In [16]: result = pd.concat([df1, df4], ignore_index=True, sort=False)
这也是 DataFrame.append() 的一个有效参数
In [17]: result = df1.append(df4, ignore_index=True, sort=False)
4 不同维度数据的连接
您也可以将 Series 和 DataFrame 对象连接起来。Series 将被转换为 DataFrame,列名为 Series 的名称
In [18]: s1 = pd.Series(["X0", "X1", "X2", "X3"], name="X")
In [19]: result = pd.concat([df1, s1], axis=1)
对于未命名的 Series,对应的列名将是连续增长的数字
In [20]: s2 = pd.Series(["_0", "_1", "_2", "_3"])
In [21]: result = pd.concat([df1, s2, s2, s2], axis=1)
传递 ignore_index=True 将删除所有名称引用
In [22]: result = pd.concat([df1, s1], axis=1, ignore_index=True)
5 使用 keys 连接
keys 参数的一个常见的用法是,基于现有 Series 创建新 DataFrame 时覆盖列名
默认的行为是如果 Series 的名称存在的话,让生成的 DataFrame 继承
In [23]: s3 = pd.Series([0, 1, 2, 3], name="foo")
In [24]: s4 = pd.Series([0, 1, 2, 3])
In [25]: s5 = pd.Series([0, 1, 4, 5])
In [26]: pd.concat([s3, s4, s5], axis=1)
Out[26]:
foo 0 1
0 0 0 0
1 1 1 1
2 2 2 4
3 3 3 5
通过 keys 参数,我们可以覆盖现有的列名
In [27]: pd.concat([s3, s4, s5], axis=1, keys=["red", "blue", "yellow"])
Out[27]:
red blue yellow
0 0 0 0
1 1 1 1
2 2 2 4
3 3 3 5
让我们考虑第一个例子的一个变体
In [28]: result = pd.concat(frames, keys=["x", "y", "z"])
你也可以在 concat 中传递一个字典,在这种情况下,字典的键将用于 keys 参数(除非指定了其他键)
In [29]: pieces = {"x": df1, "y": df2, "z": df3}
In [30]: result = pd.concat(pieces)
In [31]: result = pd.concat(pieces, keys=["z", "y"])
结果中创建了 MultiIndex,最外层为 keys 参数指定的值,而原 DataFrame 的索引在内层保留了下来
In [32]: result.index.levels
Out[32]: FrozenList([['z', 'y'], [4, 5, 6, 7, 8, 9, 10, 11]])
如果你想指定其他级别,可以使用 levels 参数:
In [33]: result = pd.concat(
....: pieces, keys=["x", "y", "z"], levels=[["z", "y", "x", "w"]], names=["group_key"]
....: )
....:
In [34]: result.index.levels
Out[34]: FrozenList([['z', 'y', 'x', 'w'], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]])
6 将行追加到DataFrame
虽然这样做效率很低,但是你可以通过传递一个 Series 或 dict 并将其追加到 DataFrame 中,同时会返回一个新的 DataFrame
In [35]: s2 = pd.Series(["X0", "X1", "X2", "X3"], index=["A", "B", "C", "D"])
In [36]: result = df1.append(s2, ignore_index=True)
你应该使用 ignore_index 来指示 DataFrame 丢弃它的索引。
如果你希望保留索引,你应该为 DataFrame 构造一个合适的索引,然后追加或连接这些对象
你也可以传递字典或 Series 列表:
In [37]: dicts = [{"A": 1, "B": 2, "C": 3, "X": 4}, {"A": 5, "B": 6, "C": 7, "Y": 8}]
In [38]: result = df1.append(dicts, ignore_index=True, sort=False)