R と SQL を対応付けてみた ~data.table 編~

data.table 便利ですよね。data.table::fread が read.table に比べて格段に速いので、データの読み込みのためだけに data.table を使うのもありかと思います。
ただ、data.table:::unique.data.table のように data.frame 感覚で関数を使うと意図しない結果になることもあるので、主要な操作を SQL と対応付けてまとめてみました。

data.frame の主要な操作については「RとSQLを対応付けてみた」「Tsukuba.R #9で『Rデータフレーム自由自在』を発表してきました」をご覧ください。

準備

前回同様、サンプルデータとして次のものを使用します。

library(data.table)

set.seed(1000)
product <- data.table(id = 101:110, name = sample(LETTERS, 10), price = sample(5:20 * 100, 10))
n <- 100
makeOrder <- function(term, n) {
    data.table(order_id = sort(sample(seq(1001, len = n * 2), n)),
        date = sort(sample(paste("2011-05-", term, sep = ""), n, replace = TRUE)),
        customer_id = sample(10001:10100, n, replace = TRUE),
        product_id = sample(product$id, n, replace = TRUE),
        number = sample(1:5, n, replace = TRUE)
    )
}

order1 <- makeOrder(21:24, n)
order2 <- makeOrder(25:28, n)

受注に関するデータと、商品に関するデータといった感じです。
order1, order2 は同じ構造のデータで、order_id(伝票番号みたいなもの)、date(受注日)、customer_id(顧客ID)、product_id(製品ID)、number(購入個数)から成り、productはid(製品ID)、name(製品名)、price(価格)から成ります。

> head(order1)
   order_id       date customer_id product_id number
1:     1002 2011-05-21       10078        106      3
2:     1003 2011-05-21       10038        109      4
3:     1004 2011-05-21       10039        109      4
4:     1006 2011-05-21       10036        101      1
5:     1007 2011-05-21       10042        110      3
6:     1009 2011-05-21       10060        110      1
> head(order2)
   order_id       date customer_id product_id number
1:     1002 2011-05-25       10036        106      3
2:     1003 2011-05-25       10031        106      4
3:     1004 2011-05-25       10039        106      5
4:     1005 2011-05-25       10024        108      5
5:     1006 2011-05-25       10025        101      4
6:     1007 2011-05-25       10099        109      1
> head(product)
    id name price
1: 101    I  1000
2: 102    S  1600
3: 103    C   900
4: 104    P  1900
5: 105    L  1400
6: 106    B   500

基本操作

列の抽出

SQL

SELECT order_id, customer_id FROM order1

data.table

order1[, .(order_id, customer_id)]

次のような書き方も可能です

# .() と list() は同義
order1[, list(order_id, customer_id)]
# data.frame 風
order1[, c("order_id", "customer_id"), with = FALSE]

LIMIT

SQL

SELECT * FROM order1 LIMIT 5

data.table

head(order1, n = 5)

次のような書き方も可能です

order1[1:5]
# data.frame 風
order1[1:5, ]

SQL

SELECT * FROM order1 LIMIT 2 OFFSET 5

data.table

order1[seq(3, len = 5)]
# data.frame 風
order1[seq(3, len = 5), ]

DISTINCT

SQL

SELECT DISTINCT customer_id FROM order1

data.table

unique(order1[, c(customer_id)])

次のような書き方も可能です。上記の書き方だとベクトルが返りますが、こちらは data.table が返ります。

unique(order1[, .(customer_id)])

SQL

SELECT DISTINCT date, product_id FROM order1

data.table

unique(order1[, .(date, product_id)], by = c("date", "product_id"))

場合によっては by を指定しなくても大丈夫ですが、もし次のように date に key が設定されていると意図しない結果になるので、面倒でも必ず by を指定することをオススメします。

> setkeyv(order1, date)
> key(order1)
[1] "date"
> unique(order1[, .(date, product_id)])
         date product_id
1: 2011-05-21        101
2: 2011-05-22        101
3: 2011-05-23        102
4: 2011-05-24        101
> # 次と同義
> unique(order1[, .(date, product_id)], by = key(order1))
         date product_id
1: 2011-05-21        101
2: 2011-05-22        101
3: 2011-05-23        102
4: 2011-05-24        101
> # 所望の結果
> unique(order1[, .(date, product_id)], by = c("date", "product_id"))
          date product_id
 1: 2011-05-21        101
 2: 2011-05-21        102
 3: 2011-05-21        104
 4: 2011-05-21        106
 5: 2011-05-21        107
 (snip)
33: 2011-05-24        108
34: 2011-05-24        109
35: 2011-05-24        110
          date product_id

COUNT

SQL

SELECT COUNT(*) FROM order1

data.table

nrow(order1)

WHERE

1つの条件

SQL

SELECT * FROM order1 WHERE customer_id = 10038

data.table

order1[customer_id == 10038]  # order1[customer_id == 10038, ] と同義

次のような書き方も可能です

order1[order1$customer_id == 10038]
subset(order1, customer_id == 10038)

AND 条件

SQL

SELECT * FROM order1 WHERE customer_id = 10038 AND date = '2011-05-21'

data.table

order1[customer_id == 10038 & date == "2011-05-21"]

OR 条件

SQL

SELECT * FROM order1 WHERE customer_id = 10038 OR customer_id = 10050

data.table

order1[customer_id == 10038 | customer_id == 10050]

IN

SQL

SELECT * FROM order1 WHERE customer_id IN (10038, 10050, 10079)

data.table

order1[customer_id %in% c(10038, 10050, 10079)]

ORDER BY

1 つのフィールドでソート

SQL

SELECT * FROM order1 ORDER BY customer_id

data.table

order1[order(customer_id)]

2 つのフィールドでソート

SQL

SELECT * FROM order1 ORDER BY customer_id, product_id DESC

data.table

order1[order(customer_id, -product_id)]

JOIN

INNER JOIN

SQL

SELECT * FROM order1 INNER JOIN order2 ON order1.order_id = order2.order_id

data.table

merge(order1, order2, by = "order_id")

※ by で指定したフィールドが key として設定されるので注意

LEFT JOIN

SQL

SELECT * FROM order1 LEFT JOIN order2 ON order1.order_id = order2.order_id

data.table

merge(order1, order2, by = "order_id", all.x = TRUE)

RIGHT JOIN

SQL

SELECT * FROM order1 RIGHT JOIN order2 ON order1.order_id = order2.order_id

data.table

merge(order1, order2, by = "order_id", all.y = TRUE)

FULL JOIN

SELECT * FROM order1 FULL JOIN order2 ON order1.order_id = order2.order_id

R

merge(order1, order2, by = "order_id", all = TRUE)

GROUP BY

1 つのフィールドでグループ化して COUNT

SQL

SELECT customer_id, COUNT(*) FROM order1 GROUP BY customer_id

data.table

order1[, .N, by = customer_id]  # order1[, .(.N), by = customer_id] と同義

.N はそのグループのレコード数を意味する特殊なシンボルで、これによって算出される値の列は N が割り当てられます。

> head(order1[, .N, by = customer_id], 2)
   customer_id N
1:       10036 2
2:       10022 1

2 つのフィールドでグループ化して COUNT

SQL

SELECT customer_id, date, COUNT(*) FROM order1 GROUP BY customer_id, date

data.table

order1[, .N, by = .(customer_id, date)]

2 つのフィールドでグループ化して COUNT してソート

SQL

SELECT
  customer_id, date, COUNT(*)
FROM
  order1
GROUP BY
  customer_id, date
ORDER BY
  customer_id, date DESC

data.table

order1[, .N, by = .(customer_id, date)][order(customer_id, -date)]
# 次と同義
# tmp <- order1[, .N, by = .(customer_id, date)]
# tmp[order(customer_id, -date)]

1 つのフィールドでグループ化して COUNT してフィルタリング

SQL

SELECT
  customer_id, COUNT(*)
FROM
  order1
GROUP BY
  customer_id
HAVING
  COUNT(*) >= 3

data.table

order1[, .N, by = customer_id][N >= 3]
# 次と同義
# tmp <- order1[, .N, by = customer_id]
# tmp[N >= 3]

1 つのフィールドでグループ化して平均

SQL

SELECT customer_id, AVG(number) FROM order1 GROUP BY customer_id

data.table

order1[, .(mean(number)), by = customer_id]

.() の中に集約関数と対象のフィールド名を与えることで集約処理を行えます。
処理結果に名前を付けることもできます。

> head(order1[, .(mean(number)), by = customer_id], 2)
   customer_id V1
1:       10036  2
2:       10022  2
> head(order1[, .(avg = mean(number)), by = customer_id], 2)
   customer_id avg
1:       10036   2
2:       10022   2

2 つのフィールドでグループ化して平均

SQL

SELECT customer_id, AVG(number) FROM order1 GROUP BY customer_id, date

data.table

order1[, .(mean(number)), by = .(customer_id, date)]

1 つのフィールドでグループ化して MIN, MAX

SQL

SELECT product_id, MIN(number), MAX(number) FROM order1 GROUP BY product_id

data.table

order1[, .(min(number), max(number)), by = product_id]

以上です。data.table に慣れると subset, table, aggregate などの関数がなくてもサクッとデータ操作ができそうで便利ですね!

参考