(学習ログ)Python018:販売データと顧客台帳のクレンジング・結合処理
1. はじめに
この記事の目的
pandasを使った実務的なデータクレンジングと結合の基本パターンを習得することを目指します。
この記事の概要
本記事では、販売データ(CSV)と顧客台帳(Excel)を読み込み、文字列の正規化、欠損値の補完、Excel特有のシリアル値形式の日付変換を経て、最終的に2つのデータを結合するまでの一連の流れを解説します。
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(" ", "")
# 顧客台帳:登録日がシリアル値(数値)か文字列かを判定
flg_is_serial = sample_customer_ledger["登録日"].astype("str").str.isdigit()
# シリアル値をExcel基準日(1900/1/1)からの経過日数として日付に変換
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])
sample_customer_ledger["登録年月"] = sample_customer_ledger["登録日"].dt.strftime("%Y%m")
# 売上データと顧客台帳を結合
sample_join_data = pd.merge(sample_uriage_data, sample_customer_ledger, left_on="customer_name", right_on="顧客名", how="left")
# 重複列(customer_name)を削除
sample_join_data = sample_join_data.drop("customer_name", axis=1)
sample_join_data
コード解説
データの読み込み
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")
文字列として読み込まれた購入日を日付型に変換し、年月(例:202401)を抽出して新しい列を作成します。
商品名の正規化
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
商品価格が欠損している行について、同じ商品名を持つ他の行の最高価格を使って補完します。これにより、同一商品の価格は統一されます。
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から読み込んだ日付がシリアル値(数値)の場合と文字列の場合が混在していることがあります。それぞれを正しい日付型に変換し、統合します。
データの結合
sample_join_data = pd.merge(sample_uriage_data, sample_customer_ledger, left_on="customer_name", right_on="顧客名", how="left")
sample_join_data = sample_join_data.drop("customer_name", axis=1)
売上データに顧客台帳の情報を紐付けます。left_onとright_onで異なる列名を指定できます。左外部結合(how="left")により、売上データの全ての行を保持します。
実行結果例
元データの想定(sample_uriage_data)
customer_name item_name item_price purchase_date purchase_month
0 山田太郎 apple NaN 2024-01-15 202401
1 佐藤花子 APPLE 150.0 2024-01-20 202401
2 鈴木一郎 Banana 200.0 2024-02-10 202402
クレンジング後
customer_name item_name item_price purchase_date purchase_month
0 山田太郎 APPLE 150.0 2024-01-15 202401
1 佐藤花子 APPLE 150.0 2024-01-20 202401
2 鈴木一郎 BANANA 200.0 2024-02-10 202402
顧客台帳(sample_customer_ledger)
顧客名 登録日 登録年月
0 山田太郎 2023-12-01 202312
1 佐藤花子 2024-01-05 202401
結合後(sample_join_data)
顧客名 item_name item_price purchase_date purchase_month 登録日 登録年月
0 山田太郎 APPLE 150.0 2024-01-15 202401 2023-12-01 202312
1 佐藤花子 APPLE 150.0 2024-01-20 202401 2024-01-05 202401
2 鈴木一郎 BANANA 200.0 2024-02-10 202402 NaN NaN
3. エラーと確認のポイント
代表的なエラー文
KeyError: 'column_name'
KeyError: 'purchase_date'
指定した列名がDataFrameに存在しない場合に発生します。列名のスペルミスや、読み込み時の文字コード問題を確認しましょう。
ValueError: unconverted data remains
ValueError: unconverted data remains: -01-15
日付の形式が想定と異なる場合に発生します。pd.to_datetime()のformatパラメータで明示的に指定するか、errors='coerce'で不正な値をNaTに変換できます。
TypeError: cannot concatenate object of type
TypeError: can only concatenate str (not "int") to str
データ型が混在している場合に発生します。.astype()で型を揃えてから処理しましょう。
よく使う確認メソッドと出力例
shape(データの行数・列数確認)
sample_uriage_data.shape
# 出力例: (1000, 5) # 1000行、5列
columns(列名の確認)
sample_uriage_data.columns
# 出力例: Index(['customer_name', 'item_name', 'item_price', 'purchase_date', 'purchase_month'], dtype='object')
head(先頭データの確認)
sample_uriage_data.head(3)
# 出力例: 先頭3行が表形式で表示される
isnull().sum()(欠損値の個数確認)
sample_uriage_data.isnull().sum()
# 出力例:
# customer_name 0
# item_name 0
# item_price 15
# purchase_date 0
# purchase_month 0
# dtype: int64
dtypes(データ型の確認)
sample_uriage_data.dtypes
# 出力例:
# customer_name object
# item_name object
# item_price float64
# purchase_date datetime64[ns]
# purchase_month object
# dtype: object
value_counts()(値の分布確認)
sample_uriage_data["item_name"].value_counts()
# 出力例:
# APPLE 450
# BANANA 320
# ORANGE 230
# Name: item_name, dtype: int64
4. まとめ
本記事を通じて、以下の実践的なデータ処理技術を習得できました。
- 異なる形式のデータの統合処理:CSVとExcelを読み込み、文字列の正規化や日付変換を行うことで、分析可能な状態に整える手法
- 実務的な欠損値補完のロジック:同一商品の最高価格で補完するなど、ビジネスルールに基づいた欠損値処理の実装方法
- Excelのシリアル値対応:Excelから読み込んだ日付データの形式混在に対応し、正しい日付型に統一する技術
Discussion