データサイエンス100本ノック(構造化データ加工編)- Python Part 3 (Q41 to Q60)の解説です。
参照(Reference) : 「データサイエンティスト協会スキル定義委員」の「データサイエンス100本ノック(構造化データ加工編)」
The Data Scientist Society Github :
Data Science 100 Knocks (Structured Data Processing) URL :
はじめに
- 初めに以下のセルを実行してください
- 必要なライブラリのインポートとデータベース(PostgreSQL)からのデータ読み込みを行います
- pandas等、利用が想定されるライブラリは以下セルでインポートしています
- その他利用したいライブラリがあれば適宜インストールしてください(”!pip install ライブラリ名”でインストールも可能)
- 処理は複数回に分けても構いません
- 名前、住所等はダミーデータであり、実在するものではありません
import os
import pandas as pd
import numpy as np
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
import math
import psycopg2
from sqlalchemy import create_engine
from sklearn import preprocessing
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
from sklearn.model_selection import TimeSeriesSplit
from imblearn.under_sampling import RandomUnderSampler
if 'PG_PORT' in os.environ:
pgconfig = {
'host': 'db',
'port': os.environ['PG_PORT'],
'database': os.environ['PG_DATABASE'],
'user': os.environ['PG_USER'],
'password': os.environ['PG_PASSWORD'],
}
# pd.read_sql用のコネクタ
conn = psycopg2.connect(**pgconfig)
df_customer = pd.read_sql(sql='select * from customer', con=conn)
df_category = pd.read_sql(sql='select * from category', con=conn)
df_product = pd.read_sql(sql='select * from product', con=conn)
df_receipt = pd.read_sql(sql='select * from receipt', con=conn)
df_store = pd.read_sql(sql='select * from store', con=conn)
df_geocode = pd.read_sql(sql='select * from geocode', con=conn)
else:
if not os.path.exists('../data/'):
!git clone https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess
os.chdir('100knocks-preprocess/docker/work/answer')
dtype = {
'customer_id': str,
'gender_cd': str,
'postal_cd': str,
'application_store_cd': str,
'status_cd': str,
'category_major_cd': str,
'category_medium_cd': str,
'category_small_cd': str,
'product_cd': str,
'store_cd': str,
'prefecture_cd': str,
'tel_no': str,
'postal_cd': str,
'street': str
}
df_customer = pd.read_csv("../data/customer.csv", dtype=dtype)
df_category = pd.read_csv("../data/category.csv", dtype=dtype)
df_product = pd.read_csv("../data/product.csv", dtype=dtype)
df_receipt = pd.read_csv("../data/receipt.csv", dtype=dtype)
df_store = pd.read_csv("../data/store.csv", dtype=dtype)
df_geocode = pd.read_csv("../data/geocode.csv", dtype=dtype)
演習問題
P-041: レシート明細データ(df_receipt)の売上金額(amount)を日付(sales_ymd)ごとに集計し、前回売上があった日からの売上金額増減を計算せよ。そして結果を10件表示せよ。
df_sales_amount_by_date = df_receipt[['sales_ymd', 'amount']].\
groupby('sales_ymd').sum().reset_index()
df_sales_amount_by_date = pd.concat([df_sales_amount_by_date,
df_sales_amount_by_date.shift()], axis=1)
df_sales_amount_by_date.columns = ['sales_ymd','amount','lag_ymd','lag_amount']
df_sales_amount_by_date['diff_amount'] = \
df_sales_amount_by_date['amount'] - df_sales_amount_by_date['lag_amount']
df_sales_amount_by_date.head(10)
sales_ymd | amount | lag_ymd | lag_amount | diff_amount | |
---|---|---|---|---|---|
0 | 20170101 | 33723 | NaN | NaN | NaN |
1 | 20170102 | 24165 | 20170101.0 | 33723.0 | -9558.0 |
2 | 20170103 | 27503 | 20170102.0 | 24165.0 | 3338.0 |
3 | 20170104 | 36165 | 20170103.0 | 27503.0 | 8662.0 |
4 | 20170105 | 37830 | 20170104.0 | 36165.0 | 1665.0 |
5 | 20170106 | 32387 | 20170105.0 | 37830.0 | -5443.0 |
6 | 20170107 | 23415 | 20170106.0 | 32387.0 | -8972.0 |
7 | 20170108 | 24737 | 20170107.0 | 23415.0 | 1322.0 |
8 | 20170109 | 26718 | 20170108.0 | 24737.0 | 1981.0 |
9 | 20170110 | 20143 | 20170109.0 | 26718.0 | -6575.0 |
解説:
このコードは、ある企業の毎日の売上金額を計算し、売上金額を1日分ずらして新しいデータフレームを作成し、前日の売上金額を表す新しい列を作成します。そして、ある日の売上金額と前日の売上金額の差を計算し、「diff_amount」という新しいカラムを作成します。
以下、一行ずつ解説していきます。
df_sales_amount_by_date = df_receipt[['sales_ymd', 'amount']].groupby('sales_ymd').sum().reset_index(): このコードは、'df_receipt' データフレームから 'sales_ymd' と 'amount' 列を選択し、'sales_ymd' 列によってデータをグループ化しています。そして、各 'sales_ymd' グループの 'amount' カラムの合計を計算し、インデックスをリセットして 'sales_ymd' と 'amount' カラムを持つ新しいデータフレーム 'df_sales_amount_by_date' を作成します。
df_sales_amount_by_date = pd.concat([df_sales_amount_by_date, df_sales_amount_by_date.shift()], axis=1): このコードは、'df_sales_amount_by_date' dataframeを、列の軸に沿って(つまり、水平方向に)シフトしたバージョンと連結するものである。この結果、4つのカラムを持つ新しいデータフレームが出来上がる。'sales_ymd', 'amount', 'lag_ymd', 'lag_amount' の4つの列を持つ新しいデータフレームになります。
df_sales_amount_by_date.columns = ['sales_ymd','amount','lag_ymd','lag_amount']: このコードは、'df_sales_amount_by_date' dataframeのカラムの名前をより意味のある名前に変更します。
df_sales_amount_by_date['diff_amount'] = df_sales_amount_by_date['amount'] - df_sales_amount_by_date['lag_amount']: このコードは、'df_sales_amount_by_date' dataframeに、'amount' カラムから 'lag_amount' カラムを減算して、新しいカラム 'diff_amount' を作成します。
df_sales_amount_by_date.head(10)。このコードは、'df_sales_amount_by_date' dataframeの最初の10行を表示します。
P-042: レシート明細データ(df_receipt)の売上金額(amount)を日付(sales_ymd)ごとに集計し、各日付のデータに対し、前回、前々回、3回前に売上があった日のデータを結合せよ。そして結果を10件表示せよ。
# コード例1:縦持ちケース
df_sales_amount_by_date = df_receipt[['sales_ymd', 'amount']]. \
groupby('sales_ymd').sum().reset_index()
for i in range(1, 4):
df_tmp = pd.concat([df_sales_amount_by_date,
df_sales_amount_by_date.shift(i)], axis=1)
if i == 1:
df_lag = df_tmp
else:
df_lag = df_lag.append(df_tmp)
df_lag.columns = ['sales_ymd', 'amount', 'lag_ymd', 'lag_amount']
df_lag.dropna().astype(int).sort_values(['sales_ymd','lag_ymd']).head(10)
sales_ymd | amount | lag_ymd | lag_amount | |
---|---|---|---|---|
1 | 20170102 | 24165 | 20170101 | 33723 |
2 | 20170103 | 27503 | 20170101 | 33723 |
2 | 20170103 | 27503 | 20170102 | 24165 |
3 | 20170104 | 36165 | 20170101 | 33723 |
3 | 20170104 | 36165 | 20170102 | 24165 |
3 | 20170104 | 36165 | 20170103 | 27503 |
4 | 20170105 | 37830 | 20170102 | 24165 |
4 | 20170105 | 37830 | 20170103 | 27503 |
4 | 20170105 | 37830 | 20170104 | 36165 |
5 | 20170106 | 32387 | 20170103 | 27503 |
解説:
このコードは、売上データに対してラグ分析を実行しています。
まず、このコードは売上データを日付ごとにグループ化し、各日付の売上金額を合計して各日の売上金額の合計を求めます。これはdf_sales_amount_by_dateに格納されています。
次に、1~3まで3回反復するforループを実行します。
forループの各繰り返しの中で、df_sales_amount_by_dateのDataFrameをi行ずつずらしたものと連結して、df_tmpという新しいDataFrameを作成します。この結果、各行が特定の日付の売上データと一つ前の日付の売上データを含むDataFrameとなる。
i が 1 の場合、結果の DataFrame は df_lag に格納される。そうでない場合は、DataFrameはdf_lagに追加される。
df_lagの列は、現在の日付と遅れた日付の売上高を示すために、sales_ymd, amount, lag_ymd, lag_amountと名前が変更される。
最後に、NaN値を含む行(最初のi行で発生する)を削除し、売上金額を整数に変換してから、sales_ymdとlag_ymdでDataFrameをソートして、最初の10行を表示します。
これにより、各日付の売上金額の合計と、1日前、2日前、3日前の売上金額の合計が表示され、ユーザは売上データのラグ分析を行うことができます。
# コード例2:横持ちケース
df_sales_amount_by_date = df_receipt[['sales_ymd', 'amount']].\
groupby('sales_ymd').sum().reset_index()
df_lag = df_sales_amount_by_date
for i in range(1, 4):
df_lag = pd.concat([df_lag, df_sales_amount_by_date.shift(i)], axis=1)
columns = [f'lag_ymd_{i}', f'lag_amount_{i}']
df_lag.columns = list(df_lag.columns)[:-len(columns)] + columns
df_lag.dropna().astype(int).sort_values(['sales_ymd']).head(10)
sales_ymd | amount | lag_ymd_1 | lag_amount_1 | lag_ymd_2 | lag_amount_2 | lag_ymd_3 | lag_amount_3 | |
---|---|---|---|---|---|---|---|---|
3 | 20170104 | 36165 | 20170103 | 27503 | 20170102 | 24165 | 20170101 | 33723 |
4 | 20170105 | 37830 | 20170104 | 36165 | 20170103 | 27503 | 20170102 | 24165 |
5 | 20170106 | 32387 | 20170105 | 37830 | 20170104 | 36165 | 20170103 | 27503 |
6 | 20170107 | 23415 | 20170106 | 32387 | 20170105 | 37830 | 20170104 | 36165 |
7 | 20170108 | 24737 | 20170107 | 23415 | 20170106 | 32387 | 20170105 | 37830 |
8 | 20170109 | 26718 | 20170108 | 24737 | 20170107 | 23415 | 20170106 | 32387 |
9 | 20170110 | 20143 | 20170109 | 26718 | 20170108 | 24737 | 20170107 | 23415 |
10 | 20170111 | 24287 | 20170110 | 20143 | 20170109 | 26718 | 20170108 | 24737 |
11 | 20170112 | 23526 | 20170111 | 24287 | 20170110 | 20143 | 20170109 | 26718 |
12 | 20170113 | 28004 | 20170112 | 23526 | 20170111 | 24287 | 20170110 | 20143 |
解説:
提供されたコードは、以下の処理を実行する。
df_receiptのDataFrameを使用して、sales_ymdとamount列を選択し、sales_ymdでグループ化し、amount列にsum()関数を適用して各日の総売上額を計算する。出来上がったDataFrameは、df_sales_amount_by_dateに格納されます。
df_sales_amount_by_dateのコピーとして、df_lagという新しいDataFrameが作成されます。
ループが3回(1~4)実行され、各反復でshift()メソッドが使われ、df_sales_amount_by_date DataFrameをi周期(日)ずつずらす。得られたDataFrameは、pd.concat()を用いてdf_lagと水平方向に連結され、df_lagに格納される。
df_lag DataFrameに追加される新しいカラムの名前を格納するために、columns変数が作成される。
df_lagのカラム名は、columnsリストを用いて更新され、結果のDataFrameはdf_lagに格納される。
Dropna()メソッドを呼び出して欠損値のある行を削除し、astype()メソッドを用いてDataFrameを整数型に変換している。最後に、sort_values()メソッドを使用して、DataFrameをsales_ymdの昇順でソートし、結果のDataFrameの最初の10行をhead()メソッドで返しています。
まとめると、このコードでは、販売実績の傾向を把握するために、各日の売上金額と過去3日間の売上金額を含むDataFrame df_lagを作成しています。出来上がったDataFrameは、各日の売上金額と過去3日間の売上金額を、売上日順に並べたものです。
P-043: レシート明細データ(df_receipt)と顧客データ(df_customer)を結合し、性別コード(gender_cd)と年代(ageから計算)ごとに売上金額(amount)を合計した売上サマリデータを作成せよ。性別コードは0が男性、1が女性、9が不明を表すものとする。
ただし、項目構成は年代、女性の売上金額、男性の売上金額、性別不明の売上金額の4項目とすること(縦に年代、横に性別のクロス集計)。また、年代は10歳ごとの階級とすること。
# コード例1
df_tmp = pd.merge(df_receipt, df_customer, how ='inner', on="customer_id")
df_tmp['era'] = df_tmp['age'].apply(lambda x: math.floor(x / 10) * 10)
df_sales_summary = pd.pivot_table(
df_tmp, index='era',
columns='gender_cd',
values='amount',
aggfunc='sum'
).reset_index()
df_sales_summary.columns = ['era', 'male', 'female', 'unknown']
df_sales_summary
era | male | female | unknown | |
---|---|---|---|---|
0 | 10 | 1591.0 | 149836.0 | 4317.0 |
1 | 20 | 72940.0 | 1363724.0 | 44328.0 |
2 | 30 | 177322.0 | 693047.0 | 50441.0 |
3 | 40 | 19355.0 | 9320791.0 | 483512.0 |
4 | 50 | 54320.0 | 6685192.0 | 342923.0 |
5 | 60 | 272469.0 | 987741.0 | 71418.0 |
6 | 70 | 13435.0 | 29764.0 | 2427.0 |
7 | 80 | 46360.0 | 262923.0 | 5111.0 |
8 | 90 | NaN | 6260.0 | NaN |
解説:
このコードでは、以下のことを行っています。
2つのデータフレームdf_receiptとdf_customerを共通の列customer_idで内部結合してマージし、結果を新しいデータフレームdf_tmpにセーブします。
df_tmpデータフレームに新しい列eraを作成します。eraは、各顧客の年齢を10で割って、10未満を切り捨てることで計算されます。
eraとgender_cdの値の一意の組み合わせごとに、df_tmpデータフレームのamount列の合計を計算し、その結果をpd.pivot_table()関数を使用して新しいデータフレームdf_sales_summaryとして保存しています。結果のデータフレームは、eraの値をインデックスとして、gender_cdの値を列として持っています。
df_sales_summaryデータフレームの列の名前をより分かりやすいものに変更します。最初の列はeraで、その後にmale、female、unknownが続き、それぞれのgender_cd値の金額列の合計を表します。
# コード例2
df_tmp = pd.merge(df_receipt, df_customer, how ='inner', on="customer_id")
df_tmp['era'] = np.floor(df_tmp['age'] / 10).astype(int) * 10
df_sales_summary = pd.pivot_table(df_tmp, index='era', columns='gender_cd',
values='amount', aggfunc='sum').reset_index()
df_sales_summary.columns = ['era', 'male', 'female', 'unknown']
df_sales_summary
era | male | female | unknown | |
---|---|---|---|---|
0 | 10 | 1591.0 | 149836.0 | 4317.0 |
1 | 20 | 72940.0 | 1363724.0 | 44328.0 |
2 | 30 | 177322.0 | 693047.0 | 50441.0 |
3 | 40 | 19355.0 | 9320791.0 | 483512.0 |
4 | 50 | 54320.0 | 6685192.0 | 342923.0 |
5 | 60 | 272469.0 | 987741.0 | 71418.0 |
6 | 70 | 13435.0 | 29764.0 | 2427.0 |
7 | 80 | 46360.0 | 262923.0 | 5111.0 |
8 | 90 | NaN | 6260.0 | NaN |
解説:
このコードは以下の処理を実行します。
df_receiptとdf_customerの2つのデータフレームを、内側結合を使って共通の列「customer_id」上で結合します。結果のデータフレームは、df_tmpという新しい変数に格納されます。
age」カラムを10で割って階数を取り、10を掛けて10年を得ることで、df_tmpに新しいカラム「era」を作成します。この列は、各顧客の年齢層を数十年で表します。
pd.pivot_table() 関数を使用して、df_sales_summary というピボット・テーブルを作成します。このピボット・テーブルは、df_tmp データフレームに基づいており、「era」列によってインデックスが付けられた行、「gender_cd」列によってグループ化された列、および sum 集約関数を使用して「amount」列から計算された値を持っています。結果として得られる表は、各年齢グループにおける各性別の売上金額の合計を示す。
df_sales_summaryのカラム名をより分かりやすく変更し、「male」、「female」、「unknown」が各年齢層の各性別の総売上金額を表しています。
P-044: 043で作成した売上サマリデータ(df_sales_summary)は性別の売上を横持ちさせたものであった。このデータから性別を縦持ちさせ、年代、性別コード、売上金額の3項目に変換せよ。ただし、性別コードは男性を”00″、女性を”01″、不明を”99″とする。
df_sales_summary.set_index('era'). \
stack().reset_index().replace({'female':'01','male':'00','unknown':'99'}). \
rename(columns={'level_1':'gender_cd', 0: 'amount'})
era | gender_cd | amount | |
---|---|---|---|
0 | 10 | 00 | 1591.0 |
1 | 10 | 01 | 149836.0 |
2 | 10 | 99 | 4317.0 |
3 | 20 | 00 | 72940.0 |
4 | 20 | 01 | 1363724.0 |
5 | 20 | 99 | 44328.0 |
6 | 30 | 00 | 177322.0 |
7 | 30 | 01 | 693047.0 |
8 | 30 | 99 | 50441.0 |
9 | 40 | 00 | 19355.0 |
10 | 40 | 01 | 9320791.0 |
11 | 40 | 99 | 483512.0 |
12 | 50 | 00 | 54320.0 |
13 | 50 | 01 | 6685192.0 |
14 | 50 | 99 | 342923.0 |
15 | 60 | 00 | 272469.0 |
16 | 60 | 01 | 987741.0 |
17 | 60 | 99 | 71418.0 |
18 | 70 | 00 | 13435.0 |
19 | 70 | 01 | 29764.0 |
20 | 70 | 99 | 2427.0 |
21 | 80 | 00 | 46360.0 |
22 | 80 | 01 | 262923.0 |
23 | 80 | 99 | 5111.0 |
24 | 90 | 01 | 6260.0 |
解説:
このコードは、pandasのDataFrame df_sales_summaryを、インデックスをeraに設定し、新しいカラムgender_cdに、女性、男性、不明を積み重ね、インデックスをリセットすることで変換しています。そして、gender_cdの値を01(女性)、00(男性)、99(不明)に置き換えています。最後に、level_1をgender_cdに、0をamountにカラム名を変更します。
以下は、ステップバイステップの説明です。
df_sales_summary.set_index('era'): これは、df_sales_summaryのインデックスをeraというカラムに設定するものです。これにより、eraに基づいてデータをグループ化したり、ピボットしたりすることが容易になります。
.stack(): female、male、unknownの各カラムを1つのカラムにスタックし、スタックされたカラムに新しいインデックスレベルが追加されます。
.reset_index(): DataFrameのインデックスをデフォルトの整数インデックスにリセットします。
.replace({'female':'01','male':'00','unknown':'99'}): gender_cdカラムの値を対応するコードに置き換えます。
.rename(columns={'level_1':'gender_cd', 0: 'amount'}): これは、カラム level_1 を gender_cd に、0 を amount にリネームします。level_1 カラムは、stack() メソッドによって作成され、スタックされたカラムの元のカラム名が含まれています。
P-045: 顧客データ(df_customer)の生年月日(birth_day)は日付型でデータを保有している。これをYYYYMMDD形式の文字列に変換し、顧客ID(customer_id)とともに10件表示せよ。
# 以下の書き方でYYYYMMDD形式の文字列に変換できる
# pd.to_datetime(df_customer['birth_day']).dt.strftime('%Y%m%d')
pd.concat([df_customer['customer_id'],
pd.to_datetime(df_customer['birth_day']).dt.strftime('%Y%m%d')],
axis = 1).head(10)
customer_id | birth_day | |
---|---|---|
0 | CS021313000114 | 19810429 |
1 | CS037613000071 | 19520401 |
2 | CS031415000172 | 19761004 |
3 | CS028811000001 | 19330327 |
4 | CS001215000145 | 19950329 |
5 | CS020401000016 | 19740915 |
6 | CS015414000103 | 19770809 |
7 | CS029403000008 | 19730817 |
8 | CS015804000004 | 19310502 |
9 | CS033513000180 | 19620711 |
解説:
このコードでは、既存のdf_customer DataFrameのcustomer_id列とbirth_day列(日付情報)の2列を連結して、新しいDataFrameを生成しています。
birth_dayカラムにpd.to_datetime()関数を適用してdatetime形式に変換し、dt.strftime('%Y%m%d')メソッドを適用してdatetimeを「%Y%m%d」(すなわち、年、月、日)形式の文字列にフォーマットしています。
これにより、新しいDataFrameに、顧客の生年月日を文字列で表した新しい列が作成されます。最後に、head()メソッドを使用して、新しいDataFrameの最初の10行を表示します。
P-046: 顧客データ(df_customer)の申し込み日(application_date)はYYYYMMDD形式の文字列型でデータを保有している。これを日付型に変換し、顧客ID(customer_id)とともに10件表示せよ。
pd.concat([df_customer['customer_id'],
pd.to_datetime(df_customer['application_date'])], axis=1).head(10)
customer_id | application_date | |
---|---|---|
0 | CS021313000114 | 2015-09-05 |
1 | CS037613000071 | 2015-04-14 |
2 | CS031415000172 | 2015-05-29 |
3 | CS028811000001 | 2016-01-15 |
4 | CS001215000145 | 2017-06-05 |
5 | CS020401000016 | 2015-02-25 |
6 | CS015414000103 | 2015-07-22 |
7 | CS029403000008 | 2015-05-15 |
8 | CS015804000004 | 2015-06-07 |
9 | CS033513000180 | 2015-07-28 |
解説:
このコードスニペットは、pd.concat()メソッドを使用して、pandas DataFrame df_customerの2つのカラム('customer_id'と 'application_date' )を新しい DataFrameに連結している。
pd.to_datetime()メソッドは、'application_date'列をdatetimeオブジェクトに変換するために使用されます。このメソッドは、様々な形式の日付文字列をpandasの標準的なdatetimeオブジェクトに変換するのに便利な方法です。
結果として得られる DataFrame は、'customer_id' と 'application_date' の 2 つのカラムを持ち、日付は datetime フォーマットで表示されます。
P-047: レシート明細データ(df_receipt)の売上日(sales_ymd)はYYYYMMDD形式の数値型でデータを保有している。これを日付型に変換し、レシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに10件表示せよ。
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
pd.to_datetime(df_receipt['sales_ymd'].astype('str'))],
axis=1).head(10)
receipt_no | receipt_sub_no | sales_ymd | |
---|---|---|---|
0 | 112 | 1 | 2018-11-03 |
1 | 1132 | 2 | 2018-11-18 |
2 | 1102 | 1 | 2017-07-12 |
3 | 1132 | 1 | 2019-02-05 |
4 | 1102 | 2 | 2018-08-21 |
5 | 1112 | 1 | 2019-06-05 |
6 | 1102 | 2 | 2018-12-05 |
7 | 1102 | 1 | 2019-09-22 |
8 | 1112 | 2 | 2017-05-04 |
9 | 1102 | 1 | 2019-10-10 |
解説:
このコードは、pd.concat関数を使って、2つのpandasデータフレームを結合しています。
df_receipt[['receipt_no', 'receipt_sub_no']]: df_receiptデータフレームからrecipate_noとrecipate_sub_noのカラムを選択しています。
pd.to_datetime(df_receipt['sales_ymd'].astype('str')): df_receipt dataframeのsales_ymdカラムをdatetime形式に変換するものです。
pd.concatのaxis=1という引数は、データフレームを水平方向に列方向に連結することを示します。出来上がったdataframeは、recipate_no、recipate_sub_no、sales_ymd(datetime形式)の列を持ち、最初の10行が.head(10)メソッドを使って表示されます。
P-048: レシート明細データ(df_receipt)の売上エポック秒(sales_epoch)は数値型のUNIX秒でデータを保有している。これを日付型に変換し、レシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに10件表示せよ。
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
pd.to_datetime(df_receipt['sales_epoch'], unit='s').rename('sales_ymd')],
axis=1).head(10)
receipt_no | receipt_sub_no | sales_ymd | |
---|---|---|---|
0 | 112 | 1 | 2018-11-03 |
1 | 1132 | 2 | 2018-11-18 |
2 | 1102 | 1 | 2017-07-12 |
3 | 1132 | 1 | 2019-02-05 |
4 | 1102 | 2 | 2018-08-21 |
5 | 1112 | 1 | 2019-06-05 |
6 | 1102 | 2 | 2018-12-05 |
7 | 1102 | 1 | 2019-09-22 |
8 | 1112 | 2 | 2017-05-04 |
9 | 1102 | 1 | 2019-10-10 |
解説:
このコードでは、以下の処理を行います。
df_receipt DataFrame から receipt_no と receipt_sub_no の2つのカラムを選択します。
pandasライブラリのto_datetime()関数を使用して、sales_epochカラムをdatetimeフォーマットに変換します。unit パラメータには、入力が秒単位であることを示す 's' が設定されています。
出来上がったdatetimeカラムは、pandasライブラリのrename()関数を使用して'sales_ymd'にリネームされます。
最後に、pandas ライブラリの concat() 関数を使用して、2 つの DataFrame を列の軸 (axis=1) に沿って連結しています。
出来上がったDataFrameは、recipate_no、recipate_sub_no、sales_ymdの3つのカラムを持ち、sales_ymdはdatetime形式の販売日時となります。head(10)関数は、結果のDataFrameの最初の10行を表示するために使用されます。
P-049: レシート明細データ(df_receipt)の売上エポック秒(sales_epoch)を日付型に変換し、「年」だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに10件表示せよ。
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
pd.to_datetime(df_receipt['sales_epoch'],
unit='s').dt.year.rename('sales_year')],
axis=1).head(10)
receipt_no | receipt_sub_no | sales_year | |
---|---|---|---|
0 | 112 | 1 | 2018 |
1 | 1132 | 2 | 2018 |
2 | 1102 | 1 | 2017 |
3 | 1132 | 1 | 2019 |
4 | 1102 | 2 | 2018 |
5 | 1112 | 1 | 2019 |
6 | 1102 | 2 | 2018 |
7 | 1102 | 1 | 2019 |
8 | 1112 | 2 | 2017 |
9 | 1102 | 1 | 2019 |
解説:
このコードは、df_receiptのrecipate_noとrecipate_sub_noの列と、to_datetimeメソッドでdatetimeオブジェクトに変換したsales_epoch列の年を連結して新しいDataFrameを作成し、単位パラメータをsに設定してsales_epochの値がUnixタイムスタンプ(すなわち1970年1月1日からの秒数)であることを表しています。
以下、コードを一行ずつ分解してみます。
pd.concat:2つのDataFrameを列の軸に沿って連結します。
df_receipt[['receipt_no', 'receipt_sub_no']]: df_receipt DataFrame から receipt_no と receipt_sub_no のカラムを選択します。
pd.to_datetime(df_receipt['sales_epoch'], unit=s'): to_datetimeメソッドでsales_epochカラムの値をdatetimeオブジェクトに変換し、unitパラメータにsを設定します。
dt.year: datetimeオブジェクトの年コンポーネントを抽出します。
rename('sales_year'): 結果のカラム名をsales_yearに変更する。
axis=1: 列方向に連結することを指定します。
.head(10): 結果のDataFrameの最初の10行を返します。
P-050: レシート明細データ(df_receipt)の売上エポック秒(sales_epoch)を日付型に変換し、「月」だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに10件表示せよ。なお、「月」は0埋め2桁で取り出すこと。
# dt.monthでも月を取得できるが、ここでは0埋め2桁で取り出すためstrftimeを利用している
df_datetime = pd.to_datetime(df_receipt['sales_epoch'],
unit='s').rename('sales_month')
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
df_datetime.dt.strftime('%m')],axis=1).head(10)
receipt_no | receipt_sub_no | sales_month | |
---|---|---|---|
0 | 112 | 1 | 11 |
1 | 1132 | 2 | 11 |
2 | 1102 | 1 | 07 |
3 | 1132 | 1 | 02 |
4 | 1102 | 2 | 08 |
5 | 1112 | 1 | 06 |
6 | 1102 | 2 | 12 |
7 | 1102 | 1 | 09 |
8 | 1112 | 2 | 05 |
9 | 1102 | 1 | 10 |
解説:
与えられたコードは、以下の処理を実行する。
df_receipt' dataframeから 'sales_epoch' カラムを抽出する。
to_datetime' 関数を用いて 'sales_epoch' の値を datetime 形式に変換し、 'unit' パラメータを秒を表す 's' に設定します。
rename' 関数を使用して、結果の系列の名前を 'sales_month' に変更します。
strftime' 関数で '%m' の書式で 'sales_month' カラムから月の値を抽出する。
concat'関数を用いて、結果の系列を 'df_receipt' dataframe の 'receipt_no' および 'receipt_sub_no' 列と連結する。
結果として得られるデータフレームは 'receipt_no', 'receipt_sub_no', 'sales_month' 列を含み、 'sales_month' には 'sales_epoch' 列から抽出された月の値が入る。
P-051: レシート明細データ(df_receipt)の売上エポック秒を日付型に変換し、「日」だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに10件表示せよ。なお、「日」は0埋め2桁で取り出すこと。
# dt.dayでも日を取得できるが、ここでは0埋め2桁で取り出すためstrftimeを利用している
df_datetime = pd.to_datetime(df_receipt['sales_epoch'],
unit='s').rename('sales_day')
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
df_datetime.dt.strftime('%d')], axis=1).head(10)
receipt_no | receipt_sub_no | sales_day | |
---|---|---|---|
0 | 112 | 1 | 03 |
1 | 1132 | 2 | 18 |
2 | 1102 | 1 | 12 |
3 | 1132 | 1 | 05 |
4 | 1102 | 2 | 21 |
5 | 1112 | 1 | 05 |
6 | 1102 | 2 | 05 |
7 | 1102 | 1 | 22 |
8 | 1112 | 2 | 04 |
9 | 1102 | 1 | 10 |
解説:
このコードは、df_receipt DataFrameからsales_epochカラムを取り出し、1970年1月1日(Unix時間としても知られています)からの秒数を格納し、pd.to_datetime()を使用して秒単位の精度でPandas datetimeオブジェクトに変換しています。出来上がったdatetimeオブジェクトは、sales_dayという新しいカラムに代入されています。
次に、strftime()メソッドがsales_dayカラムに適用され、2桁の数字と先行するゼロの形式(%dで示される)で、日付の日成分を抽出します。得られた文字列は、pd.concat()を使ってrecipate_noおよびrecipate_sub_no列と連結され、3つの列を持つ新しいDataFrameが生成される。
最後に、結果のDataFrameに対してhead()メソッドを呼び出して、最初の10行を表示します。このコードでは、df_receiptのsales_epoch列から月日を抽出し、それをレシート番号と結合して新しいDataFrameを生成しています。
P-052: レシート明細データ(df_receipt)の売上金額(amount)を顧客ID(customer_id)ごとに合計の上、売上金額合計に対して2,000円以下を0、2,000円より大きい金額を1に二値化し、顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが”Z”から始まるのものは非会員を表すため、除外して計算すること。
# コード例1
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python')
df_sales_amount = df_sales_amount[['customer_id', 'amount']]. \
groupby('customer_id').sum().reset_index()
df_sales_amount['sales_flg'] = df_sales_amount['amount']. \
apply(lambda x: 1 if x > 2000 else 0)
df_sales_amount.head(10)
customer_id | amount | sales_flg | |
---|---|---|---|
0 | CS001113000004 | 1298 | 0 |
1 | CS001114000005 | 626 | 0 |
2 | CS001115000010 | 3044 | 1 |
3 | CS001205000004 | 1988 | 0 |
4 | CS001205000006 | 3337 | 1 |
5 | CS001211000025 | 456 | 0 |
6 | CS001212000027 | 448 | 0 |
7 | CS001212000031 | 296 | 0 |
8 | CS001212000046 | 228 | 0 |
9 | CS001212000070 | 456 | 0 |
解説:
このコードでは、以下の処理を行います。
df_receiptからqueryメソッドでcustomer_idが文字'Z'で始まらない行を選択し、df_sales_amountに代入する。
df_sales_amountからcustomer_idとamountの列を選択し、groupbyメソッドでcustomer_idごとに金額の合計を算出する。その結果がdf_sales_amountに代入されます。
df_sales_amountに、ラムダ関数を使ったapplyメソッドで、金額が2000より大きい場合は1、それ以外は0を含む新しい列sales_flgを追加する。
出来上がったDataFrame df_sales_amountの最初の10行を表示させます。
つまり、結果のDataFrame df_sales_amountには、customer_id、各顧客の支出合計金額、顧客の支出が多い(2000より大きい)かどうかを示すバイナリフラグsales_flgが含まれている。
# コード例2(np.whereの活用)
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python')
df_sales_amount = df_sales_amount[['customer_id', 'amount']]. \
groupby('customer_id').sum().reset_index()
df_sales_amount['sales_flg'] = np.where(df_sales_amount['amount'] > 2000, 1, 0)
df_sales_amount.head(10)
customer_id | amount | sales_flg | |
---|---|---|---|
0 | CS001113000004 | 1298 | 0 |
1 | CS001114000005 | 626 | 0 |
2 | CS001115000010 | 3044 | 1 |
3 | CS001205000004 | 1988 | 0 |
4 | CS001205000006 | 3337 | 1 |
5 | CS001211000025 | 456 | 0 |
6 | CS001212000027 | 448 | 0 |
7 | CS001212000031 | 296 | 0 |
8 | CS001212000046 | 228 | 0 |
9 | CS001212000070 | 456 | 0 |
解説:
このコードは、顧客の売上情報のサマリーテーブルを生成しています。以下、各行の処理内容です。
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")', engine='python'): この行は、IDが "Z "で始まる顧客を除外するようにレシートデータをフィルタリングしています。ここではqueryメソッドを使用してフィルタを適用し、engine='python'を指定することで警告メッセージを回避しています。フィルタリングされたデータは、df_sales_amountという新しいdataframeに代入されます。
df_sales_amount = df_sales_amount[['customer_id', 'amount']].groupby('customer_id').sum().reset_index(): この行は、df_sales_amountデータフレームをcustomer_idでグループ化し、各グループのamount列の合計を計算し、customer_idを再び列とするためにインデックスをリセットしています。その結果、各顧客の売上金額の合計を示すデータ・フレームが得られます。
df_sales_amount['sales_flg'] = np.where(df_sales_amount['amount'] > 2000, 1, 0): この行は、df_sales_amountデータフレームにsales_flgという新しい列を追加します。この列の値は、顧客の総売上金額(amount)が2000より大きいか否かに基づいて設定されます。ここではnp.where関数を使って条件を適用し、新しい列に1か0のどちらかを割り当てています。
df_sales_amount.head(10): この行は、df_sales_amountのデータフレームの最初の10行を表示します。結果として、customer_id、売上総額、顧客のsales_flgが1(売上額が2000より大きい場合)か0(売上額が2000以下の場合)かを示すテーブルが表示されます。
P-053: 顧客データ(df_customer)の郵便番号(postal_cd)に対し、東京(先頭3桁が100〜209のもの)を1、それ以外のものを0に二値化せよ。さらにレシート明細データ(df_receipt)と結合し、全期間において売上実績のある顧客数を、作成した二値ごとにカウントせよ。
# コード例1
df_tmp = df_customer[['customer_id', 'postal_cd']].copy()
df_tmp['postal_flg'] = df_tmp['postal_cd']. \
apply(lambda x: 1 if 100 <= int(x[0:3]) <= 209 else 0)
pd.merge(df_tmp, df_receipt, how='inner', on='customer_id'). \
groupby('postal_flg').agg({'customer_id':'nunique'})
customer_id | |
---|---|
postal_flg | |
0 | 3906 |
1 | 4400 |
解説:
このコードは、以下のタスクを実行します。
df_customer "DataFrameから "customer_id "と "postal_cd "列のコピーを作成し、"df_tmp "という新しいDataFrameに代入します。
postal_cd "カラムの最初の3桁をもとに、"df_tmp "に新しいカラム "postal_flg "を作成する。最初の3桁が100から209の間であれば "postal_flg "は1に設定され、そうでなければ0に設定される。
customer_id」カラムの「df_tmp」と「df_receipt」を内部結合で結合します。
結果のDataFrameを "postal_flg "カラムでグループ化し、"nunique() "関数を用いて各グループ内のユニークな顧客IDの数をカウントします。
postal_flg」列でグループ化されたユニークな顧客IDのカウントを示す結果のDataFrameを返します。
要約すると、このコードは、購入履歴に基づいて、特定の郵便番号地域に住む顧客の数を分析するために使用されます。
# コード例2(np.where、betweenの活用)
df_tmp = df_customer[['customer_id', 'postal_cd']].copy()
df_tmp['postal_flg'] = np.where(df_tmp['postal_cd'].str[0:3].astype(int)
.between(100, 209), 1, 0)
pd.merge(df_tmp, df_receipt, how='inner', on='customer_id'). \
groupby('postal_flg').agg({'customer_id':'nunique'})
customer_id | |
---|---|
postal_flg | |
0 | 3906 |
1 | 4400 |
解説:
このコードは、顧客の郵便番号が特定の範囲にあるかどうかを示す postal_flg 列を持つ新しいデータフレーム df_tmp を作成するために使用されます。以下、コードのステップ・バイ・ステップの説明である。
df_tmp = df_customer[['customer_id', 'postal_cd']].copy() df_customer から customer_id と postal_cd 列だけを含む新しいデータフレーム df_tmp を作成します。
df_tmp['postal_flg'] = np.where(df_tmp['postal_cd'].str[0:3].astype(int).between(100, 209), 1, 0) df_tmp に np.where 関数を使用して新しい列 postal_flg を作成します。str[0:3]コードで郵便番号の最初の3文字を文字列として取り出し、astype(int)でそれを整数に変換しています。between関数は、その整数が100から209の範囲に入るかどうかをチェックします。もしそうであれば、1がpostal_flgに代入され、そうでなければ0が代入されます。
pd.merge(df_tmp, df_receipt, how='inner', on='customer_id') df_tmp と df_receipt を customer_id 列でマージし、顧客が両方のデータフレームに存在する行だけを残します。
groupby('postal_flg').agg({'customer_id':'nunique'}) マージしたデータフレームを postal_flg でグループ化し、nunique 関数を使用して各グループ内のユニークな customer_id 値の数を数えます。これは、各 postal_flg に対して1行のデータフレームを返し、各グループの顧客数を表示します。
P-054: 顧客データ(df_customer)の住所(address)は、埼玉県、千葉県、東京都、神奈川県のいずれかとなっている。都道府県毎にコード値を作成し、顧客ID、住所とともに10件表示せよ。値は埼玉県を11、千葉県を12、東京都を13、神奈川県を14とすること。
# コード例1(固定で切り出す)
df_customer_tmp = df_customer[['customer_id', 'address']].copy()
df_customer_tmp['prefecture_cd'] = \
df_customer['address'].str[0:3].map({'埼玉県': '11',
'千葉県':'12',
'東京都':'13',
'神奈川':'14'})
df_customer_tmp.head(10)
customer_id | address | prefecture_cd | |
---|---|---|---|
0 | CS021313000114 | 神奈川県伊勢原市粟窪********** | 14 |
1 | CS037613000071 | 東京都江東区南砂********** | 13 |
2 | CS031415000172 | 東京都渋谷区代々木********** | 13 |
3 | CS028811000001 | 神奈川県横浜市泉区和泉町********** | 14 |
4 | CS001215000145 | 東京都大田区仲六郷********** | 13 |
5 | CS020401000016 | 東京都板橋区若木********** | 13 |
6 | CS015414000103 | 東京都江東区北砂********** | 13 |
7 | CS029403000008 | 千葉県浦安市海楽********** | 12 |
8 | CS015804000004 | 東京都江東区北砂********** | 13 |
9 | CS033513000180 | 神奈川県横浜市旭区善部町********** | 14 |
解説:
このコードは、df_customer DataFrameのaddress列から都道府県コードを抽出し、'customer_id', 'address', 'prefecture_cd' 列を持つ新しいDataFrame df_customer_tmpを作成しています。
このコードでは、まずcopy()メソッドを使ってdf_customer DataFrameの 'customer_id' と 'address' 列のコピーを作成し、それをdf_customer_tmp変数に代入しています。
df_customer_tmp = df_customer[['customer_id', 'address']].copy()
次に、strアクセサを使用して「address」列の最初の3文字を抽出し、map()メソッドを使用して新しい列「prefecture_cd」に代入するコードです。
df_customer_tmp['prefecture_cd'] = df_customer['address'].str[0:3].map({'埼玉県': '11', '千葉県': '12', '東京都': '13', '14'})
map() メソッドは、各都道府県名を対応する都道府県コードにマッピングします。結果として得られる DataFrame df_customer_tmp は、カラム 'customer_id', 'address', および 'prefecture_cd' を持ちます。
# コード例2(正規表現を使う)
df_customer_tmp = df_customer[['customer_id', 'address']].copy()
df_customer_tmp['prefecture_cd'] = \
df_customer['address'].str.extract(r'(^.*?[都道府県])')[0].\
map({'埼玉県': '11',
'千葉県':'12',
'東京都':'13',
'神奈川県':'14'})
df_customer_tmp.head(10)
customer_id | address | prefecture_cd | |
---|---|---|---|
0 | CS021313000114 | 神奈川県伊勢原市粟窪********** | 14 |
1 | CS037613000071 | 東京都江東区南砂********** | 13 |
2 | CS031415000172 | 東京都渋谷区代々木********** | 13 |
3 | CS028811000001 | 神奈川県横浜市泉区和泉町********** | 14 |
4 | CS001215000145 | 東京都大田区仲六郷********** | 13 |
5 | CS020401000016 | 東京都板橋区若木********** | 13 |
6 | CS015414000103 | 東京都江東区北砂********** | 13 |
7 | CS029403000008 | 千葉県浦安市海楽********** | 12 |
8 | CS015804000004 | 東京都江東区北砂********** | 13 |
9 | CS033513000180 | 神奈川県横浜市旭区善部町********** | 14 |
解説:
このコードは、既存のDataFrame df_customerからcustomer_idとaddressの列を選択して、新しいDataFrame df_customer_tmpを作成します。
次に、正規表現を使って、address列から都道府県コードを抽出しています。具体的には、str.extract()メソッドを使用して、パターン(^.*?[都道府県])に一致する部分文字列を抽出する。これは、「文字列の先頭から、'都' または '道' または '府' または '県' が最初に現れるまでの任意の文字と一致する」ことを意味している。
次に、抽出された都道府県名を対応する都道府県コードにマッピングし、df_customer_tmp DataFrameにprefecture_cdという新しい列を作成する。マッピングは、都道府県名をキー、対応する都道府県コードを値とする辞書を用いた map() メソッドにより行われる。
その結果、df_customer_tmpは、customer_id、address、prefecture_cdの3つのカラムを持ち、prefecture_cdは都道府県コードを表す2桁の文字列である。DataFrameの最初の10行を表示するために、head(10)メソッドが使用されています。
P-055: レシート明細(df_receipt)データの売上金額(amount)を顧客ID(customer_id)ごとに合計し、その合計金額の四分位点を求めよ。その上で、顧客ごとの売上金額合計に対して以下の基準でカテゴリ値を作成し、顧客ID、売上金額合計とともに10件表示せよ。カテゴリ値は順に1〜4とする。
- 最小値以上第1四分位未満 ・・・ 1を付与
- 第1四分位以上第2四分位未満 ・・・ 2を付与
- 第2四分位以上第3四分位未満 ・・・ 3を付与
- 第3四分位以上 ・・・ 4を付与
# コード例1
df_sales_amount = df_receipt[['customer_id', 'amount']]. \
groupby('customer_id').sum().reset_index()
pct25 = np.quantile(df_sales_amount['amount'], 0.25)
pct50 = np.quantile(df_sales_amount['amount'], 0.5)
pct75 = np.quantile(df_sales_amount['amount'], 0.75)
def pct_group(x):
if x < pct25:
return 1
elif pct25 <= x < pct50:
return 2
elif pct50 <= x < pct75:
return 3
elif pct75 <= x:
return 4
df_sales_amount['pct_group'] = df_sales_amount['amount'].apply(pct_group)
df_sales_amount.head(10)
customer_id | amount | pct_group | |
---|---|---|---|
0 | CS001113000004 | 1298 | 2 |
1 | CS001114000005 | 626 | 2 |
2 | CS001115000010 | 3044 | 3 |
3 | CS001205000004 | 1988 | 3 |
4 | CS001205000006 | 3337 | 3 |
5 | CS001211000025 | 456 | 1 |
6 | CS001212000027 | 448 | 1 |
7 | CS001212000031 | 296 | 1 |
8 | CS001212000046 | 228 | 1 |
9 | CS001212000070 | 456 | 1 |
解説:
このコードは、各顧客の売上金額の四分位数を計算し、売上金額の四分位数に基づいて各顧客を4つのグループのいずれかに割り当てる。
以下は、コードのステップバイステップの内訳です。
df_sales_amount = df_receipt[['customer_id', 'amount']].groupby('customer_id').sum().reset_index() df_receipt データフレームを customer_id でグループ化して金額列を合計して、各顧客の合計売上金額を計算する。結果のdataframeは、customer_idとamountの2つのカラムを持つ。
pct25 = np.quantile(df_sales_amount['amount'], 0.25), pct50 = np.quantile(df_sales_amount['amount'], 0.5), pct75 = np.quantile(df_sales_amount['amount'], 0.75) 売上金額分布の25、50、75パーセンテージを計算する。
def pct_group(x): ... 売上金額xを受け取り、xが属する四分位数を表す1から4までの整数を返す関数pct_groupを定義しています。
df_sales_amount['pct_group'] = df_sales_amount['amount'].apply(pct_group) df_sales_amount の amount 列の各値に pct_group 関数を適用し、結果の四分位数を pct_group という新しい列へ代入しています。
df_sales_amount.head(10) は、customer_id、amount、pct_groupの3つのカラムを持つ、結果のデータフレームの最初の10行を表示します。
# 確認用コード
print('pct25:', pct25)
print('pct50:', pct50)
print('pct75:', pct75)
pct25: 548.5 pct50: 1478.0 pct75: 3651.0
# コード例2(cutを使った例、四分位範囲も参考までに追加表示)
df_temp = df_receipt[['customer_id', 'amount']]. \
groupby('customer_id').sum().reset_index()
pct25 = np.quantile(df_sales_amount['amount'], 0.25)
pct50 = np.quantile(df_sales_amount['amount'], 0.5)
pct75 = np.quantile(df_sales_amount['amount'], 0.75)
pct_max = df_sales_amount['amount'].max()
df_temp['quantile'] = pd.cut(df_sales_amount['amount'],[0.0, pct25, pct50, pct75,pct_max+0.1], right=False)
df_temp['pct_group'] = df_temp.groupby('quantile').ngroup() + 1
df_temp.head(10)
customer_id | amount | quantile | pct_group | |
---|---|---|---|---|
0 | CS001113000004 | 1298 | [548.5, 1478.0) | 2 |
1 | CS001114000005 | 626 | [548.5, 1478.0) | 2 |
2 | CS001115000010 | 3044 | [1478.0, 3651.0) | 3 |
3 | CS001205000004 | 1988 | [1478.0, 3651.0) | 3 |
4 | CS001205000006 | 3337 | [1478.0, 3651.0) | 3 |
5 | CS001211000025 | 456 | [0.0, 548.5) | 1 |
6 | CS001212000027 | 448 | [0.0, 548.5) | 1 |
7 | CS001212000031 | 296 | [0.0, 548.5) | 1 |
8 | CS001212000046 | 228 | [0.0, 548.5) | 1 |
9 | CS001212000070 | 456 | [0.0, 548.5) | 1 |
解説:
このコードでは、DataFrame 'df_temp'に新しい列 'pct_group' を作成し、各顧客の売上金額の合計と、その売上金額の合計に基づいて所属するパーセンタイル・グループを格納しています。
最初の行は、'df_receipt' DataFrameをcustomer_idでグループ化して、各顧客の売上金額の合計を計算し、customer_idを再び列とするためにインデックスをリセットしています。
次の4行は、先に作成した「df_temp」DataFrameと同じ「df_sales_amount」DataFrameから売上金額の25%、50%、75%、最大値を計算しています。
次の行では、定義されたパーセンタイル値で売上高を4つのビンに切り分け、'df_temp'に新しい列'quantile'を作成します。
最後の行では、'df_temp'に新しい列'pct_group'を作成し、'quantile'によってデータをグループ化し、各顧客がどのパーセンタイルグループに属するかに基づいてグループ番号(1-4)を割り当てています。
なお、最後のビンの右端は、売上金額の最大値が最後のパーセンタイルグループに含まれるように、「pct_max+0.1」と定義されています。
# 参考コード(qcutを使った例、境界値の含む/含まないが逆になっており題意を満たさないが参考までに記載)
df_temp = df_receipt.groupby('customer_id')[['amount']].sum()
df_temp['quantile'], bins = \
pd.qcut(df_receipt.groupby('customer_id')['amount'].sum(), 4, retbins=True)
df_temp['pct_group'] = df_temp.groupby('quantile').ngroup() + 1
df_temp.reset_index(inplace=True)
display(df_temp.head(10))
print('quantiles:', bins)
customer_id | amount | quantile | pct_group | |
---|---|---|---|---|
0 | CS001113000004 | 1298 | (548.5, 1478.0] | 2 |
1 | CS001114000005 | 626 | (548.5, 1478.0] | 2 |
2 | CS001115000010 | 3044 | (1478.0, 3651.0] | 3 |
3 | CS001205000004 | 1988 | (1478.0, 3651.0] | 3 |
4 | CS001205000006 | 3337 | (1478.0, 3651.0] | 3 |
5 | CS001211000025 | 456 | (69.999, 548.5] | 1 |
6 | CS001212000027 | 448 | (69.999, 548.5] | 1 |
7 | CS001212000031 | 296 | (69.999, 548.5] | 1 |
8 | CS001212000046 | 228 | (69.999, 548.5] | 1 |
9 | CS001212000070 | 456 | (69.999, 548.5] | 1 |
quantiles: [7.0000000e+01 5.4850000e+02 1.4780000e+03 3.6510000e+03 1.2395003e+07]
解説:
このコードでは、以下の処理を行います。
df_receiptデータフレームをcustomer_idでグループ化し、groupedオブジェクトのsum()メソッドを使用して、各グループの金額の合計を計算する。その結果はdf_tempという新しいデータフレームに格納されます。
qcut()関数を使用して、df_tempの金額の値を基に4つの等しいサイズのビンに分割します。retbins=Trueオプションは、ビンの端をbins変数に返します。
df_tempにquantileという新しい列が作成され、各金額値がどのビンに入るかを示しています。
ngroup()メソッドを使用して、df_temp['quantile']の各ビンにグループ番号を割り当てます。ngroup()は0インデックスの値を返すので、+1が結果に加算されます。
reset_index()メソッドがdf_tempで呼ばれ、インデックスが新しい範囲インデックスにリセットされます。
最後に、df_tempの最初の10行とビンの端が、それぞれdisplay()とprint()関数で表示されます。
P-056: 顧客データ(df_customer)の年齢(age)をもとに10歳刻みで年代を算出し、顧客ID(customer_id)、生年月日(birth_day)とともに10件表示せよ。ただし、60歳以上は全て60歳代とすること。年代を表すカテゴリ名は任意とする。
# コード例1
df_customer_era = df_customer[['customer_id', 'birth_day']].copy()
df_customer_era['era'] = df_customer['age']. \
apply(lambda x: min(math.floor(x / 10) * 10, 60))
df_customer_era.head(10)
customer_id | birth_day | era | |
---|---|---|---|
0 | CS021313000114 | 1981-04-29 | 30 |
1 | CS037613000071 | 1952-04-01 | 60 |
2 | CS031415000172 | 1976-10-04 | 40 |
3 | CS028811000001 | 1933-03-27 | 60 |
4 | CS001215000145 | 1995-03-29 | 20 |
5 | CS020401000016 | 1974-09-15 | 40 |
6 | CS015414000103 | 1977-08-09 | 40 |
7 | CS029403000008 | 1973-08-17 | 40 |
8 | CS015804000004 | 1931-05-02 | 60 |
9 | CS033513000180 | 1962-07-11 | 50 |
解説:
このコードでは、customer_id、birth_day、eraのカラムを含む新しいDataFrame df_customer_eraを作成します。
customer_idとbirth_dayカラムは、元のdf_customer DataFrameから取得されます。
era列は、df_customer DataFrameのage列から計算されます。ageの各値に対して、コードは、値を切り捨てるべき10年(10の倍数)を計算する。ageの値が60以上の場合、年代は60に設定されます。
applyメソッドでは、age列の各要素に関数(ここではlambda関数)を適用し、min関数とmath.floor関数で10年を計算する。結果の値はdf_customer_eraのeraカラムに代入されます。
最後に、headメソッドが呼び出され、結果のDataFrameの最初の10行が表示されます。
# コード例2(cutの例、カテゴリは範囲で出力)
df_customer_era = df_customer[['customer_id', 'birth_day']].copy()
df_customer_era['era'] = pd.cut(df_customer['age'],
bins=[0, 10, 20, 30, 40, 50, 60, np.inf],
right=False)
df_customer_era[['customer_id', 'birth_day', 'era']].head(10)
customer_id | birth_day | era | |
---|---|---|---|
0 | CS021313000114 | 1981-04-29 | [30.0, 40.0) |
1 | CS037613000071 | 1952-04-01 | [60.0, inf) |
2 | CS031415000172 | 1976-10-04 | [40.0, 50.0) |
3 | CS028811000001 | 1933-03-27 | [60.0, inf) |
4 | CS001215000145 | 1995-03-29 | [20.0, 30.0) |
5 | CS020401000016 | 1974-09-15 | [40.0, 50.0) |
6 | CS015414000103 | 1977-08-09 | [40.0, 50.0) |
7 | CS029403000008 | 1973-08-17 | [40.0, 50.0) |
8 | CS015804000004 | 1931-05-02 | [60.0, inf) |
9 | CS033513000180 | 1962-07-11 | [50.0, 60.0) |
解説 :
このコードでは、customer_id、birth_day、eraのカラムを持つ新しいDataFrame df_customer_eraを作成します。customer_idとbirth_dayカラムは、元のdf_customer DataFrameからコピーされたものです。
era列は、pd.cut()を使用して、顧客の年齢を異なる年齢範囲にビニングすることによって作成されます。binsパラメータは、各ビンの端を指定します。np.infは、60歳以上の顧客に対するオープンエンドのビンを指定するために使用されます。rightパラメータは、区間が左から入ることを指定するためにFalseに設定されます。つまり、年齢がちょうど30の顧客は、[30, 40]ビンに配置されることになります。
DataFrameの各行について、顧客の年齢が、指定されたビンに基づいて、対応する時代にビン詰めされます。結果として得られるDataFrameには、customer_id、birth_day、eraの各カラムが含まれ、eraのカラムには、各顧客の年齢範囲のビンが含まれます。
P-057: 056の抽出結果と性別コード(gender_cd)により、新たに性別×年代の組み合わせを表すカテゴリデータを作成し、10件表示せよ。組み合わせを表すカテゴリの値は任意とする。
# 性別コード1桁と年代コード2桁を連結した性年代コードを生成する
df_customer_era = df_customer[['customer_id', 'birth_day']].copy()
df_customer_era['era'] = df_customer['age']. \
apply(lambda x: min(math.floor(x / 10) * 10, 60))
df_customer_era['gender_era'] = \
df_customer['gender_cd'] + df_customer_era['era'].astype('str').str.zfill(2)
df_customer_era.head(10)
customer_id | birth_day | era | gender_era | |
---|---|---|---|---|
0 | CS021313000114 | 1981-04-29 | 30 | 130 |
1 | CS037613000071 | 1952-04-01 | 60 | 960 |
2 | CS031415000172 | 1976-10-04 | 40 | 140 |
3 | CS028811000001 | 1933-03-27 | 60 | 160 |
4 | CS001215000145 | 1995-03-29 | 20 | 120 |
5 | CS020401000016 | 1974-09-15 | 40 | 040 |
6 | CS015414000103 | 1977-08-09 | 40 | 140 |
7 | CS029403000008 | 1973-08-17 | 40 | 040 |
8 | CS015804000004 | 1931-05-02 | 60 | 060 |
9 | CS033513000180 | 1962-07-11 | 50 | 150 |
解説:
このコードは、DataFrame "df_customer_era "に新しい列 "era "を作成し、顧客の年齢の10年を示しています。decadeの値は、年齢を10歳未満に切り捨てた後、10を掛けることで得られます。結果が60より大きい場合は、60に切り捨てられます。
次に、コードは、性別コードと「時代」列の値を連結して、新しい列「gender_era」を作成します。gender_cd」列には性別コード(例えば、男性は「0」、女性は「1」)が含まれ、「era」列の値は文字列としてキャストされ、幅2に合わせてゼロパディングされる。
最後に、このコードは、結果のDataFrameの最初の10行を表示します。
P-058: 顧客データ(df_customer)の性別コード(gender_cd)をダミー変数化し、顧客ID(customer_id)とともに10件表示せよ。
# コード例1(すべてのコード値を項目化)
pd.get_dummies(df_customer[['customer_id', 'gender_cd']],
columns=['gender_cd']).head(10)
customer_id | gender_cd_0 | gender_cd_1 | gender_cd_9 | |
---|---|---|---|---|
0 | CS021313000114 | 0 | 1 | 0 |
1 | CS037613000071 | 0 | 0 | 1 |
2 | CS031415000172 | 0 | 1 | 0 |
3 | CS028811000001 | 0 | 1 | 0 |
4 | CS001215000145 | 0 | 1 | 0 |
5 | CS020401000016 | 1 | 0 | 0 |
6 | CS015414000103 | 0 | 1 | 0 |
7 | CS029403000008 | 1 | 0 | 0 |
8 | CS015804000004 | 1 | 0 | 0 |
9 | CS033513000180 | 0 | 1 | 0 |
解説:
このコードは、DataFrame 'df_customer' のカテゴリ変数 'gender_cd' のダミー変数を作成します。結果のDataFrameは、'gender_cd'の各カテゴリー(おそらく男性と女性)の列を持ち、各行は、元の顧客レコードが'gender_cd'にその値を持つ場合、適切な列に1を持ち、他のすべての列には0を持つことになる。
この目的のために、pandasの'get_dummies'関数が使用されています。この関数は、DataFrameとダミーエンコーディングを適用するカラムのリストを受け取ります。この場合、'df_customer' DataFrameに対して呼び出され、'columns' パラメータに 'gender_cd' 列が指定されています。
結果として得られるDataFrameは、'gender_cd'の各値に対して1つの列を持ち(おそらく、元のデータで使用されたエンコーディングに応じて、男性は0、女性は1、またはその逆)、各行はその行の'gender_cd'の値に対応する列に1を持ち、他の全ての列には0を持つことになる。結果のDataFrameの最初の10行が返されます。
# コード例2(項目を一つ削ったり区切り文字を変えたりできる)
pd.get_dummies(df_customer[['customer_id', 'gender_cd']],
columns=['gender_cd'],
drop_first=True, prefix='gen', prefix_sep='#').head(10)
customer_id | gen#1 | gen#9 | |
---|---|---|---|
0 | CS021313000114 | 1 | 0 |
1 | CS037613000071 | 0 | 1 |
2 | CS031415000172 | 1 | 0 |
3 | CS028811000001 | 1 | 0 |
4 | CS001215000145 | 1 | 0 |
5 | CS020401000016 | 0 | 0 |
6 | CS015414000103 | 1 | 0 |
7 | CS029403000008 | 0 | 0 |
8 | CS015804000004 | 0 | 0 |
9 | CS033513000180 | 1 | 0 |
解説:
このコードは、df_customer DataFrame のカテゴリ変数 gender_cd に対してダミー変数を作成しています。pandasライブラリのpd.get_dummies()関数を使用して、gender_cdのユニークなカテゴリごとにバイナリ指標変数を作成します。
drop_first=True引数は、参照カテゴリである最初のカテゴリ('0')を落とすために使用されます。これは、ダミー変数間の多重共線性を防ぐために行われる。
prefix='gen' と prefix_sep='#' 引数は、ダミー変数の列名に接頭辞を付けるために使用されます。この場合、プレフィックスは'gen'、セパレータは'#'です。
結果として得られるDataFrameは、gender_cd列のユニークな値ごとに1つの列を持ち、その値が1であれば顧客がそのカテゴリに属していることを示し、0であれば属していないことを示します。カラム名はgen#<gender_cd>の形式で、<gender_cd>は元のgender_cdカラムのユニークな値であることを示しています。
P-059: レシート明細データ(df_receipt)の売上金額(amount)を顧客ID(customer_id)ごとに合計し、売上金額合計を平均0、標準偏差1に標準化して顧客ID、売上金額合計とともに10件表示せよ。標準化に使用する標準偏差は、分散の平方根、もしくは不偏分散の平方根のどちらでも良いものとする。ただし、顧客IDが”Z”から始まるのものは非会員を表すため、除外して計算すること。
TIPS: query()の引数engineで'python'か'numexpr'かを選択でき、デフォルトはインストールされていればnumexprが、無ければpythonが使われます。さらに、文字列メソッドはengine='python'でないとquery()内で使えません。
# skleanのpreprocessing.scaleを利用するため、データの標準偏差で計算されている
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python'). \
groupby('customer_id'). \
agg({'amount':'sum'}).reset_index()
df_sales_amount['std_amount'] = preprocessing.scale(df_sales_amount['amount'])
df_sales_amount.head(10)
customer_id | amount | std_amount | |
---|---|---|---|
0 | CS001113000004 | 1298 | -0.459378 |
1 | CS001114000005 | 626 | -0.706390 |
2 | CS001115000010 | 3044 | 0.182413 |
3 | CS001205000004 | 1988 | -0.205749 |
4 | CS001205000006 | 3337 | 0.290114 |
5 | CS001211000025 | 456 | -0.768879 |
6 | CS001212000027 | 448 | -0.771819 |
7 | CS001212000031 | 296 | -0.827691 |
8 | CS001212000046 | 228 | -0.852686 |
9 | CS001212000070 | 456 | -0.768879 |
解説:
このコードでは、以下のことを行っています。
df_receiptでcustomer_idが文字 "Z "で始まる行をフィルタリングします。
残りの行をcustomer_idでグループ化し、aggメソッドを使用して各グループの金額列の合計を計算する。
結果のDataFrameのインデックスをリセットして、customer_idを通常のカラムにする。
scikit-learnのpreprocessing.scale関数をamount列に適用し、各顧客の総売上高のzスコアを計算します。
std_amountという新しい列をDataFrameに追加し、ステップ4で計算したzスコアを格納します。
その結果、DataFrame df_sales_amountには、各顧客について1行が含まれ、顧客ID、総売上金額、および売上のzスコアであるstd_amountのカラムが含まれます。
# コード例2(fitを行うことで、別のデータでも同じ平均・標準偏差で標準化を行える)
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python'). \
groupby('customer_id'). \
agg({'amount':'sum'}).reset_index()
scaler = preprocessing.StandardScaler()
scaler.fit(df_sales_amount[['amount']])
df_sales_amount['std_amount'] = scaler.transform(df_sales_amount[['amount']])
df_sales_amount.head(10)
customer_id | amount | std_amount | |
---|---|---|---|
0 | CS001113000004 | 1298 | -0.459378 |
1 | CS001114000005 | 626 | -0.706390 |
2 | CS001115000010 | 3044 | 0.182413 |
3 | CS001205000004 | 1988 | -0.205749 |
4 | CS001205000006 | 3337 | 0.290114 |
5 | CS001211000025 | 456 | -0.768879 |
6 | CS001212000027 | 448 | -0.771819 |
7 | CS001212000031 | 296 | -0.827691 |
8 | CS001212000046 | 228 | -0.852686 |
9 | CS001212000070 | 456 | -0.768879 |
解説:
このコードは、'df_sales_amount' DataFrame の 'amount' 列に対して標準化(Z スコア正規化とも呼ばれる)を実行する。
このコードでは、まずpandas DataFrameの'query'メソッドを使用して、IDが'Z'で始まる顧客によるトランザクションをフィルタリングしています。次に、残りの取引を「customer_id」でグループ化し、「agg」メソッドを使用して各顧客が使用した合計金額を計算します。最後に、結果のDataFrameのインデックスをリセットしています。
次に、このコードは scikit-learn ライブラリの 'preprocessing' モジュールから 'StandardScaler' クラスのインスタンスを作成する。StandardScaler'クラスは、平均が0、標準偏差が1になるようにデータをスケーリングする標準化に使用されます。そして、'scaler' インスタンスの 'fit' メソッドを用いて、'df_sales_amount' DataFrame の 'amount' 列の平均と標準偏差を計算する。
最後に、'scaler' インスタンスの 'transform' メソッドを使用して、'df_sales_amount' DataFrame の 'amount' カラムに対して実際の標準化を行い、結果として得られた標準化値を 'std_amount' という新しいカラムに格納します。
結果として得られる DataFrame 'df_sales_amount' には、 'customer_id' と、各顧客が使用した合計 'amount' と、標準化された 'std_amount' 列が含まれます。
P-060: レシート明細データ(df_receipt)の売上金額(amount)を顧客ID(customer_id)ごとに合計し、売上金額合計を最小値0、最大値1に正規化して顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが”Z”から始まるのものは非会員を表すため、除外して計算すること。
# コード例1
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python'). \
groupby('customer_id'). \
agg({'amount':'sum'}).reset_index()
df_sales_amount['scale_amount'] = \
preprocessing.minmax_scale(df_sales_amount['amount'])
df_sales_amount.head(10)
customer_id | amount | scale_amount | |
---|---|---|---|
0 | CS001113000004 | 1298 | 0.053354 |
1 | CS001114000005 | 626 | 0.024157 |
2 | CS001115000010 | 3044 | 0.129214 |
3 | CS001205000004 | 1988 | 0.083333 |
4 | CS001205000006 | 3337 | 0.141945 |
5 | CS001211000025 | 456 | 0.016771 |
6 | CS001212000027 | 448 | 0.016423 |
7 | CS001212000031 | 296 | 0.009819 |
8 | CS001212000046 | 228 | 0.006865 |
9 | CS001212000070 | 456 | 0.016771 |
解説:
このコードは、pandasのDataFrame df_receiptに対してデータの前処理を行うものです。以下、コードの各行が何を行っているかを説明します。
query()メソッドとregex engine pythonを使用して、顧客IDが "Z "で始まる行をフィルタリングします。得られたDataFrameをdf_sales_amountに代入する。
groupby()メソッドでDataFrame df_sales_amountを顧客IDごとにグループ化し、agg()メソッドで各グループの金額列の合計を計算する。出来上がったDataFrameをdf_sales_amountに代入しています。
reset_index()メソッドでdf_sales_amountのインデックスをリセットし、customer_idカラムを通常のカラムに変更します。
scikit-learn の前処理モジュールの minmax_scale() 関数を df_sales_amount の amount 列に適用し、0 から 1 の間で値をスケーリングします。
したがって、このコードでは、各顧客の売上金額の合計を計算し、特定の顧客(IDが「Z」で始まる)の行をフィルタリングし、各顧客の売上金額を最小-最大スケーリングを使って0と1の間でスケーリングします。
# コード例2(fitを行うことで、別のデータでも同じ最小値・最大値で標準化を行える)
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")',
engine='python'). \
groupby('customer_id'). \
agg({'amount':'sum'}).reset_index()
scaler = preprocessing.MinMaxScaler()
scaler.fit(df_sales_amount[['amount']])
df_sales_amount['scale_amount'] = scaler.transform(df_sales_amount[['amount']])
df_sales_amount.head(10)
customer_id | amount | scale_amount | |
---|---|---|---|
0 | CS001113000004 | 1298 | 0.053354 |
1 | CS001114000005 | 626 | 0.024157 |
2 | CS001115000010 | 3044 | 0.129214 |
3 | CS001205000004 | 1988 | 0.083333 |
4 | CS001205000006 | 3337 | 0.141945 |
5 | CS001211000025 | 456 | 0.016771 |
6 | CS001212000027 | 448 | 0.016423 |
7 | CS001212000031 | 296 | 0.009819 |
8 | CS001212000046 | 228 | 0.006865 |
9 | CS001212000070 | 456 | 0.016771 |
解説:
このコードは、小売店の各顧客の売上金額データに対してフィーチャースケーリングを行うものである。以下は、各行の処理内容です。
DF_SALES_AMOUNT: df_receiptデータフレームをcustomer_idでグループ化し、各グループの金額を合計することによって得られる、customer_idと各顧客が使った合計金額を含む新しいDataFrameを作成する。
scaler。scikit-learnのpreprocessingモジュールからMinMaxScalerオブジェクトを初期化し、データを[0,1]の範囲でスケールする。
scaler.fit(): スケーラーオブジェクトをdf_sales_amountのamount列にフィットさせ、データの最小値と最大値を計算します。
df_sales_amount['scale_amount']: df_sales_amount DataFrameに新しいカラムを作成し、スケールされた金額データを格納します。
scaler.transform(): scalerオブジェクトが学習した最小値と最大値を使用して、各顧客の金額データをスケーリングします。
df_sales_amount.head(10): 更新された df_sales_amount DataFrame の最初の 10 行を表示し、新しい scale_amount 列のスケールされた金額データを含む。
全体として、このコードは、各顧客の金額データの値を同じ範囲にするために特徴的なスケーリングを実行し、それらを比較可能にし、分析に役立つようにします。
Comment