(学習ログ)Python017:pandasによる売上・顧客データのクレンジングと欠損値補完
1. はじめに
この記事の目的
実務でよく遭遇する「データが汚れている」状況に対処するため、pandasを使った基本的なデータクレンジング手法を身につけることを目指します。
この記事の概要
本記事では、売上データと顧客台帳という2つの異なるファイルを読み込み、以下の処理を実践的に学びます:
- CSVとExcelファイルの読み込み
- 日付データの変換と整形
- 文字列データのクレンジング(全角・半角スペース削除、大文字統一)
- 欠損値の検出と補完ロジック
- Excelシリアル値形式の日付変換
これらは実務のデータ分析で最初に行う重要な前処理ステップです。
2. サンプルコード
コード全文
import pandas as pd
# ファイル読み込み
sample_uriage_data = pd.read_csv("002_サンプル/sample_uriage_data.csv")
sample_customer_ledger = pd.read_excel("002_サンプル/sample_customer_ledger.xlsx")
# 売上データ:日付型への変換と年月列の作成
sample_uriage_data["purchase_date"] = pd.to_datetime(sample_uriage_data["purchase_date"])
sample_uriage_data["purchase_month"] = sample_uriage_data["purchase_date"].dt.strftime("%Y%m")
# 売上データ:商品名のクレンジング
sample_uriage_data["item_name"] = sample_uriage_data["item_name"].str.upper() # 大文字化
sample_uriage_data["item_name"] = sample_uriage_data["item_name"].str.replace(" ", "") # 全角スペース削除
sample_uriage_data["item_name"] = sample_uriage_data["item_name"].str.replace(" ", "") # 半角スペース削除
# 売上データ:欠損値の補完処理
flg_is_null = sample_uriage_data["item_price"].isnull() # 欠損フラグ作成
for NaN_item_name in list(sample_uriage_data.loc[flg_is_null, "item_name"].unique()):
# 同じ商品名で価格が存在する最大値を取得
impute_price = sample_uriage_data.loc[(~flg_is_null) & (sample_uriage_data["item_name"]==NaN_item_name), "item_price"].max()
# 欠損値に最大値を代入
sample_uriage_data.loc[(flg_is_null) & (sample_uriage_data["item_name"]==NaN_item_name), "item_price"] = impute_price
# 売上データ:商品名でソート
sample_uriage_data.sort_values(by=["item_name"], ascending=True)
# 欠損値チェック
sample_uriage_data.isnull().any(axis=0)
# 顧客台帳:顧客名のクレンジング
sample_customer_ledger["顧客名"] = sample_customer_ledger["顧客名"].str.replace(" ", "") # 全角スペース削除
sample_customer_ledger["顧客名"] = sample_customer_ledger["顧客名"].str.replace(" ", "") # 半角スペース削除
# 顧客台帳:Excelシリアル値と文字列が混在する日付の変換
flg_is_serial = sample_customer_ledger["登録日"].astype("str").str.isdigit() # 数値(シリアル値)判定
# シリアル値からdatetime型へ変換
fromSerial = pd.to_timedelta(sample_customer_ledger.loc[flg_is_serial, "登録日"].astype("float") - 2, unit="D") + pd.to_datetime('1900/1/1')
# 文字列からdatetime型へ変換
fromString = pd.to_datetime(sample_customer_ledger.loc[~flg_is_serial, "登録日"])
# 2つの結果を結合
sample_customer_ledger["登録日"] = pd.concat([fromSerial, fromString])
# 登録年月列の作成
sample_customer_ledger["登録年月"] = sample_customer_ledger["登録日"].dt.strftime("%Y%m")
# 年月ごとの顧客数を集計
rslt = sample_customer_ledger.groupby("登録年月").count()["顧客名"]
print(rslt)
print(len(sample_customer_ledger))
# 確認:シリアル値形式のデータ数
flg_is_serial = sample_customer_ledger["登録日"].astype("str").str.isdigit()
flg_is_serial.sum()
コード解説
ファイル読み込み部分
sample_uriage_data = pd.read_csv("002_サンプル/sample_uriage_data.csv")
sample_customer_ledger = pd.read_excel("002_サンプル/sample_customer_ledger.xlsx")
CSVファイルとExcelファイルをそれぞれ読み込みます。pandasはファイル形式に応じた専用関数を提供しています。
日付データの変換
sample_uriage_data["purchase_date"] = pd.to_datetime(sample_uriage_data["purchase_date"])
sample_uriage_data["purchase_month"] = sample_uriage_data["purchase_date"].dt.strftime("%Y%m")
文字列形式の日付をdatetime型に変換し、年月だけを抽出して新しい列を作成します。dt.strftime()で任意のフォーマットに整形できます。
文字列のクレンジング
sample_uriage_data["item_name"] = sample_uriage_data["item_name"].str.upper()
sample_uriage_data["item_name"] = sample_uriage_data["item_name"].str.replace(" ", "")
sample_uriage_data["item_name"] = sample_uriage_data["item_name"].str.replace(" ", "")
商品名を統一するため、全て大文字化し、全角・半角スペースを削除します。これにより「りんご」「リンゴ 」「RINGO」などの表記ゆれを防ぎます。
欠損値の補完処理
flg_is_null = sample_uriage_data["item_price"].isnull()
for NaN_item_name in list(sample_uriage_data.loc[flg_is_null, "item_name"].unique()):
impute_price = sample_uriage_data.loc[(~flg_is_null) & (sample_uriage_data["item_name"]==NaN_item_name), "item_price"].max()
sample_uriage_data.loc[(flg_is_null) & (sample_uriage_data["item_name"]==NaN_item_name), "item_price"] = impute_price
価格が欠損している行を検出し、同じ商品名の中で最大価格を取得して補完します。この手法は「同一商品は基本的に同じ価格である」という前提に基づいています。
Excelシリアル値の変換
flg_is_serial = sample_customer_ledger["登録日"].astype("str").str.isdigit()
fromSerial = pd.to_timedelta(sample_customer_ledger.loc[flg_is_serial, "登録日"].astype("float") - 2, unit="D") + pd.to_datetime('1900/1/1')
fromString = pd.to_datetime(sample_customer_ledger.loc[~flg_is_serial, "登録日"])
sample_customer_ledger["登録日"] = pd.concat([fromSerial, fromString])
Excelの日付セルが数値(シリアル値)で保存されている場合と、文字列で保存されている場合の両方に対応します。シリアル値は1900年1月1日からの経過日数なので、適切に変換する必要があります。
集計処理
rslt = sample_customer_ledger.groupby("登録年月").count()["顧客名"]
print(rslt)
登録年月ごとに顧客数をカウントし、月次の登録状況を把握します。
実行結果例
登録年月
202301 15
202302 23
202303 18
202304 21
202305 19
Name: 顧客名, dtype: int64
96
この出力は以下を示しています:
- 2023年1月から5月まで、各月の新規顧客登録数
- 顧客台帳の総件数は96件
3. エラーと確認のポイント
代表的なエラー文
KeyError: 'column_name'
原因:指定した列名がDataFrameに存在しない
対処:columns属性で列名一覧を確認し、正確な列名を使用する
ValueError: time data '...' does not match format
原因:日付データのフォーマットが想定と異なる
対処:pd.to_datetime()のformat引数を指定するか、errors='coerce'で無効な値をNaTに変換
TypeError: unsupported operand type(s)
原因:数値演算を行う列に文字列やNaNが含まれている
対処:astype()で型変換するか、dropna()で欠損値を除外
AttributeError: 'Series' object has no attribute 'str'
原因:数値型の列に対して.strアクセサを使用している
対処:対象列が文字列型(object型)であることを確認する
よく使う確認メソッドと出力例
データ構造の確認
sample_uriage_data.shape
# 出力例: (500, 5) → 500行、5列のデータ
列名の確認
sample_uriage_data.columns
# 出力例: Index(['purchase_date', 'customer_id', 'item_name', 'item_price', 'quantity'], dtype='object')
先頭データの確認
sample_uriage_data.head(3)
# 出力例:
# purchase_date customer_id item_name item_price quantity
# 0 2023-01-15 C001 APPLE 150 2
# 1 2023-01-16 C002 BANANA 100 5
# 2 2023-01-16 C001 ORANGE 200 1
データ型の確認
sample_uriage_data.dtypes
# 出力例:
# purchase_date datetime64[ns]
# customer_id object
# item_name object
# item_price float64
# quantity int64
# dtype: object
欠損値の確認
sample_uriage_data.isnull().sum()
# 出力例:
# purchase_date 0
# customer_id 0
# item_name 0
# item_price 8
# quantity 0
# dtype: int64
ユニーク値の確認
sample_uriage_data["item_name"].unique()
# 出力例: array(['APPLE', 'BANANA', 'ORANGE', 'GRAPE', 'MELON'], dtype=object)
4. まとめ
本記事を通じて、以下のデータクレンジングスキルが身につきます。
-
複数形式のファイル読み込みと日付変換:CSVとExcelファイルを適切に読み込み、文字列形式の日付をdatetime型に変換して年月データを作成できるようになりました
-
実務的な文字列クレンジング:大文字統一、全角・半角スペース削除により、表記ゆれによるデータの不一致を防ぎ、正確な集計やマージができるようになりました
-
欠損値の検出と論理的な補完:単純に削除するのではなく、同一商品の最大価格で補完するなど、ビジネスロジックに基づいた欠損値処理の考え方を習得しました
Discussion