Python数据分析NumPy和pandas(二十五、数据整理--连接、合并和重塑 之二:数据连接合并操作)
pandas提供了多种方式来组合数据,常用的方法有:pandas.merge()、pandas.concat()和combine_first()等。
pandas.merge:根据一个或多个键连接 DataFrame 中的行或列。类似于数据库SQL语句的连接操作。
pandas.concat:沿轴方向将对象连接或堆叠在一起。
combine_first:将重叠的数据拼接在一起,使用一个对象中的值填充另一个对象中的缺失值。
一、DataFrame的连接
DataFrame数据集的连接操作可以通过一个或多个键(key)连接行来实现,类似于数据库的SQL join语句。例如:
import numpy as np
import pandas as pd# df1和df2的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "a", "b"],"data1": pd.Series(range(7), dtype="Int64")})
df2 = pd.DataFrame({"key": ["a", "b", "d"],"data2": pd.Series(range(3), dtype="Int64")})# many-to-one join 多对一连接
result = pd.merge(df1, df2)
df1输出:
key | data1 | |
---|---|---|
0 | b | 0 |
1 | b | 1 |
2 | a | 2 |
3 | c | 3 |
4 | a | 4 |
5 | a | 5 |
6 | b | 6 |
df2输出:
key | data2 | |
---|---|---|
0 | a | 0 |
1 | b | 1 |
2 | d | 2 |
pd.merge(df1, df2) 按df1和df2的key进行匹配,执行行连接操作,过滤掉未匹配的行,输出:
key | data1 | data2 | |
---|---|---|---|
0 | b | 0 | 1 |
1 | b | 1 | 1 |
2 | a | 2 | 0 |
3 | a | 4 | 0 |
4 | a | 5 | 0 |
5 | b | 6 | 1 |
注意:在上面的代码pd.merge(df1, df2)中没有指定作为key连接的列,这种情况 pandas.merge 使用相同(重叠)的列名作为键,所以这里使用key列名作为连接键,但是最好要明确指定关联列名信息。如:pd.merge(df1, df2, on="key")。通常,pandas.merge 操作后的列输出顺序是未指定的。
如果要连接的对象没有相同的列名,我们可以用参数left_on, right_on指定关联键,例如:
import numpy as np
import pandas as pd# df3和df4的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
df3 = pd.DataFrame({"lkey": ["b", "b", "a", "c", "a", "a", "b"],"data1": pd.Series(range(7), dtype="Int64")})
df4 = pd.DataFrame({"rkey": ["a", "b", "d"],"data2": pd.Series(range(3), dtype="Int64")})
# df3和df4中没有相同列名,用left_on, right_on参数指定关联列名
pd.merge(df3, df4, left_on="lkey", right_on="rkey")
df3输出:
lkey | data1 | |
---|---|---|
0 | b | 0 |
1 | b | 1 |
2 | a | 2 |
3 | c | 3 |
4 | a | 4 |
5 | a | 5 |
6 | b | 6 |
df4输出:
rkey | data2 | |
---|---|---|
0 | a | 0 |
1 | b | 1 |
2 | d | 2 |
pd.merge(df3, df4, left_on="lkey", right_on="rkey") 输出:
lkey | data1 | rkey | data2 | |
---|---|---|---|---|
0 | b | 0 | b | 1 |
1 | b | 1 | b | 1 |
2 | a | 2 | a | 0 |
3 | a | 4 | a | 0 |
4 | a | 5 | a | 0 |
5 | b | 6 | b | 1 |
从输出结果中可以看到,缺少 “c” 和 “d” 值以及相关数据,这是因为默认情况下,pandas.merge 执行 “inner” 内连接,结果中的键是两个表中的交集。我们还可以通过how参数指定连接方式: “left”、“right” 和 “outer”(左连接、右连接和外连接)。外连接采用键的并集,例如:
import numpy as np
import pandas as pd# df1和df2的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "a", "b"],"data1": pd.Series(range(7), dtype="Int64")})
df2 = pd.DataFrame({"key": ["a", "b", "d"],"data2": pd.Series(range(3), dtype="Int64")})# df3和df4的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
df3 = pd.DataFrame({"lkey": ["b", "b", "a", "c", "a", "a", "b"],"data1": pd.Series(range(7), dtype="Int64")})
df4 = pd.DataFrame({"rkey": ["a", "b", "d"],"data2": pd.Series(range(3), dtype="Int64")})# 外连接
pd.merge(df1, df2, how="outer")
pd.merge(df3, df4, left_on="lkey", right_on="rkey", how="outer")
pd.merge(df1, df2, how="outer") 输出:
key | data1 | data2 | |
---|---|---|---|
0 | a | 2 | 0 |
1 | a | 4 | 0 |
2 | a | 5 | 0 |
3 | b | 0 | 1 |
4 | b | 1 | 1 |
5 | b | 6 | 1 |
6 | c | 3 | <NA> |
7 | d | <NA> | 2 |
pd.merge(df3, df4, left_on="lkey", right_on="rkey", how="outer") 输出:
lkey | data1 | rkey | data2 | |
---|---|---|---|---|
0 | a | 2 | a | 0 |
1 | a | 4 | a | 0 |
2 | a | 5 | a | 0 |
3 | b | 0 | b | 1 |
4 | b | 1 | b | 1 |
5 | b | 6 | b | 1 |
6 | c | 3 | NaN | <NA> |
7 | NaN | <NA> | d | 2 |
在外连接中,左侧或右侧 DataFrame 对象的行如果与关联的 DataFrame 中的键不匹配,则关联的 DataFrame 的列中将在对应行上显示 NA 值。how 参数的不同连接类型说明如下图列表:
看一个使用how="left"左连接的例子:
import numpy as np
import pandas as pd# df3和df4的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "b"],"data1": pd.Series(range(6), dtype="Int64")})
df2 = pd.DataFrame({"key": ["a", "b", "a", "b", "d"],"data2": pd.Series(range(5), dtype="Int64")})
# 以df1为主表左连接关联df2
pd.merge(df1, df2, on="key", how="left")
df1输出:
key | data1 | |
---|---|---|
0 | b | 0 |
1 | b | 1 |
2 | a | 2 |
3 | c | 3 |
4 | a | 4 |
5 | b | 5 |
df2输出:
key | data2 | |
---|---|---|
0 | a | 0 |
1 | b | 1 |
2 | a | 2 |
3 | b | 3 |
4 | d | 4 |
pd.merge(df1, df2, on="key", how="left") 输出(多对多关联形成笛卡尔积,例如,由于左侧 DataFrame 中有 3 个 “b” 行,右侧 DataFrame 中有 2 个 “b” 行,因此结果中有 6 个 “b” 行):
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 |
再看下内连接,将参数how的值设置为inner,how="inner"。
import numpy as np
import pandas as pd# df3和df4的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "b"],"data1": pd.Series(range(6), dtype="Int64")})
df2 = pd.DataFrame({"key": ["a", "b", "a", "b", "d"],"data2": pd.Series(range(5), dtype="Int64")})
# 以df1为主表左连接关联df2
pd.merge(df1, df2, on="key", how="inner")
pd.merge(df1, df2, on="key" how="inner") 输出:
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 | a | 4 | 0 |
7 | a | 4 | 2 |
8 | b | 5 | 1 |
9 | b | 5 | 3 |
how="inner" 这种连接方式只输出双方匹配上的key所在的行,去掉了双方没有匹配的c,d值。
如果要基于多个键进行合并,可以传递列名列表,如下示例:
import numpy as np
import pandas as pd# left和right的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
left = pd.DataFrame({"key1": ["foo", "foo", "bar"],"key2": ["one", "two", "one"],"lval": pd.Series([1, 2, 3], dtype='Int64')})
right = pd.DataFrame({"key1": ["foo", "foo", "bar", "bar"],"key2": ["one", "one", "one", "two"],"rval": pd.Series([4, 5, 6, 7], dtype='Int64')})
pd.merge(left, right, on=["key1", "key2"], how="outer")
left输出:
key1 | key2 | lval | |
---|---|---|---|
0 | foo | one | 1 |
1 | foo | two | 2 |
2 | bar | one | 3 |
right输出:
key1 | key2 | rval | |
---|---|---|---|
0 | foo | one | 4 |
1 | foo | one | 5 |
2 | bar | one | 6 |
3 | bar | two | 7 |
pd.merge(left, right, on=["key1", "key2"], how="outer") 输出:
key1 | key2 | lval | rval | |
---|---|---|---|---|
0 | bar | one | 3 | 6 |
1 | bar | two | <NA> | 7 |
2 | foo | one | 1 | 4 |
3 | foo | one | 1 | 5 |
4 | foo | two | 2 | <NA> |
注意:在进行列连接时,会丢弃传 DataFrame 对象上的索引。如果需要保留索引值,可以使用 reset_index 将索引追加到列。
合并操作中还要考虑的一个问题是:怎么处理合并对象存在相同列名的显示,例如对于上面的示例中的两个DataFrame对象left和right在key1上进行关联:
import numpy as np
import pandas as pd# df3和df4的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
left = pd.DataFrame({"key1": ["foo", "foo", "bar"],"key2": ["one", "two", "one"],"lval": pd.Series([1, 2, 3], dtype='Int64')})
right = pd.DataFrame({"key1": ["foo", "foo", "bar", "bar"],"key2": ["one", "one", "one", "two"],"rval": pd.Series([4, 5, 6, 7], dtype='Int64')})#pd.merge(left, right, on=["key1", "key2"], how="outer")
pd.merge(left, right, on="key1")
pd.merge(left, right, on="key1")输出:
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 |
虽然pandas.merge默认会对重复的左右关联对象中的相同列名做处理,但是一般我们还是可以使用suffixes参数给相同列名指定后缀:
import numpy as np
import pandas as pd# df3和df4的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
left = pd.DataFrame({"key1": ["foo", "foo", "bar"],"key2": ["one", "two", "one"],"lval": pd.Series([1, 2, 3], dtype='Int64')})
right = pd.DataFrame({"key1": ["foo", "foo", "bar", "bar"],"key2": ["one", "one", "one", "two"],"rval": pd.Series([4, 5, 6, 7], dtype='Int64')})pd.merge(left, right, on="key1", suffixes=("_left", "_right"))
pd.merge(left, right, on="key1", suffixes=("_left", "_right")) 输出:
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()的参数见下图列表,学习过程中可以试着用一用:
上面学习的是对列的合并操作,下面接着来了解对行的合并或连接操作。
二、在索引(行)上合并
要在索引(行)上合并或连接数据,需要用到参数 left_index=True 或 right_index=True(或两者)等,以指示将索引(行)用作合并键。
import numpy as np
import pandas as pd# left1的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
left1 = pd.DataFrame({"key": ["a", "b", "a", "a", "b", "c"],"value": pd.Series(range(6), dtype="Int64")})
right1 = pd.DataFrame({"group_val": [3.5, 7]}, index=["a", "b"])
pd.merge(left1, right1, left_on="key", right_index=True)
left1输出:
key | value | |
---|---|---|
0 | a | 0 |
1 | b | 1 |
2 | a | 2 |
3 | a | 3 |
4 | b | 4 |
5 | c | 5 |
right1输出:
group_val | |
---|---|
a | 3.5 |
b | 7.0 |
pd.merge(left1, right1, left_on="key", right_index=True) 这行代码中,用left_on="key"指示使用left1的key列作为连接键,right_index=True指示使用right1的行索引作为连接键,这行代码输出:
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 |
从这个输出可以看出 left1的行索引(最左列0,1,2,3,4)被保留了,因为 right1 的索引是唯一的,所以这个 “多对一” 合并(使用默认的 how=“inner” 方法)可以保留 left1 中与输出中的行相对应的索引值。
由于默认的合并方法是使连接键求交集,我们也可以用外连接来形成它们的并集:
import numpy as np
import pandas as pd# left1的数据使用 pandas的扩展类型 Int64(用于可为空的整数)
left1 = pd.DataFrame({"key": ["a", "b", "a", "a", "b", "c"],"value": pd.Series(range(6), dtype="Int64")})
right1 = pd.DataFrame({"group_val": [3.5, 7]}, index=["a", "b"])
# 外连接求并集
pd.merge(left1, right1, left_on="key", right_index=True, how="outer")
pd.merge(left1, right1, left_on="key", right_index=True, how="outer") 输出(从输出也可以看出left1的行索引值被保留了):
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 |
如果对象有多层索引,处理起来会更复杂些,对于分层索引数据,因为在索引(指行索引)上连接等效于多键合并,还是看代码示例:
import numpy as np
import pandas as pd# lefth和righth的数据都是用pandas扩展类型Int64,允许空的整数
lefth = pd.DataFrame({"key1": ["Ohio", "Ohio", "Ohio","Nevada", "Nevada"],"key2": [2000, 2001, 2002, 2001, 2002],"data": pd.Series(range(5), dtype="Int64")})
righth_index = pd.MultiIndex.from_arrays([["Nevada", "Nevada", "Ohio", "Ohio", "Ohio", "Ohio"],[2001, 2000, 2000, 2000, 2001, 2002]]
)
righth = pd.DataFrame({"event1": pd.Series([0, 2, 4, 6, 8, 10], dtype="Int64",index=righth_index),"event2": pd.Series([1, 3, 5, 7, 9, 11], dtype="Int64",index=righth_index)})pd.merge(lefth, righth, left_on=["key1", "key2"], right_index=True)
lefth输出:
key1 | key2 | data | |
---|---|---|---|
0 | Ohio | 2000 | 0 |
1 | Ohio | 2001 | 1 |
2 | Ohio | 2002 | 2 |
3 | Nevada | 2001 | 3 |
4 | Nevada | 2002 | 4 |
righth输出(具有多层索引):
event1 | event2 | ||
---|---|---|---|
Nevada | 2001 | 0 | 1 |
2000 | 2 | 3 | |
Ohio | 2000 | 4 | 5 |
2000 | 6 | 7 | |
2001 | 8 | 9 | |
2002 | 10 | 11 |
pd.merge(lefth, righth, left_on=["key1", "key2"], right_index=True) 这行代码传递了一个连接键列表给left_on参数指示左侧用key1, key2作为连接键,right_index=True指示右侧使用行索引作为连接键,输出:
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 |
pd.merge(lefth, righth, left_on=["key1", "key2"], right_index=True) 这行代码我们再加上how="outer"进行外连接看看会输出什么内容:
# lefth和righth的数据都是用pandas扩展类型Int64,允许空的整数
lefth = pd.DataFrame({"key1": ["Ohio", "Ohio", "Ohio","Nevada", "Nevada"],"key2": [2000, 2001, 2002, 2001, 2002],"data": pd.Series(range(5), dtype="Int64")})
righth_index = pd.MultiIndex.from_arrays([["Nevada", "Nevada", "Ohio", "Ohio", "Ohio", "Ohio"],[2001, 2000, 2000, 2000, 2001, 2002]]
)
righth = pd.DataFrame({"event1": pd.Series([0, 2, 4, 6, 8, 10], dtype="Int64",index=righth_index),"event2": pd.Series([1, 3, 5, 7, 9, 11], dtype="Int64",index=righth_index)})
pd.merge(lefth, righth, left_on=["key1", "key2"], right_index=True, how="outer")
pd.merge(lefth, righth, left_on=["key1", "key2"], right_index=True, how="outer") 输出:
key1 | key2 | data | event1 | event2 | |
---|---|---|---|---|---|
4 | Nevada | 2000 | <NA> | 2 | 3 |
3 | Nevada | 2001 | 3 | 0 | 1 |
4 | Nevada | 2002 | 4 | <NA> | <NA> |
0 | Ohio | 2000 | 0 | 4 | 5 |
0 | Ohio | 2000 | 0 | 6 | 7 |
1 | Ohio | 2001 | 1 | 8 | 9 |
2 | Ohio | 2002 | 2 | 10 | 11 |
通过how="outer"指示进行外连接,输出的是两个DataFrame的并集。 上面保留了lefth的索引,也可以合并双方的索引,如下代码示例:
import numpy as np
import pandas as pd# left2和right2的数据都是用pandas扩展类型Int64,允许空的整数
left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],index=["a", "c", "e"],columns=["Ohio", "Nevada"]).astype("Int64")
right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],index=["b", "c", "d", "e"],columns=["Missouri", "Alabama"]).astype("Int64")pd.merge(left2, right2, how="outer", left_index=True, right_index=True)
left2输出:
Ohio | Nevada | |
---|---|---|
a | 1 | 2 |
c | 3 | 4 |
e | 5 | 6 |
right2输出:
Missouri | Alabama | |
---|---|---|
b | 7 | 8 |
c | 9 | 10 |
d | 11 | 12 |
e | 13 | 14 |
pd.merge(left2, right2, how="outer", left_index=True, right_index=True) 输出:
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 对象。在前面的示例中,可以这样写:
left2.join(right2, how="outer") 输出:
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实例的索引连接到调用join方法的 DataFrame 实例的其中一列上,相当于将join()括号中DataFrame实列的数据连接到调用了 join 方法的对象中:
left1.join(right1, on="key") 输出:
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 |
最后,索引与索引的合并,可以传递要合并的 DataFrames 对象列表给join()方法,作为下面要学习的 pandas.concat 函数的替代方法,例如:
import numpy as np
import pandas as pd# left2和right2的数据都是用pandas扩展类型Int64,允许空的整数
left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],index=["a", "c", "e"],columns=["Ohio", "Nevada"]).astype("Int64")
right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],index=["b", "c", "d", "e"],columns=["Missouri", "Alabama"]).astype("Int64")
another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],index=["a", "c", "e", "f"],columns=["New York", "Oregon"])left2.join([right2, another])
left2.join([right2, another], how="outer")
left2.join([right2, another]) 输出:
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 |
left2.join([right2, another], how="outer") 输出:
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 |
三、沿轴(Axis)进行连接
另一种沿轴方向的数据组合操作方式可互相称为 concatenation 或 stacking。NumPy 的 concatenate 函数可以对 NumPy 数组执行此操作,看代码示例:
import numpy as np
import pandas as pdarr = np.arange(12).reshape((3, 4))
print(arr)
print(np.concatenate([arr, arr], axis=1))
print(arr)输出:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
print(np.concatenate([arr, arr], axis=1)) 输出:
[[ 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,
s1 = pd.Series([0, 1], index=["a", "b"], dtype="Int64")
s2 = pd.Series([2, 3, 4], index=["c", "d", "e"], dtype="Int64")
s3 = pd.Series([5, 6], index=["f", "g"], dtype="Int64")
pd.concat([s1, s2, s3]) 输出:
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: Int64
默认情况下,pandas.concat 沿 axis=“index” 工作,生成另一个 Series。如果传递 axis=“columns”,则结果将是一个 DataFrame对象。
pd.concat([s1, s2, s3], axis="columns") 输出:
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 |
以上这种情况,由于另一个轴上没有重叠,这就相当于是索引的联合(“外”连接)。相反,我们可以通过传递参数 join=“inner” 来使它们相交:
import numpy as np
import pandas as pds1 = pd.Series([0, 1], index=["a", "b"], dtype="Int64")
s2 = pd.Series([2, 3, 4], index=["c", "d", "e"], dtype="Int64")
s3 = pd.Series([5, 6], index=["f", "g"], dtype="Int64")s4 = pd.concat([s1, s3])result = pd.concat([s1, s4], axis="columns")
result_inner = pd.concat([s1, s4], axis="columns", join="inner")
print(s4)
print(result)
print(result_inner)
s4输出:
a 0
b 1
f 5
g 6
dtype: Int64
result输出:
0 | 1 | |
---|---|---|
a | 0 | 0 |
b | 1 | 1 |
f | <NA> | 5 |
g | <NA> | 6 |
result_inner输出:
0 | 1 | |
---|---|---|
a | 0 | 0 |
b | 1 | 1 |
从result_inner输出结果可以看出,由于使用了 join=“inner” ,“f” 和 “g” 标签行被丢弃了。
上面的示例发现了一个潜在的问题是,就是在结果中无法识别串联的片段,即无法在结果中识别出s1、s2、s3、s4等。为了能够识别,我们可以在串联轴上使用keys参数创建分层索引:
import numpy as np
import pandas as pds1 = pd.Series([0, 1], index=["a", "b"], dtype="Int64")
s2 = pd.Series([2, 3, 4], index=["c", "d", "e"], dtype="Int64")
s3 = pd.Series([5, 6], index=["f", "g"], dtype="Int64")result = pd.concat([s1, s1, s3], keys=["one", "two", "three"])
print(result)print(result.unstack())
result输出:
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: Int64
print(result.unstack()) 输出:
a | b | f | g | |
---|---|---|---|---|
one | 0 | 1 | <NA> | <NA> |
two | 0 | 1 | <NA> | <NA> |
three | <NA> | <NA> | 5 | 6 |
在沿 axis=“columns” 组合 Series 的情况下,键成为 组合生成的DataFrame 的列标题:
import numpy as np
import pandas as pds1 = pd.Series([0, 1], index=["a", "b"], dtype="Int64")
s2 = pd.Series([2, 3, 4], index=["c", "d", "e"], dtype="Int64")
s3 = pd.Series([5, 6], index=["f", "g"], dtype="Int64")result = pd.concat([s1, s2, s3], axis="columns", keys=["one", "two", "three"])
print(result)
result输出:
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 对象,看如下代码示例:
import numpy as np
import pandas as pddf1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=["a", "b", "c"],columns=["one", "two"])
df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=["a", "c"],columns=["three", "four"])
print(df1)
print(df2)
result = pd.concat([df1, df2], axis="columns", keys=["level1", "level2"])
print(result)
df1输出:
one | two | |
---|---|---|
a | 0 | 1 |
b | 2 | 3 |
c | 4 | 5 |
df2输出:
three | four | |
---|---|---|
a | 5 | 6 |
c | 7 | 8 |
result输出(level1和level2作为了生成的DataFrame的列标签,这里是分层索引的第一级,并且能够通过这个标签识别出串联前的DataFrame对象):
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 |
如果我们将对象字典传递给concat方法,则字典的key将作为keys参数的值,例如:
pd.concat({"level1": df1, "level2": df2}, axis="columns")
这行代码的输出与pd.concat([df1, df2], axis="columns", keys=["level1", "level2"])的输出相同。
下图中的列表是可以用于创建分层索引的其他参数控制。
例如,我们可以使用 names 参数命名创建的轴级别:
import numpy as np
import pandas as pddf1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=["a", "b", "c"],columns=["one", "two"])
df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=["a", "c"],columns=["three", "four"])result = pd.concat([df1, df2], axis="columns", keys=["level1", "level2"], names=["upper", "lower"])
print(result)
这里用names参数给生成的分层索引命名,外层是upper,内层是lower。上面示例代码输出:
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,可以传递 ignore_index=True给concat()方法,这将丢弃每个 DataFrame 中的索引,仅连接列中的数据,并分配新的默认索引,例如:
import numpy as np
import pandas as pddf1 = pd.DataFrame(np.random.standard_normal((3, 4)),columns=["a", "b", "c", "d"])
df2 = pd.DataFrame(np.random.standard_normal((2, 3)),columns=["b", "d", "a"])result = pd.concat([df1, df2], ignore_index=True)
print(df1, "\n", df2, "\n", result)
df1输出:
a | b | c | d | |
---|---|---|---|---|
0 | -1.265934 | 0.119827 | -1.063512 | 0.332883 |
1 | -2.359419 | -0.199543 | -1.541996 | -0.970736 |
2 | -1.307030 | 0.286350 | 0.377984 | -0.753887 |
df2输出:
b | d | a | |
---|---|---|---|
0 | 0.331286 | 1.349742 | 0.069877 |
1 | 0.246674 | -0.011862 | 1.004812 |
result输出:
a | b | c | d | |
---|---|---|---|---|
0 | -1.265934 | 0.119827 | -1.063512 | 0.332883 |
1 | -2.359419 | -0.199543 | -1.541996 | -0.970736 |
2 | -1.307030 | 0.286350 | 0.377984 | -0.753887 |
3 | 0.069877 | 0.331286 | NaN | 1.349742 |
4 | 1.004812 | 0.246674 | NaN | -0.011862 |
四、使用 Overlap 合并数据
还有一种不能表示为合并或串联操作的数据组合情况,比如对两个索引完全或部分重叠的数据集进行合并,我们使用 NumPy 的 where 函数,它执行面向数组的与 if-else 表达式的等效的操作。例如:
import numpy as np
import pandas as pda = pd.Series([np.nan, 2.5, 0.0, 3.5, 4.5, np.nan],index=["f", "e", "d", "c", "b", "a"])
b = pd.Series([0., np.nan, 2., np.nan, np.nan, 5.],index=["a", "b", "c", "d", "e", "f"])
result = np.where(pd.isna(a), b, a)
print(a, "\n", b, "\n", result)
a输出:
f NaN
e 2.5
d 0.0
c 3.5
b 4.5
a NaN
dtype: float64
b输出:
a 0.0
b NaN
c 2.0
d NaN
e NaN
f 5.0
dtype: float64
result输出:
[0. 2.5 0. 3.5 4.5 5. ]
np.where(pd.isna(a), b, a) 表示只要 a 中的值为 null,就会选择 b 中的值,否则选择 a 中的非 null 值。使用 numpy.where 不会检查索引标签是否对齐(甚至不要求对象具有相同的长度)。如果要按索引排列值,我们可以使用 Series combine_first 方法。
例如对于上面的Series对象a和b:a.combine_first(b) 输出:
a 0.0
b 4.5
c 3.5
d 0.0
e 2.5
f 5.0
dtype: float64
对于 DataFrames 对象,combine_first逐列执行相同的操作,我们可以将其视为用传递给combine_first的对象中的数据“修补”调用对象中的缺失数据:
import numpy as np
import pandas as pddf1 = pd.DataFrame({"a": [1., np.nan, 5., np.nan],"b": [np.nan, 2., np.nan, 6.],"c": range(2, 18, 4)})
df2 = pd.DataFrame({"a": [5., 4., np.nan, 3., 7.],"b": [np.nan, 3., 4., 6., 8.]})result = df1.combine_first(df2)
print(df1, "\n", df2, "\n", result)
df1输出:
a | b | c | |
---|---|---|---|
0 | 1.0 | NaN | 2 |
1 | NaN | 2.0 | 6 |
2 | 5.0 | NaN | 10 |
3 | NaN | 6.0 | 14 |
df2输出:
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 |
df1.combine_first(df2)输出:
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() 方法调用的输出将具有所有列名的并集。