【Pandasデータ構造】リストをDataFrameの要素として格納し、効率的に処理する方法 🐍


 

PandasのDataFrameは、通常、各セルに単一の数値や文字列が格納されることを想定して設計されています。しかし、時には1つのセルに複数の情報(例えば、ある顧客の購入履歴リスト、タグのリスト、複数の測定値など)を格納したい場合があります。このような場合、DataFrameの要素としてPythonのリスト(list)を直接格納することが可能です。

一見すると複雑に思えるかもしれませんが、Pandasはこのようなリストを含むDataFrameを効率的に処理するための機能も提供しています。この記事では、DataFrameの要素としてリストを格納する方法、そしてそのリストデータを効果的に操作・分析するためのテクニックを、短いサンプルコードと丁寧な解説を交えてご紹介します。


 

DataFrameの要素にリストを格納するメリット・デメリット

 

DataFrameのセルにリストを格納することは、一見すると非効率に見えるかもしれません。しかし、特定のシナリオでは非常に役立ちます。

 

メリット

 

  • 柔軟なデータ表現: 1つのエンティティ(行)に関連する複数の値を、1つのセル内でまとめて保持できます。

  • データ構造の維持: 複雑な非正規化データを、元の構造をある程度維持したままDataFrameに格納できます。

  • 初期段階のデータ整理: データが完全に整形されていない初期段階で、複数の関連情報を一時的に保持するのに便利です。

 

デメリット

 

  • パフォーマンスの低下: リストを含む列は、通常の数値や文字列の列と比較して、集計やフィルタリングなどの操作でパフォーマンスが低下する可能性があります。Pandasの最適化は通常、単一のスカラ値を前提としています。

  • 操作の複雑さ: リスト内の要素にアクセスしたり、リストを操作したりするには、.strアクセサやapply()メソッドを組み合わせる必要があり、コードが少し複雑になることがあります。

  • 一部関数の非対応: 一部のPandas関数やNumPy関数は、リストを直接扱うことができません。


 

1. DataFrameにリストを格納する方法

 

DataFrameを作成する際に、列の値として直接リストを渡すことができます。

Python
 
import pandas as pd

# DataFrameの要素としてリストを格納
df_list = pd.DataFrame({
    'ID': [1, 2, 3],
    '商品リスト': [['りんご', 'みかん'], ['バナナ', 'いちご', 'ぶどう'], ['りんご']],
    '購入数リスト': [[2, 3], [1, 5, 2], [4]]
})
print("リストを格納したDataFrame:\n", df_list)

解説:

‘商品リスト’列と’購入数リスト’列は、それぞれPandasのSeriesであり、その各要素がPythonのリストになっています。


 

2. リスト要素へのアクセスと操作

 

DataFrameのセルに格納されたリスト内の要素にアクセスしたり、リスト自体を操作したりするには、いくつかのアプローチがあります。

 

strアクセサでの基本操作(文字列リストの場合)

 

リスト内の要素が文字列の場合、strアクセサを通じて文字列操作メソッドの一部を利用できる場合があります。ただし、これはリスト内の文字列要素を対象とするため、リスト自体を操作するわけではありません。

Python
 
# '商品リスト'の各リストの最初の要素を取得
df_list['最初の商品'] = df_list['商品リスト'].str[0]
print("\n各リストの最初の要素を抽出:\n", df_list)

# '商品リスト'の各リストの長さを取得
df_list['リスト長'] = df_list['商品リスト'].str.len()
print("\n各リストの長さを取得:\n", df_list)

解説:

df_list[‘商品リスト’].str[0]のように、.strの後にリストのインデックスを指定することで、各リストの対応する要素にアクセスできます。.str.len()は各リストの要素数を返します。

 

apply()メソッドでの柔軟な操作

 

リスト内の要素に対してより複雑な処理を行いたい場合や、数値のリストを扱いたい場合は、apply()メソッドが最も強力で柔軟な方法です。

Python
 
# '購入数リスト'の合計値を算出
df_list['購入数合計'] = df_list['購入数リスト'].apply(sum)
print("\n各リストの合計値を算出:\n", df_list)

# '商品リスト'に特定の文字列が含まれるかチェック
df_list['みかん購入有無'] = df_list['商品リスト'].apply(lambda x: 'みかん' in x)
print("\n'みかん'購入有無をチェック:\n", df_list)

解説:

  • df_list['購入数リスト'].apply(sum): apply()にPythonの組み込み関数sumを渡すことで、各リスト内の数値の合計が計算されます。

  • df_list['商品リスト'].apply(lambda x: 'みかん' in x): ラムダ関数を使って、各リストに'みかん'が含まれているかどうかをチェックし、ブール値の新しい列を生成しています。


 

3. リストを「展開」してフラットなDataFrameにする

 

多くの場合、リストを含む列はそのままでは分析しにくいことがあります。このような場合、リストを「展開」(または「非ネスティング」)して、各リストの要素が新しい行になるようにDataFrameを変換することがよくあります。

 

explode()メソッドでリストを展開する

 

Pandas 0.25.0以降では、explode()メソッドを使うと、リストを含むSeries(またはDataFrameの列)を、そのリストの要素ごとに新しい行として展開できます。

Python
 
# '商品リスト'列を展開
df_exploded = df_list.explode('商品リスト')
print("\n'商品リスト'列を展開後:\n", df_exploded)

解説:

df_list.explode(‘商品リスト’)とすることで、元のDataFrameの他の列の値はそのままに、’商品リスト’列の各要素が新しい行として展開されます。

例えば、IDが1の行は、元の[‘りんご’, ‘みかん’]が2つの行に分かれ、それぞれ’りんご’と’みかん’が新しい行として割り当てられます。これにより、よりフラットなデータ構造になり、その後の分析(例: groupby()での集計など)が容易になります。

 

複数列のリストを同時に展開する際の注意点

 

explode()は一度に1つの列しか展開できません。複数のリスト列を展開したい場合は、繰り返し適用するか、事前に結合するなどの工夫が必要です。また、異なる長さのリストが関連付けられている場合、展開するとデータの整合性が失われる可能性があります。

Python
 
# 複数リスト列を同時に展開すると、予期せぬ結果になる
# df_list.explode(['商品リスト', '購入数リスト']) # これはエラーになる

解説:

explode()は、リスト内の要素の「位置」に基づいて展開されるため、例えば商品リストの2番目の要素と購入数リストの2番目の要素が対応している場合、片方だけを展開してしまうと対応関係が崩れます。このような場合は、展開後に結合し直すか、事前にデータ設計を見直すことを推奨します。


 

まとめ

 

PandasのDataFrameの要素としてリストを格納することは、特定のデータの表現において非常に柔軟な選択肢を提供します。.strアクセサを使ってリスト内の文字列を操作したり、apply()メソッドで任意の関数をリストに適用したりすることで、これらのリストデータを効果的に処理できます。

しかし、パフォーマンスやその後の分析のしやすさを考慮すると、多くの場合、explode()メソッドを使ってリストをフラットな構造に「展開」することが推奨されます。リストを含むDataFrameを適切に扱い、データの構造を理解することで、より複雑なデータ分析のニーズにも対応できるようになるでしょう。