データサイエンス100本ノック(構造化データ加工編)- Python Part 5 (Q81 to Q100)の解説です。
参照(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-081: 単価(unit_price)と原価(unit_cost)の欠損値について、それぞれの平均値で補完した新たな商品データを作成せよ。なお、平均値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。
# コード例1(Pandasのfillna)
df_product_2 = df_product.fillna({
'unit_price':np.round(np.nanmean(df_product['unit_price'])),
'unit_cost':np.round(np.nanmean(df_product['unit_cost']))})
df_product_2.isnull().sum()
product_cd 0 category_major_cd 0 category_medium_cd 0 category_small_cd 0 unit_price 0 unit_cost 0 dtype: int64
解説:
このコードは、df_product という名前の pandas DataFrame に対して 2 つの処理を実行します。
fillna()メソッドを使って、unit_priceとunit_cost列の欠損値を各列の丸めた平均値で埋めます。丸めた平均値は、NumPy ライブラリの np.round() と np.nanmean() 関数を使用して計算されています。fillna() メソッドは、引数として辞書を取り、キーは列名、値は欠損値を埋めるための値である。
そして、isnull()メソッドでDataFrameに欠損値が残っているかどうかをチェックし、sum()メソッドで各列の欠損値の数を合計します。その結果、更新されたDataFrame df_product_2の各列の欠損値の数が出力されます。
更新されたDataFrame df_product_2は、unit_priceとunit_cost列の欠損値がそれぞれの丸められた平均値に置き換えられ、2行目の出力は、fillna操作後のDataFrameにどれだけの欠損値が残っているかを示しています。
# コード例2(scikit-learnのSimpleImputer)
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
imp_values = imp_mean.fit_transform(df_product[['unit_price', 'unit_cost']])
df_product_2 = df_product.copy()
df_product_2[['unit_price', 'unit_cost']] = imp_values.round()
df_product_2.isnull().sum()
product_cd 0 category_major_cd 0 category_medium_cd 0 category_small_cd 0 unit_price 0 unit_cost 0 dtype: int64
解説:
このコードでは、以下の処理を行います。
sklearn.imputeモジュールからSimpleImputerクラスをインポートしています。SimpleImputerクラスは、データセットの欠損値をインプットするために使用されます。
imp_meanというSimpleImputerのインスタンスを作成します。このインピュータは、欠損値(この場合、np.nanとして表現される)を対応する列の平均値で置き換えることになります。インピュテーションのストラテジーは 'mean' に設定されています。
fit_transform()メソッドを使用して、df_product DataFrameのunit_price列とunit_cost列にSimpleImputerオブジェクトを適用し、欠損値がインプットされた変換後のDataFrameを返します。
copy() メソッドを使用して、df_product_2 という元の df_product DataFrame のコピーを作成する。
df_product_2のunit_priceとunit_costの列を、imp_values.round()を代入してインプットした値で置き換えます。round()メソッドは、インプットされた値を最も近い整数に丸めるために使用されます。
isnull()メソッドでDataFrameに欠損値が残っていないかチェックし、sum()メソッドで各列の欠損値の数を集計します。結果として、更新されたDataFrame df_product_2の各列の欠損値の数が出力されます。
このコードの最終出力は、各列の平均値で欠損値を埋め込んだ後の、更新されたdf_product_2 DataFrameの欠損値の数を示しています。この方法は、特に欠損値の数が少ない場合、データセットの欠損データを処理するのに便利です。
P-082: 単価(unit_price)と原価(unit_cost)の欠損値について、それぞれの中央値で補完した新たな商品データを作成せよ。なお、中央値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。
# コード例1(Pandasのfillna)
df_product_3 = df_product.fillna({
'unit_price':np.round(np.nanmedian(df_product['unit_price'])),
'unit_cost':np.round(np.nanmedian(df_product['unit_cost']))})
df_product_3.isnull().sum()
product_cd 0 category_major_cd 0 category_medium_cd 0 category_small_cd 0 unit_price 0 unit_cost 0 dtype: int64
解説:
このコードでは、まず、"df_product "という名前のDataFrameの "unit_price "と "unit_cost "という2つの列の欠損値を埋めています。このコードでは、以下の引数を持つfillnaメソッドを使って欠損値を埋めています。
第一引数は、"unit_price "と "unit_cost "という二つのキーを持つ辞書で、それぞれが対応する値にマッピングされています。これらの値は、DataFrameのそれぞれの列に適用されるNumPyライブラリのnanmedian関数を使って計算される。nanmedianは、NaNで表される欠損値を無視した上で、列の中央値を計算する。
このコードでは第2引数は省略されており、DataFrameの他の列の欠損値はそのまま残されることになります。
この操作の結果は、"df_product_3 "という新しいDataFrameになります。
次のコードでは、isnull()メソッドとsum()メソッドを呼び出して、"df_product_3 "のDataFrameにまだ欠損値があるかどうかをチェックします。これは、DataFrameの各列の欠落値の数を含むSeriesオブジェクトを返します。どちらの列にも欠損値がないことが出力されれば、fillnaの操作が成功したことが確認されます。
# コード例2(scikit-learnのSimpleImputer)
imp_mean = SimpleImputer(missing_values=np.nan, strategy='median')
imp_values = imp_mean.fit_transform(df_product[['unit_price', 'unit_cost']])
df_product_3 = df_product.copy()
df_product_3[['unit_price', 'unit_cost']] = imp_values.round()
df_product_3.isnull().sum()
product_cd 0 category_major_cd 0 category_medium_cd 0 category_small_cd 0 unit_price 0 unit_cost 0 dtype: int64
解説:
このコードは、scikit-learnのSimpleImputerを使って、DataFrame "df_product "の "unit_price "と "unit_cost "の列の欠損値のインピュテーションを実行する。
コードの最初の行は、"imp_mean "という名前のSimpleImputerのインスタンスを初期化しています。このクラスは、与えられたストラテジーで欠損値をインプットする方法を提供し、この場合、欠損していない値の中央値です。missing_values引数は、欠損とみなす値を指定するもので、この場合はNaNとなります。
コードの2行目は、"df_product" DataFrame の "unit_price" と "unit_cost" 列の欠損値をインプットするために "imp_mean" オブジェクトの fit_transform メソッドを使用します。このメソッドは、インプットされた値を含む "imp_values" という名前の NumPy 配列を返します。
コードの3行目は、"df_product "DataFrameのコピーを "df_product_3 "という名前で作成しています。これは、元のDataFrameを変更しないようにするためです。
コードの4行目は、"df_product_3 "DataFrameの "unit_price "と "unit_cost "カラムにインピュートの値を代入しています。imp_values」配列に対してround()メソッドが呼び出され、最も近い整数に値を丸めています。
コードの最後の行は、isnull()メソッドとsum()メソッドを呼び出して、「df_product_3」DataFrameにまだ欠けている値があるかどうかをチェックする。出力がどちらの列にも欠損値がないことを示す場合、インピュテーション操作が成功したことが確認されます。
P-083: 単価(unit_price)と原価(unit_cost)の欠損値について、各商品のカテゴリ小区分コード(category_small_cd)ごとに算出した中央値で補完した新たな商品データを作成せよ。なお、中央値については1円未満を丸めること(四捨五入または偶数への丸めで良い)。補完実施後、各項目について欠損が生じていないことも確認すること。
# コード例1
df_tmp = (df_product.groupby('category_small_cd')
.agg(median_price=('unit_price', 'median'),
median_cost=('unit_cost', 'median')).reset_index())
df_product_4 = pd.merge(df_product, df_tmp, how='inner', on='category_small_cd')
df_product_4['unit_price'] = df_product_4[['unit_price', 'median_price']]. \
apply(lambda x: np.round(x[1]) if np.isnan(x[0]) else x[0], axis=1)
df_product_4['unit_cost'] = df_product_4[['unit_cost', 'median_cost']]. \
apply(lambda x: np.round(x[1]) if np.isnan(x[0]) else x[0], axis=1)
df_product_4.isnull().sum()
product_cd 0 category_major_cd 0 category_medium_cd 0 category_small_cd 0 unit_price 0 unit_cost 0 median_price 0 median_cost 0 dtype: int64
解説:
このコードは、DataFrame "df_product" の "unit_price" と "unit_cost" 列の欠損値を、各カテゴリの中央値を用いてインピュテーションするものである。このコードでは、以下のステップでこれを実現しています。
最初の行は、DataFrame "df_product "を "category_small_cd "列でグループ化し、各グループの "unit_price "と "unit_cost "の中央値を計算する。出来上がったDataFrameは "df_tmp "と名付けられ、3つのカラムを持つ。"category_small_cd", "median_price", "median_cost "の3つの列を持つ。
2行目は、Pandasライブラリのmerge()メソッドを使用して、"category_small_cd "カラムに基づいて、"df_product "と "df_tmp "DataFrameを結合しています。結果のDataFrameである "df_product_4 "は、"df_product "の全てのカラムと "df_tmp "の "median_price "と "median_cost "のカラムを含みます。
3行目は、"df_product_4 "に "unit_price "という新しい列を作成する。"unit_price "と "median_price "の列にラムダ関数を適用して計算される。ラムダ関数は、"unit_price "列が存在しない(すなわち、NaN)かどうかをチェックし、存在する場合は "median_price "の丸められた値を返す。そうでない場合は、"unit_price "の元の値が返される。この操作により、"unit_price "カラムの欠損値が埋められる。
4行目は、"df_product_4 "に "unit_cost "という新しい列を作成する。"unit_cost "と "median_cost "の列に同様のラムダ関数を適用して計算される。この操作は、"unit_cost "列の欠損値を埋めるものである。
最後の行は、isnull()メソッドとsum()メソッドを呼び出して、"df_product_4" DataFrameにまだ欠損値があるかどうかをチェックします。出力がどちらの列にも欠損値がないことを示す場合、インピュテーション操作が成功したことが確認されます。
# コード例2(maskの活用)
df_tmp = (df_product.groupby('category_small_cd')
.agg(median_price=('unit_price', 'median'),
median_cost=('unit_cost', 'median')).reset_index())
df_product_4 = df_product.merge(df_tmp, how='inner', on='category_small_cd')
df_product_4['unit_price'] = (df_product_4['unit_price']
.mask(df_product_4['unit_price'].isnull(),
df_product_4['median_price'].round()))
df_product_4['unit_cost'] = (df_product_4['unit_cost']
.mask(df_product_4['unit_cost'].isnull(),
df_product_4['median_cost'].round()))
df_product_4.isnull().sum()
product_cd 0 category_major_cd 0 category_medium_cd 0 category_small_cd 0 unit_price 0 unit_cost 0 median_price 0 median_cost 0 dtype: int64
解説:
このコードは、DataFrame "df_product" の "unit_price" と "unit_cost" 列の欠損値を、各カテゴリの中央値を用いてインピュテーションするものである。このコードでは、以下のステップでこれを実現しています。
最初の行は、DataFrame "df_product "を "category_small_cd "列でグループ化し、各グループの "unit_price "と "unit_cost "の中央値を計算する。出来上がったDataFrameは "df_tmp "と名付けられ、3つのカラムを持つ。"category_small_cd", "median_price", "median_cost "の3つの列を持つ。
2行目は、Pandasライブラリのmerge()メソッドを使用して、"category_small_cd "カラムに基づいて、"df_product "と "df_tmp "DataFrameを結合しています。結果のDataFrameである "df_product_4 "は、"df_product "の全てのカラムと "df_tmp "の "median_price "と "median_cost "のカラムを含みます。
3行目は、"df_product_4 "にPandasのmask()メソッドで計算された "unit_price "という新しい列を作成しています。mask()メソッドは、"unit_price "の欠損値(つまりNaN)を "median_price "の丸めた値に設定します。この操作により、"unit_price "列の欠損値が埋まる。
4行目は、"df_product_4 "に "unit_cost "という新しい列を作り、"unit_price "と同様の方法で計算を行う。この操作により、"unit_cost "カラムの欠損値が埋められる。
最後の行は、isnull()メソッドとsum()メソッドを呼び出して、"df_product_4 "のDataFrameにまだ欠損値があるかどうかをチェックします。出力がどちらの列にも欠損値がないことを示す場合、インピュテーション操作が成功したことが確認されます。
# コード例3(fillna、transformの活用)
df_product_4 = df_product.copy()
for x in ['unit_price', 'unit_cost']:
df_product_4[x] = (df_product_4[x]
.fillna(df_product_4.groupby('category_small_cd')[x]
.transform('median')
.round()))
df_product_4.isnull().sum()
product_cd 0 category_major_cd 0 category_medium_cd 0 category_small_cd 0 unit_price 0 unit_cost 0 dtype: int64
解説:
このコードは、DataFrame "df_product" の "unit_price" と "unit_cost" 列の欠損値を、各カテゴリの中央値を用いてインピュテーションするものである。このコードでは、以下の手順でこれを実現しています。
1行目は、元のDataFrame「df_product」のコピーを作成し、「df_product_4」という名前を付けます。
2行目は、forループを開始し、列「unit_price」と「unit_cost」に対して反復処理を行います。
3行目は、各カラムの欠損値を埋めています。これは、fillna()メソッドを使用して、各列の欠損値をそれぞれのカテゴリの中央値で置き換えています。これは、「df_product_4」を「category_small_cd」列でグループ化し、transform()メソッドでそれぞれの列の中央値を計算することにより行われる。round()メソッドは、中央値を最も近い整数に丸めるために使用されます。
4行目は、isnull()メソッドとsum()メソッドを呼び出して、「df_product_4」DataFrameにまだ欠損値があるかどうかをチェックします。出力がどちらの列にも欠損値がないことを示す場合、インピュテーション操作が成功したことが確認されます。
このように、このコードは、各カテゴリの中央値を用いて、DataFrame "df_product "の "unit_price" と "unit_cost" 列の欠損値をインプットし、インプットした値で新しい DataFrame "df_product_4" を作成する。インピュテーションは、元のDataFrameをコピーし、各カテゴリの中央値を用いて各列の欠損値を埋め、欠損値が残っているかどうかを確認することで行われる。
P-084: 顧客データ(df_customer)の全顧客に対して全期間の売上金額に占める2019年売上金額の割合を計算し、新たなデータを作成せよ。ただし、売上実績がない場合は0として扱うこと。そして計算した割合が0超のものを抽出し、結果を10件表示せよ。また、作成したデータに欠損が存在しないことを確認せよ。
df_receipt_2019 = df_receipt.query('20190101 <= sales_ymd <= 20191231') \
.groupby('customer_id') \
.agg(amount_2019=('amount', 'sum')) \
.reset_index()
df_receipt_all = df_receipt.groupby('customer_id')\
.agg(amount_all=('amount', 'sum')) \
.reset_index()
df_sales_rate = df_customer[['customer_id']] \
.merge(df_receipt_2019, how='left', on='customer_id') \
.merge(df_receipt_all, how='left', on='customer_id')
df_sales_rate['amount_2019'] = df_sales_rate['amount_2019'].fillna(0)
df_sales_rate['amount_all'] = df_sales_rate['amount_all'].fillna(0)
df_sales_rate['amount_rate'] = \
df_sales_rate[['amount_2019','amount_all']] \
.apply(lambda x: 0 if x[0] == 0 else x[0] / x[1], axis=1)
df_sales_rate['amount_rate'] = df_sales_rate['amount_rate'].fillna(0)
df_sales_rate.query('amount_rate > 0').head(10)
customer_id | amount_2019 | amount_all | amount_rate | |
---|---|---|---|---|
2 | CS031415000172 | 2971.0 | 5088.0 | 0.583923 |
6 | CS015414000103 | 874.0 | 3122.0 | 0.279949 |
12 | CS011215000048 | 248.0 | 3444.0 | 0.072009 |
15 | CS029415000023 | 3767.0 | 5167.0 | 0.729050 |
21 | CS035415000029 | 5823.0 | 7504.0 | 0.775986 |
23 | CS023513000066 | 208.0 | 771.0 | 0.269780 |
24 | CS035513000134 | 463.0 | 1565.0 | 0.295847 |
27 | CS001515000263 | 216.0 | 216.0 | 1.000000 |
30 | CS006415000279 | 229.0 | 229.0 | 1.000000 |
32 | CS031415000106 | 215.0 | 7741.0 | 0.027774 |
df_tmp.isnull().sum()
category_small_cd 0 median_price 0 median_cost 0 dtype: int64
解説:
このコードは、df_receiptというPandasのDataFrameに対してデータ処理を行い、2019年の顧客の総売上に対する売上率を算出します。以下、このコードが何をするのか、ステップバイステップで説明します。
最初の行は、df_receipt_2019という新しいDataFrameを作成し、2019年(20190101 <= sales_ymd <= 20191231)の売上だけを含むようにdf_receiptの行をフィルターします。そして、customer_idで行をグループ化し、各グループの金額列の合計を計算します。結果として得られるDataFrameは、customer_idとamount_2019の2つのカラムを持ちます。
2行目は、df_receipt_allという新しいDataFrameを作成し、df_receiptの行をcustomer_idでグループ化し、各グループの金額カラムの合計を計算します。出来上がったDataFrameは、customer_idとamount_allの2つのカラムを持ちます。
3行目はdf_sales_rateという新しいDataFrameを作成し、df_customerのcustomer_id列をdf_receipt_2019とdf_receipt_allのamount_2019列とamount_all列にそれぞれ結合しています。つまり、df_customerのすべての行が結果のDataFrameに含まれますが、df_receipt_2019とdf_receipt_allの一致する行だけが含まれます。結果のDataFrameには、customer_id、amount_2019、amount_allの3つのカラムがあります。
4行目と5行目では、amount_2019とamount_allのカラムに欠けている値を0で埋めています。
6行目は、amount_2019とamount_allの列にラムダ関数を適用して、df_sales_rateにamount_rateという新しい列を作成しています。λ関数は、amount_2019が0の場合は0を、それ以外の場合はamount_2019とamount_allの比を返します。結果として得られるamount_rate列は、各顧客の総売上高と比較した2019年における売上率を表しています。
7行目は、amount_rate列の欠損値を0に埋める。
8行目は、df_sales_rateの行をフィルタリングして、正のamount_rateを持つ顧客のみを含み、DataFrameをamount_rateで降順でソートし、最初の10行を返します。これらは、総売上高と比較して、2019年に最も高い売上率を持つ上位10名の顧客です。
P-085: 顧客データ(df_customer)の全顧客に対し、郵便番号(postal_cd)を用いてジオコードデータ(df_geocode)を紐付け、新たな顧客データを作成せよ。ただし、1つの郵便番号(postal_cd)に複数の経度(longitude)、緯度(latitude)情報が紐づく場合は、経度(longitude)、緯度(latitude)の平均値を算出して使用すること。また、作成結果を確認するために結果を10件表示せよ。
df_geocode_1 = df_geocode.groupby('postal_cd') \
.agg(m_longitude=('longitude', 'mean'),
m_latitude=('latitude', 'mean')).reset_index()
df_customer_1 = pd.merge(df_customer, df_geocode_1,
how='inner', on='postal_cd')
df_customer_1.head(10)
customer_id | customer_name | gender_cd | gender | birth_day | age | postal_cd | address | application_store_cd | application_date | status_cd | m_longitude | m_latitude | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | CS021313000114 | 大野 あや子 | 1 | 女性 | 1981-04-29 | 37 | 259-1113 | 神奈川県伊勢原市粟窪********** | S14021 | 20150905 | 0-00000000-0 | 139.31779 | 35.41358 |
1 | CS021303000023 | 堀 一徳 | 0 | 男性 | 1980-04-05 | 38 | 259-1113 | 神奈川県伊勢原市粟窪********** | S14021 | 20160411 | 0-00000000-0 | 139.31779 | 35.41358 |
2 | CS021303000007 | 石倉 俊二 | 0 | 男性 | 1987-07-04 | 31 | 259-1113 | 神奈川県伊勢原市粟窪********** | S14021 | 20150707 | 0-00000000-0 | 139.31779 | 35.41358 |
3 | CS021313000183 | 草野 未來 | 1 | 女性 | 1986-12-21 | 32 | 259-1113 | 神奈川県伊勢原市粟窪********** | S14021 | 20170611 | 0-00000000-0 | 139.31779 | 35.41358 |
4 | CS021314000098 | 筒井 れいな | 1 | 女性 | 1985-09-21 | 33 | 259-1113 | 神奈川県伊勢原市粟窪********** | S14021 | 20160901 | 0-00000000-0 | 139.31779 | 35.41358 |
5 | CS021314000093 | 江藤 美佐 | 1 | 女性 | 1986-06-03 | 32 | 259-1113 | 神奈川県伊勢原市粟窪********** | S14021 | 20151015 | 0-00000000-0 | 139.31779 | 35.41358 |
6 | CS021413000049 | 大野 幸子 | 1 | 女性 | 1973-04-17 | 45 | 259-1113 | 神奈川県伊勢原市粟窪********** | S14021 | 20150703 | 0-00000000-0 | 139.31779 | 35.41358 |
7 | CS037613000071 | 六角 雅彦 | 9 | 不明 | 1952-04-01 | 66 | 136-0076 | 東京都江東区南砂********** | S13037 | 20150414 | 0-00000000-0 | 139.83502 | 35.67193 |
8 | CS015415000209 | 大谷 倫子 | 1 | 女性 | 1970-11-25 | 48 | 136-0076 | 東京都江東区南砂********** | S13015 | 20150909 | B-20090610-C | 139.83502 | 35.67193 |
9 | CS037614000045 | 長沢 麻緒 | 1 | 女性 | 1952-06-19 | 66 | 136-0076 | 東京都江東区南砂********** | S13037 | 20150209 | 5-20091207-6 | 139.83502 | 35.67193 |
解説:
このコードは、df_geocodeとdf_customerという2つのPandas DataFrameに対してデータ処理を実行します。以下、このコードが何をするのか、ステップバイステップで説明します。
最初の行は、df_geocodeの行をpostal_cd(郵便番号)でグループ化し、各グループの平均経度と緯度を計算することによって、df_geocode_1という新しいDataFrameを作成します。出来上がったDataFrameは、postal_cd、m_longitude、m_latitudeの3つのカラムを持ちます。
2行目は、df_customerとdf_geocode_1をpostal_cdカラムでマージして、df_customer_1という新しいDataFrameを作成します。つまり、両方のDataFrameで一致する値を持つ行だけが結果のDataFrameに含まれます。結果のDataFrameはdf_customerのすべてのカラムとdf_geocode_1のm_longitudeとm_latitudeカラムを含んでいます。
3行目は、.head(10)メソッドを使用して、結果のDataFrame(df_customer_1)の最初の10行を表示しています。これは、顧客情報とそれに対応する郵便番号の平均経度と緯度の値を含むマージされたデータのサンプルを示しています。
P-086: 085で作成した緯度経度つき顧客データに対し、会員申込店舗コード(application_store_cd)をキーに店舗データ(df_store)と結合せよ。そして申込み店舗の緯度(latitude)・経度情報(longitude)と顧客住所(address)の緯度・経度を用いて申込み店舗と顧客住所の距離(単位:km)を求め、顧客ID(customer_id)、顧客住所(address)、店舗住所(address)とともに表示せよ。計算式は以下の簡易式で良いものとするが、その他精度の高い方式を利用したライブラリを利用してもかまわない。結果は10件表示せよ。
緯度(ラジアン):φ緯度(ラジアン):λ:距離L=6371∗arccos(sinφ1∗sinφ2+cosφ1∗cosφ2∗cos(λ1−λ2))
# コード例1
def calc_distance(x1, y1, x2, y2):
distance = 6371 * math.acos(math.sin(math.radians(x1))
* math.sin(math.radians(x2))
+ math.cos(math.radians(x1))
* math.cos(math.radians(x2))
* math.cos(math.radians(y1) - math.radians(y2)))
return distance
df_tmp = pd.merge(df_customer_1, df_store,
how='inner',
left_on='application_store_cd',
right_on='store_cd') \
.rename(columns={'address_x':'customer_address',
'address_y':'store_address'})
df_tmp['distance'] = df_tmp[['m_latitude',
'm_longitude',
'latitude',
'longitude']] \
.apply(lambda x: calc_distance(x[0], x[1], x[2], x[3]),
axis=1)
df_tmp[['customer_id', 'customer_address',
'store_address', 'distance']].head(10)
customer_id | customer_address | store_address | distance | |
---|---|---|---|---|
0 | CS021313000114 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
1 | CS021303000023 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
2 | CS021303000007 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
3 | CS021313000183 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
4 | CS021314000098 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
5 | CS021314000093 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
6 | CS021413000049 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
7 | CS021313000025 | 神奈川県伊勢原市伊勢原********** | 神奈川県伊勢原市伊勢原四丁目 | 0.474282 |
8 | CS021413000022 | 神奈川県伊勢原市伊勢原********** | 神奈川県伊勢原市伊勢原四丁目 | 0.474282 |
9 | CS021413000094 | 神奈川県伊勢原市伊勢原********** | 神奈川県伊勢原市伊勢原四丁目 | 0.474282 |
解説:
このコードは、df_customer_1、df_store、df_tmpという3つのPandas DataFrameに対して、いくつかの地理空間計算とデータ処理を実行するものである。ここでは、このコードが何をするのか、ステップバイステップで説明します。
最初の行は、calc_distanceというPython関数を定義し、x1とy1(点1の緯度と経度)、x2とy2(点2の緯度と経度)という4つの引数を取ります。この関数は、半径6371kmの球形の地球を想定して、ハバーシンの公式を使って2点間の距離をキロメートル単位で計算する。結果はdistanceとして返されます。
2行目は、df_customer_1とdf_storeをそれぞれapplication_store_cdとstore_cdカラムでマージしてdf_tmpという新しいDataFrameを作成しています。つまり、両方のDataFrameで一致する値を持つ行だけが結果のDataFrameに含まれます。結果のDataFrameは両方のDataFrameの全てのカラムを持ち、さらにいくつかのリネームされたカラムを持ちます:address_xはcustomer_addressに、address_yはstore_addressにリネームされます。
3行目は、.apply()メソッドを使ってm_latitude、m_longitude、latitude、longitude列にcalc_distance関数を適用し、df_tmpにdistanceという新しい列を作成しています。.apply()メソッドは、axis=1の引数とともに、関数を行単位で適用するために使用されます。結果の距離値は、distance列に格納される。
4行目は、二重角括弧を使ってdf_tmpからカラムのサブセット(customer_id, customer_address, store_address, and distance)を選択し、.head(10)メソッドを用いて結果のDataFrameの最初の10行を表示しています。これは、顧客と店舗の情報を含むマージされたデータのサンプルと、それらの間の対応する距離を示しています。
# コード例2
def calc_distance_numpy(x1, y1, x2, y2):
x1_r = np.radians(x1)
x2_r = np.radians(x2)
y1_r = np.radians(y1)
y2_r = np.radians(y2)
return 6371 * np.arccos(np.sin(x1_r) * np.sin(x2_r)
+ np.cos(x1_r) * np.cos(x2_r)
* np.cos(y1_r - y2_r))
df_tmp = df_customer_1.merge(df_store,
how='inner',
left_on='application_store_cd',
right_on='store_cd') \
.rename(columns={'address_x':'customer_address',
'address_y':'store_address'})
df_tmp['distance'] = calc_distance_numpy(df_tmp['m_latitude'],
df_tmp['m_longitude'],
df_tmp['latitude'],
df_tmp['longitude'])
df_tmp[['customer_id', 'customer_address',
'store_address', 'distance']].head(10)
customer_id | customer_address | store_address | distance | |
---|---|---|---|---|
0 | CS021313000114 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
1 | CS021303000023 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
2 | CS021303000007 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
3 | CS021313000183 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
4 | CS021314000098 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
5 | CS021314000093 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
6 | CS021413000049 | 神奈川県伊勢原市粟窪********** | 神奈川県伊勢原市伊勢原四丁目 | 1.394409 |
7 | CS021313000025 | 神奈川県伊勢原市伊勢原********** | 神奈川県伊勢原市伊勢原四丁目 | 0.474282 |
8 | CS021413000022 | 神奈川県伊勢原市伊勢原********** | 神奈川県伊勢原市伊勢原四丁目 | 0.474282 |
9 | CS021413000094 | 神奈川県伊勢原市伊勢原********** | 神奈川県伊勢原市伊勢原四丁目 | 0.474282 |
解説:
このコードは、df_customer_1、df_store、df_tmpという3つのPandas DataFrameに対して、いくつかの地理空間計算とデータ処理を実行するものである。ここでは、このコードが何をするのか、ステップバイステップで説明します。
最初の行は、calc_distance_numpyというPython関数を定義し、x1とy1(点1の緯度と経度)、x2とy2(点2の緯度と経度)という4つの引数を取ります。この関数は、半径6371kmの球状の地球を想定して、ハバーシンの公式を使って2点間の距離をキロメートル単位で計算します。この実装では、以前の実装よりも効率的に計算を行うためにNumPy関数を使用しています。
2行目は、df_customer_1 と df_store をそれぞれ application_store_cd と store_cd 列でマージして、df_tmp という新しい DataFrame を作成しています。つまり、両方のDataFrameで一致する値を持つ行だけが結果のDataFrameに含まれます。結果のDataFrameは両方のDataFrameの全てのカラムを持ち、さらにいくつかのリネームされたカラムを持ちます:address_xはcustomer_addressにリネームされ、address_yはstore_addressにリネームされています。
3行目は、df_tmpの4つの列に対して、NumPyの配列操作を使ってcalc_distance_numpy関数を呼び出して、distanceという新しい列を作成しています。この実装では、.apply()メソッドを使用する代わりに、NumPyの配列に対して要素ごとの計算を行う機能を利用しています。結果の距離値は、distance列に格納されます。
4行目は、二重の角括弧を使ってdf_tmpからカラムのサブセット(customer_id, customer_address, store_address, and distance)を選択し、.head(10)メソッドを使って結果のDataFrameの最初の10行を表示しています。これは、顧客と店舗の情報を含むマージされたデータのサンプルと、それらの間の対応する距離を示しています。
P-087: 顧客データ(df_customer)では、異なる店舗での申込みなどにより同一顧客が複数登録されている。名前(customer_name)と郵便番号(postal_cd)が同じ顧客は同一顧客とみなして1顧客1レコードとなるように名寄せした名寄顧客データを作成し、顧客データの件数、名寄顧客データの件数、重複数を算出せよ。ただし、同一顧客に対しては売上金額合計が最も高いものを残し、売上金額合計が同一もしくは売上実績がない顧客については顧客ID(customer_id)の番号が小さいものを残すこととする。
df_receipt_tmp = df_receipt.groupby('customer_id') \
.agg(sum_amount=('amount','sum')).reset_index()
df_customer_u = pd.merge(df_customer, df_receipt_tmp,
how='left',
on='customer_id')
df_customer_u['sum_amount'] = df_customer_u['sum_amount'].fillna(0)
df_customer_u = df_customer_u.sort_values(['sum_amount', 'customer_id'],
ascending=[False, True])
df_customer_u.drop_duplicates(subset=['customer_name', 'postal_cd'],
keep='first', inplace=True)
print('df_customer_cnt:', len(df_customer),
'df_customer_u_cnt:', len(df_customer_u),
'diff:', len(df_customer) - len(df_customer_u))
df_customer_cnt: 21971 df_customer_u_cnt: 21941 diff: 30
解説:
このコードは、以下のタスクを実行します。
df_receiptデータフレームをcustomer_idでグループ化し、各顧客の金額の合計を計算する。結果のデータフレームはdf_receipt_tmpに格納される。
customer_id列に基づいて、df_customerデータフレームをdf_receipt_tmpデータフレームと結合します。これは左結合なので、df_customerのすべての顧客が結果のデータフレームdf_customer_uに含まれることになります。
sum_amountカラムの欠損値を0に置き換えます。
df_customer_uの行をsum_amountで降順に、customer_idで昇順にソートします。
customer_name列とpostal_cd列に基づいて、df_customer_uの重複を削除し、重複の最初の出現分だけを残す。結果のデータフレームがdf_customer_uに戻される。
元のdf_customerデータフレームの行数(df_customer_cnt)、更新後のdf_customer_uデータフレームの行数(df_customer_u_cnt)、両者の差(diff)をプリントアウトします。
全体として、このコードはデータクリーニングと操作を行い、新しいデータフレームdf_customer_uを作成しています。このデータフレームには、各顧客の合計購入金額(sum_amount)に関する情報が含まれ、この金額で降順にソートされ、重複するものはcustomer_nameとpostal_cd列に基づいて削除されます。
P-088: 087で作成したデータを元に、顧客データに統合名寄IDを付与したデータを作成せよ。ただし、統合名寄IDは以下の仕様で付与するものとする。
- 重複していない顧客:顧客ID(customer_id)を設定
- 重複している顧客:前設問で抽出したレコードの顧客IDを設定
顧客IDのユニーク件数と、統合名寄IDのユニーク件数の差も確認すること。
df_customer_n = pd.merge(df_customer,
df_customer_u[['customer_name',
'postal_cd', 'customer_id']],
how='inner', on =['customer_name', 'postal_cd'])
df_customer_n.rename(columns={'customer_id_x':'customer_id',
'customer_id_y':'integration_id'}, inplace=True)
print('ID数の差', len(df_customer_n['customer_id'].unique())
- len(df_customer_n['integration_id'].unique()))
ID数の差 30
解説:
このコードは、df_customerとdf_customer_uという2つのデータフレームをマージして、名前と郵便番号に基づいてユニークな顧客を識別し、マッチングしています。
マージされた結果のデータフレームは、df_customer_nという新しいデータフレームに格納されます。2つのデータフレームを結合するためにmerge関数が使用され、on=['customer_name', 'postal_cd']という引数は、これら2つの列に基づいて結合を行うことを指定する。
マージされたデータフレームには、customer_id_xとcustomer_id_yという2つの顧客IDカラムが含まれます。混乱を避けるため、rename関数を使用して、これらの列の名前をそれぞれcustomer_idとintegration_idに変更します。
最後の行では、customer_idカラムのユニークな顧客IDの数とintegration_idカラムのユニークな顧客IDの数を比較します。差がゼロの場合、マージされたデータフレームに重複した顧客が存在しないことを意味します。差がゼロでない場合は、名前と郵便番号の照合がうまくいかなかった顧客がまだ存在し、そのIDが重複していることを意味します。
P-閑話: df_customer_1, df_customer_nは使わないので削除する。
del df_customer_1
del df_customer_n
P-089: 売上実績がある顧客を、予測モデル構築のため学習用データとテスト用データに分割したい。それぞれ8:2の割合でランダムにデータを分割せよ。
df_sales_customer = df_receipt.groupby('customer_id') \
.agg({'amount':sum}).reset_index()
df_sales_customer = df_sales_customer.query('amount > 0')
df_tmp = pd.merge(df_customer, df_sales_customer['customer_id'],
how='inner', on='customer_id')
df_train, df_test = train_test_split(df_tmp, test_size=0.2, random_state=71)
print('学習データ割合: ', len(df_train) / len(df_tmp))
print('テストデータ割合: ', len(df_test) / len(df_tmp))
学習データ割合: 0.7999036840837949 テストデータ割合: 0.20009631591620516
解説:
このコードは、各顧客の売上情報に基づいて、データの訓練とテストの分割を実行します。
まず、df_receipt dataframeをcustomer_idでグループ化し、amount列を合計することで、各顧客の売上情報を集計する。得られたデータフレームは、df_sales_customerに格納される。
次に、クエリメソッドを使用して、購入をしなかった顧客(すなわち、金額が0に等しい顧客)をフィルタリングし、これらの顧客のみを含むようにdf_sales_customerを更新する。
次に、df_sales_customerのcustomer_idカラムとdf_customerの内部結合を行い、購入を行った顧客のみを保持します。結果のデータフレームはdf_tmpに格納されます。
最後に、scikit-learnのtrain_test_splitメソッドを用いて、df_tmpをトレーニングセットとテストセットに分割します。テストサイズは0.2、ランダムステートは71です。そして、トレーニングセットとテストセットに含まれるデータの割合を表示する。
P-090: レシート明細データ(df_receipt)は2017年1月1日〜2019年10月31日までのデータを有している。売上金額(amount)を月次で集計し、学習用に12ヶ月、テスト用に6ヶ月の時系列モデル構築用データを3セット作成せよ。
# コード例1(自作関数)
df_ts_amount = df_receipt[['sales_ymd', 'amount']].copy()
df_ts_amount['sales_ym'] = df_ts_amount['sales_ymd'].astype('str').str[0:6]
df_ts_amount = df_ts_amount.groupby('sales_ym') \
.agg({'amount':'sum'}).reset_index()
# 長期間データに対する多数のデータセットもループなどで処理できるように関数化
def split_data(df, train_size, test_size, slide_window, start_point):
train_start = start_point * slide_window
test_start = train_start + train_size
return df[train_start:test_start], df[test_start:test_start + test_size]
df_train_1, df_test_1 = split_data(df_ts_amount, train_size=12,
test_size=6, slide_window=6, start_point=0)
df_train_2, df_test_2 = split_data(df_ts_amount, train_size=12,
test_size=6, slide_window=6, start_point=1)
df_train_3, df_test_3 = split_data(df_ts_amount, train_size=12,
test_size=6, slide_window=6, start_point=2)
# df_train_2とdf_train_3の表示は割愛
df_train_1
sales_ym | amount | |
---|---|---|
0 | 201701 | 902056 |
1 | 201702 | 764413 |
2 | 201703 | 962945 |
3 | 201704 | 847566 |
4 | 201705 | 884010 |
5 | 201706 | 894242 |
6 | 201707 | 959205 |
7 | 201708 | 954836 |
8 | 201709 | 902037 |
9 | 201710 | 905739 |
10 | 201711 | 932157 |
11 | 201712 | 939654 |
解説:
このコードは、以下のタスクを実行しています。
sales_ymd」列、「amount」列、「sales_ym」列を持つdf_ts_amount dataframeが作成される。sales_ym列はsales_ymd列の最初の6文字から文字列に変換されて作られる。
df_ts_amount データフレームを 'sales_ym' カラムでグループ化し、 'sum' 関数で 'amount' カラムを集計し、インデックスをリセットする。出来上がったデータフレームは同じ変数'df_ts_amount'に格納される。
関数split_dataは,df, train size, test size, slide window, start pointを引数に取る.split_data関数は、trainデータフレームとtestデータフレームの2つのデータフレームを返します。
split_data関数は、df_ts_amountデータフレームをtrainデータフレームとtestデータフレームに分割するために、毎回異なる引数で3回呼び出されます。訓練データとテストデータの3つのセットが作成され、df_train_1、df_test_1、df_train_2、df_test_2、df_train_3、df_test_3に保存されます。
データを複数に分割する目的は、時系列予測モデルの訓練セットと検証セットとして使用するためと思われます。データを異なる集合に分割することで、モデルを異なる期間で学習させ、異なるテスト期間で性能を評価することができる。これにより、オーバーフィッティングを回避し、モデルの性能をより正確に推定することができます。
# df_test_2とdf_test_3の表示は割愛
df_test_1
sales_ym | amount | |
---|---|---|
12 | 201801 | 944509 |
13 | 201802 | 864128 |
14 | 201803 | 946588 |
15 | 201804 | 937099 |
16 | 201805 | 1004438 |
17 | 201806 | 1012329 |
# コード例2(scikit-learnのTimeSeriesSplit)
tscv = TimeSeriesSplit(gap=0, max_train_size=12, n_splits=3, test_size=6)
# TimeSeriesSplitは最新のデータが使われるように分割されるが、
# SQL、Rの解答例と同じとなるようにデータ期間を調整
# できる限り最新データを使うようにするなら不要
df_ts_amount = df_ts_amount.query('sales_ym <= "201906"')
series_list = []
for train_index, test_index in tscv.split(df_ts_amount):
series_list.append((df_ts_amount.loc[train_index],
df_ts_amount.loc[test_index]))
df_train_1, df_test_1 = series_list[0]
df_train_2, df_test_2 = series_list[1]
df_train_3, df_test_3 = series_list[2]
# df_train_2とdf_train_3の表示は割愛
df_train_1
sales_ym | amount | |
---|---|---|
0 | 201701 | 902056 |
1 | 201702 | 764413 |
2 | 201703 | 962945 |
3 | 201704 | 847566 |
4 | 201705 | 884010 |
5 | 201706 | 894242 |
6 | 201707 | 959205 |
7 | 201708 | 954836 |
8 | 201709 | 902037 |
9 | 201710 | 905739 |
10 | 201711 | 932157 |
11 | 201712 | 939654 |
# df_test_2とdf_test_3の表示は割愛
df_test_1
sales_ym | amount | |
---|---|---|
12 | 201801 | 944509 |
13 | 201802 | 864128 |
14 | 201803 | 946588 |
15 | 201804 | 937099 |
16 | 201805 | 1004438 |
17 | 201806 | 1012329 |
解説:
このコードは、pandasのDataFrame df_ts_amountに対して時系列クロスバリデーションを実行しています。sklearn.model_selection モジュールの TimeSeriesSplit 関数を使用して、3 つの訓練とテストの分割を生成しています。
TimeSeriesSplitは、時系列データサンプルを分割するためのトレーニング/テストインデックスを提供するクロスバリデーターです。4つのパラメータを持ちます。
n_splits: 分割の数
max_train_size: 学習データの最大サイズ
test_size: テストデータの大きさ
gap: 新しいスプリットを開始する前にスキップするサンプル数
コードでは、n_splits=3、max_train_size=12、test_size=6、gap=0を設定して、学習期間12ヶ月、テスト期間6ヶ月の3分割を作成します。
次にコードは、2019年6月までの売上データのみを含むようにデータをフィルタリングし(df_ts_amount.query('sales_ym <= "201906"'))、リスト series_list を作成して TimeSeriesSplit で生成したトレーニングおよびテストセットを格納する。forループは、TimeSeriesSplitによって生成された分割を繰り返し、各分割をseries_listに追加しています。最後に,最初の訓練とテストの分割を df_train_1 と df_test_1 に,2番目の分割を df_train_2 と df_test_2 に,3番目の分割を df_train_3 と df_test_3 に割り当てています.
P-091: 顧客データ(df_customer)の各顧客に対し、売上実績がある顧客数と売上実績がない顧客数が1:1となるようにアンダーサンプリングで抽出せよ。
df_tmp = df_receipt.groupby('customer_id').agg({'amount':'sum'}).reset_index()
df_tmp = pd.merge(df_customer, df_tmp, how='left', on='customer_id')
df_tmp['is_buy_flag'] = np.where(df_tmp['amount'].isnull(), 0, 1)
rs = RandomUnderSampler(random_state=71)
df_down_sampling, _ = rs.fit_resample(df_tmp, df_tmp.is_buy_flag)
print('0の件数', len(df_down_sampling.query('is_buy_flag == 0')))
print('1の件数', len(df_down_sampling.query('is_buy_flag == 1')))
0の件数 8306 1の件数 8306
解説:
このコードは、バイナリターゲット変数is_buy_flagのバランスをとるためにランダムアンダーサンプリングを実行する。
まず、各顧客の購入金額の合計がgroupbyを使って計算され、df_tmpに格納されます。次に、df_customerとdf_tmpは左結合を使用してマージされ、たとえ購入をしていなくてもすべての顧客を含むようにします。is_buy_flag列は、np.whereを使用して、購入をした顧客に1を、購入しなかった顧客に0を割り当てることによって作成されます。
次に、imblearnライブラリのRandomUnderSampler関数を用いて、対象変数のバランスをとる。df_tmpに対して、is_buy_flag列を対象変数としてfit_resampleメソッドが適用されます。これにより、両方のターゲット変数の値に対して同数のケースを持つ新しいDataFrame、df_down_samplingが作成されます。
最後に、is_buy_flagの値が0と1のケースの数が出力され、ランダムアンダーサンプリングが成功したことが確認されます。
P-092: 顧客データ(df_customer)の性別について、第三正規形へと正規化せよ。
df_gender_std = df_customer[['gender_cd', 'gender']].drop_duplicates()
df_customer_std = df_customer.drop(columns='gender')
# データの内容確認
df_customer_std.head(3)
customer_id | customer_name | gender_cd | birth_day | age | postal_cd | address | application_store_cd | application_date | status_cd | |
---|---|---|---|---|---|---|---|---|---|---|
0 | CS021313000114 | 大野 あや子 | 1 | 1981-04-29 | 37 | 259-1113 | 神奈川県伊勢原市粟窪********** | S14021 | 20150905 | 0-00000000-0 |
1 | CS037613000071 | 六角 雅彦 | 9 | 1952-04-01 | 66 | 136-0076 | 東京都江東区南砂********** | S13037 | 20150414 | 0-00000000-0 |
2 | CS031415000172 | 宇多田 貴美子 | 1 | 1976-10-04 | 42 | 151-0053 | 東京都渋谷区代々木********** | S13031 | 20150529 | D-20100325-C |
# データの内容確認
df_gender_std.head(3)
gender_cd | gender | |
---|---|---|
0 | 1 | 女性 |
1 | 9 | 不明 |
5 | 0 | 男性 |
解説:
このコードでは、df_customer データフレームから 'gender_cd' と 'gender' 列を選択し、drop_duplicates() メソッドを使用して重複する行を削除することによって、df_gender_std を作成しています。これにより、'gender_cd' と 'gender' の値のユニークな組み合わせの新しいデータフレームが作成されます。
df_customer_std は、df_customer dataframe から 'gender' 列を、columns パラメータを 'gender' に設定した drop() メソッドを使用して削除することで作成されます。これにより、df_customerから'gender'カラム以外のすべてのカラムを持つ新しいdataframeが作成されます。
このコードの目的は、'gender_cd' と 'gender' のユニークな組み合わせを持つデータフレームと、df_customer の 'gender' 以外のすべてのカラムを持つデータフレームを別々に作成することにあります。これらのデータフレームは、さらなる分析やモデリングに使用することができます。
P-093: 商品データ(df_product)では各カテゴリのコード値だけを保有し、カテゴリ名は保有していない。カテゴリデータ(df_category)と組み合わせて非正規化し、カテゴリ名を保有した新たな商品データを作成せよ。
df_product_full = pd.merge(df_product, df_category[['category_small_cd',
'category_major_name',
'category_medium_name',
'category_small_name']],
how = 'inner', on = 'category_small_cd')
# データの内容確認
df_product_full.head(3)
product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost | category_major_name | category_medium_name | category_small_name | |
---|---|---|---|---|---|---|---|---|---|
0 | P040101001 | 04 | 0401 | 040101 | 198.0 | 149.0 | 惣菜 | 御飯類 | 弁当類 |
1 | P040101002 | 04 | 0401 | 040101 | 218.0 | 164.0 | 惣菜 | 御飯類 | 弁当類 |
2 | P040101003 | 04 | 0401 | 040101 | 230.0 | 173.0 | 惣菜 | 御飯類 | 弁当類 |
解説:
このコードは、2つのデータフレームdf_productとdf_categoryの間で、category_small_cd列で内部結合を実行します。結果のdf_product_fullはdf_productとdf_categoryのすべてのカラムを含み、結合はcategory_small_cdの一致する値に基づいています。
新しいデータフレームdf_product_fullには、df_categoryの追加カラム、特にcategory_major_name、category_medium_name、category_small_nameも含まれています。これらの列は、商品カテゴリに関するカテゴリ情報を含んでおり、両方のデータフレームで一致する値を持つ行だけを保持する、how='inner'オプションを使用してマージされたデータフレームに含まれます。.head(3)メソッドは、結果として得られるマージされたデータフレームの最初の3行を表示するために使用されます。
P-094: 093で作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。
|ファイル形式|ヘッダ有無|文字エンコーディング| |:–:|:–:|:–:| |CSV(カンマ区切り)|有り|UTF-8|
ファイル出力先のパスは以下のようにすること
|出力先| |:–:| |./data|
# コード例1
# 解答ファイルの置き場所が設問ファイルと異なるため、パスが'../data'となっている点に注意
df_product_full.to_csv('../data/P_df_product_full_UTF-8_header.csv',
encoding='UTF-8', index=False)
解説:
このコードは、PandasのDataFrame df_product_fullを、ファイルパスとファイル名を指定したCSVファイル ../data/P_df_product_full_UTF-8_header.csv にエクスポートしています。エクスポートされるCSVファイルは、幅広い文字をサポートするためにUTF-8エンコーディングを使用し、引数index=Falseは、DataFrameの行インデックスをエクスポートされるCSVファイルに含めないことを指定します。
# コード例2(BOM付きでExcelの文字化けを防ぐ)
df_product_full.to_csv('../data/P_df_product_full_UTF-8BOM_header.csv',
encoding='utf_8_sig', index=False)
解説:
このコードは、df_product_fullのDataFrameをP_df_product_full_UTF-8BOM_header.csvというファイル名のCSVファイルにエクスポートしています。
pandas DataFrameのto_csv()メソッドは、DataFrameの内容をCSVファイルに書き込むために使用されます。このメソッドには、ファイル名や使用するエンコーディングなど、いくつかのパラメータがあります。
この場合、filenameパラメータには'../data/P_df_product_full_UTF-8BOM_header.csv'が設定されており、CSVファイルのディレクトリとファイル名が指定されます。'./data/'の接頭辞は、ファイルを保存する相対ディレクトリを指定する。
encodingパラメータには、CSVファイルに使用するエンコード形式を指定する'utf_8_sig'が設定される。utf_8_sig」は、ファイルの先頭にバイトオーダーマーク(BOM)を含むUTF-8エンコーディングの一種である。BOMは、ファイルのバイトオーダーを示す特別なマーカーで、ファイルを開くときにプログラムがエンコード形式を判断するのに役立ちます。
indexパラメータはFalseに設定されており、DataFrameのインデックスをCSVファイルに含めないことを指定する。
P-095: 093で作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。
|ファイル形式|ヘッダ有無|文字エンコーディング| |:–:|:–:|:–:| |CSV(カンマ区切り)|有り|CP932|
ファイル出力先のパスは以下のようにすること。
|出力先| |:–:| |./data|
# 解答ファイルの置き場所が設問ファイルと異なるため、パスが'../data'となっている点に注意
df_product_full.to_csv('../data/P_df_product_full_CP932_header.csv',
encoding='CP932', index=False)
解説:
このコードは、pandasのDataFrameであるdf_product_fullを、「./data/」ディレクトリの「P_df_product_full_CP932_header.csv」という名前のCSVファイルにエクスポートしています。
エクスポートされたCSVファイルは、「Windows-31J」とも呼ばれる日本語の文字エンコーディングである「CP932」文字エンコーディングを使用しています。このエンコーディングは、日本ではテキストデータによく使われており、Windowsオペレーティングシステムと互換性があります。
パラメータencoding='CP932'は、CSVファイルをエクスポートするときに使用する文字エンコーディングを指定します。
index=Falseパラメータは、エクスポートされたCSVファイルにindexカラムを含めないことを指定します。
P-096: 093で作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。
|ファイル形式|ヘッダ有無|文字エンコーディング| |:–:|:–:|:–:| |CSV(カンマ区切り)|無し|UTF-8|
ファイル出力先のパスは以下のようにすること。
|出力先| |:–:| |./data|
# 解答ファイルの置き場所が設問ファイルと異なるため、パスが'../data'となっている点に注意
df_product_full.to_csv('../data/P_df_product_full_UTF-8_noh.csv',
header=False, encoding='UTF-8', index=False)
解説:
df_product_full DataFrameを"../data/"ディレクトリの "P_df_product_full_UTF-8_noh.csv "というCSVファイルに保存するコードである。
第1引数には、CSVファイルの保存先となるファイル名とパスを指定する。header引数はFalseに設定され、DataFrameのヘッダー行は出力ファイルに含まれないことを意味する。encoding引数には'UTF-8'が設定され、ファイル書き込み時に使用する文字エンコーディングを指定する。これは、DataFrameのインデックスが出力ファイルに含まれないことを意味します。
全体として、このコードは、df_product_full DataFrameを、ヘッダー行を含まず、UTF-8エンコーディングでCSVファイルにエクスポートしています。
P-097: 094で作成した以下形式のファイルを読み込み、データを3件を表示させて正しく取り込まれていることを確認せよ。
|ファイル形式|ヘッダ有無|文字エンコーディング| |:–:|:–:|:–:| |CSV(カンマ区切り)|有り|UTF-8|
# 解答ファイルの置き場所が設問ファイルと異なるため、パスが'../data'となっている点に注意
df_product_full = pd.read_csv('../data/P_df_product_full_UTF-8_header.csv',
dtype={'category_major_cd':str,
'category_medium_cd':str,
'category_small_cd':str},
encoding='UTF-8')
df_product_full.head(3)
product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost | category_major_name | category_medium_name | category_small_name | |
---|---|---|---|---|---|---|---|---|---|
0 | P040101001 | 04 | 0401 | 040101 | 198.0 | 149.0 | 惣菜 | 御飯類 | 弁当類 |
1 | P040101002 | 04 | 0401 | 040101 | 218.0 | 164.0 | 惣菜 | 御飯類 | 弁当類 |
2 | P040101003 | 04 | 0401 | 040101 | 230.0 | 173.0 | 惣菜 | 御飯類 | 弁当類 |
解説:
上記のコードは、UTF-8エンコーディングでヘッダー行がないCSVファイルP_df_product_full_UTF-8_noh.csvを読み込み、データフレームに列名を割り当てています。
pd.read_csv('../data/P_df_product_full_UTF-8_noh.csv', dtype={1:str, 2:str, 3:str}, encoding='UTF-8', header=None): ヘッダ行のないCSVファイルを読み込み、2列目、3列目、4列目を文字列データ型に設定します。結果として得られるデータフレームには、0、1、2、3、4、5、6、7、8という名前のカラムがあります。
df_product_full.columns = ['product_cd','category_major_cd', 'category_medium_cd', 'category_small_cd', 'unit_price','unit_cost','category_major_name', 'category_medium_name', 'category_small_name']: これは、データフレームのカラムに新しいカラム名を割り当てるものです。結果として得られるdataframeは、product_cd、category_major_cd、category_medium_cd、category_small_cd、unit_price、unit_cost、category_major_name、category_medium_name、およびcategory_small_nameという列を持つ。
df_product_full.head(3): df_product_fullの最初の3行を表示します。
P-098: 096で作成した以下形式のファイルを読み込み、データを3件を表示させて正しく取り込まれていることを確認せよ。
|ファイル形式|ヘッダ有無|文字エンコーディング| |:–:|:–:|:–:| |CSV(カンマ区切り)|ヘッダ無し|UTF-8|
# コード例1(後から項目名をつける)
# 解答ファイルの置き場所が設問ファイルと異なるため、パスが'../data'となっている点に注意
df_product_full = pd.read_csv('../data/P_df_product_full_UTF-8_noh.csv',
dtype={1:str,
2:str,
3:str},
encoding='UTF-8', header=None)
df_product_full.columns = ['product_cd','category_major_cd',
'category_medium_cd', 'category_small_cd',
'unit_price','unit_cost','category_major_name',
'category_medium_name', 'category_small_name']
df_product_full.head(3)
product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost | category_major_name | category_medium_name | category_small_name | |
---|---|---|---|---|---|---|---|---|---|
0 | P040101001 | 04 | 0401 | 040101 | 198.0 | 149.0 | 惣菜 | 御飯類 | 弁当類 |
1 | P040101002 | 04 | 0401 | 040101 | 218.0 | 164.0 | 惣菜 | 御飯類 | 弁当類 |
2 | P040101003 | 04 | 0401 | 040101 | 230.0 | 173.0 | 惣菜 | 御飯類 | 弁当類 |
解説:
このコードは、'../data' ディレクトリにある 'P_df_product_full_UTF-8_noh.csv' という名前の CSV ファイルを読み込む。names'パラメータでカラム名を指定し、'c_names'と呼ばれる文字列のリストとして使用する。dtype」パラメータは、「category_major_cd」、「category_medium_cd」、「category_small_cd」という名前の列のデータ型を「str」(文字列)で指定する。encoding」パラメータは、CSVファイルが「UTF-8」を使用してエンコードされることを指定する。最後に、'header' パラメータは、ファイルにヘッダー行がないことを示すために 'None' に設定される。
出来上がったデータフレームは変数 'df_product_full' に代入され、 'head' メソッドを用いて最初の3行が表示される。
# コード例2(先に項目名を定義する)
c_names = ['product_cd','category_major_cd','category_medium_cd',
'category_small_cd','unit_price','unit_cost',
'category_major_name','category_medium_name','category_small_name']
df_product_full = pd.read_csv('../data/P_df_product_full_UTF-8_noh.csv',
names=c_names,
dtype={'category_major_cd':str,
'category_medium_cd':str,
'category_small_cd':str},
encoding='UTF-8', header=None)
df_product_full.head(3)
product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost | category_major_name | category_medium_name | category_small_name | |
---|---|---|---|---|---|---|---|---|---|
0 | P040101001 | 04 | 0401 | 040101 | 198.0 | 149.0 | 惣菜 | 御飯類 | 弁当類 |
1 | P040101002 | 04 | 0401 | 040101 | 218.0 | 164.0 | 惣菜 | 御飯類 | 弁当類 |
2 | P040101003 | 04 | 0401 | 040101 | 230.0 | 173.0 | 惣菜 | 御飯類 | 弁当類 |
解説:
このコードは、read_csv関数を使用して、CSVファイルをカスタマイズされたカラム名を持つPandas DataFrameに読み込むものです。
以下、コードの解説をします。
c_names: カスタマイズされたカラム名を定義する文字列のリストです。
pd.read_csv(): CSVファイルを読み込んでDataFrameを返すPandas関数です。
'../data/P_df_product_full_UTF-8_noh.csv': 読み込むCSVファイルのファイルパスです。
names=c_names: c_namesリストを使ってカスタマイズしたカラム名を指定します。
dtype={'category_major_cd':str, 'category_medium_cd':str, 'category_small_cd':str}: category_major_cd、category_medium_cd、category_small_cdのカラムのデータ型をstrにすることを指定する。
encoding='UTF-8': ファイルのエンコーディングが'UTF-8'であることを指定します。
header=None: CSVファイルにヘッダー行がないことを指定します。
最後に、head()メソッドを使用して、結果のDataFrameを表示し、最初の3行を表示します。
P-099: 093で作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。
|ファイル形式|ヘッダ有無|文字エンコーディング| |:–:|:–:|:–:| |TSV(タブ区切り)|有り|UTF-8|
ファイル出力先のパスは以下のようにすること
|出力先| |:–:| |./data|
# 解答ファイルの置き場所が設問ファイルと異なるため、パスが'../data'となっている点に注意
df_product_full.to_csv('../data/P_df_product_full_UTF-8_header.tsv',
sep='\t', encoding='UTF-8', index=False)
解説:
このコードは、df_product_fullというpandasのDataFrameの内容を、'../d/P_df_product_full_UTF-8_header.tsv'にあるTSV (Tab-Separated Values) ファイルに書いています。
.to_csv() メソッドは df_product_full DataFrame に対して呼び出され、これはデータを CSV (Comma-Separated Values) または TSV ファイルにエクスポートするために pandas が提供する方法です。
メソッドの第一引数である '../d/P_df_product_full_UTF-8_header.tsv' は、DataFrame を保存するためのファイルパスとファイル名です。ファイルは./d/ディレクトリにP_df_product_full_UTF-8_header.tsvという名で保存されます。
第2引数のsep='˶'は、ファイル内の値をカンマの代わりにタブで区切ることを指定する。
第3引数のencoding='UTF-8'は、ファイルのエンコーディングを、Unicode文字の標準エンコーディングであるUTF-8にすることを指定するものである。
第4引数のindex=Falseは、DataFrameのインデックスを出力ファイルに含めないことを指定する。
P-100: 099で作成した以下形式のファイルを読み込み、データを3件を表示させて正しく取り込まれていることを確認せよ。
|ファイル形式|ヘッダ有無|文字エンコーディング| |:–:|:–:|:–:| |TSV(タブ区切り)|有り|UTF-8|
# コード例1(read_table)
# 解答ファイルの置き場所が設問ファイルと異なるため、パスが'../data'となっている点に注意
df_product_full = pd.read_table('../data/P_df_product_full_UTF-8_header.tsv',
dtype={'category_major_cd':str,
'category_medium_cd':str,
'category_small_cd':str},
encoding='UTF-8')
df_product_full.head(3)
product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost | category_major_name | category_medium_name | category_small_name | |
---|---|---|---|---|---|---|---|---|---|
0 | P040101001 | 04 | 0401 | 040101 | 198.0 | 149.0 | 惣菜 | 御飯類 | 弁当類 |
1 | P040101002 | 04 | 0401 | 040101 | 218.0 | 164.0 | 惣菜 | 御飯類 | 弁当類 |
2 | P040101003 | 04 | 0401 | 040101 | 230.0 | 173.0 | 惣菜 | 御飯類 | 弁当類 |
解説:
このコードでは、'../data/P_df_product_full_UTF-8_header.tsv'にあるTSV (Tab-Separated Values) ファイルからdf_product_fullというpandas DataFrameにデータを読み込んでいます。
ファイルからデータを読み込むために、pd.read_table()関数が呼び出されます。関数の第一引数である '../data/P_df_product_full_UTF-8_header.tsv' は、読み込むファイルのパスと名前を指定します。
関数の第二引数dtype={'category_major_cd':str, 'category_medium_cd':str, 'category_small_cd':str} は、DataFrameの特定の列のデータ型を指定する。具体的には、category_major_cd、category_medium_cd、category_small_cdの各列をstring(str)型に設定しています。
この関数の第3引数encoding='UTF-8'は、ファイルのエンコーディングがUTF-8であることを指定する。
そして、出来上がったDataFrameであるdf_product_fullをhead(3)メソッドで表示すると、DataFrameの最初の3行が表示されます。
# コード例2(read_csv)
df_product_full = pd.read_csv('../data/P_df_product_full_UTF-8_header.tsv',
dtype={'category_major_cd':str,
'category_medium_cd':str,
'category_small_cd':str},
sep='\t', encoding='UTF-8')
df_product_full.head(3)
product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost | category_major_name | category_medium_name | category_small_name | |
---|---|---|---|---|---|---|---|---|---|
0 | P040101001 | 04 | 0401 | 040101 | 198.0 | 149.0 | 惣菜 | 御飯類 | 弁当類 |
1 | P040101002 | 04 | 0401 | 040101 | 218.0 | 164.0 | 惣菜 | 御飯類 | 弁当類 |
2 | P040101003 | 04 | 0401 | 040101 | 230.0 | 173.0 | 惣菜 | 御飯類 | 弁当類 |
解説:
このコードでは、'../data/P_df_product_full_UTF-8_header.tsv'にあるTSV (Tab-Separated Values) ファイルからdf_product_fullというpandas DataFrameにデータを読み込んでいます。
ファイルからデータを読み込むために、pd.read_csv()関数が呼び出されます。関数の第一引数である '../data/P_df_product_full_UTF-8_header.tsv' は、読み込むファイルのパスと名前を指定します。
関数の第二引数dtype={'category_major_cd':str, 'category_medium_cd':str, 'category_small_cd':str} は、DataFrameの特定の列のデータ型を指定する。具体的には、category_major_cd、category_medium_cd、category_small_cdの各列をstring(str)型に設定しています。
第3引数のsep='˶'は、ファイル内の値をカンマではなくタブで区切ることを指定する。
第4引数のencoding='UTF-8'は、ファイルのエンコーディングがUTF-8であることを指定する。
そして、出来上がったDataFrameであるdf_product_fullをhead(3)メソッドで表示すると、DataFrameの最初の3行が表示される。
Comment