pandasでDataFrameのセルにlistを代入する

pandasのDataFrameのセル(1つのマス)にpythonのリスト(配列)を代入しようとして、苦労したのでやり方をまとめておく。
(pandasの公式ドキュメントではセルをcellとは呼ばず、valueもしくはscalar valueと呼んでいるようだ。)

注意

おそらく、DataFrameのセルにlistを入れようとするのはあまり良い方法ではない。 この使い方を、pandas側があまり想定していないような気がする。
私がDataFrameのセルにlistを持たせたときは、色々な処理がいちいちうまく行かないので、少しやってみたけどやめてしまった。
結局データの持ち方を変えて、やりたい分析を実施した。
データの持ち方を変えて別の方法でできないか検討したほうが良いだろう。

準備

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 = pd.DataFrame({
    'col_A': [1.2 ,3.4, 5.6],
    'col_B': [9.8, 7.6, 5.4],
    'col_string': ['hello', 'good_morning', 'good_night']
})
df

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

   col_A  col_B    col_string
0    1.2    9.8         hello
1    3.4    7.6  good_morning
2    5.6    5.4    good_night

失敗例 loc, ilocだとエラーになる

上記のdfの一番右下、「good_night」と書いてあるところにリストを代入したいとしよう。
locilocを使ってやろうとするとエラーになる。
(pandasのバージョンによっては別のエラーメッセージになるかもしれない)

my_list = ['this' , 'is', 'a', 'list']
print(df.loc[2, 'col_string'])
df.loc[2, 'col_string'] = my_list

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

good_night
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-5-4bd9c63f53aa> in <module>
          1 print(df.loc[2, 'col_string'])
    ----> 2 df.loc[2, 'col_string'] = my_list
    
    /usr/local/lib/python3.8/site-packages/pandas/core/indexing.py in __setitem__(self, key, value)
        668 
        669         iloc = self if self.name == "iloc" else self.obj.iloc
    --> 670         iloc._setitem_with_indexer(indexer, value)
        671 
        672     def _validate_key(self, key, axis: int):
    /usr/local/lib/python3.8/site-packages/pandas/core/indexing.py in _setitem_with_indexer(self, indexer, value)
       1664                 if is_list_like_indexer(value) and 0 != lplane_indexer != len(value):
       1665                     # Exclude zero-len for e.g. boolean masking that is all-false
    -> 1666                     raise ValueError(
       1667                         "cannot set using a multi-index "
       1668                         "selection indexer with a different "
    ValueError: cannot set using a multi-index selection indexer with a different length than the value
print(df.iloc[2, 2])
df.iloc[2, 2] = my_list

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

good_night
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-6-dd2da9191db9> in <module>
          1 print(df.iloc[2, 2])
    ----> 2 df.iloc[2, 2] = my_list
    
    /usr/local/lib/python3.8/site-packages/pandas/core/indexing.py in __setitem__(self, key, value)
        668 
        669         iloc = self if self.name == "iloc" else self.obj.iloc
    --> 670         iloc._setitem_with_indexer(indexer, value)
        671 
        672     def _validate_key(self, key, axis: int):
    /usr/local/lib/python3.8/site-packages/pandas/core/indexing.py in _setitem_with_indexer(self, indexer, value)
       1664                 if is_list_like_indexer(value) and 0 != lplane_indexer != len(value):
       1665                     # Exclude zero-len for e.g. boolean masking that is all-false
    -> 1666                     raise ValueError(
       1667                         "cannot set using a multi-index "
       1668                         "selection indexer with a different "
    ValueError: cannot set using a multi-index selection indexer with a different length than the value

DataFrameのセルにlistを代入するためには、at, iatを使う

locilocは複数のセルを選択することもできるので、右辺が配列だと「え?これ複数のセルに代入したいんじゃないの? 左辺が単一のセルなのに右辺がセル4つ分の値なんだから、これじゃダメだよ」とpandasが勘違いするんだろう。たぶん。
pandasには必ず一つのセルを選択する(複数のセルを選択できない)関数がある。atiatだ。これを使うとセルにlistを代入できる。

atを使った例。locと同様に、行と列の名前で位置を指定する。

print(df.at[2, 'col_string'])
df.at[2, 'col_string'] = my_list

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

good_night
print(df)
print('...')
print(df.loc[2, 'col_string'])
print(type(df.loc[2, 'col_string']))

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

   col_A  col_B           col_string
0    1.2    9.8                hello
1    3.4    7.6         good_morning
2    5.6    5.4  [this, is, a, list]
...
['this', 'is', 'a', 'list']
<class 'list'>

iatを使った例。ilocと同様に、行と列の番号(何行目・何列目)で位置を指定する。

my_list = ['another', 'list']
print(df.iat[0, 2])
df.iat[0, 2] = my_list

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

hello
print(df)
print('...')
print(df.iloc[0, 2])
print(type(df.iloc[0, 2]))

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

   col_A  col_B           col_string
0    1.2    9.8      [another, list]
1    3.4    7.6         good_morning
2    5.6    5.4  [this, is, a, list]
...
['another', 'list']
<class 'list'>

数値の列にリストを入れたい

ここまではうまく行った。しかし、数値の入っている列のセルにリストを代入しようとするとエラーが生じる。

my_list = [3, 4, 5]
print(df.at[0, 'col_A'])
df.at[0, 'col_A'] = my_list

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

1.2
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    TypeError: float() argument must be a string or a number, not 'list'
    
    The above exception was the direct cause of the following exception:
    ValueError                                Traceback (most recent call last)
    <ipython-input-12-02f17b50862e> in <module>
          1 my_list = [3, 4, 5]
          2 print(df.at[0, 'col_A'])
    ----> 3 df.at[0, 'col_A'] = my_list
    
    /usr/local/lib/python3.8/site-packages/pandas/core/indexing.py in __setitem__(self, key, value)
       2089             return
       2090 
    -> 2091         return super().__setitem__(key, value)
       2092 
       2093 
    /usr/local/lib/python3.8/site-packages/pandas/core/indexing.py in __setitem__(self, key, value)
       2040             raise ValueError("Not enough indexers for scalar access (setting)!")
       2041 
    -> 2042         self.obj._set_value(*key, value=value, takeable=self._takeable)
       2043 
       2044 
    /usr/local/lib/python3.8/site-packages/pandas/core/frame.py in _set_value(self, index, col, value, takeable)
       3145             validate_numeric_casting(series.dtype, value)
       3146 
    -> 3147             series._values[loc] = value
       3148             # Note: trying to use series._set_value breaks tests in
       3149             #  tests.frame.indexing.test_indexing and tests.indexing.test_partial
    ValueError: setting an array element with a sequence.

これはデータ型(dtype)の問題である。
col_Aの列は浮動小数点数を1つ入れるデータ型になっているので、配列を代入しようとするとデータ型が合わずにエラーになるのだ。

pandasのdtypeについては、こちらも参照。公式ドキュメントを個人的に翻訳した記事だ。

linus-mk.hatenablog.com

df.dtypes

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

col_A         float64
col_B         float64
col_string     object
dtype: object

listを入れるならば、該当する列のデータ型をobjectにすれば良い。それにはastype()関数を用いる。

df['col_A'] = df['col_A'].astype('object')
df.dtypes

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

col_A          object
col_B         float64
col_string     object
dtype: object
print(df.at[0, 'col_A'])
df.at[0, 'col_A'] = my_list

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

1.2
df

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

       col_A  col_B           col_string
0  [3, 4, 5]    9.8      [another, list]
1        3.4    7.6         good_morning
2        5.6    5.4  [this, is, a, list]

参考文献

Python pandas insert list into a cell - Stack Overflow
python - pandas: how to store a list in a dataframe? - Stack Overflow

それでは。