实战讲解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中merge和join的区别

Pandas文摘:Join And Merge Pandas Dataframe

Pandas 中的 join 和 merge 有啥区别?

Pandas -- Merge,join and concatenate

PANDAS 数据合并与重塑(join/merge篇)