「データサイエンス100本ノック(構造化データ加工編)」の R と SQL をオリジナルに実装したコードを丁寧に解説するシリーズです。 本編にはない、R によるデータベース操作の方法についても、あわせて紹介しています。
R のデータ加工は dplyr をベースとしており、データフレーム操作とデータベース操作の両面から学べる構成になっています。
RStudio や VSCode など、一般的な R の開発環境でそのまま実行できるようにしているので、実践的なスキルを身につけたい方におすすめです。
参考情報:
[R & SQL] データサイエンス100本ノック+α - 概要・導入
- 設問文は データサイエンス100本ノック(構造化データ加工編) より引用しています。
- すべてのコードはコピー&ペーストでそのまま実行できます。
演習問題
出力イメージ:
sales_ymd amount pre_sales_ymd pre_amount diff_amount
1 20170101 33723 NA NA NA
2 20170102 24165 20170101 33723 -9558
3 20170103 27503 20170102 24165 3338
4 20170104 36165 20170103 27503 8662
5 20170105 37830 20170104 36165 1665
6 20170106 32387 20170105 37830 -5443
7 20170107 23415 20170106 32387 -8972
8 20170108 24737 20170107 23415 1322
9 20170109 26718 20170108 24737 1981
10 20170110 20143 20170109 26718 -6575実行環境の構築について
R コード実行環境の構築に必要な手順は こちらから確認できます 。 セットアップが完了すると、必要なパッケージとデータを使用できるようになります。
amount が nullable (NULL を許容する) の場合の解答例を以下に示します。
各コードの出力結果を統一させるため、sales_ymd の昇順でソートしています。
R (データフレーム操作)
利用するデータ
以下のデータを利用します。
df_receiptの売上年⽉⽇ (sales_ymd) と売上金額 (amount)
主要なカラムを抜き出してデータの一部を確認します。
df_receipt %>% select(sales_ymd, amount)# A tibble: 104,681 × 2
sales_ymd amount
<int> <dbl>
1 20181103 158
2 20181118 81
3 20170712 170
4 20190205 25
5 20180821 90
6 20190605 138
7 20181205 30
8 20190922 128
9 20170504 770
...解答例と実行結果
df_receipt データフレームから、売上金額の合計を日別に集計し、前回との売上金額の差分を求めます。
df_receipt %>%
summarise(
amount = sum(amount, na.rm = TRUE), .by = sales_ymd
) %>%
filter(amount > 0.0) %>%
arrange(sales_ymd) %>%
mutate(
pre_sales_ymd = lag(sales_ymd),
pre_amount = lag(amount),
diff_amount = amount - pre_amount
) %>%
head(10)# A tibble: 10 × 5
sales_ymd amount pre_sales_ymd pre_amount diff_amount
<int> <dbl> <int> <dbl> <dbl>
1 20170101 33723 NA NA NA
2 20170102 24165 20170101 33723 -9558
3 20170103 27503 20170102 24165 3338
4 20170104 36165 20170103 27503 8662
5 20170105 37830 20170104 36165 1665
6 20170106 32387 20170105 37830 -5443
7 20170107 23415 20170106 32387 -8972
8 20170108 24737 20170107 23415 1322
9 20170109 26718 20170108 24737 1981
10 20170110 20143 20170109 26718 -6575解説
summarise()で売上合計を計算sales_ymdごとにamountの合計を算出します。na.rm = TRUEにより、NA値を除外します。
filter(amount > 0.0)で不要なデータを除外amountが0.0の行を除外します。arrange(sales_ymd)で日付順に並び替えsales_ymdの昇順に並べ替えます。
mutate()で前回の売上金額の差分を計算lag(sales_ymd)により、前回の売上日pre_sales_ymdを取得します。lag(amount)により、前回の売上pre_amountを取得します。amount - pre_amountにより、前回との差分diff_amountを計算します。
head(10)で先頭10件を取得- 計算結果の最初の10件を取得します。
R (データベース操作)
利用するデータ
以下のデータを利用します。
db_receiptの売上年⽉⽇ (sales_ymd) と売上金額 (amount)
主要なカラムを抜き出してデータの一部を確認します。
db_receipt %>% select(sales_ymd, amount) %>% collect()# A tibble: 104,681 × 2
sales_ymd amount
<int> <dbl>
1 20181103 158
2 20181118 81
3 20170712 170
4 20190205 25
5 20180821 90
6 20190605 138
7 20181205 30
8 20190922 128
9 20170504 770
...解答例と実行結果
db_receipt テーブル参照から、売上金額の合計を日別に集計し、前回との売上金額の差分を求めます。
db_result = db_receipt %>%
summarise(
amount = sum(amount, na.rm = TRUE), .by = sales_ymd
) %>%
filter(!is.na(amount)) %>%
window_order(sales_ymd) %>%
mutate(
pre_sales_ymd = lag(sales_ymd),
pre_amount = lag(amount),
diff_amount = amount - pre_amount
) %>%
arrange(sales_ymd) %>%
head(10)
db_result %>% collect()# A tibble: 10 × 5
sales_ymd amount pre_sales_ymd pre_amount diff_amount
<int> <dbl> <int> <dbl> <dbl>
1 20170101 33723 NA NA NA
2 20170102 24165 20170101 33723 -9558
3 20170103 27503 20170102 24165 3338
4 20170104 36165 20170103 27503 8662
5 20170105 37830 20170104 36165 1665
6 20170106 32387 20170105 37830 -5443
7 20170107 23415 20170106 32387 -8972
8 20170108 24737 20170107 23415 1322
9 20170109 26718 20170108 24737 1981
10 20170110 20143 20170109 26718 -6575解説
データフレーム操作との相違点は以下の箇所です。
filter(!is.na(amount))で不要なデータを除外amountがNAとなるレコードを除外します。window_order(sales_ymd)でウィンドウ関数の順序を指定lag()で前回のデータを取得する際に、sales_ymdの昇順で処理を行うように指定します。db_result %>% collect()
データベース操作の結果を R のデータフレーム (tibble) として取得します。
SQL
利用するデータ
以下のデータを利用します。
receiptテーブルの売上年⽉⽇ (sales_ymd) と売上金額 (amount)
主要なカラムを抜き出してデータの一部を確認します。
"SELECT sales_ymd, amount FROM receipt" %>% db_get_query(con)# A tibble: 104,681 × 2
sales_ymd amount
<int> <dbl>
1 20181103 158
2 20181118 81
3 20170712 170
4 20190205 25
5 20180821 90
6 20190605 138
7 20181205 30
8 20190922 128
9 20170504 770
...自動生成された SQL クエリ
データベース操作による結果 (db_result) に基づき、自動生成された SQLクエリを show_query() で確認できます。
db_result %>% show_query(cte = TRUE)WITH q01 AS (
SELECT sales_ymd, SUM(amount) AS amount
FROM receipt
GROUP BY sales_ymd
HAVING (NOT(((SUM(amount)) IS NULL)))
),
q02 AS (
SELECT
q01.*,
LAG(sales_ymd, 1, NULL) OVER (ORDER BY sales_ymd) AS pre_sales_ymd,
LAG(amount, 1, NULL) OVER (ORDER BY sales_ymd) AS pre_amount
FROM q01
)
SELECT q01.*, amount - pre_amount AS diff_amount
FROM q02 q01
ORDER BY sales_ymd
LIMIT 10解答例
このクエリをより簡潔な形に書き直すと、次のようになります。
WITH sales_by_date AS (
SELECT
sales_ymd,
SUM(amount) AS amount
FROM
receipt
GROUP BY
sales_ymd
HAVING
SUM(amount) IS NOT NULL
),
sales_by_date_with_lag AS (
SELECT
sales_ymd,
amount,
LAG(sales_ymd) OVER win AS pre_sales_ymd,
LAG(amount) OVER win AS pre_amount
FROM
sales_by_date
WINDOW win AS (ORDER BY sales_ymd)
)
SELECT
sales_ymd,
amount,
pre_sales_ymd,
pre_amount,
amount - pre_amount AS diff_amount
FROM
sales_by_date_with_lag
ORDER BY
sales_ymd
LIMIT 10解説
receipt テーブルから、売上金額の合計を日別に集計し、前回との売上金額の差分を求めます。
sales_by_date の作成
WITH sales_by_date AS (
SELECT
sales_ymd,
SUM(amount) AS amount
FROM
receipt
GROUP BY
sales_ymd
HAVING
SUM(amount) IS NOT NULL
)SUM(amount) と GROUP BYによりsales_ymdごとにamountの合計を算出します。HAVING SUM(amount) IS NOT NULLにより、売上データが存在しない日を除外します。
sales_by_date_with_lag の作成
sales_by_date_with_lag AS (
SELECT
sales_ymd,
amount,
LAG(sales_ymd) OVER win AS pre_sales_ymd,
LAG(amount) OVER win AS pre_amount
FROM
sales_by_date
WINDOW win AS (ORDER BY sales_ymd)
)LAG(sales_ymd) OVER winウィンドウ関数により、前回の売上日pre_sales_ymdを取得します。LAG(amount) OVER winウィンドウ関数により、前回の売上pre_amountを取得します。WINDOW win AS (ORDER BY sales_ymd)winというウィンドウフレームを定義し、sales_ymdの昇順で処理を行うよう指定します。- これにより、
LAG()関数がsales_ymdの昇順に基づいて前回のデータを取得できるようになります。
メインクエリ
SELECT
sales_ymd,
amount,
pre_sales_ymd,
pre_amount,
amount - pre_amount AS diff_amount
FROM
sales_by_date_with_lag
ORDER BY
sales_ymd
LIMIT 10amount - pre_amount AS diff_amountにより、前回との差分diff_amountを計算します。ORDER BY sales_ymdにより、売上日の昇順に並べ替えます。LIMIT 10により、最初の10件を取得します。
実行結果の確認
この SQLクエリの実行結果は、次のようにして確認できます。
query = sql("
WITH sales_by_date AS (
SELECT
sales_ymd,
SUM(amount) AS amount
FROM
receipt
GROUP BY
sales_ymd
HAVING
SUM(amount) IS NOT NULL
),
sales_by_date_with_lag AS (
SELECT
sales_ymd,
amount,
LAG(sales_ymd) OVER win AS pre_sales_ymd,
LAG(amount) OVER win AS pre_amount
FROM
sales_by_date
WINDOW win AS (ORDER BY sales_ymd)
)
SELECT
sales_ymd,
amount,
pre_sales_ymd,
pre_amount,
amount - pre_amount AS diff_amount
FROM
sales_by_date_with_lag
ORDER BY
sales_ymd
LIMIT 10
"
)
query %>% db_get_query(con)# A tibble: 10 × 5
sales_ymd amount pre_sales_ymd pre_amount diff_amount
<int> <dbl> <int> <dbl> <dbl>
1 20170101 33723 NA NA NA
2 20170102 24165 20170101 33723 -9558
3 20170103 27503 20170102 24165 3338
4 20170104 36165 20170103 27503 8662
5 20170105 37830 20170104 36165 1665
6 20170106 32387 20170105 37830 -5443
7 20170107 23415 20170106 32387 -8972
8 20170108 24737 20170107 23415 1322
9 20170109 26718 20170108 24737 1981
10 20170110 20143 20170109 26718 -6575![Featured image of post [R & SQL] データサイエンス100本ノック+α - R-041 (解説)](/images/ds-drills/free/cover-drills-free_R-041.png)