(学習ログ)Python016:販売データのクレンジングと欠損値補完
1. はじめに
この記事の目的
実務でよく遭遇する「汚れたデータ」を、pandasを使って効率的にクリーニングする方法を学びます。
この記事の概要
本記事では、販売データ(CSV)と顧客台帳(Excel)を読み込み、以下の処理を行います:
- 日付データの変換と月次集計用の加工
- 商品名の表記ゆれを統一(大文字化・空白除去)
- 欠損している価格データを、同じ商品の既存データから補完
- データの並び替えと欠損値の最終確認
2. サンプルコード
コード全文
import pandas as pd
# CSVとExcelファイルの読み込み
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(" ", "")
# 欠損値の補完処理
# 価格がNaNの行を特定するフラグを作成
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(" ", "")
# 結果の確認
sample_customer_ledger.head()
コード解説
データの読み込み部分
sample_uriage_data = pd.read_csv("002_サンプル/sample_uriage_data.csv")
sample_customer_ledger = pd.read_excel("002_サンプル/sample_customer_ledger.xlsx")
CSVファイルはread_csv()、Excelファイルはread_excel()で読み込みます。それぞれDataFrame型のオブジェクトとして格納されます。
日付データの加工
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")
to_datetime()で文字列を日付型に変換し、dt.strftime("%Y%m")で「202501」のような年月形式の文字列を生成します。これにより月次集計が容易になります。
商品名の正規化
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(" ", "")
str.upper()で全て大文字に統一し、str.replace()で全角・半角スペースを削除します。これにより「Apple Pie」「APPLE PIE」「apple pie」などの表記ゆれを統一できます。
欠損値の補完ロジック
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
価格が欠損している商品を特定し、同じ商品名で価格が記録されている行から最大値を取得して補完します。これにより、「商品Aの価格が一部の行で空欄」という状況に対応できます。
データの確認
sample_uriage_data.sort_values(by=["item_name"], ascending=True)
sample_uriage_data.isnull().any(axis=0)
商品名でソートして見やすくし、isnull().any(axis=0)で各列に欠損値が残っていないかを確認します。
実行結果例
元データ(sample_uriage_data.csv)
purchase_date,item_name,item_price
2025-01-15,Apple Pie,500
2025-01-16,APPLE PIE,
2025-01-17,apple pie,520
2025-01-18,Banana Cake,300
処理後のpurchase_month列
202501
202501
202501
202501
正規化後のitem_name列
APPLEPIE
APPLEPIE
APPLEPIE
BANANACAKE
欠損値補完後のitem_price列
500
520 ← 最大値520で補完された
520
300
欠損値チェックの結果
sample_uriage_data.isnull().any(axis=0)
purchase_date False
item_name False
item_price False
purchase_month False
dtype: bool
全ての列でFalseとなり、欠損値が存在しないことが確認できます。
3. エラーと確認のポイント
代表的なエラー文
エラー1: FileNotFoundError
FileNotFoundError: [Errno 2] No such file or directory: '002_サンプル/sample_uriage_data.csv'
原因: ファイルパスが間違っているか、ファイルが存在しません。カレントディレクトリとファイルの配置を確認しましょう。
エラー2: KeyError
KeyError: 'item_price'
原因: 指定した列名がDataFrameに存在しません。df.columnsで列名を確認し、スペルミスや全角・半角の違いをチェックしましょう。
エラー3: AttributeError
AttributeError: Can only use .str accessor with string values
原因: 文字列型でない列に.strメソッドを使っています。df.dtypesで型を確認し、必要に応じてastype(str)で変換しましょう。
よく使う確認メソッドと出力例
shape - データの行数と列数を確認
sample_uriage_data.shape
(150, 4) # 150行、4列のデータ
columns - 列名の一覧を確認
sample_uriage_data.columns
Index(['purchase_date', 'item_name', 'item_price', 'purchase_month'], dtype='object')
head() - 先頭5行を確認
sample_uriage_data.head()
purchase_date item_name item_price purchase_month
0 2025-01-15 APPLEPIE 500 202501
1 2025-01-16 APPLEPIE 520 202501
2 2025-01-17 APPLEPIE 520 202501
3 2025-01-18 BANANACAKE 300 202501
4 2025-01-19 CHEESECAKE 450 202501
dtypes - 各列のデータ型を確認
sample_uriage_data.dtypes
purchase_date datetime64[ns]
item_name object
item_price float64
purchase_month object
dtype: object
isnull().sum() - 各列の欠損値の数を確認
sample_uriage_data.isnull().sum()
purchase_date 0
item_name 0
item_price 0
purchase_month 0
dtype: int64
4. まとめ
本記事では、実務で頻繁に発生するデータクレンジングの基本的な流れを学びました。
-
日付データの加工と文字列の正規化:
to_datetime()やstr.upper()、str.replace()を使うことで、表記ゆれを統一し、後続の集計処理をスムーズにできる - 欠損値の賢い補完方法: 単純に平均値で埋めるのではなく、同じ商品名のデータから最大値(または最頻値)を取得することで、より実態に即した補完が可能
-
処理後の確認を怠らない:
isnull().any()やhead()、dtypesなどで、意図通りに処理が完了しているかを必ず確認する習慣をつけることが、バグの早期発見につながる
Discussion