实战讲解pandas中merge, join, concat的区别
Posted bitcarmanlee
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实战讲解pandas中merge, join, concat的区别相关的知识,希望对你有一定的参考价值。
1.从一个需求说起
最近经常有这么一堆数据需要处理一下,而且是很常见的需求:
有一个数据集,数据集里全是数字,需要对数据集按区间段进行个数统计,并计算各区间段的占比,所以本质上就是个算占比的事情。
有的同志对此不屑一顾,这算哪门子事,搞个excel还不是很简单。
当然excel是可以解决上面的问题。问题在于,第一,程序猿是很讨厌使用excel这种带.xxx的文件的,.xxx意味着通用性很差,必须用特定的软件程序才能打开。第二,大部分的开发环境是linux或者macos,这两开发环境不像windows,对于office系列的支持很差,而且office系列的东西都相当耗资源占CPU占内存,个人非常反感为了处理一个数据去开一个特别重的客户端软件。第三,excel再强大,毕竟没有写代码灵活,平时还是有很多稀奇古怪的需求,尤其是字符串处理,用代码处理还是首选。
所以,如果处理数据第一反应想到的是excel而不是写code解决,那一定是个运营汪而不是程序猿…
所以说了这么多,先直接上代码看看怎么搞定上面的事情。
2.pandas cut concat
import pandas as pd
def t1():
data1 = [552, 462, 565, 810, 720, 753, 602, 485, 475, 380, 590, 402, 501]
data2 = [553, 362, 585, 710, 720, 559, 760, 785, 375, 680, 690, 403, 512]
bins = [300, 400, 450, 550, 650, 750, 850]
cut1 = pd.cut(data1, bins)
cut2 = pd.cut(data2, bins)
ret1 = pd.value_counts(cut1, ascending=False)
ret2 = pd.value_counts(cut2, ascending=False)
nret1 = pd.value_counts(cut1, normalize=True, ascending=False)
nret2 = pd.value_counts(cut2, normalize=True, ascending=False)
concat1 = pd.concat([ret1, nret1, ret2, nret2], axis=1)
print(concat1)
t1()
上面的输出结果:
0 1 2 3
(300, 400] 1 0.076923 2 0.153846
(400, 450] 1 0.076923 1 0.076923
(450, 550] 4 0.307692 1 0.076923
(550, 650] 4 0.307692 3 0.230769
(650, 750] 1 0.076923 4 0.307692
(750, 850] 2 0.153846 2 0.153846
上面的代码中,pd.cut, pd.value_counts的用法之前都已经讲过了,不再重点多讲,唯一需要再提一点的是pd.value_counts方法中normalize=True时,输出的就是占比,否则是数量。
重点看看这一行
concat1 = pd.concat([ret1, nret1, ret2, nret2], axis=1)
pandas中的concat只是单纯地将两个表"连接"在一起。这个过程叫作绑定(binding)或堆叠(stacking)。上面的例子,因为我们要保持"行"不变,而将"列"追加到一起,所以设置axis=1。
如果没有指定axis参数,默认的是axis=0, 意思就是保持"列"不变,按行追加。看个简单的例子
def t2():
data1 = {"A": [1, 2, 3], "B": [4, 5, 6]}
data2 = {"C": [7, 8, 9], "D": [10, 11, 12]}
df1 = pd.DataFrame(data1)
df2 = pd.DataFrame(data2)
print(pd.concat([df1, df2]))
t2()
结果为:
A B C D
0 1.0 4.0 NaN NaN
1 2.0 5.0 NaN NaN
2 3.0 6.0 NaN NaN
0 NaN NaN 7.0 10.0
1 NaN NaN 8.0 11.0
2 NaN NaN 9.0 12.0
concat方法中要有一个ignore_index参数。ignore_index 忽略需要连接的frame本身的index,当原本的index没有特别意义的时候可以使用该参数。
如果将上面的代码中稍作修改如下:
print(pd.concat([df1, df2], ignore_index=True))
则结果变为
A B C D
0 1.0 4.0 NaN NaN
1 2.0 5.0 NaN NaN
2 3.0 6.0 NaN NaN
3 NaN NaN 7.0 10.0
4 NaN NaN 8.0 11.0
5 NaN NaN 9.0 12.0
3.merge方法
实际处理数据过程中,我们还经常需要做的一件事情就是join,就是数据库中的join操作,将两个表中的数据根据某一列拼接到一起。
同样看一个很常见的例子:
def t3():
agedata = {"name": ["lucy", "lili", "xiaoming"], "age": [15, 18, 21]}
citydata = {"name": ["lucy", "lili", "xiaohua"], "city": ["beijing", "shanghai", "guangzhou"]}
df1 = pd.DataFrame(agedata)
df2 = pd.DataFrame(citydata)
ret = pd.merge(df1, df2, on="name", how="left")
print(ret)
t3()
输出结果如下
name age city
0 lucy 15 beijing
1 lili 18 shanghai
2 xiaoming 21 NaN
上面的例子,就是sql中的join。两个dataframe,根据name字段进行left join,得到最终的结果。
merge方法的签名如下
@Substitution("\\nleft : DataFrame")
@Appender(_merge_doc, indents=0)
def merge(
left,
right,
how: str = "inner",
on=None,
left_on=None,
right_on=None,
left_index: bool = False,
right_index: bool = False,
sort: bool = False,
suffixes=("_x", "_y"),
copy: bool = True,
indicator: bool = False,
validate=None,
) -> "DataFrame":
op = _MergeOperation(
left,
right,
how=how,
on=on,
left_on=left_on,
right_on=right_on,
left_index=left_index,
right_index=right_index,
sort=sort,
suffixes=suffixes,
copy=copy,
indicator=indicator,
validate=validate,
)
return op.get_result()
4.merge各个参数的作用
参数left_index和right_index,最开始不明白这两参数的作用,后来经过尝试发现他们的作用如下。
上面的例子我们是用on来指定连接的主键。不光可以通过on来指定,我们还可以用索引作为拼接的主键,只需要将left_index与right_index参数设置为true就可以。
def t4():
agedata = {"name": ["lucy", "lili", "xiaoming"], "age": [15, 18, 21]}
citydata = {"name": ["lucy", "lili", "xiaohua"], "city": ["beijing", "shanghai", "guangzhou"]}
df1 = pd.DataFrame(agedata)
df2 = pd.DataFrame(citydata)
ret = pd.merge(df1, df2, left_index=True, right_index=True)
print(ret)
t4()
输出结果为
name_x age name_y city
0 lucy 15 lucy beijing
1 lili 18 lili shanghai
2 xiaoming 21 xiaohua guangzhou
df1与df2默认的索引均为0,1,2,所以根据索引进行连接时,就刚好将两个df完美地拼接到了一起。其中suffixes默认为("_x", “_y”),连接的时候如果有相同的字段名自动将其添加相应的后缀。
how参数控制拼接方式,默认内连接(inner)。连接方式与sql中一样,有left,right,inner,outer这几种。
5.join方法
通过上面的例子,我们不难看出,sql中传统的join,在pandas中实际上是通过merge方法实现的。但是pandas中也有join方法,那么pandas中的join方法实现的是啥功能?
先说结论:
join方法拼接列主要是基于行索引上的合并。
看几个例子
def t5():
agedata = {"name": ["lucy", "lili", "xiaoming"], "age": [15, 18, 21]}
citydata = {"name": ["lucy", "lili", "xiaohua"], "city": ["beijing", "shanghai", "guangzhou"]}
df1 = pd.DataFrame(agedata)
df2 = pd.DataFrame(citydata)
df1.join(df2)
这个方法运行会报错
ValueError: columns overlap but no suffix specified: Index(['name'], dtype='object')
将上面的代码修改一下
def t5():
agedata = {"name": ["lucy", "lili", "xiaoming"], "age": [15, 18, 21]}
citydata = {"name": ["lucy", "lili", "xiaohua"], "city": ["beijing", "shanghai", "guangzhou"]}
df1 = pd.DataFrame(agedata)
df2 = pd.DataFrame(citydata)
ret = df1.join(df2, lsuffix="_x", rsuffix="_y")
print(ret)
结果如下
name_x age name_y city
0 lucy 15 lucy beijing
1 lili 18 lili shanghai
2 xiaoming 21 xiaohua guangzhou
是不是与上面merge方法基于行索引合并的结果一致?
实际上我们查看join方法的源码
def join(
self, other, on=None, how="left", lsuffix="", rsuffix="", sort=False
) -> "DataFrame":
"""
Join columns of another DataFrame.
Join columns with `other` DataFrame either on index or on a key
column. Efficiently join multiple DataFrame objects by index at once by
passing a list.
.......
return self._join_compat(
other, on=on, how=how, lsuffix=lsuffix, rsuffix=rsuffix, sort=sort
)
def _join_compat(
self, other, on=None, how="left", lsuffix="", rsuffix="", sort=False
):
from pandas.core.reshape.merge import merge
from pandas.core.reshape.concat import concat
if isinstance(other, Series):
if other.name is None:
raise ValueError("Other Series must have a name")
other = DataFrame({other.name: other})
if isinstance(other, DataFrame):
return merge(
self,
other,
left_on=on,
how=how,
left_index=on is None,
right_index=True,
suffixes=(lsuffix, rsuffix),
sort=sort,
)
else:
if on is not None:
raise ValueError(
"Joining multiple DataFrames only supported for joining on index"
)
frames = [self] + list(other)
can_concat = all(df.index.is_unique for df in frames)
# join indexes only using concat
if can_concat:
if how == "left":
res = concat(
frames, axis=1, join="outer", verify_integrity=True, sort=sort
)
return res.reindex(self.index, copy=False)
else:
return concat(
frames, axis=1, join=how, verify_integrity=True, sort=sort
)
joined = frames[0]
for frame in frames[1:]:
joined = merge(
joined, frame, how=how, left_index=True, right_index=True
)
return joined
通过上面代码,我们也不难看出,join方法,其实最终调用的,也是merge方法。或者说,join方法其实就是merge的一个特例而已。
6.结论
综上所述
1.要想实现sql中的join,需要使用merge方法。
2.pandas中的join方法,相比merge,只是个弟弟,使用场景有限。
3.concat实现的只是将两个df按行或者案列简单进行拼接的功能,并没有实现sql中的join功能。
以上是关于实战讲解pandas中merge, join, concat的区别的主要内容,如果未能解决你的问题,请参考以下文章
pandas 合并数据函数merge join concat combine_first 区分
Pandas文摘:Join And Merge Pandas Dataframe