🐍

(学習ログ)Python023:pandasで複数データを結合して集計・フィルタリングする

に公開

1. はじめに

複数のCSVファイルを読み込んで結合し、集計・フィルタリングを行う方法を学びます。

実務のデータ分析では、顧客情報やログデータが複数のファイルに分かれていることがよくあります。本記事では、pandasのmerge() を使ったデータ結合、groupby() による集計、そして日付データのフィルタリング方法を、実践的なコード例とともに解説します。

2. サンプルコード

コード全文

import pandas as pd

# 利用ログデータを読み込む
sample_usagelog = pd.read_csv('003_サンプル/usage_log.csv')

# 顧客マスターを読み込む
sample_customer = pd.read_csv('003_サンプル/customer_master.csv')

# クラスマスターを読み込む(会員ランク情報など)
sample_class_master = pd.read_csv('003_サンプル/class_master.csv')

# キャンペーンマスターを読み込む(キャンペーン名など)
sample_campaign_master = pd.read_csv('003_サンプル/campaign_master.csv')

# 顧客マスターとクラスマスターを結合(class列をキーに左結合)
sample_customer_join = pd.merge(sample_customer, sample_class_master, on="class", how="left")

# さらにキャンペーンマスターを結合(campaign_id列をキーに左結合)
sample_customer_join = pd.merge(sample_customer_join, sample_campaign_master, on="campaign_id", how="left")

# クラス名ごとの顧客数を集計
sample_customer_join.groupby("class_name").count()["customer_id"]

# キャンペーン名ごとの顧客数を集計
sample_customer_join.groupby("campaign_name").count()["customer_id"]

# 性別ごとの顧客数を集計
sample_customer_join.groupby("gender").count()["customer_id"]

# 削除フラグごとの顧客数を集計
sample_customer_join.groupby("is_deleted").count()["customer_id"]

# start_date列を文字列から日付型に変換
sample_customer_join["start_date"] = pd.to_datetime(sample_customer_join["start_date"])

# 2018年4月1日より後に開始した顧客のみを抽出
sample_customer_start = sample_customer_join.loc[sample_customer_join["start_date"]>pd.to_datetime("20180401")]

# 抽出された顧客数を表示
print(len(sample_customer_start))

コード解説

ブロック1:データの読み込み

sample_usagelog = pd.read_csv('003_サンプル/usage_log.csv')
sample_customer = pd.read_csv('003_サンプル/customer_master.csv')
sample_class_master = pd.read_csv('003_サンプル/class_master.csv')
sample_campaign_master = pd.read_csv('003_サンプル/campaign_master.csv')

何をしているか
4つのCSVファイルをそれぞれDataFrameとして読み込んでいます。

なぜそうするか
実務では、データが用途別に複数ファイルに分かれていることが一般的です。まず個別に読み込んでから、必要に応じて結合していきます。

初心者ポイント
ファイルパスは実行環境によって異なります。相対パスで指定する場合、Pythonスクリプトの実行位置に注意しましょう。

ブロック2:データの結合

sample_customer_join = pd.merge(sample_customer, sample_class_master, on="class", how="left")
sample_customer_join = pd.merge(sample_customer_join, sample_campaign_master, on="campaign_id", how="left")

何をしているか
merge() 関数を使って、複数のDataFrameを順番に結合しています。

なぜそうするか
顧客マスターにはclasscampaign_idというIDしかありません。実際の分析では、それらに対応する「名前」や「詳細情報」が必要になるため、マスターデータと結合します。

how="left"の意味
左側のDataFrame(ここではsample_customer)のすべての行を保持し、右側のDataFrameから一致する情報を追加します。SQLのLEFT JOINと同じ考え方です。

初心者ポイント

  • on="列名"で結合キーを指定します
  • 左右で列名が異なる場合はleft_onright_onを使います

ブロック3:グループごとの集計

sample_customer_join.groupby("class_name").count()["customer_id"]
sample_customer_join.groupby("campaign_name").count()["customer_id"]
sample_customer_join.groupby("gender").count()["customer_id"]
sample_customer_join.groupby("is_deleted").count()["customer_id"]

何をしているか
groupby() を使って、特定の列(カテゴリ)ごとに顧客数をカウントしています。

なぜそうするか
「クラスごとに何人いるか」「キャンペーン別の参加者数は」といった集計は、データ分析の基本です。

["customer_id"]の意味
count()だけだとすべての列がカウントされて見づらいので、代表的な列(ここでは顧客ID)のみを表示しています。

ブロック4:日付フィルタリング

sample_customer_join["start_date"] = pd.to_datetime(sample_customer_join["start_date"])
sample_customer_start = sample_customer_join.loc[sample_customer_join["start_date"]>pd.to_datetime("20180401")]
print(len(sample_customer_start))

何をしているか

  1. 文字列として保存されている日付データを日付型に変換
  2. 2018年4月1日より後のデータだけを抽出
  3. 抽出された行数を表示

なぜそうするか
CSVから読み込んだ日付は文字列型です。日付として比較・計算するには、pd.to_datetime() で明示的に変換する必要があります。

初心者ポイント

  • .loc[]は条件に一致する行を抽出する方法です
  • len()でDataFrameの行数(レコード数)を取得できます

実行結果例

データが以下のような構成だと仮定します

customer_master.csv (5件):

  • customer_id, class, campaign_id, gender, start_date, is_deleted

class_master.csv (3件):

  • class, class_name (例: 1→"ゴールド", 2→"シルバー", 3→"ブロンズ")

campaign_master.csv (2件):

  • campaign_id, campaign_name (例: 101→"春キャンペーン", 102→"夏キャンペーン")

実行結果

# クラス名ごとの顧客数
class_name
ゴールド    2
シルバー    2
ブロンズ    1
Name: customer_id, dtype: int64

# キャンペーン名ごとの顧客数
campaign_name
春キャンペーン    3
夏キャンペーン    2
Name: customer_id, dtype: int64

# 性別ごとの顧客数
gender
F    2
M    3
Name: customer_id, dtype: int64

# 削除フラグごとの顧客数
is_deleted
0    4
1    1
Name: customer_id, dtype: int64

# 2018年4月1日以降に開始した顧客数
3

このように、各カテゴリごとの内訳と、条件に合う顧客数が確認できます。

3. エラーと確認のポイント

代表的なエラー

エラー1:KeyError

KeyError: 'class'

原因
結合キーに指定した列名が、DataFrameに存在しない場合に発生します。

対処法

# 列名を確認
print(sample_customer.columns)
print(sample_class_master.columns)

列名のスペルミスや全角・半角の違いをチェックしましょう。

エラー2:FileNotFoundError

FileNotFoundError: [Errno 2] No such file or directory: '003_サンプル/usage_log.csv'

原因
指定したファイルパスが間違っているか、ファイルが存在しません。

対処法

import os
# 現在のディレクトリを確認
print(os.getcwd())
# ファイルの存在確認
print(os.path.exists('003_サンプル/usage_log.csv'))

相対パスではなく絶対パスで指定するのも一つの方法です。

エラー3:TypeError(日付比較時)

TypeError: '>' not supported between instances of 'str' and 'Timestamp'

原因
日付列を**pd.to_datetime()**で変換せずに、文字列のまま比較しようとした場合に発生します。

対処法

# 必ず日付型に変換してから比較
sample_customer_join["start_date"] = pd.to_datetime(sample_customer_join["start_date"])

よく使う確認メソッド

1. head():先頭データの確認

sample_customer_join.head()

出力例

   customer_id  class  campaign_id gender start_date  class_name  campaign_name
0         1001      1          101      M 2018-05-10      ゴールド    春キャンペーン
1         1002      2          101      F 2018-03-15      シルバー    春キャンペーン
...

結合後のデータ構造を視覚的に確認できます。

2. info():データ型とnull値の確認

sample_customer_join.info()

出力例

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 7 columns):
customer_id      5 non-null int64
class            5 non-null int64
start_date       5 non-null datetime64[ns]
...

各列のデータ型と欠損値の有無が一目でわかります。

3. isnull().sum():欠損値の数

sample_customer_join.isnull().sum()

出力例

customer_id      0
class_name       1
campaign_name    0
...

結合時に対応するデータがなかった場合、NaN(欠損値)になります。

4. value_counts():値の分布確認

sample_customer_join["class_name"].value_counts()

出力例

ゴールド    2
シルバー    2
ブロンズ    1

groupby().count()と似ていますが、より簡潔に分布を確認できます。

4. まとめ

  • 複数のCSVファイルをpandasで読み込む方法
  • merge()を使った複数DataFrameの結合テクニック(LEFT JOINの概念)
  • groupby()による集計とカテゴリごとの分析方法
  • 日付データの型変換とフィルタリング手法

Discussion