データサイエンス100本ノック(構造化データ加工編)- R Part 4 (Q61 to Q80)の解説です。
参照(Reference) : 「データサイエンティスト協会スキル定義委員」の「データサイエンス100本ノック(構造化データ加工編)」
The Data Scientist Society Github :
Data Science 100 Knocks (Structured Data Processing) URL :
はじめに
- 初めに以下のセルを実行してください
- 必要なライブラリのインポートとデータベース(PostgreSQL)からのデータ読み込みを行います
- 利用が想定されるライブラリは以下セルでインポートしています
- その他利用したいライブラリがあればinstall.packages()で適宜インストールしてください
- 名前、住所等はダミーデータであり、実在するものではありません
require("RPostgreSQL")
require("tidyr")
require("dplyr")
require("stringr")
require("caret")
require("lubridate")
require("rsample")
require("recipes")
require("themis")
host <- "db"
port <- Sys.getenv()["PG_PORT"]
dbname <- Sys.getenv()["PG_DATABASE"]
user <- Sys.getenv()["PG_USER"]
password <- Sys.getenv()["PG_PASSWORD"]
con <- dbConnect(PostgreSQL(), host=host, port=port, dbname=dbname, user=user, password=password)
df_customer <- dbGetQuery(con,"SELECT * FROM customer")
df_category <- dbGetQuery(con,"SELECT * FROM category")
df_product <- dbGetQuery(con,"SELECT * FROM product")
df_receipt <- dbGetQuery(con,"SELECT * FROM receipt")
df_store <- dbGetQuery(con,"SELECT * FROM store")
df_geocode <- dbGetQuery(con,"SELECT * FROM geocode")
Loading required package: RPostgreSQL Loading required package: DBI Loading required package: tidyr Loading required package: dplyr Attaching package: ‘dplyr’ The following objects are masked from ‘package:stats’: filter, lag The following objects are masked from ‘package:base’: intersect, setdiff, setequal, union Loading required package: stringr Loading required package: caret Loading required package: ggplot2 Loading required package: lattice Loading required package: lubridate Attaching package: ‘lubridate’ The following objects are masked from ‘package:base’: date, intersect, setdiff, union Loading required package: rsample Loading required package: recipes Attaching package: ‘recipes’ The following object is masked from ‘package:stringr’: fixed The following object is masked from ‘package:stats’: step Loading required package: themis Registered S3 methods overwritten by 'themis': method from bake.step_downsample recipes bake.step_upsample recipes prep.step_downsample recipes prep.step_upsample recipes tidy.step_downsample recipes tidy.step_upsample recipes tunable.step_downsample recipes tunable.step_upsample recipes Attaching package: ‘themis’ The following objects are masked from ‘package:recipes’: step_downsample, step_upsample
演習問題
R-061: レシート明細データ(df_receipt)の売上金額(amount)を顧客ID(customer_id)ごとに合計し、売上金額合計を常用対数化(底10)して顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが”Z”から始まるのものは非会員を表すため、除外して計算すること。
df_receipt %>%
filter(!grepl("^Z", customer_id)) %>%
group_by(customer_id) %>%
summarise(sum_amount = sum(amount), .groups = "drop") %>%
mutate(log_amount = log((sum_amount + 0.5), base = 10)) %>%
slice(1:10)
customer_id | sum_amount | log_amount |
---|---|---|
<chr> | <int> | <dbl> |
CS001113000004 | 1298 | 3.113442 |
CS001114000005 | 626 | 2.796921 |
CS001115000010 | 3044 | 3.483516 |
CS001205000004 | 1988 | 3.298526 |
CS001205000006 | 3337 | 3.523421 |
CS001211000025 | 456 | 2.659441 |
CS001212000027 | 448 | 2.651762 |
CS001212000031 | 296 | 2.472025 |
CS001212000046 | 228 | 2.358886 |
CS001212000070 | 456 | 2.659441 |
解説:
このコードは、df_receiptデータフレームに対して、以下の処理を実行します。
filter関数を使用して、customer_idが "Z "で始まる行をフィルタリングします。
group_by関数を使用して、customer_idごとにデータをグループ化します。
summarise関数を使用して合計を取ることにより、各顧客IDの金額列を要約する。
.groups = "drop "引数を使用して、ステップ2で作成したグループを削除します。
sum_amountの対数に0.5を加えたものを10進数で表したlog_amountという新しい列を作成します。
slice関数を使用して、結果のデータフレームから最初の10行を選択します。
全体として、このコードはdf_receiptデータフレームに各顧客が使った合計金額の対数を持つ新しい列を作成し、これは歪んだデータを正規化するためによく使われる変換です。
R-062: レシート明細データ(df_receipt)の売上金額(amount)を顧客ID(customer_id)ごとに合計し、売上金額合計を自然対数化(底e)して顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが”Z”から始まるのものは非会員を表すため、除外して計算すること。
df_receipt %>%
filter(!grepl("^Z", customer_id)) %>%
group_by(customer_id) %>%
summarise(sum_amount = sum(amount), .groups = "drop") %>%
mutate(log_amount = log(sum_amount + 0.5)) %>%
slice(1:10)
customer_id | sum_amount | log_amount |
---|---|---|
<chr> | <int> | <dbl> |
CS001113000004 | 1298 | 7.168965 |
CS001114000005 | 626 | 6.440149 |
CS001115000010 | 3044 | 8.021092 |
CS001205000004 | 1988 | 7.595136 |
CS001205000006 | 3337 | 8.112977 |
CS001211000025 | 456 | 6.123589 |
CS001212000027 | 448 | 6.105909 |
CS001212000031 | 296 | 5.692047 |
CS001212000046 | 228 | 5.431536 |
CS001212000070 | 456 | 6.123589 |
解説:
このコードは、df_receiptというデータフレームを処理します。以下のステップを実行します。
customer_idが "Z "で始まる行をフィルタリングします。これは、dplyrパッケージのfilter()関数と条件として!grepl()関数を使用して行われます。
dplyrパッケージのgroup_by()関数を使用して、残りの行をcustomer_idでグループ化します。
金額列の sum() 関数を使用して、各顧客の購入金額の合計を計算します。これは、dplyrパッケージのsummarise()関数を使用して行われます。
sum_amount列の自然対数に0.5を加算したlog_amountという新しい列を追加します。これは、dplyrパッケージのmutate()関数とベースRのlog()関数を使用して行われます。
最後に、dplyrパッケージのslice()関数を使用して、結果のデータフレームの最初の10行を選択する。
R-063: 商品データ(df_product)の単価(unit_price)と原価(unit_cost)から各商品の利益額を算出し、結果を10件表示せよ。
df_product %>%
mutate(unit_profit = unit_price - unit_cost) %>%
slice(1:10)
product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost | unit_profit |
---|---|---|---|---|---|---|
<chr> | <chr> | <chr> | <chr> | <int> | <int> | <int> |
P040101001 | 04 | 0401 | 040101 | 198 | 149 | 49 |
P040101002 | 04 | 0401 | 040101 | 218 | 164 | 54 |
P040101003 | 04 | 0401 | 040101 | 230 | 173 | 57 |
P040101004 | 04 | 0401 | 040101 | 248 | 186 | 62 |
P040101005 | 04 | 0401 | 040101 | 268 | 201 | 67 |
P040101006 | 04 | 0401 | 040101 | 298 | 224 | 74 |
P040101007 | 04 | 0401 | 040101 | 338 | 254 | 84 |
P040101008 | 04 | 0401 | 040101 | 420 | 315 | 105 |
P040101009 | 04 | 0401 | 040101 | 498 | 374 | 124 |
P040101010 | 04 | 0401 | 040101 | 580 | 435 | 145 |
解説:
このコードはデータフレームdf_productを受け取り、mutate()関数を使用して新しい列unit_profitを作成します。unit_profit列は、df_productの既存の列であるunit_priceとunit_costの差として計算されています。
そして、slice()関数を使用して、新しいunit_profitカラムを含む結果のデータフレームの最初の10行を表示します。
R-064: 商品データ(df_product)の単価(unit_price)と原価(unit_cost)から、各商品の利益率の全体平均を算出せよ。ただし、単価と原価には欠損が生じていることに注意せよ。
df_product %>%
mutate(unit_profit_rate = (unit_price - unit_cost) / unit_price) %>%
summarise(total_mean = mean(unit_profit_rate, na.rm = TRUE))
total_mean |
---|
<dbl> |
0.2491139 |
解説:
このコードは、df_productという名前のデータフレームに対していくつかの計算を実行しています。
mutateはunit_profit_rateという新しい列を作成するために使用されています。この列は、単価から単価を引き、その結果を単価で割ることによって計算されます。
次にsummarise関数を使用してunit_profit_rate列の平均を計算します。その結果はtotal_meanという新しい列に代入されます。
要約すると、このコードはdf_productで販売された製品の各ユニットの平均利益率を計算していることになります。
R-065: 商品データ(df_product)の各商品について、利益率が30%となる新たな単価を求めよ。ただし、1円未満は切り捨てること。そして結果を10件表示させ、利益率がおよそ30%付近であることを確認せよ。ただし、単価(unit_price)と原価(unit_cost)には欠損が生じていることに注意せよ。
df_product[c("product_cd", "unit_price", "unit_cost")] %>%
mutate(new_price = trunc(unit_cost / 0.7)) %>%
mutate(new_profit_rate = (new_price - unit_cost)/ new_price) %>% slice(1:10)
product_cd | unit_price | unit_cost | new_price | new_profit_rate |
---|---|---|---|---|
<chr> | <int> | <int> | <dbl> | <dbl> |
P040101001 | 198 | 149 | 212 | 0.2971698 |
P040101002 | 218 | 164 | 234 | 0.2991453 |
P040101003 | 230 | 173 | 247 | 0.2995951 |
P040101004 | 248 | 186 | 265 | 0.2981132 |
P040101005 | 268 | 201 | 287 | 0.2996516 |
P040101006 | 298 | 224 | 320 | 0.3000000 |
P040101007 | 338 | 254 | 362 | 0.2983425 |
P040101008 | 420 | 315 | 450 | 0.3000000 |
P040101009 | 498 | 374 | 534 | 0.2996255 |
P040101010 | 580 | 435 | 621 | 0.2995169 |
解説:
このコードは、以下のステップを実行します。
データフレーム "df_product "から "product_cd"、"unit_price"、"unit_cost "の列のみを選択する。
単価を0.7で割り、その結果を最も近い整数に切り捨てることにより、「new_price」という新しい列を作成する。これは、商品の価格を一定の割合で下げるための任意の計算である。
新しい価格から単価を引き、その結果を新しい価格で割ることによって、「new_profit_rate」という別の新しい列を作成します。これは、新価格の利益率を計算するためのものです。
最後に、出来上がったデータフレームから最初の10行を選択します。
R-066: 商品データ(df_product)の各商品について、利益率が30%となる新たな単価を求めよ。今回は、1円未満を丸めること(四捨五入または偶数への丸めで良い)。そして結果を10件表示させ、利益率がおよそ30%付近であることを確認せよ。ただし、単価(unit_price)と原価(unit_cost)には欠損が生じていることに注意せよ。
df_product[c("product_cd", "unit_price", "unit_cost")] %>%
mutate(new_price = round(unit_cost / 0.7)) %>%
mutate(new_profit_rate = (new_price - unit_cost) / new_price) %>%
slice(1:10)
product_cd | unit_price | unit_cost | new_price | new_profit_rate |
---|---|---|---|---|
<chr> | <int> | <int> | <dbl> | <dbl> |
P040101001 | 198 | 149 | 213 | 0.3004695 |
P040101002 | 218 | 164 | 234 | 0.2991453 |
P040101003 | 230 | 173 | 247 | 0.2995951 |
P040101004 | 248 | 186 | 266 | 0.3007519 |
P040101005 | 268 | 201 | 287 | 0.2996516 |
P040101006 | 298 | 224 | 320 | 0.3000000 |
P040101007 | 338 | 254 | 363 | 0.3002755 |
P040101008 | 420 | 315 | 450 | 0.3000000 |
P040101009 | 498 | 374 | 534 | 0.2996255 |
P040101010 | 580 | 435 | 621 | 0.2995169 |
解説:
このコードは、商品データ・フレーム(df_product)に対していくつかの計算を実行しています。
まず、"product_cd"、"unit_price"、"unit_cost "の3つのカラムを選択しています。
これは、"unit_cost "を0.7で割り(つまり、コストを30%削減)、その結果をround()関数を使って最も近い整数に丸めることによって算出されます。
次に、新しい列「new_profit_rate」を作成します。これは、「new_price」から「unit_cost」を引き、その結果を「new_price」で割ることで算出します。
最後に、出来上がったデータフレームの最初の10行を選択し、表示します。
このコードは、基本的に、生産コストを30%削減した場合の各製品の新しい価格と利益率を計算します。
R-067: 商品データ(df_product)の各商品について、利益率が30%となる新たな単価を求めよ。今回は、1円未満を切り上げること。そして結果を10件表示させ、利益率がおよそ30%付近であることを確認せよ。ただし、単価(unit_price)と原価(unit_cost)には欠損が生じていることに注意せよ。
df_product[c("product_cd", "unit_price", "unit_cost")] %>%
mutate(new_price = ceiling(unit_cost / 0.7)) %>%
mutate(new_profit_rate = (new_price - unit_cost) / new_price) %>%
slice(1:10)
product_cd | unit_price | unit_cost | new_price | new_profit_rate |
---|---|---|---|---|
<chr> | <int> | <int> | <dbl> | <dbl> |
P040101001 | 198 | 149 | 213 | 0.3004695 |
P040101002 | 218 | 164 | 235 | 0.3021277 |
P040101003 | 230 | 173 | 248 | 0.3024194 |
P040101004 | 248 | 186 | 266 | 0.3007519 |
P040101005 | 268 | 201 | 288 | 0.3020833 |
P040101006 | 298 | 224 | 320 | 0.3000000 |
P040101007 | 338 | 254 | 363 | 0.3002755 |
P040101008 | 420 | 315 | 451 | 0.3015521 |
P040101009 | 498 | 374 | 535 | 0.3009346 |
P040101010 | 580 | 435 | 622 | 0.3006431 |
解説:
このコードは、2つの新しい変数 new_price と new_profit_rate を追加して、df_product データフレームを操作します。
mutate(new_price = ceiling(unit_cost / 0.7)) は、ceiling(unit_cost / 0.7) という式の結果を含む新しい列 new_price を作成します。ceiling()関数は、unit_cost / 0.7という式の結果を最も近い整数に丸めます。この結果、単価より30%高い新価格になります。
mutate(new_profit_rate = (new_price - unit_cost) / new_price) は新しい列 new_profit_rate を作成し、 (new_price - unit_cost) / new_price という式の結果を格納します。この式は、新価格に占める利益の割合である新利益率を計算します。これは、新価格と単価の差を新価格で割った値として計算されます。
slice(1:10)関数は、出来上がったデータフレームの最初の10行を表示するために使用されます。
R-068: 商品データ(df_product)の各商品について、消費税率10%の税込み金額を求めよ。1円未満の端数は切り捨てとし、結果を10件表示せよ。ただし、単価(unit_price)には欠損が生じていることに注意せよ。
head(cbind(product_cd = df_product$product_cd,
df_product$unit_price,
tax_price = trunc(df_product$unit_price * 1.1)), 10)
product_cd | unit_price | tax_price |
---|---|---|
P040101001 | 198 | 217 |
P040101002 | 218 | 239 |
P040101003 | 230 | 253 |
P040101004 | 248 | 272 |
P040101005 | 268 | 294 |
P040101006 | 298 | 327 |
P040101007 | 338 | 371 |
P040101008 | 420 | 462 |
P040101009 | 498 | 547 |
P040101010 | 580 | 638 |
解説:
このコードでは、cbind関数を使用して、df_productデータフレームの3つの列、すなわちproduct_cd、unit_price、tax_priceを新しいデータフレームに結合しています。次にhead関数を用いて、この新しいデータフレームの最初の10行を表示する。
新しいデータフレームの最初の列はproduct_cdと表示され、df_productのproduct_cd列の値が含まれています。2列目はdf_product$unit_priceというラベルで、df_productのunit_price列の値が格納されています。
3番目の列はtax_priceというラベルで、unit_price列の各値に1.1を掛けた結果にtrunc関数を適用して作成されています。これは、10%の税金を含む製品の価格を計算するものです。
cbind関数がすでにdf_productからunit_price列を抽出しているので、2列目の列名にdf_product$を使用する必要はないことに注意してください。
R-069: レシート明細データ(df_receipt)と商品データ(df_product)を結合し、顧客毎に全商品の売上金額合計と、カテゴリ大区分コード(category_major_cd)が”07″(瓶詰缶詰)の売上金額合計を計算の上、両者の比率を求めよ。抽出対象はカテゴリ大区分コード”07″(瓶詰缶詰)の売上実績がある顧客のみとし、結果を10件表示せよ。
# コード例1
df_tmp_1 <- df_receipt %>%
group_by(customer_id) %>%
summarise(sum_all=sum(amount))
df_tmp_2 <- inner_join(df_receipt, df_product[c("product_cd","category_major_cd")], by="product_cd") %>%
filter(category_major_cd == "07") %>% group_by(customer_id) %>%
summarise(sum_07 = sum(amount), .groups = "drop")
inner_join(df_tmp_1, df_tmp_2, by = "customer_id") %>%
mutate(sales_rate = sum_07 / sum_all) %>%
slice(1:10)
customer_id | sum_all | sum_07 | sales_rate |
---|---|---|---|
<chr> | <int> | <int> | <dbl> |
CS001113000004 | 1298 | 1298 | 1.0000000 |
CS001114000005 | 626 | 486 | 0.7763578 |
CS001115000010 | 3044 | 2694 | 0.8850197 |
CS001205000004 | 1988 | 346 | 0.1740443 |
CS001205000006 | 3337 | 2004 | 0.6005394 |
CS001212000027 | 448 | 200 | 0.4464286 |
CS001212000031 | 296 | 296 | 1.0000000 |
CS001212000046 | 228 | 108 | 0.4736842 |
CS001212000070 | 456 | 308 | 0.6754386 |
CS001213000018 | 243 | 145 | 0.5967078 |
解説:
# コード例2
本コードは、以下の処理を行う。
df_receiptデータフレームの各顧客が使った金額の合計を計算し、df_tmp_1に格納する。
df_receiptデータフレームとdf_productデータフレームをproduct_cdで結合し、category_major_cdが「07」に等しい行だけをフィルタリングする。
フィルタリングされたデータフレームの各顧客の金額列の合計を計算し、df_tmp_2に格納する。
df_tmp_1とdf_tmp_2データフレームをcustomer_idで結合する。
各顧客のメジャーコード "07 "の売上率を計算し、sales_rateという新しいカラムに格納する。
結果のデータフレームをスライスして、最初の10行を表示する。
inner_join関数は、共通の変数でデータフレームを結合するために使用されます。mutate 関数は、既存の列を基に計算された新しい列をデータフレームに追加します。slice関数は、データフレームから行のサブセットを抽出するために使用されます。
# コード例2(%>%ですべての処理をつなげる)
inner_join(df_receipt, df_product[c("product_cd","category_major_cd")], by="product_cd") %>% filter(category_major_cd == "07") %>%
group_by(customer_id) %>%
summarise(sum_07 = sum(amount), .groups = "drop") %>%
inner_join(df_receipt, by="customer_id") %>%
group_by(customer_id) %>%
summarise(sum_all=sum(amount), sum_07=max(sum_07), .groups = "drop") %>%
mutate(sales_rate = sum_07 / sum_all) %>%
slice(1:10)
customer_id | sum_all | sum_07 | sales_rate |
---|---|---|---|
<chr> | <int> | <int> | <dbl> |
CS001113000004 | 1298 | 1298 | 1.0000000 |
CS001114000005 | 626 | 486 | 0.7763578 |
CS001115000010 | 3044 | 2694 | 0.8850197 |
CS001205000004 | 1988 | 346 | 0.1740443 |
CS001205000006 | 3337 | 2004 | 0.6005394 |
CS001212000027 | 448 | 200 | 0.4464286 |
CS001212000031 | 296 | 296 | 1.0000000 |
CS001212000046 | 228 | 108 | 0.4736842 |
CS001212000070 | 456 | 308 | 0.6754386 |
CS001213000018 | 243 | 145 | 0.5967078 |
解説:
このコードは、カテゴリー07の商品について、その商品を最も多く購入した上位10名の顧客に対する販売率を計算しています。各行が何を行っているのか、その内訳を説明します。
inner_join(df_receipt, df_product[c("product_cd", "category_major_cd")], by="product_cd"): これは、df_receiptとdf_productのデータフレームをproduct_cd列に基づいて結合し、df_productのproduct_cdとcategory_major_cd列のみを保持する。その結果、df_receiptで購入した各商品のカテゴリに関する情報を持つ新しいデータフレームが出来上がります。
%>% filter(category_major_cd == "07"): ステップ1の結合データフレームをフィルタリングして、category_major_cdカラムが "07 "に等しい行のみを保持するようにします。
%>% group_by(customer_id) %>% summarise(sum_07 = sum(amount), .groups = "drop"): これは、ステップ2でフィルタリングされたデータフレームをcustomer_idでグループ化し、各グループのamountカラムの合計を計算する。その結果、各顧客がカテゴリー07の製品に費やした金額の合計を持つ新しいデータフレームが得られます。
%>% inner_join(df_receipt, by="customer_id"): これは、customer_id列に基づいて、ステップ3のデータフレームと元のdf_receiptデータフレームを結合する。その結果、各顧客がすべての商品に対して使った金額の合計を持つ新しいデータフレームができます。
%>% group_by(customer_id) %>% summarise(sum_all=sum(amount), sum_07=max(sum_07), .groups = "drop"): これはステップ4で結合したデータフレームをcustomer_idでグループ化し、各グループのamount列の合計と、各グループのsum_07列の最大値を計算するものです。その結果、各顧客が全商品に費やした金額の合計と、各顧客がカテゴリー07の商品に費やした金額の最大値を持つ新しいデータフレームが出来上がります。
%>% mutate(sales_rate = sum_07 / sum_all): カテゴリー07の商品に対する最大購入金額(sum_07)を全商品に対する合計金額(sum_all)で割ることで、各顧客の売上率を算出するものです。
%>% slice(1:10) : これは、売上率カラムに基づくデータフレームの上位10行のみを保持します。その結果、カテゴリー07製品の販売率が最も高い上位10人の顧客と、全製品に費やした合計金額、カテゴリー07製品に費やした最大金額が表示された新しいデータフレームになります。
R-070: レシート明細データ(df_receipt)の売上日(sales_ymd)に対し、顧客データ(df_customer)の会員申込日(application_date)からの経過日数を計算し、顧客ID(customer_id)、売上日、会員申込日とともに10件表示せよ(sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。
df_receipt[c("customer_id", "sales_ymd")] %>%
distinct(.,.keep_all=TRUE) %>% inner_join(df_customer[c("customer_id","application_date")], by="customer_id") %>% mutate(elapsed_days = as.integer( strptime(as.character(sales_ymd), "%Y%m%d") - strptime(application_date, "%Y%m%d"))) %>%
select(customer_id, sales_ymd, application_date, elapsed_days) %>%
slice(1:10)
customer_id | sales_ymd | application_date | elapsed_days |
---|---|---|---|
<chr> | <int> | <chr> | <int> |
CS006214000001 | 20181103 | 20150201 | 1371 |
CS008415000097 | 20181118 | 20150322 | 1337 |
CS028414000014 | 20170712 | 20150711 | 732 |
CS025415000050 | 20180821 | 20160131 | 933 |
CS003515000195 | 20190605 | 20150306 | 1552 |
CS024514000042 | 20181205 | 20151010 | 1152 |
CS040415000178 | 20190922 | 20150627 | 1548 |
CS027514000015 | 20191010 | 20151101 | 1439 |
CS025415000134 | 20190918 | 20150720 | 1521 |
CS021515000126 | 20171010 | 20150508 | 886 |
解説:
このコードは、以下のタスクを実行します。
df_receipt' データフレームから 'customer_id' と 'sales_ymd' のカラムを選択します。
すべてのカラムを保持したまま、重複する行を削除する(「distinct」)。
出来上がったデータフレームと 'df_customer' データフレームを 'customer_id' で結合します。
新しいカラム 'elapsed_days' を追加します。これは 'sales_ymd' カラムと 'application_date' カラムの間の日数の差を表すもので、整数に変換されます。
customer_id', 'sales_ymd', 'application_date', 'elapsed_days' カラムのみを選択する。
結果のデータフレームの最初の10行を返します。
コードの内部動作は、以下のステップに分けることができます。
# df_receipt から customer_id と sales_ymd 列を選択 df1 <- df_receipt[c("customer_id", "sales_ymd")] # 全ての列を保持したまま重複する行を削除 df2 <- distinct(df1, . keep_all = TRUE) # 顧客IDでdf_customerと結合 df3 <- inner_join(df2, df_customer[c("customer_id", "application_date")], by = "customer_id") #経過日数の新しい列を追加 df4 <- mutate(df3, elapsed_days = as. integer(strptime(as.character(sales_ymd), "%Y%m%d") - strptime(application_date, "%Y%m%d"))) # 列を選択して最初の10行を返す df5 <- select(df4, customer_id, sales_ymd, application_date, elapsed_days) slice(df5, 1:10)
このコードでは、dplyr パッケージを使用してデータ操作を行います。distinct関数は、すべての列に基づいて重複する行を削除し、inner_join関数は、共通の列に基づいて2つのデータフレームを結合しています。mutate関数はデータフレームに新しい列を作成し、select関数はデータフレームから特定の列を選択する。最後に、slice 関数は、データフレームから行のサブセットを返します。
R-071: レシート明細データ(df_receipt)の売上日(sales_ymd)に対し、顧客データ(df_customer)の会員申込日(application_date)からの経過月数を計算し、顧客ID(customer_id)、売上日、会員申込日とともに10件表示せよ(sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。1ヶ月未満は切り捨てること。
df_receipt[c("customer_id", "sales_ymd")] %>%
distinct(., .keep_all = TRUE) %>%
inner_join(df_customer[c("customer_id", "application_date")], by = "customer_id") %>% mutate(elapsed_months = trunc(time_length( interval( strptime(application_date, "%Y%m%d"), strptime(as.character(sales_ymd), "%Y%m%d") ), "month"))) %>%
select(customer_id, sales_ymd, application_date, elapsed_months) %>%
slice(1:10)
customer_id | sales_ymd | application_date | elapsed_months |
---|---|---|---|
<chr> | <int> | <chr> | <dbl> |
CS006214000001 | 20181103 | 20150201 | 45 |
CS008415000097 | 20181118 | 20150322 | 43 |
CS028414000014 | 20170712 | 20150711 | 24 |
CS025415000050 | 20180821 | 20160131 | 30 |
CS003515000195 | 20190605 | 20150306 | 50 |
CS024514000042 | 20181205 | 20151010 | 37 |
CS040415000178 | 20190922 | 20150627 | 50 |
CS027514000015 | 20191010 | 20151101 | 47 |
CS025415000134 | 20190918 | 20150720 | 49 |
CS021515000126 | 20171010 | 20150508 | 29 |
解説:
このコードでは、以下の処理を行います。
df_receipt[c("customer_id", "sales_ymd")] df_receipt データフレームから customer_id と sales_ymd の列のみを選択します。
distinct(., .keep_all = TRUE) は、すべての列を保持したまま、重複する行を削除します。これにより、customer_idとsales_ymdのユニークな組み合わせに対して1行しか存在しないことが保証されます。
inner_join(df_customer[c("customer_id", "application_date")], by = "customer_id") 前のステップの結果と df_customer データフレームを customer_id 列で結合し、df_customer から customer_id と application_date 列のみを選択する。
mutate(elapsed_months = trunc(time_length( interval( strptime(application_date, "%Y%m%d"), strptime(as.character(sales_ymd), "%Y%m%d") ), "month")) application_date と sales_ymd 間 の月数を計算する新しい列 elapsed_months を生成します。これは、strptimeを使って日付を適切な形式に変換し、interval関数を使って差を計算し、time_lengthを使って月数を計算することによって行われます。trunc関数は、最も近い整数に切り捨てるために使用されます。
select(customer_id, sales_ymd, application_date, elapsed_months) customer_id, sales_ymd, application_date, elapsed_months の列のみを選択する。
slice(1:10)は、結果のデータフレームの最初の10行のみを選択します。
全体として、このコードは、各顧客の購入履歴に基づいて、申込日と販売日の間の月数を計算しています。
R-072: レシート明細データ(df_receipt)の売上日(df_customer)に対し、顧客データ(df_customer)の会員申込日(application_date)からの経過年数を計算し、顧客ID(customer_id)、売上日、会員申込日とともに10件表示せよ(sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。1年未満は切り捨てること。
df_receipt[c("customer_id", "sales_ymd")] %>%
distinct(., .keep_all = TRUE) %>%
inner_join(df_customer[c("customer_id", "application_date")], by = "customer_id") %>% mutate(elapsed_years = trunc(time_length(interval( strptime(application_date, "%Y%m%d"), strptime(as.character(sales_ymd), "%Y%m%d")), "year")))%>%
select(customer_id, sales_ymd, application_date, elapsed_years) %>%
slice(1:10)
customer_id | sales_ymd | application_date | elapsed_years |
---|---|---|---|
<chr> | <int> | <chr> | <dbl> |
CS006214000001 | 20181103 | 20150201 | 3 |
CS008415000097 | 20181118 | 20150322 | 3 |
CS028414000014 | 20170712 | 20150711 | 2 |
CS025415000050 | 20180821 | 20160131 | 2 |
CS003515000195 | 20190605 | 20150306 | 4 |
CS024514000042 | 20181205 | 20151010 | 3 |
CS040415000178 | 20190922 | 20150627 | 4 |
CS027514000015 | 20191010 | 20151101 | 3 |
CS025415000134 | 20190918 | 20150720 | 4 |
CS021515000126 | 20171010 | 20150508 | 2 |
解説:
このコードは、以下の処理を実行します。
df_receiptデータフレームからcustomer_idとsales_ymdの2つのカラムを抽出する。
上記2列の重複行を削除し、customer_idとsales_ymdの各ユニークペアの最初の出現分のみを保持する。
得られたデータフレームをdf_customerデータフレームとcustomer_idカラムで結合する。
interval関数で区間を作成し、time_length関数で年数を抽出することで、各顧客のapplication_dateとsales_ymdの間の経過年数を算出する。trunc 関数を使用して、結果を整数値に切り捨てます。
customer_id、sales_ymd、application_date、elapsed_yearsの各カラムを選択します。
最後に、slice関数を使用して、結果のデータフレームの最初の10行を選択します。
要約すると、このコードは、顧客のapplication_dateとsales_ymdの日付の間の経過年数を計算するものです。
R-073: レシート明細データ(df_receipt)の売上日(sales_ymd)に対し、顧客データ(df_customer)の会員申込日(application_date)からのエポック秒による経過時間を計算し、顧客ID(customer_id)、売上日、会員申込日とともに10件表示せよ(なお、sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。なお、時間情報は保有していないため各日付は0時0分0秒を表すものとする。
df_receipt[c("customer_id", "sales_ymd")] %>%
distinct(., .keep_all = TRUE) %>% inner_join(df_customer[c("customer_id","application_date")], by="customer_id") %>% mutate(elapsed_epoch = as.numeric(strptime(as.character(sales_ymd), "%Y%m%d")) - as.numeric(strptime(application_date, "%Y%m%d"))) %>%
select(customer_id, sales_ymd, application_date, elapsed_epoch) %>%
slice(1:10)
customer_id | sales_ymd | application_date | elapsed_epoch |
---|---|---|---|
<chr> | <int> | <chr> | <dbl> |
CS006214000001 | 20181103 | 20150201 | 118454400 |
CS008415000097 | 20181118 | 20150322 | 115516800 |
CS028414000014 | 20170712 | 20150711 | 63244800 |
CS025415000050 | 20180821 | 20160131 | 80611200 |
CS003515000195 | 20190605 | 20150306 | 134092800 |
CS024514000042 | 20181205 | 20151010 | 99532800 |
CS040415000178 | 20190922 | 20150627 | 133747200 |
CS027514000015 | 20191010 | 20151101 | 124329600 |
CS025415000134 | 20190918 | 20150720 | 131414400 |
CS021515000126 | 20171010 | 20150508 | 76550400 |
解説:
このコードは、顧客とその購入履歴に関連するいくつかのデータを処理しています。以下、コードを一行ずつ解説していきます。
df_receipt[c("customer_id", "sales_ymd")]: "df_receipt" データフレームから "customer_id" と "sales_ymd" 列を選択します。
distinct(., .keep_all = TRUE): すべてのカラムを保持したまま、重複する行を削除する。
inner_join(df_customer[c("customer_id", "application_date")], by="customer_id"): "df_customer" データフレームに "customer_id" 列を使用して参加します。
mutate(elapsed_epoch = as.numeric(strptime(as.character(sales_ymd), "%Y%m%d")) - as.numeric(strptime(application_date, "%Y%m%d")): "sales_ymd" 列と "application_date" 列の間の秒数を計算する、新しい列 "elapsed_epoch" を作成します。
select(customer_id, sales_ymd, application_date, elapsed_epoch): 結合したデータフレームから "customer_id", "sales_ymd", "application_date", "elapsed_epoch" カラムを選択します。
slice(1:10): 結果のデータフレームから最初の10行を選択する。
R-074: レシート明細データ(df_receipt)の売上日(sales_ymd)に対し、当該週の月曜日からの経過日数を計算し、売上日、直前の月曜日付とともに10件表示せよ(sales_ymdは数値でデータを保持している点に注意)。
df_receipt["sales_ymd"] %>%
# 以下では開始日が日曜日となるため1日前シフトさせることで日曜日を前週の最終日に変換している。
# その状態でfloor_dateをすると、週の開始日として前週の日曜日が取得できるため、
# 開始日を月曜日にするために1を足している。
mutate(monday = as.Date(floor_date( strptime(as.character(sales_ymd), "%Y%m%d") - 1 , unit = "week")) + 1) %>%
mutate(elapsed_days = as.integer( as.Date( strptime(as.character(sales_ymd), "%Y%m%d")) - monday)) %>%
select(sales_ymd, elapsed_days, monday) %>%
slice(1:10)
sales_ymd | elapsed_days | monday |
---|---|---|
<int> | <int> | <date> |
20181103 | 5 | 2018-10-29 |
20181118 | 6 | 2018-11-12 |
20170712 | 2 | 2017-07-10 |
20190205 | 1 | 2019-02-04 |
20180821 | 1 | 2018-08-20 |
20190605 | 2 | 2019-06-03 |
20181205 | 2 | 2018-12-03 |
20190922 | 6 | 2019-09-16 |
20170504 | 3 | 2017-05-01 |
20191010 | 3 | 2019-10-07 |
解説:
このコードは、df_receiptデータフレーム内の各売上日について、週の始まり(月曜日)からの経過日数を計算します。以下、コードの内訳です。
df_receipt["sales_ymd"]: df_receiptデータ・フレームからsales_ymdカラムを選択します。
%>%: これは、前の操作から次の操作に結果を渡すパイプ演算子です。
mutate(monday = as.Date(floor_date( strptime(as.character(sales_ymd), "%Y%m%d") - 1 , unit = "週")) + 1): これは、各売上日の週の月曜日を表す monday という新しいカラムを作成します。これは、sales_ymdカラムを日付オブジェクトに変換し、1日引いて前週に切り捨て(日曜日が週の終わり)、1日足して週の始まり(月曜日)とすることで実現します。
%>%: もうひとつのパイプ演算子。
mutate(elapsed_days = as.integer( as.Date( strptime(as.character(sales_ymd), "%Y%m%d")) - monday)): これは、elapsed_daysという新しいカラムを作成し、各売上日の週の始まり(月曜日)から経過した日数を表しています。これは、sales_ymdカラムを日付オブジェクトに変換し、(mondayカラムから)週の月曜日を差し引き、その結果を整数に変換することで実現します。
%>%: もうひとつのパイプ演算子。
select(sales_ymd, elapsed_days, monday): sales_ymd、elapsed_days、mondayのカラムを選択します。
%>%: もう一つのパイプ演算子。
slice(1:10)。結果のデータフレームから最初の10行を選択します。
R-075: 顧客データ(df_customer)からランダムに1%のデータを抽出し、先頭から10件表示せよ。
head(sample_frac(tbl = df_customer, size = 0.01), 10)
customer_id | customer_name | gender_cd | gender | birth_day | age | postal_cd | address | application_store_cd | application_date | status_cd | |
---|---|---|---|---|---|---|---|---|---|---|---|
<chr> | <chr> | <chr> | <chr> | <date> | <int> | <chr> | <chr> | <chr> | <chr> | <chr> | |
1 | CS001612000311 | 岡本 美佳 | 1 | 女性 | 1951-03-21 | 68 | 211-0015 | 神奈川県川崎市中原区北谷町********** | S13001 | 20171118 | 0-00000000-0 |
2 | CS018415000209 | 西村 由美子 | 1 | 女性 | 1974-11-26 | 44 | 203-0051 | 東京都東久留米市小山********** | S13018 | 20151001 | C-20101026-D |
3 | CS020415000109 | 岡村 里奈 | 1 | 女性 | 1969-02-02 | 50 | 173-0001 | 東京都板橋区本町********** | S13020 | 20150207 | A-20100922-D |
4 | CS040403000046 | 辻 獅童 | 0 | 男性 | 1977-01-23 | 42 | 226-0016 | 神奈川県横浜市緑区霧が丘********** | S14040 | 20150813 | 0-00000000-0 |
5 | CS044313000008 | 河原 育子 | 1 | 女性 | 1985-06-03 | 33 | 144-0056 | 東京都大田区西六郷********** | S13044 | 20161204 | 0-00000000-0 |
6 | CS038502000025 | 小柳 ケンイチ | 0 | 男性 | 1963-06-01 | 55 | 134-0015 | 東京都江戸川区西瑞江********** | S13038 | 20151214 | 0-00000000-0 |
7 | CS034402000064 | 川越 明 | 0 | 男性 | 1972-11-27 | 46 | 213-0031 | 神奈川県川崎市高津区宇奈根********** | S14034 | 20170913 | 0-00000000-0 |
8 | CS015303000005 | 寺西 一徳 | 0 | 男性 | 1987-01-16 | 32 | 135-0016 | 東京都江東区東陽********** | S13015 | 20150223 | 0-00000000-0 |
9 | CS029411000007 | 森永 璃子 | 1 | 女性 | 1976-12-01 | 42 | 134-0085 | 東京都江戸川区南葛西********** | S12029 | 20150709 | 5-20101007-9 |
10 | CS048612000001 | 劇団 恵子 | 1 | 女性 | 1956-01-11 | 63 | 224-0053 | 神奈川県横浜市都筑区池辺町********** | S14048 | 20170920 | 0-00000000-0 |
解説:
このコードは、dplyr パッケージの sample_frac() 関数を使用して、df_customer データフレーム内の行の 1% をランダムに選択し、head() 関数を使用して結果のデータフレームの最初の 10 行を返します。
sample_frac() 関数は、データフレームから一部の行をランダムにサンプリングするために使用されます。この場合、tbl引数はサンプリングするデータフレームを指定し、size引数はサンプリングする行の割合(0.01、つまり1%)を指定します。結果のデータフレームはhead()関数に渡され、最初の10行を返します。
R-076: 顧客データ(df_customer)から性別コード(gender_cd)の割合に基づきランダムに10%のデータを層化抽出し、性別コードごとに件数を集計せよ。
set.seed(71) df_customer %>%
group_by(gender_cd) %>%
sample_frac(0.1) %>%
summarise(customer_num = n(), .groups = "drop")
gender_cd | customer_num |
---|---|
<chr> | <int> |
0 | 298 |
1 | 1792 |
9 | 107 |
解説:
このコードは、dplyrパッケージを使用して、各性別グループから顧客の10%をランダムにサンプリングし、各サンプル内の顧客数をカウントします。
set.seed(71) は、サンプリングの再現性を確保するために、乱数生成器の種を設定します。
df_customer %>%はdf_customerデータフレームを入力として受け取り、パイプ演算子%>%を使って次の処理に渡しています。
group_by(gender_cd) 性別コード列でデータをグループ化します。
sample_frac(0.1) は、各性別グループから顧客の10%をサンプリングします。sample_frac()関数は、グループ化されたデータフレームから行の一部をランダムにサンプリングするために使用されます。引数0.1はサンプリングされる割合を指定する。
summarise(customer_num = n(), .groups = "drop") は、各性別グループにおける顧客の数を計算します。n()関数は、各グループの行数を数え、結果の列をcustomer_numにリネームする。.groups 引数を "drop" に設定すると、出力からグループ化情報が削除されます。
最終的な出力は、gender_cd と customer_num の2つのカラムを持つデータフレームになります。gender_cd列は性別コードを指定し、customer_num列は各性別グループからサンプリングされた顧客の数を指定する。
R-077: レシート明細データ(df_receipt)の売上金額を顧客単位に合計し、合計した売上金額の外れ値を抽出せよ。なお、外れ値は売上金額合計を対数化したうえで平均と標準偏差を計算し、その平均から3σを超えて離れたものとする(自然対数と常用対数のどちらでも可)。結果は10件表示せよ。
df_receipt %>%
group_by(customer_id) %>%
summarise(sum_amount = sum(amount), .groups = "drop") %>%
mutate(log_sum_amount = log(sum_amount + 0.5)) %>%
filter(abs(log_sum_amount - mean(log_sum_amount)) / sd(log_sum_amount) > 3) %>% slice(1:10)
customer_id | sum_amount | log_sum_amount |
---|---|---|
<chr> | <int> | <dbl> |
ZZ000000000000 | 12395003 | 16.3328 |
解説:
このコードは、df_receiptというデータフレームを処理し、以下のことを行っています。
group_by関数を使用して、データフレームをcustomer_idでグループ化する。
summarise関数を使用して各顧客の金額の合計を計算し、sum_amountという新しいカラムに格納する。
sum_amount列の対数をとり、log_sum_amountという新しい列に格納する。
各値からlog_sum_amountの平均値を引き、その結果をlog_sum_amountの標準偏差で割って、log_sum_amountのzスコアを計算する。
log_sum_amountのzスコアが3より大きい顧客(すなわち、log_sum_amountが平均値から標準偏差で3以上離れている顧客)をフィルタリングする。
slice関数を使用して、結果のデータフレームの最初の10行を選択する。
要約すると、このコードは、Zスコアを計算し、Zスコアがある閾値を超える顧客をフィルタリングすることで、総支出額が平均より大幅に高いか低い顧客を特定しています。
R-078: レシート明細データ(df_receipt)の売上金額(amount)を顧客単位に合計し、合計した売上金額の外れ値を抽出せよ。ただし、顧客IDが”Z”から始まるのものは非会員を表すため、除外して計算すること。なお、ここでは外れ値を第1四分位と第3四分位の差であるIQRを用いて、「第1四分位数-1.5×IQR」を下回るもの、または「第3四分位数+1.5×IQR」を超えるものとする。結果は10件表示せよ。
df_receipt %>%
group_by(customer_id) %>%
filter(!grepl("^Z", customer_id)) %>%
summarise(sum_amount = sum(amount), .groups = "drop") %>%
filter( sum_amount < quantile(sum_amount)[2] - 1.5 * (quantile(sum_amount)[4] - quantile(sum_amount)[2]) | sum_amount > quantile(sum_amount)[4] + 1.5 * (quantile(sum_amount)[4] - quantile(sum_amount)[2]) ) %>%
slice(1:10)
customer_id | sum_amount |
---|---|
<chr> | <int> |
CS001414000048 | 8584 |
CS001605000009 | 18925 |
CS002415000594 | 9568 |
CS004414000181 | 9584 |
CS005415000137 | 8734 |
CS006414000001 | 9156 |
CS006414000029 | 9179 |
CS006415000105 | 10042 |
CS006415000147 | 12723 |
CS006415000157 | 10648 |
解説:
本コードは、以下の処理を行う。
df_receiptデータフレームをcustomer_idでグループ化する。
customer_idが文字 "Z "で始まるすべての行をフィルタリングする。
各 customer_id の金額を合計する。
四分位範囲(IQR)法を用いて、sum_amountの値の上位と下位1.5%をフィルタリングする。
出来上がったデータフレームの最初の10行を選択する。
IQR法は、四分位数に基づいてデータセットから外れ値を検出・除去する方法です。この場合、上界と下界は以下のように計算される。
上界:quantile(sum_amount)[4] + 1.5 * (quantile(sum_amount)[4] - quantile(sum_amount)[2])
下界:quantile(sum_amount)[2] - 1.5 * (quantile(sum_amount)[4] - quantile(sum_amount)[2])
これらの範囲から外れるsum_amountの値はフィルタリングされます。結果として得られるデータフレームには、フィルター条件を満たした最初の10行のcustomer_idとsum_amountが含まれます。
R-079: 商品データ(df_product)の各項目に対し、欠損数を確認せよ。
sapply(df_product, function(x) sum(is.na(x)))
product_cd
- 0
- category_major_cd
- 0
- category_medium_cd
- 0
- category_small_cd
- 0
- unit_price
- 7
- unit_cost
- 7
解説:
このコードは、sapply()関数を使用して、df_product データフレームの各列における欠損値の数を計算する。
sapply()は、df_productのデータフレームの各列に関数を適用します。ここで、使用される関数は function(x) sum(is.na(x)) で、入力列 x の欠損値 (NA) の数を計算します。is.na() は、入力ベクトルと同じ長さで、欠損値 (NA) があれば TRUE、なければ FALSE の論理ベクトルを返し、sum() は論理ベクトルの TRUE 値を合計し、列中の欠損値のカウントを与えます。
このコードの出力は、df_productの列名を名前として、それに対応する欠損値のカウントを値として持つ名前付きベクトルです。
R-080: 商品データ(df_product)のいずれかの項目に欠損が発生しているレコードを全て削除した新たな商品データを作成せよ。なお、削除前後の件数を表示させ、079で確認した件数だけ減少していることも確認すること。
df_product_1 <- na.omit(df_product) paste("削除前:", nrow(df_product)) paste("削除後:", nrow(df_product_1))
'削除前: 10030'
'削除後: 10023'
解説:
1行目のコードでは、na.omit()関数を使って、元のデータフレームdf_productから欠損値(NA)を持つすべての行を削除した新しいデータフレームdf_product_1を作成しています。
2行目のコードでは、paste()関数を使用して、NAの除去前と除去後のdf_productの行数を示すメッセージを表示しています。具体的には、nrow(df_product)はdf_productの行数を返し、最初のpaste()呼び出しはこの数と文字列「削除前:」を連結しています。同様に、nrow(df_product_1)は新しいデータフレームdf_product_1の行数を返し、2番目のpaste()呼び出しはこの数を "After deletion: "という文字列と連結させる。
このコードは、データフレーム内のオブザベーションの数に対するNAの削除の効果をチェックする迅速な方法を提供します。
Comment