この記事はデータサイエンスを勉強しながら、データサイエンス協会が提供する__データサイエンス100本ノック(構造化データ加工編)__を解く過程を自分用にまとめたものです。
チャプター | リンク | チャプター | リンク |
---|---|---|---|
P-001~P-016 | part1 | P-052~P-062 | part6 |
P-017~P-022 | part2 | P-063~P-068 | part7 |
P-023~P-031 | part3 | P-069~P-078 | part8 |
P-032~P-039 | part4 | P-079~P-088 | part9 |
P-040~P-051 | part5 | P-089~P-100 | part10 |
- 差分・変化率
diff()
- データをずらす
shift()
- 切り捨て
math.floor()
pd.pivot_table()
- インデックスの割り当て
set_index()
stack()
pd.to_datetime()
strftime()
astype()
P-040
P-040: 全ての店舗と全ての商品を組み合わせると何件のデータとなるか調査したい。店舗(df_store)と商品(df_product)を直積した件数を計算せよ。
df_store_tmp = df_store.copy()
df_product_tmp = df_product.copy()
df_store_tmp['key'] = 0
df_product_tmp['key'] = 0
len(pd.merge(df_store_tmp, df_product_tmp, how='outer', on='key'))
直積を計算するためにdf_store
とdf_product
を外部結合し要素数を取得する。
共通列がないため新たにkey
という列を追加した。
データフレームを直接いじるため、最初にコピーを取っておく。
直積を計算するだけなので以下のコードではいけないのかな...
len(df_store) * len(df_product)
P-041 diff()
, shift()
P-041: レシート明細データフレーム(df_receipt)の売上金額(amount)を日付(sales_ymd)ごとに集計し、前日からの売上金額増減を計算せよ。なお、計算結果は10件表示すればよい。
df_tmp = df_receipt.groupby('sales_ymd').amount.sum().reset_index()
df_tmp = pd.concat([df_tmp, df_tmp['amount'].diff()], axis=1)
df_tmp.columns = ['sales_ymd', 'amount', 'amount_diff']
df_tmp.head(10)
diff()
:行または列の差分・変化率を取得。
第一引数で比較したい行を渡す。(デフォルトは1, 負数を渡すことも可能)
axis=1
で列をの差分を得る。
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)
解答はこの通り
差分だけではなく元のデータをずらした値もデータフレームに結合した方がわかりやすい。
shift()
:データを行または列方向にずらす。
引数にずらし幅を指定できる。(デフォルトは1)
列方向にずらす場合は引数にaxis=1
を指定。
P-042
P-042: レシート明細データフレーム(df_receipt)の売上金額(amount)を日付(sales_ymd)ごとに集計し、各日付のデータに対し、1日前、2日前、3日前のデータを結合せよ。結果は10件表示すればよい。
df_amount_sum = df_receipt.groupby('sales_ymd').amount.sum().reset_index()
df_1da = df_amount_sum.shift(1)
df_2da = df_amount_sum.shift(2)
df_3da = df_amount_sum.shift(3)
df_tmp = pd.concat([df_amount_sum, df_1da['amount'], df_2da['amount'], df_3da['amount']], axis=1)
df_tmp.columns = ['sales_ymd', 'amount', '1days_ago', '2days_ago', '3days_ago']
df_tmp.dropna().head(10)
df_receipt
のsales_ymd
でグルーピングし、amountで和を取ったデータフレーム(df_amount_sum)をshift()
を使って1,2,3日ずらしたデータフレームをconcat()
で横方向に結合している。(変数名のdf_1da
は__1 days ago__の意味)
P-043 math.floor()
, pd.pivot_table()
P-043: レシート明細データフレーム(df_receipt)と顧客データフレーム(df_customer)を結合し、性別(gender)と年代(ageから計算)ごとに売上金額(amount)を合計した売上サマリデータフレーム(df_sales_summary)を作成せよ。性別は0が男性、1が女性、9が不明を表すものとする。
ただし、項目構成は年代、女性の売上金額、男性の売上金額、性別不明の売上金額の4項目とすること(縦に年代、横に性別のクロス集計)。また、年代は10歳ごとの階級とすること。
df_tmp = pd.merge(df_receipt, df_customer, how='inner', on='customer_id')
df_tmp['age_band'] = df_tmp['age'].apply(lambda x: math.floor(x/10)*10)
df_sales_summary = pd.pivot_table(df_tmp, index='age_band', columns='gender_cd', values='amount', aggfunc=sum).reset_index()
df_sales_summary.columns = ['age_band', 'male', 'female', 'anknown']
df_sales_summary
math.floor()
:小数点以下を切り捨て。(切り上げはmath.ceil()
)
年代の計算方法(age=22の場合)
math.floor(22/10)*10 = math.floor(2.2)*10 = 2.0(小数点以下を切り捨てた)*10 = 20(代)
pd.pivot_table()
:カテゴリ毎の統計量などを算出。pandas.DataFrame型を返す。
引数は以下の通り。
-
data
:参照にするデータフレーム -
index
:元データの列名。結果の行見出しとなる。 -
columns
:元データの列名。結果の列見出しとなる。 -
values
に元データの列名を指定すると、その列に対する結果のみ計算する。 -
aggfunc
に関数を指定するといろんな値を計算できる。(デフォルトは平均値)
P-044 set_index()
, stack()
P-044: 前設問で作成した売上サマリデータフレーム(df_sales_summary)は性別の売上を横持ちさせたものであった。このデータフレームから性別を縦持ちさせ、年代、性別コード、売上金額の3項目に変換せよ。ただし、性別コードは男性を'00'、女性を'01'、不明を'99'とする。
df_tmp = df_sales_summary.set_index('age_band').stack().reset_index()
df_tmp.columns = ['age_band', 'gender_cd', 'amount']
df_tmp.replace({'male':'00', 'female':'01', 'anknown':'99'})
解答は以下のように一行でまとめている。
df_sales_summary = df_sales_summary.set_index('era'). \
stack().reset_index().replace({'female':'01',
'male':'00',
'unknown':'99'}).rename(columns={'level_1':'gender_cd', 0: 'amount'})
set_index()
:既存の列をインデックスに割り当てる。
stack()
:列から行へピポット。(行方向に並び替え)
pandas.Series型を返す。
他にも行から列へのピポットやテーブルの再形成は以下を参照。
P-045 pd.to_datetime()
, strftime()
P-045: 顧客データフレーム(df_customer)の生年月日(birth_day)は日付型(Date)でデータを保有している。これをYYYYMMDD形式の文字列に変換し、顧客ID(customer_id)とともに抽出せよ。データは10件を抽出すれば良い。
pd.concat([df_customer['customer_id'], pd.to_datetime(df_customer['birth_day']).dt.strftime('%Y%m%d')], axis=1).head(10)
pd.to_datetime()
:文字列データや数値データを日付データである__datetime64型__に変換する。
df_customer['birth_day']
のデータ型は__object型__であるため、そのままではstrftime()
メソッドを適用することができない。
そのため、まずto_datetime()
で__datetime64型__に変換する。
strftime()
:datetime.date型の日付を整形する。
※pd.to_datetime()メソッドを使わない場合(applyを使って)
pd.concat([df_customer['customer_id'], df_customer['birth_day'].apply(lambda x: x.strftime('%Y%m%d'))], axis=1).head(10)
P-046
P-046: 顧客データフレーム(df_customer)の申し込み日(application_date)はYYYYMMDD形式の文字列型でデータを保有している。これを日付型(dateやdatetime)に変換し、顧客ID(customer_id)とともに抽出せよ。データは10件を抽出すれば良い。
pd.concat([df_customer['customer_id'], pd.to_datetime(df_customer['application_date'])], axis=1).head(10)
P-047 astype()
P-047: レシート明細データフレーム(df_receipt)の売上日(sales_ymd)はYYYYMMDD形式の数値型でデータを保有している。これを日付型(dateやdatetime)に変換し、レシート番号(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)
astype()
:データ型の変換
df_receipt
の'sales_ymd'
列のデータ型は__int64型__になっている。
そのままto_datetime()
メソッドを使って変換しようとすると下図のようになる。
そのため、astype()
メソッドを使って__str型__に変換した後、__datetime型__に変換している。
P-048
P-048: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)は数値型のUNIX秒でデータを保有している。これを日付型(dateやdatetime)に変換し、レシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。データは10件を抽出すれば良い。
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
pd.to_datetime(df_receipt['sales_epoch'], unit='s')], axis=1).head(10)
UNIX時間の表記は秒数なので、 unit='s'
として表記を秒単位にします。
(UNIX時間;コンピューター上での時刻表現方法の一つです。 「1970年1月1日午前0時(UTC)」からの経過秒数)
※datetimeモジュールを使った方法
import datetime
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
df_receipt['sales_epoch'].apply(lambda x: datetime.datetime.fromtimestamp(x))], axis=1).head(10)
P-049
P-049: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)を日付型(timestamp型)に変換し、"年"だけ取り出してレシート番号(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], axis=1)
P-50
P-050: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)を日付型(timestamp型)に変換し、"月"だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。なお、"月"は0埋め2桁で取り出すこと。データは10件を抽出すれば良い。
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
pd.to_datetime(df_receipt['sales_epoch'], unit='s').dt.strftime('%m')], axis=1).head(10)
P-51
P-051: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)を日付型(timestamp型)に変換し、"日"だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。なお、"日"は0埋め2桁で取り出すこと。データは10件を抽出すれば良い。
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
pd.to_datetime(df_receipt['sales_epoch'], unit='s').dt.strftime('%d')], axis=1).head(10)