【Python】実践データ分析100本ノック 第2章

2024/03/06に公開

この記事は、現場で即戦力として活躍することを目指して作られた現場のデータ分析の実践書である「Python 実践データ分析 100本ノック(秀和システム社)」で学んだことをまとめています。

ただし、本を参考にして自分なりに構成などを変更している部分が多々あるため、ご注意ください。細かい解説などは是非本をお手に取って読まれてください。

目的

小売店の売上履歴と顧客台帳データを用いて、データの分析や予測を行うために必要なデータの加工について学ぶ

Import

%%time

import pandas as pd
from colorama import Fore, Style, init                # Pythonの文字色指定ライブラリ
from IPython.display import display_html, clear_output
from gc import collect                                # ガーベッジコレクション
import matplotlib.pyplot as plt
%matplotlib inline
%%time

# テキスト出力の設定
def PrintColor(text:str, color = Fore.GREEN, style = Style.BRIGHT):
    print(style + color + text + Style.RESET_ALL);

# displayの表示設定
pd.set_option('display.max_columns', 50);
pd.set_option('display.max_rows', 50); 

print()
collect()

Knock11:データの読み込み

必要なデータを読み込んでいきます。

なお、テキストのデータダウンロードは以下に格納されています。

https://www.shuwasystem.co.jp/support/7980html/6727.html

ここでは第2章で用いるデータをまとめて読み込んでいるため、テキストとは大きく異なるのでご注意ください。

%%time

"""データ読み込み"""
uriage_data  = pd.read_csv('uriage.csv')
kokyaku_data = pd.read_excel('kokyaku_daicho.xlsx')

print()
collect()

読み込んだデータを表示してみます。

%%time

"""読み込んだデータを確認"""
PrintColor(f'\n uriage_data')
display(uriage_data.head())

PrintColor(f'\n kokyaku_data')
display(kokyaku_data.head())

print()
collect()

Knock12:データの揺れを確認する

売上履歴のitem_nameとitem_priceを抽出し、データの揺れを確認します。

%%time

PrintColor(f'\n item_name')
display(uriage_data['item_name'].head())

PrintColor(f'\n item_price')
display(uriage_data['item_price'].head())

print()
collect()

Knock13:データに揺れがあるまま集計してみる

「売上履歴」から商品ごとの月売上合計を集計してみます。

%%time

# purchase_date列をdatetime型に変換する
uriage_data['purchase_date']  = pd.to_datetime(uriage_data['purchase_date'])
# 'purchase_date列から新しくpurchase_month列を作成する
uriage_data['purchase_month'] = uriage_data['purchase_date'].dt.strftime('%Y%m')

res = uriage_data.pivot_table(index      = 'purchase_month',
                              columns    = 'item_name',
                              aggfunc    = 'size', # 月ごとの各item_nameの件数を集計
                              fill_value = 0
                             )
res

本来商品数が26個であるのに対し、99商品に増えてしまっています。

次に、横軸にitem_priceを設定して集計を行ってみます。

%%time

res = uriage_data.pivot_table(index      = 'purchase_month',
                              columns    = 'item_name',
                              values     = 'item_price',
                              aggfunc    = 'sum',
                              fill_value = 0
                             )
res

こちらも同様に商品が実際より増えてしまっていることが確認できます。

Knock14:商品名の揺れを補正する

まずは商品名の揺れを補正していきます。

%%time

# 商品名の小文字を大文字に変換する
uriage_data['item_name'] = uriage_data['item_name'].str.upper()
# 商品名の全角スペースを取り除く
uriage_data['item_name'] = uriage_data['item_name'].str.replace(' ', '')
# 商品名の半角スペースを取り除く
uriage_data['item_name'] = uriage_data['item_name'].str.replace(' ', '')
# item_name順にソートをかける
uriage_data.sort_values(by = ['item_name'], ascending = True)

正しく補正されたか、商品名の一覧を確認します。

%%time

PrintColor(f'\n uriage_data["item_name"]_unique')
display(pd.unique(uriage_data['item_name']))

PrintColor(f'\n uriage_data["item_name"]_len')
display(len(pd.unique(uriage_data['item_name'])))

print()
collect()

Knock15:金額欠損値の補完をする

まずはデータ全体から、欠損値が含まれているかを確認します。

uriage_data.isnull().sum()
%%time

# item_priceの中で、欠損値のある個所を特定する
flg_is_null = uriage_data['item_price'].isnull()

# 確認用
flg_is_null
# データが欠損している商品名の一覧を作成する
null_list = list(uriage_data.loc[flg_is_null, 'item_name'].unique())

null_list

%%time

# 金額欠損値の補完処理を行う
for trg in null_list:
    # 欠損値ではなく、かつnull_listから取り出している商品である値の中で、最も大きい値(金額)を取得する
    price = uriage_data.loc[(~flg_is_null) & (uriage_data['item_name'] == trg), 'item_price'].max()
    # price = uriage_data.loc[(flg_is_null == False) & (uriage_data['item_name'] == trg), 'item_price'].max() と同じ
    
    # 欠損値であり、かつnull_listから取り出している商品である値(つまり欠損値)に、先ほどの最大値を代入する
    uriage_data.loc[(flg_is_null) & (uriage_data['item_name'] == trg), 'item_price'] = price
    # price = uriage_data.loc[(flg_is_null == True) & (uriage_data['item_name'] == trg), 'item_price'] = price と同じ

uriage_data.head()
# 欠損値が解消されたか確認
uriage_data.isnull().any(axis = 0)

より細かく、各商品の金額が正しく補完されたかを確認します。

for trg in list(uriage_data['item_name'].sort_values().unique()):
    print(trg +\
          'の最大額:' + \
          str(uriage_data.loc[uriage_data['item_name'] == trg]['item_price'].max()) + \
          'の最小額:' + \
          str(uriage_data.loc[uriage_data['item_name'] == trg]['item_price'].min(skipna = False)))

Knock16:顧客名の揺れを補正する

顧客台帳の顧客名の揺れを補正していきます。まずデータの確認を行います。

%%time

PrintColor(f"\n kokyaku_data['顧客名']")
display(kokyaku_data['顧客名'].head())

PrintColor(f"\n uriage_data['customer_name']")
display(uriage_data['customer_name'].head())

print()
collect()

不要なスペースの除去を行います。

%%time

"""不要なスペースを除去する"""
kokyaku_data['顧客名'] = kokyaku_data['顧客名'].str.replace(' ', '') # 全角
kokyaku_data['顧客名'] = kokyaku_data['顧客名'].str.replace(' ',  '') # 半角

print()
collect()
%%time

# 修正後の結果を確認する
PrintColor(f"\n kokyaku_data['顧客名']")
display(kokyaku_data['顧客名'].head())

print()
collect()

Knock17:日付の揺れを補正する

顧客台帳の登録日の揺れを補正します。まずは日付が数値として取り込まれているデータの数を確認します。

%%time

flg_is_serial = kokyaku_data['登録日'].astype('str').str.isdigit()

PrintColor(f'\n 数値判定の日付の数')
display(flg_is_serial.sum())

print()
collect()
%%time

# pd.to_timedelta()を用いて数値から日付に変換する
fromSerial = pd.to_timedelta(kokyaku_data.loc[flg_is_serial, '登録日'].astype('float') - 2, unit = 'D') + pd.to_datetime('1900/1/1')

PrintColor(f'\n fromSerial')
display(fromSerial)

print()
collect()

日付として取り込まれているデータも、書式統一のため処理します。

%%time

fromString = pd.to_datetime(kokyaku_data.loc[~flg_is_serial, '登録日'])

PrintColor(f'\n fromString')
display(fromString)

print()
collect()
%%time

# fromSerialとfromStringを結合します。
kokyaku_data['登録日'] = pd.concat([fromSerial, fromString])

PrintColor(f'\n kokyaku_data')
display(kokyaku_data)

print()
collect()

登録日から登録月を算出し、集計を行います。

%%time

kokyaku_data['登録年月'] = kokyaku_data['登録日'].dt.strftime('%Y%m')
rslt = kokyaku_data.groupby('登録年月').count()['顧客名']

display(rslt)
display(len(kokyaku_data))

print()
collect()

登録日列に数値データが残っていないか確認をします。

%%time

flg_is_serial = kokyaku_data['登録日'].astype('str').str.isdigit()

PrintColor(f'\n flg_is_serial')
display(flg_is_serial.sum())

print()
collect()

Knock18:顧客名をキーにして2つのデータを結合する

売上履歴と顧客台帳を結合し、集計のベースとなるデータを作成します。

%%time

join_data = pd.merge(uriage_data,
                     kokyaku_data,
                     left_on  = 'customer_name',
                     right_on = '顧客名',
                     how      = 'left'
                    )
join_data = join_data.drop('customer_name', axis = 1)

PrintColor(f'\n join_data')
display(join_data)

print()
collect()

Knock19:クレンジングしたデータをCSVにダンプ(出力)する

ダンプする前に列の配置を調整します。

%%time

dump_data = join_data[['purchase_date',
                       'purchase_month',
                       'item_name',
                       'item_price',
                       '顧客名',
                       'かな',
                       '地域',
                       'メールアドレス',
                       '登録日']]

PrintColor(f'\n dump_data')
display(dump_data)

print()
collect()
%%time

# csvにダンプする
dump_data.to_csv('dump_data.csv', index = False)

print()
collect()

Knock20:データを集計する

まずはダンプファイルを読み込みます。

%%time

"""データ読み込み"""
import_data = pd.read_csv('dump_data.csv')

"""読み込んだデータを確認"""
PrintColor(f'\n import_data')
display(import_data)

print()
collect()

purchase_monthを縦軸に、商品毎の集計を行う。

%%time

byItem = import_data.pivot_table(index      = 'purchase_month',
                                 columns    = 'item_name',
                                 aggfunc    = 'size',
                                 fill_value = 0
                                )

PrintColor(f'\n byItem')
display(byItem)

print()
collect()

次にpurchase_monthを縦軸に、売上金額、顧客、地域の集計を行う。

%%time

byPrice = import_data.pivot_table(index     = 'purchase_month',
                                 columns    = 'item_name',
                                 values     = 'item_price',
                                 aggfunc    = 'sum',
                                 fill_value = 0
                                )

PrintColor(f'\n byPrice')
display(byPrice)

print()
collect()
%%time

byCustomer = import_data.pivot_table(index      = 'purchase_month',
                                     columns    = '顧客名',
                                     aggfunc    = 'size',
                                     fill_value = 0
                                    )

PrintColor(f'\n byCustomer')
display(byCustomer)

print()
collect()
%%time

byRegion = import_data.pivot_table(index      = 'purchase_month',
                                   columns    = '地域',
                                   aggfunc    = 'size',
                                   fill_value = 0
                                  )

PrintColor(f'\n byRegion')
display(byRegion)

print()
collect()

最後に集計期間で購入していないユーザーがいないか確認を行う。

%%time

away_data = pd.merge(uriage_data,
                     kokyaku_data,
                     left_on  = 'customer_name',
                     right_on = '顧客名',
                     how      = 'right'
                    )
away_data[away_data['purchase_date'].isnull()][['顧客名', 'メールアドレス', '登録日']]

実践データ分析記事一覧

Discussion