八、数据整理:连接、合并和重塑
原文:
wesmckinney.com/book/data-wrangling
译者:飞龙
此开放访问网络版本的《Python 数据分析第三版》现已作为印刷版和数字版的伴侣提供。如果您发现任何勘误,请在此处报告。请注意,由 Quarto 生成的本站点的某些方面与 O’Reilly 的印刷版和电子书版本的格式不同。
如果您发现本书的在线版本有用,请考虑订购纸质版或无 DRM 的电子书以支持作者。本网站的内容不得复制或再生产。代码示例采用 MIT 许可,可在 GitHub 或 Gitee 上找到。
在许多应用程序中,数据可能分布在许多文件或数据库中,或者以不便于分析的形式排列。本章重点介绍帮助组合、连接和重新排列数据的工具。
首先,我介绍了 pandas 中层次索引的概念,这在某些操作中被广泛使用。然后我深入研究了特定的数据操作。您可以在第十三章:数据分析示例中看到这些工具的各种应用用法。
8.1 层次索引
层次索引是 pandas 的一个重要特性,它使您能够在轴上具有多个(两个或更多)索引级别。另一种思考方式是,它为您提供了一种以较低维度形式处理较高维度数据的方法。让我们从一个简单的示例开始:创建一个 Series,其索引为列表的列表(或数组):
In [11]: data = pd.Series(np.random.uniform(size=9), ....: index=[["a", "a", "a", "b", "b", "c", "c", "d", "d"], ....: [1, 2, 3, 1, 3, 1, 2, 2, 3]]) In [12]: data Out[12]: a 1 0.929616 2 0.316376 3 0.183919 b 1 0.204560 3 0.567725 c 1 0.595545 2 0.964515 d 2 0.653177 3 0.748907 dtype: float64
您看到的是一个带有MultiIndex
作为索引的 Series 的美化视图。索引显示中的“间隙”表示“使用直接上面的标签”:
In [13]: data.index Out[13]: MultiIndex([('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 3), ('c', 1), ('c', 2), ('d', 2), ('d', 3)], )
对于具有层次索引的对象,可以进行所谓的部分索引,使您能够简洁地选择数据的子集:
In [14]: data["b"] Out[14]: 1 0.204560 3 0.567725 dtype: float64 In [15]: data["b":"c"] Out[15]: b 1 0.204560 3 0.567725 c 1 0.595545 2 0.964515 dtype: float64 In [16]: data.loc[["b", "d"]] Out[16]: b 1 0.204560 3 0.567725 d 2 0.653177 3 0.748907 dtype: float64
甚至可以从“内部”级别进行选择。在这里,我从第二个索引级别选择所有具有值2
的值:
In [17]: data.loc[:, 2] Out[17]: a 0.316376 c 0.964515 d 0.653177 dtype: float64
层次索引在重塑数据和基于组的操作(如形成数据透视表)中发挥着重要作用。例如,您可以使用其unstack
方法将这些数据重新排列为 DataFrame:
In [18]: data.unstack() Out[18]: 1 2 3 a 0.929616 0.316376 0.183919 b 0.204560 NaN 0.567725 c 0.595545 0.964515 NaN d NaN 0.653177 0.748907
unstack
的逆操作是stack
:
In [19]: data.unstack().stack() Out[19]: a 1 0.929616 2 0.316376 3 0.183919 b 1 0.204560 3 0.567725 c 1 0.595545 2 0.964515 d 2 0.653177 3 0.748907 dtype: float64
stack
和unstack
将在重塑和透视中更详细地探讨。
对于 DataFrame,任一轴都可以具有分层索引:
In [20]: frame = pd.DataFrame(np.arange(12).reshape((4, 3)), ....: index=[["a", "a", "b", "b"], [1, 2, 1, 2]], ....: columns=[["Ohio", "Ohio", "Colorado"], ....: ["Green", "Red", "Green"]]) In [21]: frame Out[21]: Ohio Colorado Green Red Green a 1 0 1 2 2 3 4 5 b 1 6 7 8 2 9 10 11
层次级别可以有名称(作为字符串或任何 Python 对象)。如果有的话,这些名称将显示在控制台输出中:
In [22]: frame.index.names = ["key1", "key2"] In [23]: frame.columns.names = ["state", "color"] In [24]: frame Out[24]: state Ohio Colorado color Green Red Green key1 key2 a 1 0 1 2 2 3 4 5 b 1 6 7 8 2 9 10 11
这些名称取代了仅用于单级索引的name
属性。
注意
请注意,索引名称"state"
和"color"
不是行标签(frame.index
值)的一部分。
您可以通过访问其nlevels
属性来查看索引具有多少级别:
In [25]: frame.index.nlevels Out[25]: 2
通过部分列索引,您也可以类似地选择列组:
In [26]: frame["Ohio"] Out[26]: color Green Red key1 key2 a 1 0 1 2 3 4 b 1 6 7 2 9 10
MultiIndex
可以单独创建,然后重复使用;具有级别名称的前述 DataFrame 中的列也可以这样创建:
pd.MultiIndex.from_arrays([["Ohio", "Ohio", "Colorado"], ["Green", "Red", "Green"]], names=["state", "color"])
重新排序和排序级别
有时您可能需要重新排列轴上级别的顺序或按特定级别的值对数据进行排序。swaplevel
方法接受两个级别编号或名称,并返回一个级别互换的新对象(但数据本身不变):
In [27]: frame.swaplevel("key1", "key2") Out[27]: state Ohio Colorado color Green Red Green key2 key1 1 a 0 1 2 2 a 3 4 5 1 b 6 7 8 2 b 9 10 11
sort_index
默认按所有索引级别词典顺序对数据进行排序,但您可以选择通过传递level
参数仅使用单个级别或一组级别进行排序。例如:
In [28]: frame.sort_index(level=1) Out[28]: state Ohio Colorado color Green Red Green key1 key2 a 1 0 1 2 b 1 6 7 8 a 2 3 4 5 b 2 9 10 11 In [29]: frame.swaplevel(0, 1).sort_index(level=0) Out[29]: state Ohio Colorado color Green Red Green key2 key1 1 a 0 1 2 b 6 7 8 2 a 3 4 5 b 9 10 11
注意
如果索引按字典顺序排序,从最外层级别开始,那么在具有分层索引的对象上进行数据选择性能要好得多——也就是说,调用sort_index(level=0)
或sort_index()
的结果。
按级别汇总统计
DataFrame 和 Series 上的许多描述性和汇总统计信息具有level
选项,您可以在特定轴上指定要按级别聚合的级别。考虑上面的 DataFrame;我们可以按行或列的级别进行聚合,如下所示:
In [30]: frame.groupby(level="key2").sum() Out[30]: state Ohio Colorado color Green Red Green key2 1 6 8 10 2 12 14 16 In [31]: frame.groupby(level="color", axis="columns").sum() Out[31]: color Green Red key1 key2 a 1 2 1 2 8 4 b 1 14 7 2 20 10
我们将在第十章:数据聚合和分组操作中更详细地讨论groupby
。
使用 DataFrame 的列进行索引
希望使用一个或多个 DataFrame 列作为行索引并不罕见;或者,您可能希望将行索引移入 DataFrame 的列中。这是一个示例 DataFrame:
In [32]: frame = pd.DataFrame({"a": range(7), "b": range(7, 0, -1), ....: "c": ["one", "one", "one", "two", "two", ....: "two", "two"], ....: "d": [0, 1, 2, 0, 1, 2, 3]}) In [33]: frame Out[33]: a b c d 0 0 7 one 0 1 1 6 one 1 2 2 5 one 2 3 3 4 two 0 4 4 3 two 1 5 5 2 two 2 6 6 1 two 3
DataFrame 的set_index
函数将使用一个或多个列作为索引创建一个新的 DataFrame:
In [34]: frame2 = frame.set_index(["c", "d"]) In [35]: frame2 Out[35]: a b c d one 0 0 7 1 1 6 2 2 5 two 0 3 4 1 4 3 2 5 2 3 6 1
默认情况下,列会从 DataFrame 中移除,但您可以通过向set_index
传递drop=False
来保留它们:
In [36]: frame.set_index(["c", "d"], drop=False) Out[36]: a b c d c d one 0 0 7 one 0 1 1 6 one 1 2 2 5 one 2 two 0 3 4 two 0 1 4 3 two 1 2 5 2 two 2 3 6 1 two 3
另一方面,reset_index
的作用与set_index
相反;层次化索引级别被移动到列中:
In [37]: frame2.reset_index() Out[37]: c d a b 0 one 0 0 7 1 one 1 1 6 2 one 2 2 5 3 two 0 3 4 4 two 1 4 3 5 two 2 5 2 6 two 3 6 1
8.2 合并和组合数据集
pandas 对象中包含的数据可以以多种方式组合:
pandas.merge
基于一个或多个键连接 DataFrame 中的行。这将为使用 SQL 或其他关系数据库的用户提供熟悉的操作,因为它实现了数据库join操作。
pandas.concat
沿轴连接或“堆叠”对象。
combine_first
将重叠数据拼接在一起,用另一个对象中的值填充另一个对象中的缺失值。
我将逐个讨论这些并给出一些示例。它们将在本书的其余部分的示例中使用。
数据库风格的 DataFrame 连接
合并或连接操作通过使用一个或多个键链接行来合并数据集。这些操作在关系数据库(例如基于 SQL 的数据库)中尤为重要。pandas 中的pandas.merge
函数是使用这些算法在您的数据上的主要入口点。
让我们从一个简单的例子开始:
In [38]: df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "a", "b"], ....: "data1": pd.Series(range(7), dtype="Int64")}) In [39]: df2 = pd.DataFrame({"key": ["a", "b", "d"], ....: "data2": pd.Series(range(3), dtype="Int64")}) In [40]: df1 Out[40]: key data1 0 b 0 1 b 1 2 a 2 3 c 3 4 a 4 5 a 5 6 b 6 In [41]: df2 Out[41]: key data2 0 a 0 1 b 1 2 d 2
在这里,我使用 pandas 的Int64
扩展类型来表示可空整数,详细讨论请参见第 7.3 章:扩展数据类型。
这是一个多对一连接的示例;df1
中的数据有多行标记为a
和b
,而df2
中的每个值在key
列中只有一行。使用这些对象调用pandas.merge
,我们得到:
In [42]: pd.merge(df1, df2) Out[42]: key data1 data2 0 b 0 1 1 b 1 1 2 b 6 1 3 a 2 0 4 a 4 0 5 a 5 0
请注意,我没有指定要连接的列。如果没有指定该信息,pandas.merge
将使用重叠的列名作为键。不过,最好明确指定:
In [43]: pd.merge(df1, df2, on="key") Out[43]: key data1 data2 0 b 0 1 1 b 1 1 2 b 6 1 3 a 2 0 4 a 4 0 5 a 5 0
一般来说,在pandas.merge
操作中,列的输出顺序是不确定的。
如果每个对象中的列名不同,您可以分别指定它们:
In [44]: df3 = pd.DataFrame({"lkey": ["b", "b", "a", "c", "a", "a", "b"], ....: "data1": pd.Series(range(7), dtype="Int64")}) In [45]: df4 = pd.DataFrame({"rkey": ["a", "b", "d"], ....: "data2": pd.Series(range(3), dtype="Int64")}) In [46]: pd.merge(df3, df4, left_on="lkey", right_on="rkey") Out[46]: lkey data1 rkey data2 0 b 0 b 1 1 b 1 b 1 2 b 6 b 1 3 a 2 a 0 4 a 4 a 0 5 a 5 a 0
您可能会注意到结果中缺少"c"
和"d"
值及其相关数据。默认情况下,pandas.merge
执行的是"inner"
连接;结果中的键是交集,或者是在两个表中都找到的公共集合。其他可能的选项是"left"
、"right"
和"outer"
。外连接取键的并集,结合了应用左连接和右连接的效果:
In [47]: pd.merge(df1, df2, how="outer") Out[47]: key data1 data2 0 b 0 1 1 b 1 1 2 b 6 1 3 a 2 0 4 a 4 0 5 a 5 0 6 c 3 <NA> 7 d <NA> 2 In [48]: pd.merge(df3, df4, left_on="lkey", right_on="rkey", how="outer") Out[48]: lkey data1 rkey data2 0 b 0 b 1 1 b 1 b 1 2 b 6 b 1 3 a 2 a 0 4 a 4 a 0 5 a 5 a 0 6 c 3 NaN <NA> 7 NaN <NA> d 2
在外连接中,左侧或右侧 DataFrame 对象中与另一个 DataFrame 中的键不匹配的行将在另一个 DataFrame 的列中出现 NA 值。
请参阅表 8.1 以获取how
选项的摘要。
表 8.1:使用how
参数的不同连接类型
选项 | 行为 |
how="inner" |
仅使用在两个表中观察到的键组合 |
how="left" |
使用在左表中找到的所有键组合 |
how="right" |
使用在右表中找到的所有键组合 |
how="outer" |
使用两个表中观察到的所有键组合 |
多对多 合并形成匹配键的笛卡尔积。以下是一个示例:
In [49]: df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "b"], ....: "data1": pd.Series(range(6), dtype="Int64")}) In [50]: df2 = pd.DataFrame({"key": ["a", "b", "a", "b", "d"], ....: "data2": pd.Series(range(5), dtype="Int64")}) In [51]: df1 Out[51]: key data1 0 b 0 1 b 1 2 a 2 3 c 3 4 a 4 5 b 5 In [52]: df2 Out[52]: key data2 0 a 0 1 b 1 2 a 2 3 b 3 4 d 4 In [53]: pd.merge(df1, df2, on="key", how="left") Out[53]: key data1 data2 0 b 0 1 1 b 0 3 2 b 1 1 3 b 1 3 4 a 2 0 5 a 2 2 6 c 3 <NA> 7 a 4 0 8 a 4 2 9 b 5 1 10 b 5 3
由于左侧 DataFrame 中有三行"b"
,右侧 DataFrame 中有两行"b"
,因此结果中有六行"b"
。传递给how
关键字参数的连接方法仅影响结果中出现的不同键值:
In [54]: pd.merge(df1, df2, how="inner") Out[54]: key data1 data2 0 b 0 1 1 b 0 3 2 b 1 1 3 b 1 3 4 b 5 1 5 b 5 3 6 a 2 0 7 a 2 2 8 a 4 0 9 a 4 2
要使用多个键进行合并,请传递列名列表:
In [55]: left = pd.DataFrame({"key1": ["foo", "foo", "bar"], ....: "key2": ["one", "two", "one"], ....: "lval": pd.Series([1, 2, 3], dtype='Int64')}) In [56]: right = pd.DataFrame({"key1": ["foo", "foo", "bar", "bar"], ....: "key2": ["one", "one", "one", "two"], ....: "rval": pd.Series([4, 5, 6, 7], dtype='Int64')}) In [57]: pd.merge(left, right, on=["key1", "key2"], how="outer") Out[57]: key1 key2 lval rval 0 foo one 1 4 1 foo one 1 5 2 foo two 2 <NA> 3 bar one 3 6 4 bar two <NA> 7
要确定根据合并方法的选择将出现在结果中的哪些键组合,请将多个键视为形成元组数组,用作单个连接键。
注意
当您在列上进行列连接时,传递的 DataFrame 对象的索引会被丢弃。如果需要保留索引值,可以使用reset_index
将索引附加到列中。
合并操作中要考虑的最后一个问题是处理重叠列名的方式。例如:
In [58]: pd.merge(left, right, on="key1") Out[58]: key1 key2_x lval key2_y rval 0 foo one 1 one 4 1 foo one 1 one 5 2 foo two 2 one 4 3 foo two 2 one 5 4 bar one 3 one 6 5 bar one 3 two 7
虽然您可以手动处理重叠(请参阅 Ch 7.2.4:重命名轴索引部分以重命名轴标签),pandas.merge
具有一个suffixes
选项,用于指定要附加到左侧和右侧 DataFrame 对象中重叠名称的字符串:
In [59]: pd.merge(left, right, on="key1", suffixes=("_left", "_right")) Out[59]: key1 key2_left lval key2_right rval 0 foo one 1 one 4 1 foo one 1 one 5 2 foo two 2 one 4 3 foo two 2 one 5 4 bar one 3 one 6 5 bar one 3 two 7
请参阅 pandas.merge 中的表 8.2,了解有关参数的参考。下一节将介绍使用 DataFrame 的行索引进行连接。
表 8.2:pandas.merge
函数参数
参数 | 描述 |
left |
要在左侧合并的 DataFrame。 |
right |
要在右侧合并的 DataFrame。 |
how |
要应用的连接类型:"inner" 、"outer" 、"left" 或"right" 之一;默认为"inner" 。 |
on |
要连接的列名。必须在两个 DataFrame 对象中找到。如果未指定并且没有给出其他连接键,则将使用left 和right 中的列名的交集作为连接键。 |
left_on |
用作连接键的left DataFrame 中的列。可以是单个列名或列名列表。 |
right_on |
与right DataFrame 的left_on 类似。 |
left_index |
使用left 中的行索引作为其连接键(或键,如果是MultiIndex )。 |
right_index |
与left_index 类似。 |
sort |
按连接键按字典顺序对合并数据进行排序;默认为False 。 |
suffixes |
字符串元组值,用于在重叠的列名后追加(默认为("_x", "_y") ,例如,如果两个 DataFrame 对象中都有"data" ,则在结果中会显示为"data_x" 和"data_y" 。 |
copy |
如果为False ,则在某些特殊情况下避免将数据复制到结果数据结构中;默认情况下始终复制。 |
validate |
验证合并是否是指定类型,一对一、一对多或多对多。有关选项的完整详细信息,请参阅文档字符串。 |
| indicator
| 添加一个特殊列_merge
,指示每行的来源;值将根据每行中连接数据的来源为"left_only"
、"right_only"
或"both"
。
在索引上合并
在某些情况下,DataFrame 中的合并键会在其索引(行标签)中找到。在这种情况下,您可以传递left_index=True
或right_index=True
(或两者都传递)来指示索引应该用作合并键:
In [60]: left1 = pd.DataFrame({"key": ["a", "b", "a", "a", "b", "c"], ....: "value": pd.Series(range(6), dtype="Int64")}) In [61]: right1 = pd.DataFrame({"group_val": [3.5, 7]}, index=["a", "b"]) In [62]: left1 Out[62]: key value 0 a 0 1 b 1 2 a 2 3 a 3 4 b 4 5 c 5 In [63]: right1 Out[63]: group_val a 3.5 b 7.0 In [64]: pd.merge(left1, right1, left_on="key", right_index=True) Out[64]: key value group_val 0 a 0 3.5 2 a 2 3.5 3 a 3 3.5 1 b 1 7.0 4 b 4 7.0
注意
如果您仔细观察这里,您会发现left1
的索引值已被保留,而在上面的其他示例中,输入 DataFrame 对象的索引已被丢弃。由于right1
的索引是唯一的,这种“一对多”合并(使用默认的how="inner"
方法)可以保留与输出中的行对应的left1
的索引值。
由于默认合并方法是交集连接键,您可以使用外连接来形成它们的并集:
In [65]: pd.merge(left1, right1, left_on="key", right_index=True, how="outer") Out[65]: key value group_val 0 a 0 3.5 2 a 2 3.5 3 a 3 3.5 1 b 1 7.0 4 b 4 7.0 5 c 5 NaN
对于具有分层索引的数据,情况会更加复杂,因为在索引上进行连接等效于多键合并:
In [66]: lefth = pd.DataFrame({"key1": ["Ohio", "Ohio", "Ohio", ....: "Nevada", "Nevada"], ....: "key2": [2000, 2001, 2002, 2001, 2002], ....: "data": pd.Series(range(5), dtype="Int64")}) In [67]: righth_index = pd.MultiIndex.from_arrays( ....: [ ....: ["Nevada", "Nevada", "Ohio", "Ohio", "Ohio", "Ohio"], ....: [2001, 2000, 2000, 2000, 2001, 2002] ....: ] ....: ) In [68]: righth = pd.DataFrame({"event1": pd.Series([0, 2, 4, 6, 8, 10], dtype="I nt64", ....: index=righth_index), ....: "event2": pd.Series([1, 3, 5, 7, 9, 11], dtype="I nt64", ....: index=righth_index)}) In [69]: lefth Out[69]: key1 key2 data 0 Ohio 2000 0 1 Ohio 2001 1 2 Ohio 2002 2 3 Nevada 2001 3 4 Nevada 2002 4 In [70]: righth Out[70]: event1 event2 Nevada 2001 0 1 2000 2 3 Ohio 2000 4 5 2000 6 7 2001 8 9 2002 10 11
在这种情况下,您必须指示要合并的多个列作为列表(注意使用how="outer"
处理重复索引值):
In [71]: pd.merge(lefth, righth, left_on=["key1", "key2"], right_index=True) Out[71]: key1 key2 data event1 event2 0 Ohio 2000 0 4 5 0 Ohio 2000 0 6 7 1 Ohio 2001 1 8 9 2 Ohio 2002 2 10 11 3 Nevada 2001 3 0 1 In [72]: pd.merge(lefth, righth, left_on=["key1", "key2"], ....: right_index=True, how="outer") Out[72]: key1 key2 data event1 event2 0 Ohio 2000 0 4 5 0 Ohio 2000 0 6 7 1 Ohio 2001 1 8 9 2 Ohio 2002 2 10 11 3 Nevada 2001 3 0 1 4 Nevada 2002 4 <NA> <NA> 4 Nevada 2000 <NA> 2 3
使用合并的两侧的索引也是可能的:
In [73]: left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]], ....: index=["a", "c", "e"], ....: columns=["Ohio", "Nevada"]).astype("Int64") In [74]: right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]], ....: index=["b", "c", "d", "e"], ....: columns=["Missouri", "Alabama"]).astype("Int64") In [75]: left2 Out[75]: Ohio Nevada a 1 2 c 3 4 e 5 6 In [76]: right2 Out[76]: Missouri Alabama b 7 8 c 9 10 d 11 12 e 13 14 In [77]: pd.merge(left2, right2, how="outer", left_index=True, right_index=True) Out[77]: Ohio Nevada Missouri Alabama a 1 2 <NA> <NA> b <NA> <NA> 7 8 c 3 4 9 10 d <NA> <NA> 11 12 e 5 6 13 14
DataFrame 有一个join
实例方法,可以简化按索引合并。它还可以用于合并许多具有相同或类似索引但列不重叠的 DataFrame 对象。在前面的例子中,我们可以这样写:
In [78]: left2.join(right2, how="outer") Out[78]: Ohio Nevada Missouri Alabama a 1 2 <NA> <NA> b <NA> <NA> 7 8 c 3 4 9 10 d <NA> <NA> 11 12 e 5 6 13 14
与pandas.merge
相比,DataFrame 的join
方法默认在连接键上执行左连接。它还支持将传递的 DataFrame 的索引与调用 DataFrame 的某一列进行连接:
In [79]: left1.join(right1, on="key") Out[79]: key value group_val 0 a 0 3.5 1 b 1 7.0 2 a 2 3.5 3 a 3 3.5 4 b 4 7.0 5 c 5 NaN
您可以将此方法视为将数据“合并”到调用其join
方法的对象中。
最后,对于简单的索引对索引合并,您可以将 DataFrame 的列表传递给join
,作为使用下一节中描述的更一般的pandas.concat
函数的替代方法:
In [80]: another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]], ....: index=["a", "c", "e", "f"], ....: columns=["New York", "Oregon"]) In [81]: another Out[81]: New York Oregon a 7.0 8.0 c 9.0 10.0 e 11.0 12.0 f 16.0 17.0 In [82]: left2.join([right2, another]) Out[82]: Ohio Nevada Missouri Alabama New York Oregon a 1 2 <NA> <NA> 7.0 8.0 c 3 4 9 10 9.0 10.0 e 5 6 13 14 11.0 12.0 In [83]: left2.join([right2, another], how="outer") Out[83]: Ohio Nevada Missouri Alabama New York Oregon a 1 2 <NA> <NA> 7.0 8.0 c 3 4 9 10 9.0 10.0 e 5 6 13 14 11.0 12.0 b <NA> <NA> 7 8 NaN NaN d <NA> <NA> 11 12 NaN NaN f <NA> <NA> <NA> <NA> 16.0 17.0
沿轴连接
另一种数据组合操作被称为连接或堆叠。NumPy 的concatenate
函数可以使用 NumPy 数组来执行此操作:
In [84]: arr = np.arange(12).reshape((3, 4)) In [85]: arr Out[85]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) In [86]: np.concatenate([arr, arr], axis=1) Out[86]: array([[ 0, 1, 2, 3, 0, 1, 2, 3], [ 4, 5, 6, 7, 4, 5, 6, 7], [ 8, 9, 10, 11, 8, 9, 10, 11]])
在 pandas 对象(如 Series 和 DataFrame)的上下文中,具有标记轴使您能够进一步推广数组连接。特别是,您有许多额外的考虑:
- 如果对象在其他轴上的索引不同,我们应该合并这些轴中的不同元素还是仅使用共同的值?
- 连接的数据块在结果对象中需要被识别吗?
- “连接轴”中包含需要保留的数据吗?在许多情况下,DataFrame 中的默认整数标签在连接时最好被丢弃。
pandas 中的concat
函数提供了一种一致的方法来解决这些问题。我将给出一些示例来说明它是如何工作的。假设我们有三个没有索引重叠的 Series:
In [87]: s1 = pd.Series([0, 1], index=["a", "b"], dtype="Int64") In [88]: s2 = pd.Series([2, 3, 4], index=["c", "d", "e"], dtype="Int64") In [89]: s3 = pd.Series([5, 6], index=["f", "g"], dtype="Int64")
使用这些对象的列表调用pandas.concat
会将值和索引粘合在一起:
In [90]: s1 Out[90]: a 0 b 1 dtype: Int64 In [91]: s2 Out[91]: c 2 d 3 e 4 dtype: Int64 In [92]: s3 Out[92]: f 5 g 6 dtype: Int64 In [93]: pd.concat([s1, s2, s3]) Out[93]: a 0 b 1 c 2 d 3 e 4 f 5 g 6 dtype: Int64
默认情况下,pandas.concat
沿着axis="index"
工作,产生另一个 Series。如果传递axis="columns"
,结果将是一个 DataFrame:
In [94]: pd.concat([s1, s2, s3], axis="columns") Out[94]: 0 1 2 a 0 <NA> <NA> b 1 <NA> <NA> c <NA> 2 <NA> d <NA> 3 <NA> e <NA> 4 <NA> f <NA> <NA> 5 g <NA> <NA> 6
在这种情况下,另一个轴上没有重叠,您可以看到这是索引的并集("outer"
连接)。您可以通过传递join="inner"
来取交集:
In [95]: s4 = pd.concat([s1, s3]) In [96]: s4 Out[96]: a 0 b 1 f 5 g 6 dtype: Int64 In [97]: pd.concat([s1, s4], axis="columns") Out[97]: 0 1 a 0 0 b 1 1 f <NA> 5 g <NA> 6 In [98]: pd.concat([s1, s4], axis="columns", join="inner") Out[98]: 0 1 a 0 0 b 1 1
在这个最后的例子中,"f"
和"g"
标签消失了,因为使用了join="inner"
选项。
一个潜在的问题是结果中无法识别连接的片段。假设您希望在连接轴上创建一个分层索引。为此,请使用keys
参数:
In [99]: result = pd.concat([s1, s1, s3], keys=["one", "two", "three"]) In [100]: result Out[100]: one a 0 b 1 two a 0 b 1 three f 5 g 6 dtype: Int64 In [101]: result.unstack() Out[101]: a b f g one 0 1 <NA> <NA> two 0 1 <NA> <NA> three <NA> <NA> 5 6
在沿axis="columns"
组合 Series 的情况下,keys
变成了 DataFrame 的列标题:
In [102]: pd.concat([s1, s2, s3], axis="columns", keys=["one", "two", "three"]) Out[102]: one two three a 0 <NA> <NA> b 1 <NA> <NA> c <NA> 2 <NA> d <NA> 3 <NA> e <NA> 4 <NA> f <NA> <NA> 5 g <NA> <NA> 6
相同的逻辑也适用于 DataFrame 对象:
In [103]: df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=["a", "b", "c"], .....: columns=["one", "two"]) In [104]: df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=["a", "c"], .....: columns=["three", "four"]) In [105]: df1 Out[105]: one two a 0 1 b 2 3 c 4 5 In [106]: df2 Out[106]: three four a 5 6 c 7 8 In [107]: pd.concat([df1, df2], axis="columns", keys=["level1", "level2"]) Out[107]: level1 level2 one two three four a 0 1 5.0 6.0 b 2 3 NaN NaN c 4 5 7.0 8.0
在这里,keys
参数用于创建一个分层索引,其中第一级可以用于标识每个连接的 DataFrame 对象。
如果您传递的是对象字典而不是列表,那么字典的键将用于keys
选项:
In [108]: pd.concat({"level1": df1, "level2": df2}, axis="columns") Out[108]: level1 level2 one two three four a 0 1 5.0 6.0 b 2 3 NaN NaN c 4 5 7.0 8.0
有一些额外的参数控制如何创建分层索引(参见表 8.3)。例如,我们可以使用names
参数为创建的轴级别命名:
In [109]: pd.concat([df1, df2], axis="columns", keys=["level1", "level2"], .....: names=["upper", "lower"]) Out[109]: upper level1 level2 lower one two three four a 0 1 5.0 6.0 b 2 3 NaN NaN c 4 5 7.0 8.0
最后一个考虑因素涉及行索引不包含任何相关数据的 DataFrame:
In [110]: df1 = pd.DataFrame(np.random.standard_normal((3, 4)), .....: columns=["a", "b", "c", "d"]) In [111]: df2 = pd.DataFrame(np.random.standard_normal((2, 3)), .....: columns=["b", "d", "a"]) In [112]: df1 Out[112]: a b c d 0 1.248804 0.774191 -0.319657 -0.624964 1 1.078814 0.544647 0.855588 1.343268 2 -0.267175 1.793095 -0.652929 -1.886837 In [113]: df2 Out[113]: b d a 0 1.059626 0.644448 -0.007799 1 -0.449204 2.448963 0.667226
在这种情况下,您可以传递ignore_index=True
,这将丢弃每个 DataFrame 的索引并仅连接列中的数据,分配一个新的默认索引:
In [114]: pd.concat([df1, df2], ignore_index=True) Out[114]: a b c d 0 1.248804 0.774191 -0.319657 -0.624964 1 1.078814 0.544647 0.855588 1.343268 2 -0.267175 1.793095 -0.652929 -1.886837 3 -0.007799 1.059626 NaN 0.644448 4 0.667226 -0.449204 NaN 2.448963
表 8.3 描述了pandas.concat
函数的参数。
表 8.3:pandas.concat
函数参数
参数 | 描述 |
objs |
要连接的 pandas 对象的列表或字典;这是唯一必需的参数 |
axis |
要沿着连接的轴;默认为沿着行连接(axis="index" ) |
join |
要么是"inner" 要么是"outer" (默认为"outer" );是否沿着其他轴相交(inner)或联合(outer)索引 |
keys |
与要连接的对象关联的值,形成沿着连接轴的分层索引;可以是任意值的列表或数组,元组的数组,或数组的列表(如果在levels 中传递了多级数组) |
levels |
用作分层索引级别的特定索引,如果传递了键 |
names |
如果传递了keys 和/或levels ,则为创建的分层级别命名 |
verify_integrity |
检查连接对象中的新轴是否存在重复项,如果存在则引发异常;默认情况下(False )允许重复项 |
ignore_index |
不保留沿着连接axis 的索引,而是生成一个新的range(total_length) 索引 |
组合具有重叠部分的数据
还有另一种数据组合情况,既不能表示为合并操作也不能表示为连接操作。您可能有两个具有完全或部分重叠索引的数据集。作为一个激励性的例子,考虑 NumPy 的where
函数,它执行数组导向的 if-else 表达式的等效操作:
In [115]: a = pd.Series([np.nan, 2.5, 0.0, 3.5, 4.5, np.nan], .....: index=["f", "e", "d", "c", "b", "a"]) In [116]: b = pd.Series([0., np.nan, 2., np.nan, np.nan, 5.], .....: index=["a", "b", "c", "d", "e", "f"]) In [117]: a Out[117]: f NaN e 2.5 d 0.0 c 3.5 b 4.5 a NaN dtype: float64 In [118]: b Out[118]: a 0.0 b NaN c 2.0 d NaN e NaN f 5.0 dtype: float64 In [119]: np.where(pd.isna(a), b, a) Out[119]: array([0. , 2.5, 0. , 3.5, 4.5, 5. ])
在这里,每当a
中的值为空时,将选择b
中的值,否则将选择a
中的非空值。使用numpy.where
不会检查索引标签是否对齐(甚至不需要对象具有相同的长度),因此如果要按索引对齐值,请使用 Seriescombine_first
方法:
In [120]: a.combine_first(b) Out[120]: a 0.0 b 4.5 c 3.5 d 0.0 e 2.5 f 5.0 dtype: float64
对于 DataFrame,combine_first
按列执行相同的操作,因此您可以将其视为使用传递的对象中的数据“修补”调用对象中的缺失数据:
In [121]: df1 = pd.DataFrame({"a": [1., np.nan, 5., np.nan], .....: "b": [np.nan, 2., np.nan, 6.], .....: "c": range(2, 18, 4)}) In [122]: df2 = pd.DataFrame({"a": [5., 4., np.nan, 3., 7.], .....: "b": [np.nan, 3., 4., 6., 8.]}) In [123]: df1 Out[123]: a b c 0 1.0 NaN 2 1 NaN 2.0 6 2 5.0 NaN 10 3 NaN 6.0 14 In [124]: df2 Out[124]: a b 0 5.0 NaN 1 4.0 3.0 2 NaN 4.0 3 3.0 6.0 4 7.0 8.0 In [125]: df1.combine_first(df2) Out[125]: a b c 0 1.0 NaN 2.0 1 4.0 2.0 6.0 2 5.0 4.0 10.0 3 3.0 6.0 14.0 4 7.0 8.0 NaN
使用 DataFrame 对象的combine_first
的输出将具有所有列名称的并集。
Python 数据分析(PYDA)第三版(四)(2)https://developer.aliyun.com/article/1482384