pandasでValueError: len(left_on) must equal the number of levels in the index of "right"

pandasを使ってDataFrameを結合しようとしたら、ValueError: len(left_on) must equal the number of levels in the index of "right"
という見慣れないエラーが出てきた。

mergeを使うべきところでjoinを使ったのが原因だった。
以下のStackOverflowを見たら疑問は解決したけど、自分の頭を整理するためにまとめておく。
python - Why does Pandas inner join give ValueError: len(left_on) must equal the number of levels in the index of "right"? - Stack Overflow

また、joinのonキーワードがどのように使われているのかについても調べたので、併せてまとめる。

mergeを使うべきところでjoinを使ったのが原因だった

  • DataFrameを結合するためにmergeを使うべきところで、joinを使う
  • onに複数列を指定する

以上の条件で発生するエラーであった。

pandasのコミッターであるsinhrksさんの記事がとても秀逸なので、DataFrameを連結・結合する処理で困ったらここを見よう。
Python pandas 図でみる データ連結 / 結合処理 - StatsFragments
その後、公式ドキュメントにもこの記事が追加された。上記は2015年の記事なので、最新の仕様については公式ドキュメントを見たほうが良い。
本記事の例は公式ドキュメントのものを一部変更している。
Merge, join, concatenate and compare — pandas 1.2.3 documentation

import pandas as pd
import numpy as np
pd.options.display.notebook_repr_html = False  # jupyter notebook上での出力形式を制御するために書いています。無くても動きます。
# 動作環境の確認
print(pd.__version__)
print(np.__version__)

# --------------------

1.1.2
1.19.1
df_left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
                     'key2': ['K0', 'K1', 'K0', 'K1'],
                     'A': ['A0', 'A1', 'A2', 'A3'],
                     'B': ['B0', 'B1', 'B2', 'B3']})
df_right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
                      'key2': ['K0', 'K0', 'K0', 'K0'],
                      'C': ['C0', 'C1', 'C2', 'C3'],
                      'D': ['D0', 'D1', 'D2', 'D3']})
print(df_left)
print("~~~~~~")
print(df_right)

# --------------------

  key1 key2   A   B
0   K0   K0  A0  B0
1   K0   K1  A1  B1
2   K1   K0  A2  B2
3   K2   K1  A3  B3
~~~~~~
  key1 key2   C   D
0   K0   K0  C0  D0
1   K1   K0  C1  D1
2   K1   K0  C2  D2
3   K2   K0  C3  D3

この2つのDataFrameを['key1', 'key2']という2つの列を基準に結合する。これはmergeを使うのが正しいやり方だ。

result = pd.merge(df_left,df_right, on=['key1', 'key2'])
result

# --------------------

  key1 key2   A   B   C   D
0   K0   K0  A0  B0  C0  D0
1   K1   K0  A2  B2  C1  D1
2   K1   K0  A2  B2  C2  D2

さらに、DataFrameに対してmerge関数を適用することもできる。結果は全く同じになる。

result = df_left.merge(df_right, on=['key1', 'key2'])
result

# --------------------

  key1 key2   A   B   C   D
0   K0   K0  A0  B0  C0  D0
1   K1   K0  A2  B2  C1  D1
2   K1   K0  A2  B2  C2  D2

上記において、間違えてmergeではなくjoinを使ってしまった。

result = df_left.join(df_right, on=['key1', 'key2'])

# --------------------

---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-23-302ff90d7a3b> in <module>
    ----> 1 result = df_left.join(df_right, on=['key1', 'key2'])
    
    /usr/local/lib/python3.8/site-packages/pandas/core/frame.py in join(self, other, on, how, lsuffix, rsuffix, sort)
       7867         5  K5  A5  NaN
       7868         """
    -> 7869         return self._join_compat(
       7870             other, on=on, how=how, lsuffix=lsuffix, rsuffix=rsuffix, sort=sort
       7871         )
    /usr/local/lib/python3.8/site-packages/pandas/core/frame.py in _join_compat(self, other, on, how, lsuffix, rsuffix, sort)
       7883 
       7884         if isinstance(other, DataFrame):
    -> 7885             return merge(
       7886                 self,
       7887                 other,
    /usr/local/lib/python3.8/site-packages/pandas/core/reshape/merge.py in merge(left, right, how, on, left_on, right_on, left_index, right_index, sort, suffixes, copy, indicator, validate)
         72     validate=None,
         73 ) -> "DataFrame":
    ---> 74     op = _MergeOperation(
         75         left,
         76         right,
    /usr/local/lib/python3.8/site-packages/pandas/core/reshape/merge.py in __init__(self, left, right, how, on, left_on, right_on, axis, left_index, right_index, sort, suffixes, copy, indicator, validate)
        643             warnings.warn(msg, UserWarning)
        644 
    --> 645         self._validate_specification()
        646 
        647         # note this function has side effects
    /usr/local/lib/python3.8/site-packages/pandas/core/reshape/merge.py in _validate_specification(self)
       1233             if self.right_index:
       1234                 if len(self.left_on) != self.right.index.nlevels:
    -> 1235                     raise ValueError(
       1236                         "len(left_on) must equal the number "
       1237                         'of levels in the index of "right"'
    ValueError: len(left_on) must equal the number of levels in the index of "right"

はい。ValueError: len(left_on) must equal the number of levels in the index of "right" というエラーが発生した。

一応注記しておくと、エラー文の中の"right"はDataFrameの変数名とは無関係である。
join中で右側に指定したDataFrame、という意味であろう。

上記のコードを正しく動かすためには、「joinではなくmergeを使えば良い」で終わりである。
が、どうしてこんなエラーになったのか、いまいちよく分からん。
では、他の場合でもmergeではなくjoinにしてしまったら、どういうエラーが出るのだろうか?試してみよう。

DataFrameを結合する関数、mergeとjoinの違い。

その前に一旦、mergeとjoinの違いを見ておこう。

Merge, join, concatenate and compare — pandas 1.2.3 documentation

DataFrame.join() is a convenient method for combining the columns of two potentially differently-indexed DataFrames into a single result DataFrame.
拙訳:DataFrame.join()は、異なるindexの可能性のある2つの列を結合し、1つのDataFrameを結果として出力する、便利なメソッドである。

joinはあくまで便利メソッドなんだよね。
mergeは列やindexを使ってDataFrameを結合できる。joinはindexを使ってDataFrameを結合できる。
mergeはjoinを含んでjoinより広い範囲のことができるので、joinを使うところはmergeで置き換えることもできる。

なお、pandasのドキュメントからソースコードに飛んで、
pandas/frame.py at v1.1.2 · pandas-dev/pandas · GitHub
を見ると、joinはその内部でmerge関数を実行しているのが分かる。

joinにはleft_on, right_onがない

mergeにはleft_on, right_onというパラメータが指定できるが、joinにはこれらのパラメータがない。 したがって、joinでleft_on, right_onを指定すると「そんなパラメータは指定できません」というエラーを返す。これは分かりやすいね。

# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.merge.html
df1 = pd.DataFrame({'lkey': ['foo', 'bar', 'baz', 'foo'],
                    'value': [1, 2, 3, 5]})
df2 = pd.DataFrame({'rkey': ['foo', 'bar', 'baz', 'foo'],
                    'value': [5, 6, 7, 8]})
df1

# --------------------

  lkey  value
0  foo      1
1  bar      2
2  baz      3
3  foo      5
df2

# --------------------

  rkey  value
0  foo      5
1  bar      6
2  baz      7
3  foo      8
df1.merge(df2, left_on='lkey', right_on='rkey')

# --------------------

  lkey  value_x rkey  value_y
0  foo        1  foo        5
1  foo        1  foo        8
2  foo        5  foo        5
3  foo        5  foo        8
4  bar        2  bar        6
5  baz        3  baz        7
df1.join(df2, left_on='lkey', right_on='rkey')

# --------------------

---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-7-b2a9def786b8> in <module>
    ----> 1 df1.join(df2, left_on='lkey', right_on='rkey')
    
    TypeError: join() got an unexpected keyword argument 'left_on'

joinでonを1列だけ指定した場合のエラー

onを複数列指定したときは最初に書いたエラーになる。1列だけの場合はどうなるだろうか?

df1 = pd.DataFrame({'key': ['foo', 'bar', 'baz', 'foo'],
                    'value_df1': [1, 2, 3, 5]})
df2 = pd.DataFrame({'key': ['foo', 'bar', 'baz', 'foo'],
                    'value_df2': [5, 6, 7, 8]})
df1.merge(df2, on='key')

# --------------------

   key  value_df1  value_df2
0  foo          1          5
1  foo          1          8
2  foo          5          5
3  foo          5          8
4  bar          2          6
5  baz          3          7
df1.join(df2, on='key')

# --------------------

---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-10-3d5d0d53e524> in <module>
    ----> 1 df1.join(df2, on='key')
    
    /usr/local/lib/python3.8/site-packages/pandas/core/frame.py in join(self, other, on, how, lsuffix, rsuffix, sort)
       7867         5  K5  A5  NaN
       7868         """
    -> 7869         return self._join_compat(
       7870             other, on=on, how=how, lsuffix=lsuffix, rsuffix=rsuffix, sort=sort
       7871         )
    /usr/local/lib/python3.8/site-packages/pandas/core/frame.py in _join_compat(self, other, on, how, lsuffix, rsuffix, sort)
       7883 
       7884         if isinstance(other, DataFrame):
    -> 7885             return merge(
       7886                 self,
       7887                 other,
    /usr/local/lib/python3.8/site-packages/pandas/core/reshape/merge.py in merge(left, right, how, on, left_on, right_on, left_index, right_index, sort, suffixes, copy, indicator, validate)
         72     validate=None,
         73 ) -> "DataFrame":
    ---> 74     op = _MergeOperation(
         75         left,
         76         right,
    /usr/local/lib/python3.8/site-packages/pandas/core/reshape/merge.py in __init__(self, left, right, how, on, left_on, right_on, axis, left_index, right_index, sort, suffixes, copy, indicator, validate)
        654         # validate the merge keys dtypes. We may need to coerce
        655         # to avoid incompatible dtypes
    --> 656         self._maybe_coerce_merge_keys()
        657 
        658         # If argument passed to validate,
    /usr/local/lib/python3.8/site-packages/pandas/core/reshape/merge.py in _maybe_coerce_merge_keys(self)
       1163                     inferred_right in string_types and inferred_left not in string_types
       1164                 ):
    -> 1165                     raise ValueError(msg)
       1166 
       1167             # datetimelikes must match exactly
    ValueError: You are trying to merge on object and int64 columns. If you wish to proceed you should use pd.concat

んーー? ちょっと言ってることがよく分からない。
mergeを使うべきところでうっかりjoinを使ってしまったので「you should use pd.merge」って言ってくれるのが一番ありがたい。しかし、「you should use pd.concat」と言われてしまった。一体何で……?

joinのonキーワードは一体何をしているのか?

join関数は何に基づいてDataFrameを結合するのか?
それはindexである。joinを使った時点で、indexに基づいて結合すると決まっている。
じゃあわざわざonで指定する必要も無いのではないか?

joinのonキーワードについてドキュメントを見てみよう。

以下2つの操作は完全に等価である、とドキュメントには書いてある(Merge, join, concatenate and compare — pandas 1.2.3 documentation)。

left.join(right, on=key_or_keys)
pd.merge(left, right, left_on=key_or_keys, right_index=True, how='left', sort=False)

公式ドキュメントを色々読むと、どうやら以下の仕様であると分かった。

  • joinはDataFrame.join()の形式で使う。pd.joinの形式では使えない。
  • joinの中でonを指定した場合、joinの左側のDataFrameで使う列名(またはindexレベル名)となる。
  • joinの中でonを指定した場合でも、joinの右側のDataFrameでindexを基準に結合するということは変わらない。(常にright_index=Trueになることに注意!)
  • joinの中でonに複数の値を指定した場合、右側のDataFrameはMultiIndexでなければならない。
  • Merge, join, concatenate and compare — pandas 1.2.3 documentationには、joinの中でonに単一の値、複数の値を指定した例がある。

エラーの意味を解明する

joinのonの意味を把握すると、ようやく最初に書いた謎めいたエラーの意味がわかってくる。最初の例からコードとエラー文を再掲する。

result = df_left.join(df_right, on=['key1', 'key2'])

# --------------------

    ValueError: len(left_on) must equal the number of levels in the index of "right"

pandasの気持ちになると、こういう思考過程でエラーを上げている。
「joinに2つの要素からなるonが指定されているから、右側のDataFrameは2段階のindexからなるMultiIndexのDataFrameのはずだよね。あれ、それなのに実際には右側のDataFrameはMultiIndexではないぞ。これはおかしいぞ、エラーだ。onの中の要素数(len(left_on)) ≠ 右側のindexの段階数'(the number of levels in the index of "right")なので、エラーを出そう。」
内部のコードを見てはいないが、エラー文章とも整合するので、多分こうだろう。

次のエラー。コードとエラー文を再掲する。

df1.join(df2, on='key')

# --------------------

    ValueError: You are trying to merge on object and int64 columns. If you wish to proceed you should use pd.concat

pandasの気持ちになると、こういう思考過程でエラーを上げている。
「joinに1つの要素からなるonが指定されている。左側のDataFrameのkey列と、右側のDataFrameのindexに基づいてデータを結合しよう。あれ、左側のDataFrameのkey列はobject型(文字列が入っている)、右側のDataFrameのindexはint64型じゃないか。これじゃデータを結合できるわけが無いや。エラーを出そう。」
内部のコードをあまり見ていないが、エラー文章とも整合するので、多分こうだろう。

(付け加えると、pandas内部の_maybe_coerce_merge_keys()関数で、違うdtypeでもうまく変換してdtypeを揃えられないか試しているようだ。そのおかげで、例えばint64の列とint8の列はうまくmergeできる。だが、文字列とint64ではdtypeを揃えるのはどうやっても無理なので、結局エラーになる。)

参考文献

今回参考にしたページ。どれもここまでに既に挙げたものだ。

エラーメッセージでググると真っ先に出てくるStackOverflow。
python - Why does Pandas inner join give ValueError: len(left_on) must equal the number of levels in the index of "right"? - Stack Overflow

DataFrameの結合について。 Python pandas 図でみる データ連結 / 結合処理 - StatsFragments その後、この記事を元に公式ドキュメントにも英語の記事が追加された。 Merge, join, concatenate and compare — pandas 1.2.3 documentation

今回公式ドキュメントを少し詳しく読んでみたが、説明の充実度は公式ドキュメントのほうが圧倒的に高い。 もとはこのブログ記事であるとはいえ、英語に翻訳したあとに大幅な加筆修正が入っている。
その分、どこに何が書いてあるかがすぐには分かりづらい面もある。

初心者は日本語のPython pandas 図でみる データ連結 / 結合処理 - StatsFragments
少し慣れてきて詳しいことを知りたかったら、内容たっぷりの公式ドキュメントのMerge, join, concatenate and compare — pandas 1.2.3 documentation
が良いと思った。

今回調べてみて、RやSQLでいくらjoinが使われていても、pandasではmergeを使うんだと覚えておこう、と思った。

それでは。