UPDATE: 2022-10-28 19:13:01
data.tableパッケージを頻繁に使うわけではないが、利用するたびに文法を忘れるので、N回目のdata.tableパッケージ入門を行う。このページを検索すればdata.tableの文法というか使い方を検索できるように真面目にまとめておく。これを最後にできるように。
章立てはdata.table cheatsheetsを参考にdata.tableの使い方をまとめておく。dplyrライクにdata.tableを利用できるdtplyrパッケージもあるが、ここではdata.tableパッケージの使い方に焦点を当てる。
必要なライブラリは下記の通り。
# remove.packages("data.table")
# install.packages("data.table")
library(data.table)
library(fasttime)
library(tidyverse)
library(lubridate)
library(fuzzyjoin)
library(nycflights13)
# packageVersion("data.table")
# [1] ‘1.13.2’
data.tableの基本的な文法は下記の通りで、i
で行を操作し(SQLのWHERE)、j
で列を操作し(SQLのSELECT)、by
でグループ化を行う(SQLのGROUP
BY)。data.tableを使用する際は[i,j,by]
をすべて指定する必要はなく、必要なものを指定すれば良いが、ここではカンマ,
は操作を明示するために記載する。
dt[i, j, by]
data.tableを作成する場合は、data.table()
を利用する。
dt <- data.table::data.table(
intger = c(1L, 2L, 3L, 4L, 5L),
double = c(1.0, 2.0, 3.0, 4.0, 5.0),
character = c("a", "b", "c", "d", "e"),
logical = c(TRUE, FALSE, T, F, NA),
datetime = c(
"2019-12-31 14:00:00",
"2020-12-31 15:00:00",
"2020-12-31 16:00:00",
"2020-12-31 17:00:00",
"2020-12-31 18:00:00")
)
dt
intger double character logical datetime
1: 1 1 a TRUE 2019-12-31 14:00:00
2: 2 2 b FALSE 2020-12-31 15:00:00
3: 3 3 c TRUE 2020-12-31 16:00:00
4: 4 4 d FALSE 2020-12-31 17:00:00
5: 5 5 e NA 2020-12-31 18:00:00
str(dt)
Classes ‘data.table’ and 'data.frame': 5 obs. of 5 variables:
$ intger : int 1 2 3 4 5
$ double : num 1 2 3 4 5
$ character: chr "a" "b" "c" "d" ...
$ logical : logi TRUE FALSE TRUE FALSE NA
$ datetime : chr "2019-12-31 14:00:00" "2020-12-31 15:00:00" "2020-12-31 16:00:00" "2020-12-31 17:00:00" ...
- attr(*, ".internal.selfref")=<externalptr>
クラスがdata.tableとdata.frameになっているので、data.tableクラスに対応する関数がない場合、data.frameクラスの関数のメソッドが呼びだされ、利用できる。データの変換については、as.data.table()
でdata.frameを変換できる。
data.table::as.data.table(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1: 5.1 3.5 1.4 0.2 setosa
2: 4.9 3.0 1.4 0.2 setosa
3: 4.7 3.2 1.3 0.2 setosa
4: 4.6 3.1 1.5 0.2 setosa
5: 5.0 3.6 1.4 0.2 setosa
---
146: 6.7 3.0 5.2 2.3 virginica
147: 6.3 2.5 5.0 1.9 virginica
148: 6.5 3.0 5.2 2.0 virginica
149: 6.2 3.4 5.4 2.3 virginica
150: 5.9 3.0 5.1 1.8 virginica
data.table::setDT()
を使うことで、代入せずとも型変換が可能。
iris2 <- iris
class(iris2)
[1] "data.frame"
data.table::setDT(iris2)
class(iris2)
[1] "data.table" "data.frame"
テーブルの内容を確認するための関数も利用できる。
head(dt, 3);tail(dt, 3)
order_id order_datetime user_id branch_num price product_id
1: 2 2003-12-20 09:10:59 53605 1 200 33120
2: 2 2003-12-20 09:10:59 53605 2 110 28985
3: 2 2003-12-20 09:10:59 53605 3 410 9327
order_id order_datetime user_id branch_num price product_id
1: 3421083 2002-01-25 07:38:57 842528 8 260 4600
2: 3421083 2002-01-25 07:38:57 842528 9 350 24852
3: 3421083 2002-01-25 07:38:57 842528 10 400 5020
nrow(dt);ncol(dt);dim(dt)
[1] 32434489
[1] 6
[1] 32434489 6
names(dt)
[1] "order_id" "order_datetime" "user_id" "branch_num" "price" "product_id"
データに欠損値NA
が含まれているかどうかは、anyNA()
で確認できる。
# NAなし
dt_iris <- as.data.table(iris)
# NAあり
dt_airquality <- as.data.table(airquality)
anyNA(dt_iris)
[1] FALSE
anyNA(dt_airquality)
[1] TRUE
# NAを0で埋めてよい場合はいつもどおり
dt_airquality[is.na(dt_airquality)] <- 0
sum(is.na(dt_airquality))
[1] 0
読み込みと書き出しに対応する関数はfread()
とfwrite()
。fread()
は他にも引数があるが、ここでは必要そうなもの例として記載しておく。data.tableには他にはないkey
という引数がある。これはデータベースのテーブルのインデックスに当たるもので、データを検索する場合に、検索速度を高める事ができるので、必要なカラムをインデックスとして指定しておくと、処理のスピードを向上させることができる。setkey(dt, key1,key2,...)
と指定して、後から変更できる。
cols_dt <- c(
order_id = "integer",
order_datetime = "character", #日付型はないので文字型
user_id = "integer",
branch_num = "integer",
price = "integer",
product_id = "integer"
)
dt <- data.table::fread(
file = "~/Desktop/orders_30m.csv",
key = c("order_id", "order_datetime"),
colClasses = cols_dt,
header = TRUE,
encoding = "UTF-8",
sep = ",",
na.strings = ",,",
skip = 0
)
dt
order_id order_datetime user_id branch_num price product_id
1: 2 2003-12-20 09:10:59 53605 1 200 33120
2: 2 2003-12-20 09:10:59 53605 2 110 28985
3: 2 2003-12-20 09:10:59 53605 3 410 9327
4: 2 2003-12-20 09:10:59 53605 4 230 45918
5: 2 2003-12-20 09:10:59 53605 5 330 30035
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020
必要は列だけを読み込む場合はselect
引数を利用する。
dt2 <- data.table::fread(
file = "~/Desktop/orders_30m.csv",
select = c("order_id", "order_datetime")
)
dt2
order_id order_datetime
1: 2 2003-12-20 09:10:59
2: 2 2003-12-20 09:10:59
3: 2 2003-12-20 09:10:59
4: 2 2003-12-20 09:10:59
5: 2 2003-12-20 09:10:59
---
32434485: 3421083 2002-01-25 07:38:57
32434486: 3421083 2002-01-25 07:38:57
32434487: 3421083 2002-01-25 07:38:57
32434488: 3421083 2002-01-25 07:38:57
32434489: 3421083 2002-01-25 07:38:57
fwrite()
にも多くの引数があるが、ここでは必要そうなもの例として記載しておく。
data.table::fwrite(
x = dt,
file = "~/Desktop/fwrite.csv",
append = FALSE,
quote = "auto", # surrounded by double quotes
sep = ",",
na = "", # NA is a blank string ""
dec = ".", # decimal separator
row.names = FALSE,
col.names = TRUE,
dateTimeAs = "ISO"
)
簡易的ではあるが、fread()
がどの程度速いのか確認しておく。読み込むデータは100万行100列のCSVデータ。
set.seed(1989)
mat <- data.frame(matrix(runif(1e7), nrow=1e8))
dim(mat)
[1] 100000000 1
fwrite(mat, "~/Desktop/mat.csv", row.names = FALSE)
読み込み速度を検証する。read.csv()
よりも10倍、read_csv()
より5倍くらい速くなった。時間あるときにでもbench::mark()
でやってみよう。
system.time(read.csv("~/Desktop/mat.csv"))
ユーザ システム 経過
108.951 5.395 116.763
system.time(readr::read_csv("~/Desktop/mat.csv"))
ユーザ システム 経過
40.533 5.944 59.838
system.time(data.table::fread("~/Desktop/mat.csv"))
ユーザ システム 経過
4.474 2.602 12.534
data.tableでは、日付型として読み込むことができず、文字型として読み込まれるので、fasttimeパッケージのfastPOSIXct()
で日付に変換する。注意せずにそのままfastPOSIXct()
を利用し、タイムゾーンを考慮する必要があるような分析を行う場合、場合によっては痛い目にあうかもしれない。
dt <- data.table::data.table(
datetime = c(
"2019-12-31 14:00:00",
"2020-12-31 15:00:00",
"2020-12-31 16:00:00",
"2020-12-31 17:00:00",
"2020-12-31 18:00:00")
)
引数tz
をつけない場合、Sys.timezone()
がデフォルトで利用されるので、日本時間で2020-12-31 15:00:00
としたい場合、勝手にGMTからAsia/Tokyoへの変換が行われるので、2020-12-31 15:00:00
は+9時間の2021-01-01 00:00:00
となってしまう。3列目は明示的に引数を設定した場合だが、2列目のデフォルト設定と同じである。そのため、日本時間2020-12-31 15:00:00
としたければ、そこから9時間戻す必要がある。5列目は引数にGMTをつけた場合、2020-12-31 15:00:00
は2020-12-31 15:00:00
と変換されている。
# デフォルトのSys.timezone()が使用されて、tz = "Asia/Tokyo"
dt[, Default := fasttime::fastPOSIXct(datetime),]
dt[, Tokyo := fasttime::fastPOSIXct(datetime, tz = "Asia/Tokyo"),]
dt[, Coordinated_Tokyo := fasttime::fastPOSIXct(datetime, tz = "Asia/Tokyo") - lubridate::dhours(9),]
dt[, GMT := fasttime::fastPOSIXct(datetime, tz = "GMT"),]
dt
datetime Default Tokyo Coordinated_Tokyo GMT
1: 2019-12-31 14:00:00 2019-12-31 23:00:00 2019-12-31 23:00:00 2019-12-31 14:00:00 2019-12-31 14:00:00
2: 2020-12-31 15:00:00 2021-01-01 00:00:00 2021-01-01 00:00:00 2020-12-31 15:00:00 2020-12-31 15:00:00
3: 2020-12-31 16:00:00 2021-01-01 01:00:00 2021-01-01 01:00:00 2020-12-31 16:00:00 2020-12-31 16:00:00
4: 2020-12-31 17:00:00 2021-01-01 02:00:00 2021-01-01 02:00:00 2020-12-31 17:00:00 2020-12-31 17:00:00
5: 2020-12-31 18:00:00 2021-01-01 03:00:00 2021-01-01 03:00:00 2020-12-31 18:00:00 2020-12-31 18:00:00
タイムゾーンの確認もしておく。
attributes(dt$default)
NULL
attributes(dt$Tokyo)
$class
[1] "POSIXct" "POSIXt"
$tzone
[1] "Asia/Tokyo"
attributes(dt$Coordinated_Tokyo)
$tzone
[1] "Asia/Tokyo"
$class
[1] "POSIXct" "POSIXt"
attributes(dt$GMT)
$class
[1] "POSIXct" "POSIXt"
$tzone
[1] "GMT"
ちなみに、Dockerでrockerイメージを使ってRstudioServerを起動すると、タイムゾーンはデフォルトでEtc/UTC
なので、日本時間2020-12-31 15:00:00
だと思って文字列をデフォルト設定で変換しても2020-12-31 15:00:00
となる。実行環境がこのように変わる場合、デフォルトの挙動が変わるので、注意が必要ですね(完全に自戒)。
# Sys.timezone()
# [1] "Etc/UTC"
dt[, Default := fasttime::fastPOSIXct(datetime),]
dt[, Tokyo := fasttime::fastPOSIXct(datetime, tz = "Asia/Tokyo"),]
dt[, Coordinated_Tokyo := fasttime::fastPOSIXct(datetime, tz = "Asia/Tokyo") - lubridate::dhours(9),]
dt[, GMT := fasttime::fastPOSIXct(datetime, tz = "GMT"),]
dt
datetime Default Tokyo Coordinated_Tokyo GMT
1: 2019-12-31 14:00:00 2019-12-31 14:00:00 2019-12-31 23:00:00 2019-12-31 14:00:00 2019-12-31 14:00:00
2: 2020-12-31 15:00:00 2020-12-31 15:00:00 2021-01-01 00:00:00 2020-12-31 15:00:00 2020-12-31 15:00:00
3: 2020-12-31 16:00:00 2020-12-31 16:00:00 2021-01-01 01:00:00 2020-12-31 16:00:00 2020-12-31 16:00:00
4: 2020-12-31 17:00:00 2020-12-31 17:00:00 2021-01-01 02:00:00 2020-12-31 17:00:00 2020-12-31 17:00:00
5: 2020-12-31 18:00:00 2020-12-31 18:00:00 2021-01-01 03:00:00 2020-12-31 18:00:00 2020-12-31 18:00:00
i
に数値を指定することで行を操作できる。
dt[1:5, , ]
order_id order_datetime user_id branch_num price product_id
1: 2 2003-12-20 09:10:59 53605 1 200 33120
2: 2 2003-12-20 09:10:59 53605 2 110 28985
3: 2 2003-12-20 09:10:59 53605 3 410 9327
4: 2 2003-12-20 09:10:59 53605 4 230 45918
5: 2 2003-12-20 09:10:59 53605 5 330 30035
dt[seq(0,100000,30000)*5, , ]
order_id order_datetime user_id branch_num price product_id
1: 15807 2005-11-11 20:16:48 20286 7 410 31769
2: 31628 2001-10-16 05:09:42 906869 9 420 26139
3: 47638 2004-08-21 01:48:01 12774 3 290 29820
==
、>
、<
、>=
、<=
、is.na()
、!is.na()
、%in%
、%like%
、%between%
などの比較演算子を利用して行を操作できる。
dt[order_id == 3421080, , ]
order_id order_datetime user_id branch_num price product_id
1: 3421080 2004-06-22 23:21:36 911407 1 240 27845
2: 3421080 2004-06-22 23:21:36 911407 2 360 4932
3: 3421080 2004-06-22 23:21:36 911407 3 250 18811
4: 3421080 2004-06-22 23:21:36 911407 4 320 41950
5: 3421080 2004-06-22 23:21:36 911407 5 400 31717
6: 3421080 2004-06-22 23:21:36 911407 6 300 12935
7: 3421080 2004-06-22 23:21:36 911407 7 150 25122
8: 3421080 2004-06-22 23:21:36 911407 8 510 10667
9: 3421080 2004-06-22 23:21:36 911407 9 450 38061
dt[order_id > 3421081, , ]
order_id order_datetime user_id branch_num price product_id
1: 3421082 2002-06-30 19:21:21 362401 1 450 17279
2: 3421082 2002-06-30 19:21:21 362401 2 320 12738
3: 3421082 2002-06-30 19:21:21 362401 3 450 16797
【長いので略】
15: 3421083 2002-01-25 07:38:57 842528 8 260 4600
16: 3421083 2002-01-25 07:38:57 842528 9 350 24852
17: 3421083 2002-01-25 07:38:57 842528 10 400 5020
and条件の&
または、or条件の|
で比較演算子をつなげればよい。
dt[order_id == 3421083 & branch_num == 10, , ]
order_id order_datetime user_id branch_num price product_id
1: 3421083 2002-01-25 07:38:57 842528 10 400 5020
dt[order_id >= 3421080 & branch_num == 5, , ]
order_id order_datetime user_id branch_num price product_id
1: 3421080 2004-06-22 23:21:36 911407 5 400 31717
2: 3421081 2000-09-25 14:36:25 185767 5 200 20539
3: 3421082 2002-06-30 19:21:21 362401 5 430 32700
4: 3421083 2002-01-25 07:38:57 842528 5 50 35211
あいまい検索(like)を行う場合は%like%
を利用する。
flights2 <- data.table::copy(flights)
data.table::setDT(flights2)
flights2[tailnum %like% "N1110"]
flights2[tailnum %like% "N1110", "tailnum", ]
tailnum
1: N11107
2: N11107
3: N11107
4: N11106
5: N11106
---
421: N11109
422: N11106
423: N11109
424: N11109
425: N11109
否定演算子と組み合わせることもできる。%nin%
はデフォルトではないので、!
と%in%
を組み合わせれば同じことができる。
flights2[!tailnum %like% "N11", "tailnum", ]
tailnum
1: N14228
2: N24211
3: N619AA
4: N804JB
5: N668DN
---
331178: <NA>
331179: <NA>
331180: N535MQ
331181: N511MQ
331182: N839MQ
ちなみにカラム名をダブルクオーテーションで囲まなければ、1列の指定の場合ベクトルで取り出せる。
flights2[tailnum %like% "N1110", tailnum, ][1:10]
[1] "N11107" "N11107" "N11107" "N11106" "N11106" "N11109" "N11109" "N11107" "N11107" "N11107"
%between%
で区間を指定する場合。ここでは、“2002-01-25
07:38:55”から”2002-01-25
07:38:59”までの4秒間で発生したトランザクションを検索する。
# 日付型に変換
dt[, order_datetime := fasttime::fastPOSIXct(order_datetime, tz = "Asia/Tokyo") - lubridate::dhours(9),]
# dt["2002-01-25 07:38:59" >= order_datetime & order_datetime >= "2002-01-25 07:38:55", ,] と同じ
dt[order_datetime %between% c("2002-01-25 07:38:55", "2002-01-25 07:38:59"), ,]
order_id order_datetime user_id branch_num price product_id
1: 3421083 2002-01-25 07:38:57 842528 1 330 7854
2: 3421083 2002-01-25 07:38:57 842528 2 490 45309
3: 3421083 2002-01-25 07:38:57 842528 3 480 21162
4: 3421083 2002-01-25 07:38:57 842528 4 360 18176
5: 3421083 2002-01-25 07:38:57 842528 5 50 35211
6: 3421083 2002-01-25 07:38:57 842528 6 360 39678
7: 3421083 2002-01-25 07:38:57 842528 7 240 11352
8: 3421083 2002-01-25 07:38:57 842528 8 260 4600
9: 3421083 2002-01-25 07:38:57 842528 9 350 24852
10: 3421083 2002-01-25 07:38:57 842528 10 400 5020
dt[order_id %between% c(700, 780), ,]
order_id order_datetime user_id branch_num price product_id
1: 700 2003-07-01 13:01:33 478908 1 260 21903
2: 700 2003-07-01 13:01:33 478908 2 280 8571
3: 700 2003-07-01 13:01:33 478908 3 380 46149
4: 700 2003-07-01 13:01:33 478908 4 260 196
5: 700 2003-07-01 13:01:33 478908 5 220 14715
---
792: 778 2005-12-31 23:57:25 35254 17 260 43789
793: 779 2006-05-14 16:27:44 810647 1 450 16797
794: 779 2006-05-14 16:27:44 810647 2 310 31915
795: 780 2006-07-15 15:58:22 354652 1 290 19348
796: 780 2006-07-15 15:58:22 354652 2 300 14905
基本的には、%付き演算子のほうが検索速度が速い。これも要検証。
system.time(dt[order_id %between% c(1, 780), ,])
ユーザ システム 経過
0.094 0.001 0.094
system.time(dt[780 >= order_id & order_id >= 1, ,])
ユーザ システム 経過
0.195 0.001 0.199
行の並び替えを行う場合はsetorder()
を利用する。デフォルトは昇順の並び替えになる。
dt[1:10, , ]
order_id order_datetime user_id branch_num price product_id
1: 2 2003-12-20 09:10:59 53605 1 200 33120
2: 2 2003-12-20 09:10:59 53605 2 110 28985
3: 2 2003-12-20 09:10:59 53605 3 410 9327
4: 2 2003-12-20 09:10:59 53605 4 230 45918
5: 2 2003-12-20 09:10:59 53605 5 330 30035
6: 2 2003-12-20 09:10:59 53605 6 120 17794
7: 2 2003-12-20 09:10:59 53605 7 410 40141
8: 2 2003-12-20 09:10:59 53605 8 460 1819
9: 2 2003-12-20 09:10:59 53605 9 320 43668
10: 3 2006-12-29 09:21:27 561254 1 400 33754
data.table::setorder(dt, order_datetime)
dt[1:10, , ]
order_id order_datetime user_id branch_num price product_id
1: 1140762 2000-01-01 00:00:41 906591 1 230 40486
2: 1140762 2000-01-01 00:00:41 906591 2 300 36810
3: 1140762 2000-01-01 00:00:41 906591 3 430 15945
4: 1140762 2000-01-01 00:00:41 906591 4 280 8424
5: 1140762 2000-01-01 00:00:41 906591 5 380 40586
6: 1140762 2000-01-01 00:00:41 906591 6 380 48364
7: 1140762 2000-01-01 00:00:41 906591 7 240 15100
8: 1948161 2000-01-01 00:01:20 955985 1 460 34209
9: 1948161 2000-01-01 00:01:20 955985 2 210 23362
10: 1948161 2000-01-01 00:01:20 955985 3 230 13712
複数のカラムを基準に並び替える際は、複数指定し、降順にする場合は-
を付与する。
setorder(dt, order_datetime, -branch_num)
dt[1:10, , ]
order_id order_datetime user_id branch_num price product_id
1: 1140762 2000-01-01 00:00:41 906591 7 240 15100
2: 1140762 2000-01-01 00:00:41 906591 6 380 48364
3: 1140762 2000-01-01 00:00:41 906591 5 380 40586
4: 1140762 2000-01-01 00:00:41 906591 4 280 8424
5: 1140762 2000-01-01 00:00:41 906591 3 430 15945
6: 1140762 2000-01-01 00:00:41 906591 2 300 36810
7: 1140762 2000-01-01 00:00:41 906591 1 230 40486
8: 1948161 2000-01-01 00:01:20 955985 11 250 40604
9: 1948161 2000-01-01 00:01:20 955985 10 320 1090
10: 1948161 2000-01-01 00:01:20 955985 9 200 28050
# もとの並び順に戻す
setorder(dt, order_id, branch_num)
j
に数値を指定することで列を操作できる。
dt[, c(1, 2, 3), ]
order_id order_datetime user_id
1: 2 2003-12-20 09:10:59 53605
2: 2 2003-12-20 09:10:59 53605
3: 2 2003-12-20 09:10:59 53605
4: 2 2003-12-20 09:10:59 53605
5: 2 2003-12-20 09:10:59 53605
---
32434485: 3421083 2002-01-25 07:38:57 842528
32434486: 3421083 2002-01-25 07:38:57 842528
32434487: 3421083 2002-01-25 07:38:57 842528
32434488: 3421083 2002-01-25 07:38:57 842528
32434489: 3421083 2002-01-25 07:38:57 842528
j
に文字を指定することで列を操作できる。
dt[, c("price", "product_id"), ]
price product_id
1: 200 33120
2: 110 28985
3: 410 9327
4: 230 45918
5: 330 30035
---
32434485: 360 39678
32434486: 240 11352
32434487: 260 4600
32434488: 350 24852
32434489: 400 5020
他にもlist
や.
を使う方法がある。
dt[, list(price, product_id), ]
price product_id
1: 200 33120
2: 110 28985
3: 410 9327
4: 230 45918
5: 330 30035
---
32434485: 360 39678
32434486: 240 11352
32434487: 260 4600
32434488: 350 24852
32434489: 400 5020
dt[, .(price, product_id), ]
price product_id
1: 200 33120
2: 110 28985
3: 410 9327
4: 230 45918
5: 330 30035
---
32434485: 360 39678
32434486: 240 11352
32434487: 260 4600
32434488: 350 24852
32434489: 400 5020
カラム数が多く、特定の列だけを除きたい場合は!
を利用する。もちろん、さきに除外したいカラムをdrop <- c("x", "y", "z")
として、dt[, , !drop, ]
としても同じ。
dt[, !c("order_id"), ]
order_datetime user_id branch_num price product_id
1: 2003-12-20 09:10:59 53605 1 200 33120
2: 2003-12-20 09:10:59 53605 2 110 28985
3: 2003-12-20 09:10:59 53605 3 410 9327
4: 2003-12-20 09:10:59 53605 4 230 45918
5: 2003-12-20 09:10:59 53605 5 330 30035
---
32434485: 2002-01-25 07:38:57 842528 6 360 39678
32434486: 2002-01-25 07:38:57 842528 7 240 11352
32434487: 2002-01-25 07:38:57 842528 8 260 4600
32434488: 2002-01-25 07:38:57 842528 9 350 24852
32434489: 2002-01-25 07:38:57 842528 10 400 5020
dt[, !c("order_id", "order_datetime"), ]
user_id branch_num price product_id
1: 53605 1 200 33120
2: 53605 2 110 28985
3: 53605 3 410 9327
4: 53605 4 230 45918
5: 53605 5 330 30035
---
32434485: 842528 6 360 39678
32434486: 842528 7 240 11352
32434487: 842528 8 260 4600
32434488: 842528 9 350 24852
32434489: 842528 10 400 5020
is.*
系のヘルパー関数で列の操作を行う場合は、which()
とsapply()
を組み合わせて利用する。
# POSIXctだけ
dt[, .SD, .SDcols = which(sapply(dt, is.POSIXct))]
order_datetime
1: 2003-12-20 09:10:59
2: 2003-12-20 09:10:59
3: 2003-12-20 09:10:59
4: 2003-12-20 09:10:59
5: 2003-12-20 09:10:59
---
32434485: 2002-01-25 07:38:57
32434486: 2002-01-25 07:38:57
32434487: 2002-01-25 07:38:57
32434488: 2002-01-25 07:38:57
32434489: 2002-01-25 07:38:57
# POSIXct以外
dt[, .SD, .SDcols = !which(sapply(dt, is.POSIXct))]
order_id user_id branch_num price product_id
1: 2 53605 1 200 33120
2: 2 53605 2 110 28985
3: 2 53605 3 410 9327
4: 2 53605 4 230 45918
5: 2 53605 5 330 30035
---
32434485: 3421083 842528 6 360 39678
32434486: 3421083 842528 7 240 11352
32434487: 3421083 842528 8 260 4600
32434488: 3421083 842528 9 350 24852
32434489: 3421083 842528 10 400 5020
正規表現で列を操作する場合、%like%
やgrep()
が利用できる。with
は位置だけでその列を取得したい場合、with=FALSE
という引数を追加する必要がある。
dt[,names(dt) %like% "order", with=FALSE]
order_id order_datetime
1: 2 2003-12-20 09:10:59
2: 2 2003-12-20 09:10:59
3: 2 2003-12-20 09:10:59
4: 2 2003-12-20 09:10:59
5: 2 2003-12-20 09:10:59
---
32434485: 3421083 2002-01-25 07:38:57
32434486: 3421083 2002-01-25 07:38:57
32434487: 3421083 2002-01-25 07:38:57
32434488: 3421083 2002-01-25 07:38:57
32434489: 3421083 2002-01-25 07:38:57
dt[, grep("^order", names(dt)), with = FALSE]
order_id order_datetime
1: 2 2003-12-20 09:10:59
2: 2 2003-12-20 09:10:59
3: 2 2003-12-20 09:10:59
4: 2 2003-12-20 09:10:59
5: 2 2003-12-20 09:10:59
---
32434485: 3421083 2002-01-25 07:38:57
32434486: 3421083 2002-01-25 07:38:57
32434487: 3421083 2002-01-25 07:38:57
32434488: 3421083 2002-01-25 07:38:57
32434489: 3421083 2002-01-25 07:38:57
列名を変更する場合は、setnames()
を利用しベクトルを渡すことで名前を変更できる。
data.table::setnames(dt, c("order_id", "user_id"), c("ORDER_ID", "USER_ID"))
dt
ORDER_ID order_datetime USER_ID branch_num price product_id
1: 2 2003-12-20 09:10:59 53605 1 200 33120
2: 2 2003-12-20 09:10:59 53605 2 110 28985
3: 2 2003-12-20 09:10:59 53605 3 410 9327
4: 2 2003-12-20 09:10:59 53605 4 230 45918
5: 2 2003-12-20 09:10:59 53605 5 330 30035
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020
# 上書きされるのでもとに戻す
data.table::setnames(dt, c("ORDER_ID", "USER_ID"), c("order_id", "user_id"))
ベクトルを渡せば良いので、全カラムを大文字、小文字に変換もできる。
data.table::setnames(dt, names(dt), stringr::str_to_upper(names(dt)))
dt
ORDER_ID ORDER_DATETIME USER_ID BRANCH_NUM PRICE PRODUCT_ID
1: 2 2003-12-20 09:10:59 53605 1 200 33120
2: 2 2003-12-20 09:10:59 53605 2 110 28985
3: 2 2003-12-20 09:10:59 53605 3 410 9327
4: 2 2003-12-20 09:10:59 53605 4 230 45918
5: 2 2003-12-20 09:10:59 53605 5 330 30035
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020
# もとに戻す
setnames(dt, names(dt), stringr::str_to_lower(names(dt)))
sum()
、mean()
、sd()
、var()
、median()
、min()
、max()
などの集計関数を利用する場合は下記のように記述する。チートシートにあるようにdt[, .(name = sum(x)), ]
という書き方が一般的の模様。
dt[, sum(price), ]
[1] 9912645520
dt[, sum_price = sum(price), ]
`[.data.table`(dt, , sum_price = sum(price), ) でエラー:
使われていない引数 (sum_price = sum(price))
dt[, .(sum_price = sum(price)), ]
sum_price
1: 9912645520
複数の集計を同時に行う場合は、そのまま.()
内に記述すればよい。
dt[, .(
mean = mean(price, na.rm = TRUE),
median = median(price, na.rm = TRUE),
min = min(price, na.rm = TRUE),
max = max(price, na.rm = TRUE)
)]
mean median min max
1: 305.6205 300 0 780
重複を削除する場合は、unique()
を利用する。ここでは、flights2データのc("year", "month", "day")
のユニークな行にしている。そのため、1日ごとのデータに変わっている。また、ユニークな組み合わせの数を求める際はuniqueN()
を利用する。
# 特に計算に意味はない
unique(flights2, by = c("year", "month", "day"))
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour
1: 2013 1 1 517 515 2 830 819 11 UA 1545 N14228 EWR IAH 227 1400 5 15 2013-01-01 05:00:00
2: 2013 1 2 42 2359 43 518 442 36 B6 707 N580JB JFK SJU 189 1598 23 59 2013-01-02 23:00:00
3: 2013 1 3 32 2359 33 504 442 22 B6 707 N763JB JFK SJU 193 1598 23 59 2013-01-03 23:00:00
4: 2013 1 4 25 2359 26 505 442 23 B6 707 N554JB JFK SJU 194 1598 23 59 2013-01-04 23:00:00
5: 2013 1 5 14 2359 15 503 445 18 B6 739 N592JB JFK PSE 201 1617 23 59 2013-01-05 23:00:00
---
361: 2013 9 26 451 500 -9 619 648 -29 US 1877 N155UW EWR CLT 72 529 5 0 2013-09-26 05:00:00
362: 2013 9 27 453 500 -7 627 648 -21 US 1877 N161UW EWR CLT 76 529 5 0 2013-09-27 05:00:00
363: 2013 9 28 451 500 -9 627 648 -21 US 1877 N181UW EWR CLT 77 529 5 0 2013-09-28 05:00:00
364: 2013 9 29 521 520 1 739 800 -21 UA 322 N564UA EWR IAH 179 1400 5 20 2013-09-29 05:00:00
365: 2013 9 30 453 500 -7 621 648 -27 US 1877 N186US EWR CLT 76 529 5 0 2013-09-30 05:00:00
uniqueN(flights2, by = c("year", "month", "day"))
[1] 365
この表現が正しいかはさておき、データの組み合わせを集計できるrollup()
とcube()
という便利関数がある。複数の合計を生成するグループ化の様々なレベルでの集計を計算してくれる便利な関数。SQLでいうところのGROUPING
SETS。
dt_roll_cube <- data.table::data.table(
c1 = c("A", "B", "A", "B", "A", "B", "B"),
c2 = c("a", "b", "a", "a", "b", "c", "c"),
x = 1:7)
setorder(dt_roll_cube, c1, c2)
dt_roll_cube
c1 c2 x
1: A a 1
2: A a 3
3: A b 5
4: B a 4
5: B b 2
6: B c 6
7: B c 7
このデータをもとにrollup()
とcube()
を使うことで、A-a
、A-b
、B-a
、B-b
、B-c
、A-NA
、B-NA
、NA-a
、NA-b
、NA-b
、NA-NA
という組み合わせで集計できる。cube()
のほうがより多くの組み合わせを返す。グループ化計算でも似たようなことができるが、それはあくまでグループの組み合わせ数しか計算できない。
data.table::rollup(dt_roll_cube, sum(x), by = c("c1", "c2"), id = TRUE)
grouping c1 c2 V1
1: 0 A a 4
2: 0 A b 5
3: 0 B a 4
4: 0 B b 2
5: 0 B c 13
6: 1 A <NA> 9
7: 1 B <NA> 19
8: 3 <NA> <NA> 28
data.table::cube(dt_roll_cube, sum(x), by = c("c1", "c2"), id = TRUE)
grouping c1 c2 V1
1: 0 A a 4
2: 0 A b 5
3: 0 B a 4
4: 0 B b 2
5: 0 B c 13
6: 1 A <NA> 9
7: 1 B <NA> 19
8: 2 <NA> a 8
9: 2 <NA> b 7
10: 2 <NA> c 13
11: 3 <NA> <NA> 28
dplyrパッケージのmutate()
のように列の計算を行う場合はj
に:=
セイウチ演算子を利用し、計算式を記述する。代入しなくても既存のdata.tableが上書きされる。
dt[, price_with_tax := price * 1.08,]
dt
order_id order_datetime user_id branch_num price product_id price_with_tax
1: 2 2003-12-20 09:10:59 53605 1 200 33120 216.0
2: 2 2003-12-20 09:10:59 53605 2 110 28985 118.8
3: 2 2003-12-20 09:10:59 53605 3 410 9327 442.8
4: 2 2003-12-20 09:10:59 53605 4 230 45918 248.4
5: 2 2003-12-20 09:10:59 53605 5 330 30035 356.4
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678 388.8
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352 259.2
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600 280.8
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852 378.0
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020 432.0
ちなみに、列の計算を新しいオブジェクトにだけ適用しようとしても、既存のテーブルも更新されるので注意。set*
や:=
はメモリアドレスをそのまま利用するため。
d <- dt[, price_with_tax := price * 1.08,]
identical(d,dt)
[1] TRUE
data.tableでは条件をつけて列の計算がシンプルに記述できる。例えばbranch_num
が3より小さければ、price
には0をかける、という条件付きの計算ができる。もちろん、if文を使って同じこともできる。
dt[branch_num < 3, price_with_tax := price * 0,]
dt
order_id order_datetime user_id branch_num price product_id price_with_tax
1: 2 2003-12-20 09:10:59 53605 1 200 33120 0.0
2: 2 2003-12-20 09:10:59 53605 2 110 28985 0.0
3: 2 2003-12-20 09:10:59 53605 3 410 9327 442.8
4: 2 2003-12-20 09:10:59 53605 4 230 45918 248.4
5: 2 2003-12-20 09:10:59 53605 5 330 30035 356.4
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678 388.8
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352 259.2
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600 280.8
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852 378.0
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020 432.0
# 4より小さい場合にして違いがわかるようにする
dt[, price_with_tax := ifelse(branch_num < 4, price * 0, price),]
dt
order_id order_datetime user_id branch_num price product_id price_with_tax
1: 2 2003-12-20 09:10:59 53605 1 200 33120 0
2: 2 2003-12-20 09:10:59 53605 2 110 28985 0
3: 2 2003-12-20 09:10:59 53605 3 410 9327 0
4: 2 2003-12-20 09:10:59 53605 4 230 45918 230
5: 2 2003-12-20 09:10:59 53605 5 330 30035 330
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678 360
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352 240
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600 260
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852 350
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020 400
列を削除する場合は、カラム名に対してNULL
を代入することで、指定したカラムをドロップできる。
dt[, price_with_tax := NULL, ]
dt
order_id order_datetime user_id branch_num price product_id
1: 2 2003-12-20 09:10:59 53605 1 200 33120
2: 2 2003-12-20 09:10:59 53605 2 110 28985
3: 2 2003-12-20 09:10:59 53605 3 410 9327
4: 2 2003-12-20 09:10:59 53605 4 230 45918
5: 2 2003-12-20 09:10:59 53605 5 330 30035
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020
複数列の計算を行う場合は:=
をバックティックで囲んで、複数の式を記述する。
dt[, `:=`(price10 = price * 10, price100 = price * 100),]
dt
order_id order_datetime user_id branch_num price product_id price10 price100
1: 2 2003-12-20 09:10:59 53605 1 200 33120 2000 20000
2: 2 2003-12-20 09:10:59 53605 2 110 28985 1100 11000
3: 2 2003-12-20 09:10:59 53605 3 410 9327 4100 41000
4: 2 2003-12-20 09:10:59 53605 4 230 45918 2300 23000
5: 2 2003-12-20 09:10:59 53605 5 330 30035 3300 33000
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678 3600 36000
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352 2400 24000
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600 2600 26000
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852 3500 35000
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020 4000 40000
複数の列計算を行う場合は条件付き計算ができないので注意。
dt[branch_num < 3, `:=`(price10 = price * 10, price100 = price * 100),]
dt
order_id order_datetime user_id branch_num price product_id price10 price100
1: 2 2003-12-20 09:10:59 53605 1 200 33120 2000 20000
2: 2 2003-12-20 09:10:59 53605 2 110 28985 1100 11000
3: 2 2003-12-20 09:10:59 53605 3 410 9327 4100 41000
4: 2 2003-12-20 09:10:59 53605 4 230 45918 2300 23000
5: 2 2003-12-20 09:10:59 53605 5 330 30035 3300 33000
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678 3600 36000
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352 2400 24000
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600 2600 26000
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852 3500 35000
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020 4000 40000
列の計算と同じように型変換関数を利用すればよい。ここでは小数点型のprice_with_tax
を整数型に変換する。これによって、小数点が丸まり、データ型が変換されていることがわかる。
dt[, price_with_tax := price * 1.08,]
dt
order_id order_datetime user_id branch_num price product_id price_with_tax
1: 2 2003-12-20 09:10:59 53605 1 200 33120 216.0
2: 2 2003-12-20 09:10:59 53605 2 110 28985 118.8
3: 2 2003-12-20 09:10:59 53605 3 410 9327 442.8
4: 2 2003-12-20 09:10:59 53605 4 230 45918 248.4
5: 2 2003-12-20 09:10:59 53605 5 330 30035 356.4
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678 388.8
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352 259.2
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600 280.8
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852 378.0
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020 432.0
dt[, price_with_tax := as.integer(price_with_tax)]
dt
order_id order_datetime user_id branch_num price product_id price_with_tax
1: 2 2003-12-20 09:10:59 53605 1 200 33120 216
2: 2 2003-12-20 09:10:59 53605 2 110 28985 118
3: 2 2003-12-20 09:10:59 53605 3 410 9327 442
4: 2 2003-12-20 09:10:59 53605 4 230 45918 248
5: 2 2003-12-20 09:10:59 53605 5 330 30035 356
---
32434485: 3421083 2002-01-25 07:38:57 842528 6 360 39678 388
32434486: 3421083 2002-01-25 07:38:57 842528 7 240 11352 259
32434487: 3421083 2002-01-25 07:38:57 842528 8 260 4600 280
32434488: 3421083 2002-01-25 07:38:57 842528 9 350 24852 378
32434489: 3421083 2002-01-25 07:38:57 842528 10 400 5020 432
# もとに戻す
dt[, price_with_tax := NULL, ]
グループ化計算は、by
にカラムを指定することでグループ化されるので、それにより行う。まずは、このトランザクションデータに年order_yaer
と月order_month
を追加する。
dt[, order_yaer := lubridate::year(order_datetime),]
dt[, order_month := lubridate::month(order_datetime),]
data.table::setkey(dt, "order_yaer", "order_month")
グループ化集計は下記のように記述する。
# 1つのグループ化と1つの集計関数
dt[, .(sum_price = sum(price)), by = order_yaer]
order_yaer sum_price
1: 2000 990548810
2: 2001 986052220
3: 2002 992225870
4: 2003 991483970
5: 2004 993533550
6: 2005 990051130
7: 2006 990772120
8: 2007 989867690
9: 2008 992720950
10: 2009 992566890
11: 2010 2822320
.()
をつけないと、エラーになるので注意。
dt[, sum_price = sum(price), by = order_yaer]
`[.data.table`(dt, , sum_price = sum(price), by = order_yaer) でエラー:
使われていない引数 (sum_price = sum(price))
グループ化の単位を複数指定することも可能。その場合はby
にベクトルを渡す。
dt[, .(sum_price = sum(price)), by = c("order_yaer","order_month")]
order_yaer order_month sum_price
1: 2000 1 83586700
2: 2000 2 77677530
3: 2000 3 83966190
4: 2000 4 81226780
5: 2000 5 84364940
---
117: 2009 9 81803670
118: 2009 10 84297040
119: 2009 11 82029220
120: 2009 12 84424100
121: 2010 1 2822320
グループ化の単位を複数指定し、集計関数を複数指定することも可能。その場合は、.()
内に複数の計算式を記述する。
dt[,
.(sum_price = sum(price),
avg_price = mean(price)
),
by = c("order_yaer","order_month")]
order_yaer order_month sum_price avg_price
1: 2000 1 83586700 305.4109
2: 2000 2 77677530 305.5849
3: 2000 3 83966190 305.4404
4: 2000 4 81226780 305.7109
5: 2000 5 84364940 305.5162
---
117: 2009 9 81803670 305.8927
118: 2009 10 84297040 305.8019
119: 2009 11 82029220 305.8817
120: 2009 12 84424100 305.7382
121: 2010 1 2822320 306.1085
.SD
を使った複数列と1つの集計関数集計関数は1つだが、複数の列に適用したい場合、.SD
という演算子が利用できる。.SD
は’Subset
of
Data’の略で、データのサブセットを作ってくれる。下記の例では、サブセットにc("order_id","order_datetime","user_id","branch_num", "price", "product_id")
を指定し、それに対し、lapply(.SD, min)
とすることで、各サブセットのカラムに対し、min()
を適用できる。
dt[,
lapply(.SD, min),
.SDcols = c("order_id","order_datetime","user_id","branch_num", "price", "product_id")]
order_id order_datetime user_id branch_num price product_id
1: 2 2000-01-01 00:00:41 1 1 0 1
.SD
を使った計算でも、グループ化することは可能。通常通りby
に指定する。
dt[,
lapply(.SD, sum),
by = order_yaer,
.SDcols = c("branch_num", "price")]
order_yaer branch_num price
1: 2000 27078348 990548810
2: 2001 26919797 986052220
3: 2002 27090914 992225870
4: 2003 27083785 991483970
5: 2004 27160601 993533550
6: 2005 27110321 990051130
7: 2006 27040760 990772120
8: 2007 27017839 989867690
9: 2008 27134445 992720950
10: 2009 27148734 992566890
11: 2010 77323 2822320
もちろん、複数列と複数のグループ化と1つの集計関数という組み合わせも可能。
dt[,
lapply(.SD, sum),
by = c("order_yaer","order_month"),
.SDcols = c("branch_num", "price")]
order_yaer order_month branch_num price
1: 2000 1 2273233 83586700
2: 2000 2 2147128 77677530
3: 2000 3 2291413 83966190
4: 2000 4 2232009 81226780
5: 2000 5 2309220 84364940
---
117: 2009 9 2265171 81803670
118: 2009 10 2293936 84297040
119: 2009 11 2239036 82029220
120: 2009 12 2309258 84424100
121: 2010 1 77323 2822320
集計関数は1つしか指定できないので注意。
dt[,
lapply(.SD, sum, max),
by = c("order_yaer","order_month"),
.SDcols = c("branch_num", "price")]
sum(branch_num, max) でエラー: 引数 'type' (builtin) が不正です
エラー文をみるとsum(branch_num, max)
となっており、max
がsum
に内包されている。つまり、sum()
の引数であれば動く。
dt[,
lapply(.SD, sum, na.rm = TRUE),
by = c("order_yaer","order_month"),
.SDcols = c("branch_num", "price")]
order_yaer order_month branch_num price
1: 2000 1 2273233 83586700
2: 2000 2 2147128 77677530
3: 2000 3 2291413 83966190
4: 2000 4 2232009 81226780
5: 2000 5 2309220 84364940
---
117: 2009 9 2265171 81803670
118: 2009 10 2293936 84297040
119: 2009 11 2239036 82029220
120: 2009 12 2309258 84424100
121: 2010 1 77323 2822320
dplyrパッケージのgroup_by()
とmutate()
を組み合わせ、グループ化した単位で集計した結果を追加するような計算をしたい場合、下記のように記述する。
col <- "price"
dt[order_id == 154 | order_id == 387,
paste0(col, "_sum") := lapply(.SD, sum),
.SDcols = col,
by = order_id]
dt[order_id == 154 | order_id == 387,,]
order_id order_datetime user_id branch_num price product_id order_yaer order_month price_sum
1: 154 2000-01-09 10:13:00 351476 1 190 30489 2000 1 1150
2: 154 2000-01-09 10:13:00 351476 2 260 25246 2000 1 1150
3: 154 2000-01-09 10:13:00 351476 3 360 41801 2000 1 1150
4: 154 2000-01-09 10:13:00 351476 4 340 11806 2000 1 1150
5: 387 2000-01-18 17:09:56 912079 1 300 12916 2000 1 3710
6: 387 2000-01-18 17:09:56 912079 2 310 2457 2000 1 3710
7: 387 2000-01-18 17:09:56 912079 3 420 13032 2000 1 3710
8: 387 2000-01-18 17:09:56 912079 4 440 40688 2000 1 3710
9: 387 2000-01-18 17:09:56 912079 5 570 39275 2000 1 3710
10: 387 2000-01-18 17:09:56 912079 6 450 16797 2000 1 3710
11: 387 2000-01-18 17:09:56 912079 7 210 32538 2000 1 3710
12: 387 2000-01-18 17:09:56 912079 8 240 35561 2000 1 3710
13: 387 2000-01-18 17:09:56 912079 9 200 19380 2000 1 3710
14: 387 2000-01-18 17:09:56 912079 10 250 32403 2000 1 3710
15: 387 2000-01-18 17:09:56 912079 11 320 19887 2000 1 3710
data.tableにはいくつかの特別なシンボルがある。例えば、.SD
、.BY
、.N
、.I
、.GRP
、and
、.NGRP
などである。データの最後の行を確認したい際は.N
を利用する。
dt[.N, , ]
order_id order_datetime user_id branch_num price product_id order_yaer
1: 2834347 2009-12-21 13:23:33 1000000 17 400 21137 2009
行数のカウントを行う際は.N
関数を利用する。
# カラム名が必要ない場合は dt[, .N, by = order_yaer] でもよい
dt[, .(cnt = .N), by = order_yaer]
order_yaer cnt
1: 2001 3226029
2: 2003 3243705
3: 2006 3242308
4: 2000 3242456
5: 2004 3251308
6: 2008 3247951
7: 2002 3246198
8: 2005 3238845
9: 2007 3239304
10: 2009 3247165
11: 2010 9220
.SDcols
で補集合を作り、それを列に.SD
として適用できる。
dt[, .SD, .SDcols = user_id:order_yaer]
user_id branch_num price product_id order_yaer
1: 1 1 330 9894 2001
2: 1 2 180 10912 2001
3: 1 3 150 34466 2001
4: 1 4 190 13733 2001
5: 1 5 330 27307 2001
---
32434485: 1000000 13 400 31717 2009
32434486: 1000000 14 120 14235 2009
32434487: 1000000 15 290 38530 2009
32434488: 1000000 16 330 43122 2009
32434489: 1000000 17 400 21137 2009
グループでカウンターを作る場合は、.GRP
を利用する。.GRP
はグループ単位で数字をインクリメントして付与してくれる。それにグループの数をカウントして付与する.NGRP
を組み合わせると、グループの割合が計算できる。
DT = data.table(x=rep(c("b","a","c"),each=3), v=c(1,1,1,2,2,1,1,2,2), y=c(1,3,6), a=1:9, b=9:1)
DT[, grp := .GRP, by = x]
DT[, ngrp := .NGRP, by = x]
DT[, grp_pct := .GRP/.NGRP, by = x]
DT
x v y a b grp ngrp grp_pct
1: b 1 1 1 9 1 3 0.3333333
2: b 1 3 2 8 1 3 0.3333333
3: b 1 6 3 7 1 3 0.3333333
4: a 2 1 4 6 2 3 0.6666667
5: a 2 3 5 5 2 3 0.6666667
6: a 1 6 6 4 2 3 0.6666667
7: c 1 1 7 3 3 3 1.0000000
8: c 2 3 8 2 3 3 1.0000000
9: c 2 6 9 1 3 3 1.0000000
set(dt, i, j, value)
は、値を代入する際に使える関数で、for-loopでも非常に高速に動作する。data.tableだけではなく、data.frameでも使える。
m = matrix(1,nrow=100000,ncol=100)
DF = as.data.frame(m)
DT = as.data.table(m)
system.time(
for (i in 1:10000){
DF[i,1] <- i
}
)
#> 13.483 seconds
system.time(
for (i in 1:10000) {
DT[i, V1:= i, ]
}
)
#> 3.559 seconds
system.time(
for (i in 1:10000) {
set(DT, i, 1L, i)
}
)
#> 0.039 seconds
後片付けを実行する。
# もとに戻す
dt[,price_sum := NULL,]
dt[,c("order_yaer", "order_month") := NULL,]
data.tableのJoinは、個人的には馴染めない部分が多い。InnerJoinしかしないのであれば、それほど気にする必要はない。下記のような簡単例を使ってユニークな点を検証する。tbl1
はトランザクション、tbl2
は会員マスタみたいなイメージ。
tbl1 <- data.table::data.table(id1 = c("X001", "X001", "X002", "X003", "X004"), x = 0:4)
tbl2 <- data.table::data.table(id2 = c("X001", "X002", "X003", "X005"), gender = c("M", "F", "M", NA), y = c(1,3,4,5))
tbl1
id1 x
1: X001 0
2: X001 1
3: X002 2
4: X003 3
5: X004 4
tbl2
id2 gender y
1: X001 M 1
2: X002 F 3
3: X003 M 4
4: X005 <NA> 5
トランザクションをプライマリーテーブルとしてLeftJoinするというケースを考えると、tbl1
のX004
は表示され、tbl2
のX005
は紐付かないはずであるが、下記のように記述すると、テーブルの主従関係が逆になる。感覚的には、LeftJoinなので、プライマリーテーブルtbl1
を左に記述したいところである。
# leftjoinを想定するとテーブルが逆
tbl1[tbl2, on = .(id1 = id2),]
id1 x gender y
1: X001 0 M 1
2: X001 1 M 1
3: X002 2 F 3
4: X003 3 M 4
5: X005 NA <NA> 5
トランザクションをプライマリーテーブルとしてLeftJoinするというケースは下記のように記述しなければいけない。これであれば想定通りの紐付けが可能。文法はこうなっているleft[right, on = "key",]
。
# leftjoin
tbl2[tbl1, on = .(id2 = id1),]
id2 gender y x
1: X001 M 1 0
2: X001 M 1 1
3: X002 F 3 2
4: X003 M 4 3
5: X004 <NA> NA 4
ただ、紐付けはうまくいくが、カラム名を見ると、tbl1
はid1
、tbl2
はid2
というカラムを持っており、プライマリーテーブルをtbl1
としたのであれば、キーとして表示されるカラムはid2
ではなく、id1
であってほしい。追加の引数などで解決できるのかまでは調べてない。baseのmerge()
やdplyrの*_join
でJoinする場合は下記のように記述する。data.tableでInnerJoinする場合はleft[right, on = "key", nomatch = 0]
とnomatch
をつける。
# leftjoin
merge(x = tbl1, y = tbl2, by.x = "id1", by.y = "id2", all.x = TRUE)
dplyr::left_join(x = tbl1, y = tbl2, by = c("id1" = "id2"), keep = TRUE)
# innerjoin
merge(x = tbl1, y = tbl2, by.x = "id1", by.y = "id2", all = FALSE)
dplyr::inner_join(x = tbl1, y = tbl2, by = c("id1" = "id2"), keep = TRUE)
# outerjoin
merge(x = tbl1, y = tbl2, by.x = "id1", by.y = "id2", all = TRUE)
dplyr::full_join(x = tbl1, y = tbl2, by = c("id1" = "id2"), keep = TRUE)
ただ、data.tableのJoinには、baseのmerge()
やdplyrの*_join
とは異なっている点がある。それは「不等号」を使ってJOINできる点である。tbl2
のy
と、tbl1
のx
とをy > x
という条件で比較して、一致するものを紐付けるというJoinが可能。2行目はy > x
という関係がFALSE
なので、gender
がNA
になっている。
# leftjoinで「一致」と「不等号」の複数条件
tbl2[tbl1, on = .(id2 = id1),]
id2 gender y x
1: X001 M 1 0
2: X001 M 1 1
3: X002 F 3 2
4: X003 M 4 3
5: X004 <NA> NA 4
tbl2[tbl1, on = .(id2 = id1, y > x),]
id2 gender y
1: X001 M 0
2: X001 <NA> 1
3: X002 F 2
4: X003 M 3
5: X004 <NA> 4
分析系のSQLを記述する際には頻繁にJoinで不等号は使用するので、data.tableの非常にありがたい点であるのは間違いないが、テーブルの位置が少し馴染めない。かといって、baseのmerge()
やdplyrの*_join
では不等号のJoinを使用できない。ここをみると、バージョンによっては出来たのかもしれない。
# 今は動かない
dplyr::left_join(x = tbl1, y = tbl2,
join_by(id1 == id2)
)
join_by(id1 == id2) でエラー:
関数 "join_by" を見つけることができませんでした
ということで、fuzzyjoinパッケージを利用するのが妥当かもしれない(fuzzyjoin一択というわけではない)。fuzzyjoinパッケージの説明は省くが、このパッケージは文字列のあいまい検索結合や不等号などの結合もサポートしている非常に便利なパッケージである。
fuzzyjoin::fuzzy_left_join(x = tbl1, y = tbl2,
by = c("id1" = "id2", "x" = "y"),
match_fun = list(`==`, `<`))
id1 x id2 gender y
1 X001 0 X001 M 1
2 X001 1 <NA> <NA> NA
3 X002 2 X002 F 3
4 X003 3 X003 M 4
5 X004 4 <NA> <NA> NA
このように不等号の条件を増やしても実行可能である。広告の配信履歴と購買データを紐付けるが、会員、広告は一致していて、注文日と広告の配信日などの期間を条件付ける場合などのケースを想定している。
ad_dt <- tibble(cu_id = c(paste0("U00",1:3)),
ad_id = c(paste0("X00",1:3)),
ad_dt = lubridate::ymd("2018-03-01")) %>%
mutate(end_dt = (ad_dt + lubridate::days(3))) %>% setDT()
tran_dt <- data.table::data.table(cu_id = c(paste0("U00",1:5)),
ad_id = c(paste0("X00",1:3),paste0("X00",1:2)),
order_dt = lubridate::ymd(c("2018-02-28", "2018-03-01", "2018-03-05",
"2018-03-05", "2018-03-06")),
total = seq(1000, 5000, 1000)) %>% setDT()
res <- fuzzyjoin::fuzzy_left_join(x = ad_dt, y = tran_dt,
by = c("cu_id" = "cu_id",
"ad_id" = "ad_id",
"ad_dt" = "order_dt",
"end_dt" = "order_dt"),
match_fun = list(`==`, `==`, `<=`, `>=`))
res
cu_id.x ad_id.x ad_dt end_dt cu_id.y ad_id.y order_dt total
1: U001 X001 2018-03-01 2018-03-04 <NA> <NA> <NA> NA
2: U002 X002 2018-03-01 2018-03-04 U002 X002 2018-03-01 2000
3: U003 X003 2018-03-01 2018-03-04 <NA> <NA> <NA> NA
これまで使用してきたトランザクションのデータにマージするための会員マスタデータを読み込んで準備しておく。
cols_du <- c(
user_id = "integer",
gender = "character",
area = "character",
age = "integer"
)
du <- data.table::fread(
file = "~/Desktop/cust_mst_1m.csv",
key = c("user_id"),
colClasses = cols_du,
header = TRUE,
encoding = "UTF-8",
sep = ",",
na.strings = ",,",
skip = 0
)
du
user_id gender area age
1: 1 Male area_B 27
2: 2 Female area_C 37
3: 4 Female area_C 30
4: 5 Male area_C 23
5: 6 Male area_B 41
---
959672: 999996 Male 27
959673: 999997 Male area_C 49
959674: 999998 Female area_C 32
959675: 999999 Female area_C NA
959676: 1000000 Male area_A 34
かといってfuzzyjoin一択というわけにもいかない。簡単なJoinでも内部の実装の違いの問題か、サイズの大きいデータでは、PCのメモリとの関係で、メモリが足りなくなる場合もある。
data.table::setkey(dt, "user_id")
system.time(du[dt, on = .(user_id = user_id),])
ユーザ システム 経過
3.922 1.641 5.910
system.time(merge(x = dt, y = du, by.x = "user_id", by.y = "user_id", all.x = TRUE))
ユーザ システム 経過
3.919 0.981 5.508
system.time(dplyr::left_join(x = dt, y = du, by = c("user_id" = "user_id"), keep = TRUE))
ユーザ システム 経過
3.168 1.191 4.893
system.time(fuzzyjoin::fuzzy_left_join(x = dt, y = du, by = c("user_id" = "user_id"), match_fun = list(`==`)))
エラー: ベクトルのメモリを使い切りました (上限に達した?)
Timing stopped at: 142.2 2.299 145.5
スペックなどにもよるので、参考までにちなみに同じデータで、同じことをpostgreでやってみると1分ちょいかかるので、やはりインメモリってめちゃ速い。postgresの設定はこちら。
CREATE INDEX user_id_idx ON public.orders (user_id);
CREATE INDEX order_user_id_idx ON public.users (user_id);
SELECT *
FROM public.orders as o
LEFT JOIN public.users as u
ON o.user_id = u.user_id;
Successfully run. Total query runtime: 1 min 23 secs.
32434489 rows affected.
なんだかんだでdata.tableの記法になれるしかないかも。
バインドを行う場合の例を見ていく。行をバインドする場合は、rbind()
を利用する。この場合、列名が異なるとバインドできないので注意。
t1 <- data.table::data.table(x = c("a", "b"), x1 = 1:2)
t2 <- data.table::data.table(x = c("c", "d"), x1 = 3:4)
t <- rbind(t1, t2)
t
x x1
1: a 1
2: b 2
3: c 3
4: d 4
t3 <- data.table::data.table(x = c("e", "f"), x2 = 5:6)
rbind(t, t3)
rbindlist(l, use.names, fill, idcol) でエラー:
Column 2 ['x2'] of item 2 is missing in item 1. Use fill=TRUE to fill with NA (NULL for list columns), or use.names=FALSE to ignore column names.
列をバインドする際はcbind()
を利用する。cbind()
で行が足りていない場合はリサイクル規則が働いてバインドされる。
t1 <- data.table::data.table(x = c("a", "b"), x1 = 1:2)
t2 <- data.table::data.table(x = c("c", "d"), x1 = 3:4)
t <- cbind(t1, t2)
t
x x1 x x1
1: a 1 c 3
2: b 2 d 4
t3 <- data.table::data.table(x = "c", x1 = 3)
cbind(t, t3)
x x1 x x1 x x1
1: a 1 c 3 c 3
2: b 2 d 4 c 3
data.tableにもdplyrパッケージのpivot_*()
(昔であればgather(), spread()
)に相当するdcast()
、melt()
がある。reshapeパッケージの名前と同じである。dcast()
はLong
to Wide変換に対応し、melt()
はWide to
Long変換に対応する。
サンプルのテーブルを用意する。
t <- data.table::data.table(pref = c("A", "B", "C"),
y2000 = 1:3, x2000 = letters[1:3],
y2005 = 4:6, x2005 = letters[4:6],
y2010 = 7:9, x2010 = letters[7:9],
y2015 = 10:12, x2015 = letters[10:12],
y2020 = 13:15, x2020 = letters[13:15])
t
pref y2000 x2000 y2005 x2005 y2010 x2010 y2015 x2015 y2020 x2020
1: A 1 a 4 d 7 g 10 j 13 m
2: B 2 b 5 e 8 h 11 k 14 n
3: C 3 c 6 f 9 i 12 l 15 o
これをpref
、year
、
y*
カラムの値vals
のLong型データに変形するには、下記のように記述する。
m1 <- data.table::melt(
data = t,
id.vars = "pref",
measure.vars = patterns("^y"),
variable.name = "year",
value.name = "vals"
)
m1
pref year vals
1: A y2000 1
2: B y2000 2
3: C y2000 3
4: A y2005 4
5: B y2005 5
6: C y2005 6
7: A y2010 7
8: B y2010 8
9: C y2010 9
10: A y2015 10
11: B y2015 11
12: C y2015 12
13: A y2020 13
14: B y2020 14
15: C y2020 15
もちろん、pref
、year
、
x*
カラムの値letters
のLong型データにも変形できる。
m2 <- data.table::melt(
data = t,
id.vars = "pref",
measure.vars = patterns("^x"),
variable.name = "year",
value.name = "letters"
)
m2
pref year letters
1: A x2000 a
2: B x2000 b
3: C x2000 c
4: A x2005 d
5: B x2005 e
6: C x2005 f
7: A x2010 g
8: B x2010 h
9: C x2010 i
10: A x2015 j
11: B x2015 k
12: C x2015 l
13: A x2020 m
14: B x2020 n
15: C x2020 o
分けて書くこともできるが、同時に実行することも可能。この場合、year
カラムにはy*
かx*
かは指定はできないので、カラム数の番号が付与される。
m3 <- data.table::melt(
data = t,
id.vars = "pref",
measure.vars = patterns("^y", "^x"),
variable.name = "year",
value.name = c("vals", "letters")
)
m3
pref year vals letters
1: A 1 1 a
2: B 1 2 b
3: C 1 3 c
4: A 2 4 d
5: B 2 5 e
6: C 2 6 f
7: A 3 7 g
8: B 3 8 h
9: C 3 9 i
10: A 4 10 j
11: B 4 11 k
12: C 4 12 l
13: A 5 13 m
14: B 5 14 n
15: C 5 15 o
そのため、わけて実行し、バインドするほうが場合によっては良いのかもしれない。
data.table::data.table(cbind(m1, m2), check.names = TRUE)
pref year vals pref.1 year.1 letters
1: A y2000 1 A x2000 a
2: B y2000 2 B x2000 b
3: C y2000 3 C x2000 c
4: A y2005 4 A x2005 d
5: B y2005 5 B x2005 e
6: C y2005 6 C x2005 f
7: A y2010 7 A x2010 g
8: B y2010 8 B x2010 h
9: C y2010 9 C x2010 i
10: A y2015 10 A x2015 j
11: B y2015 11 B x2015 k
12: C y2015 12 C x2015 l
13: A y2020 13 A x2020 m
14: B y2020 14 B x2020 n
15: C y2020 15 C x2020 o
さきほどのm1
、m2
テーブルを使って、Long to
Wide変換を行う。つまり、変形前のもとの形に戻す。
d1 <- data.table::dcast(
data = m1,
formula = pref ~ year,
value.var = "vals"
)
d1
pref y2000 y2005 y2010 y2015 y2020
1: A 1 4 7 10 13
2: B 2 5 8 11 14
3: C 3 6 9 12 15
d2 <- data.table::dcast(
data = m2,
formula = pref ~ year,
value.var = "letters"
)
d2
pref x2000 x2005 x2010 x2015 x2020
1: A a d g j m
2: B b e h k n
3: C c f i l o
こちらの同時に変形することができる。さきほど同じく、名前の問題が出てきてしまう。
d3 <- data.table::dcast(
data = m3,
formula = pref ~ year,
value.var = c("vals", "letters")
)
d3
pref vals_1 vals_2 vals_3 vals_4 vals_5 letters_1 letters_2 letters_3 letters_4 letters_5
1: A 1 4 7 10 13 a d g j m
2: B 2 5 8 11 14 b e h k n
3: C 3 6 9 12 15 c f i l o
名前の問題を回避するためには、pref.1
となってしまうが分けて実行しバインドするのが手っ取り早い。
data.table::data.table(cbind(d1, d2), check.names = TRUE)
pref y2000 y2005 y2010 y2015 y2020 pref.1 x2000 x2005 x2010 x2015 x2020
1: A 1 4 7 10 13 A a d g j m
2: B 2 5 8 11 14 B b e h k n
3: C 3 6 9 12 15 C c f i l o
単純に変形するのではなく、集計関数を使って集計しながら集計テーブルに変換できる。俗に言うピボットテーブルである。例えば、user_id
ごとに、各年で購入があったかどうかのフラグを立てたい場合や、機械学習でおなじみのワンホットエンコーディングしたい場合に使うと便利。
dt_unq <- unique(dt, by = c("user_id", "order_yaer"))
dt_unq[,flg := 1,]
data.table::dcast.data.table(dt_unq,
user_id ~ order_yaer,
fun.aggregate = max,
value.var = "flg",
fill = 0)
user_id 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010
1: 1 0 1 0 1 0 0 1 0 0 0 0
2: 2 1 1 0 1 1 0 0 0 1 0 0
3: 4 0 1 1 1 1 1 1 1 0 0 0
4: 5 0 0 0 1 0 0 0 1 1 0 0
5: 6 0 1 0 0 0 0 1 0 1 0 0
---
959672: 999996 0 0 0 1 0 0 0 0 1 0 0
959673: 999997 1 1 0 1 1 1 0 0 0 0 0
959674: 999998 0 0 0 1 0 0 0 0 0 0 0
959675: 999999 1 0 0 0 0 1 0 0 0 0 0
959676: 1000000 0 0 0 1 0 0 1 1 0 1 0
後述するが、data.tableでもパイプ%>%
は使えなくはない。.
は前の関数の結果を受け取るものなので、dt[i,j,by]
のdt
の部分を.
で置き換える。他にもdata.tableの用のパイプの記述があるので、そこで再度紹介する。
unique(dt, by = c("user_id", "order_yaer")) %>%
.[,flg := 1,] %>%
data.table::dcast.data.table(.,
user_id ~ order_yaer,
fun.aggregate = max,
value.var = "flg",
fill = 0)
user_id 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010
1: 1 0 1 0 1 0 0 1 0 0 0 0
2: 2 1 1 0 1 1 0 0 0 1 0 0
3: 4 0 1 1 1 1 1 1 1 0 0 0
4: 5 0 0 0 1 0 0 0 1 1 0 0
5: 6 0 1 0 0 0 0 1 0 1 0 0
---
959672: 999996 0 0 0 1 0 0 0 0 1 0 0
959673: 999997 1 1 0 1 1 1 0 0 0 0 0
959674: 999998 0 0 0 1 0 0 0 0 0 0 0
959675: 999999 1 0 0 0 0 1 0 0 0 0 0
959676: 1000000 0 0 0 1 0 0 1 1 0 1 0
このトランザクションテーブルのproduct_id
はユニークで5万近くの商品があるが、これをワンホットエンコーディングすると、.Machine$integer.max=2147483647
の制約でエラーが返される。単純計算で、総要素数が47,673,824,652になるので、Rの限界(正確な数値は忘れた)を超えてしまうので、こんなことをカジュアルにやってはいけない。そもそもRでは2,147,483,647行または列(=2^31
- 1= 2147483647)が限界。こちらを参照。
length(unique(dt$product_id))
[1] 49677
unique(dt, by = c("user_id", "product_id")) %>%
.[,flg := 1,] %>%
data.table::dcast.data.table(.,
user_id ~ product_id,
fun.aggregate = max,
value.var = "flg",
fill = 0)
CJ(1:959676, 1:49677) でエラー:
Cross product of elements provided to CJ() would result in 47673824652 rows which exceeds .Machine$integer.max == 2147483647
Timing stopped at: 56.1 5.56 71.55
分割して小さくしたなら出来なくもないが、おすすめはしない…。こんなことをする必要かどうかを考えて、もう一度必要かを考えて、さらにもう一度考えて、必要なのであれば、Rを諦めて、他のプログラミング言語やシステムで考えるべきな気がする。もしくはRstudio社のThree Strategies for Working with Big Data in Rにあるように、(私の仕事の分析要件としても)サプリングが妥当に思える。
ゲノムとか、自然言語処理とか、画像処理、レコメンド系からしたら小さいもん?なのかもしれないが、そっちの知見はない・・・。もしRでいくならビックデータ系のff bigmemoryパッケージ、ffパッケージとかスパース系のパッケージを組み合わせたらいけるのかな…行は問題ないと思うけど列はどうなんだろうか。というかこんなデータを機械学習のアルゴリズムで計算するなんて、スペック的にどんなもんが必要なのか想像もつかん。
# dt_unq <- unique(dt, by = c("user_id", "order_yaer"))
# dt_unq[,flg := 1,]
data.table::setorder(dt_unq, user_id)
dt_unq1 <- dt_unq[user_id %between% c(1,50000), ,]
system.time(
data.table::dcast.data.table(dt_unq1,
user_id ~ product_id,
fun.aggregate = max,
value.var = "flg",
fill = 0)
)
ユーザ システム 経過
27.557 12.559 52.522
data.tableにもウインドウ関数が用意されている。例えば、行番号を振るdplyr::row_number()
に相当するものは下記のように記述すれば実現できる。ここではわかりやすいように、user_id
を限定する。この書き方をすると、条件付けられた計算になるので、他のuser_id
はNULL
になることに注意。グループ単位で行番号を振りたい場合は、by
でグループ化を行う。
dt[user_id == 351476,
rowid := 1:.N,]
dt[user_id == 351476,
rowid_by := 1:.N,
by = order_id]
dt[user_id == 351476,,]
order_id order_datetime user_id branch_num price product_id order_yaer rowid rowid_by
1: 154 2000-01-09 10:13:00 351476 1 190 30489 2000 1 1
2: 154 2000-01-09 10:13:00 351476 2 260 25246 2000 2 2
3: 154 2000-01-09 10:13:00 351476 3 360 41801 2000 3 3
4: 154 2000-01-09 10:13:00 351476 4 340 11806 2000 4 4
5: 690175 2002-05-26 19:03:18 351476 1 360 21586 2002 5 1
6: 690175 2002-05-26 19:03:18 351476 2 310 36186 2002 6 2
7: 690175 2002-05-26 19:03:18 351476 3 310 12013 2002 7 3
8: 690175 2002-05-26 19:03:18 351476 4 410 21394 2002 8 4
9: 690175 2002-05-26 19:03:18 351476 5 270 15424 2002 9 5
10: 1260215 2004-01-22 15:17:58 351476 1 250 12732 2004 10 1
11: 1260215 2004-01-22 15:17:58 351476 2 340 14032 2004 11 2
# もとに戻す
dt[,rowid := NULL,]
dt[,rowid_by := NULL,]
行をずらすdplyr::lead()
、dplyr::lag()
と同様の操作を行いたい場合は、shift()
を利用する。グループ単位で行をずらしたい場合は、by
でグループ化を行う。
dt[user_id == 351476,
lag := shift(branch_num, 1, type = "lag"),]
dt[user_id == 351476,
lead := shift(branch_num, 1, type = "lead"),]
dt[user_id == 351476,
lag_by := shift(branch_num, 1, type = "lag"),
by = order_id]
dt[user_id == 351476,
lead_by := shift(branch_num, 1, type = "lead"),
by = order_id]
dt[user_id == 351476,,]
order_id order_datetime user_id branch_num price product_id order_yaer lag lead lag_by lead_by
1: 154 2000-01-09 10:13:00 351476 1 190 30489 2000 NA 2 NA 2
2: 154 2000-01-09 10:13:00 351476 2 260 25246 2000 1 3 1 3
3: 154 2000-01-09 10:13:00 351476 3 360 41801 2000 2 4 2 4
4: 154 2000-01-09 10:13:00 351476 4 340 11806 2000 3 1 3 NA
5: 690175 2002-05-26 19:03:18 351476 1 360 21586 2002 4 2 NA 2
6: 690175 2002-05-26 19:03:18 351476 2 310 36186 2002 1 3 1 3
7: 690175 2002-05-26 19:03:18 351476 3 310 12013 2002 2 4 2 4
8: 690175 2002-05-26 19:03:18 351476 4 410 21394 2002 3 5 3 5
9: 690175 2002-05-26 19:03:18 351476 5 270 15424 2002 4 1 4 NA
10: 1260215 2004-01-22 15:17:58 351476 1 250 12732 2004 5 2 NA 2
11: 1260215 2004-01-22 15:17:58 351476 2 340 14032 2004 1 NA 1 NA
# もとに戻す
dt[,lag := NULL,]
dt[,lead := NULL,]
dt[,lag_by := NULL,]
dt[,lead_by := NULL,]
累計合計を計算する場合はcumsum()
を利用する。
dt[user_id == 351476,
cumsum := cumsum(price),]
dt[user_id == 351476,
cumsum_by := cumsum(price),
by = order_id]
dt[user_id == 351476,,]
order_id order_datetime user_id branch_num price product_id order_yaer cumsum cumsum_by
1: 154 2000-01-09 10:13:00 351476 1 190 30489 2000 190 190
2: 154 2000-01-09 10:13:00 351476 2 260 25246 2000 450 450
3: 154 2000-01-09 10:13:00 351476 3 360 41801 2000 810 810
4: 154 2000-01-09 10:13:00 351476 4 340 11806 2000 1150 1150
5: 690175 2002-05-26 19:03:18 351476 1 360 21586 2002 1510 360
6: 690175 2002-05-26 19:03:18 351476 2 310 36186 2002 1820 670
7: 690175 2002-05-26 19:03:18 351476 3 310 12013 2002 2130 980
8: 690175 2002-05-26 19:03:18 351476 4 410 21394 2002 2540 1390
9: 690175 2002-05-26 19:03:18 351476 5 270 15424 2002 2810 1660
10: 1260215 2004-01-22 15:17:58 351476 1 250 12732 2004 3060 250
11: 1260215 2004-01-22 15:17:58 351476 2 340 14032 2004 3400 590
# もとに戻す
dt[,cumsum := NULL,]
dt[,cumsum_by := NULL,]
ランク付けを行う場合は、frank()
を利用する。ties.method
がたくさんあるので、必要に応じて使い分ける。デフォルトはaverage
。na.last
は最後にNA
にランクをつけるか、最初につけるかの違い。
dt_rank <- data.table::data.table(val = c(1, 2, 2, 5, 6, 7, NA_integer_))
dt_rank[,.(val,
rank_average = frank(x = val, na.last = TRUE, ties.method = "average"),
rank_first = frank(x = val, na.last = TRUE, ties.method = "first"),
rank_last = frank(x = val, na.last = TRUE, ties.method = "last"),
rank_random = frank(x = val, na.last = TRUE, ties.method = "random"),
rank_max = frank(x = val, na.last = TRUE, ties.method = "max"),
rank_min = frank(x = val, na.last = TRUE, ties.method = "min"),
rank_dense = frank(x = val, na.last = TRUE, ties.method = "dense")
)
,]
val rank_average rank_first rank_last rank_random rank_max rank_min rank_dense
1: 1 1.0 1 1 1 1 1 1
2: 2 2.5 2 3 2 3 2 2
3: 2 2.5 3 2 3 3 2 2
4: 5 4.0 4 4 4 4 4 3
5: 6 5.0 5 5 5 5 5 4
6: 7 6.0 6 6 6 6 6 5
7: NA 7.0 7 7 7 7 7 6