0
1

More than 3 years have passed since last update.

データサイエンス100本ノック(構造化データ加工編)をやってみた part5(P-040~P-051)

Last updated at Posted at 2021-08-07

 この記事はデータサイエンスを勉強しながら、データサイエンス協会が提供するデータサイエンス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)を直積した件数を計算せよ。

p040.py
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_storedf_productを外部結合し要素数を取得する。
 共通列がないため新たにkeyという列を追加した。
 データフレームを直接いじるため、最初にコピーを取っておく。

 直積を計算するだけなので以下のコードではいけないのかな...

len(df_store) * len(df_product)

P-041 diff(), shift()

P-041: レシート明細データフレーム(df_receipt)の売上金額(amount)を日付(sales_ymd)ごとに集計し、前日からの売上金額増減を計算せよ。なお、計算結果は10件表示すればよい。

myp041.py
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で列をの差分を得る。

p041.py
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件表示すればよい。

p042.py
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_receiptsales_ymdでグルーピングし、amountで和を取ったデータフレーム(df_amount_sum)をshift()を使って1,2,3日ずらしたデータフレームをconcat()で横方向に結合している。(変数名のdf_1da1 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歳ごとの階級とすること。

p043.py
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'とする。

p044.py
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'})

 解答は以下のように一行でまとめている。

p044.py
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件を抽出すれば良い。

p045.py
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件を抽出すれば良い。

p046.py
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件を抽出すれば良い。

p047.py
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()メソッドを使って変換しようとすると下図のようになる。

スクリーンショット 2021-08-07 15.24.06.png

 そのため、astype()メソッドを使ってstr型に変換した後、datetime型に変換している。

P-048

P-048: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)は数値型のUNIX秒でデータを保有している。これを日付型(dateやdatetime)に変換し、レシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。データは10件を抽出すれば良い。

p048.py
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件を抽出すれば良い。

p049.py
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件を抽出すれば良い。

p050.py
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件を抽出すれば良い。

p051.py
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)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1