1. ホーム
  2. パイソン

pandasのmerge, join, concatの違いに関する実践的な解説

2022-02-25 14:52:44
<パス

1. 要件から始める

最近、ちょっとした作業が必要なデータが大量にあり、それは非常に一般的な要件である
数字でいっぱいのデータセットがあり、データセット内のセグメントの数を区間ごとに数え、各区間の割合を計算する必要があります。つまり、本質的にはパーセントを数えるようなものです。
問題は、第一に、プログラマーは.xxxファイルでエクセルを使うことを嫌います。.xxxは汎用性が低く、それを開くには特定のソフトウェアプログラムを使わなければならないことを意味します。第二に、開発環境のほとんどはLinuxやmacos、Windowsとは異なり、2つの開発環境は、オフィスシリーズのサポートは非常に貧弱であり、オフィスシリーズのものはかなりリソース集約型のCPUメモリ、個人的に非常に嫌なのは、特に重いクライアントソフトを開くためにデータに対処するためである。第三に、Excelは再び強力な、すべての後に、柔軟なコードを書いていない、通常はまだ奇妙な、奇妙なニーズがたくさんある、特に文字列処理、コード処理とまたは優先されます。
それでは、さっそくコードを見て、上記のことをどのように修正するか見てみましょう。

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は単純に2つのテーブルを結合するものです。この処理は結合または積み重ねと呼ばれます。上の例では、"rows" は同じまま、"columns" を一緒に追加したいので、axis=1 と設定しています。
axisパラメータを指定しない場合、デフォルトはaxis=0で、これは"columns"を変更せず、行ごとにアペンドすることを意味します。簡単な例を見てみましょう。

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


ignore_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. マージ方法

その他、実際にデータを扱う際によく必要になるのが結合です。結合とは、特定のカラムに基づいて2つのテーブルのデータをつなぎ合わせるデータベース結合操作のことです。

もう一度、ごく一般的な例を見てみましょう。

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です。2つのデータフレームを、nameフィールドに基づいて左結合し、最終的な結果を得ます。

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, left_index=left_index,
        right_index=right_index,
        sort=sort,
        suffixes=suffixes,
        copy=copy,
        indicator=indicator,
        validate=validate,
    )
    return op.get_result()



4. マージの各パラメータの役割

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なので、接続するインデックスに従って、2つのdfが完全にスプライスされます。サフィックスのデフォルトが("_x", "_y")の場合、同じフィールド名があれば結合時に自動的に適切なサフィックスに追加されます。

howパラメータはスプライスの方法を制御し、デフォルトはinnerです。結合はsqlと同じで、left、right、inner、outerです。

5. ジョインメソッド

上記の例で、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


これは、行インデックスに基づく上記のマージ方法と一致していますか?
実際に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
        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 は、2つの df を行または case 列で単純につなげる機能を実装しており、sql の join 関数は実装していない。