如 图 1 所示,在R中使用data.table的语法非常简洁。
- DT为遥操作的
data.table
对象。
- i为索引,可以指定行。
- j为操作相应列的表达式。j表达式非常灵活,可以选择、修改、汇总和计算新列,甚至可以接受任意的表达式。需要注意的是,值得注意的是,只要返回的list元素是等长元素或长度为1的元素,那么每个list的元素将转化为结果data.table的一列。
- by为分组操作的表达式,即根据by分组。
data.table的通用语法
创建data.table
x y
<int> <char>
1: 1 v1
2: 2 v2
3: 3 v3
# 加载其他数据集
data(mtcars)
mtcars$carname <- rownames(mtcars)
mtcars_dt <- as.data.table(mtcars)[1:10][, c("carb", "disp") := NULL]
mtcars_dt
使用as.data.table()
函数可以将普通数据转换为data.table。如果只想按引用转化,则使用setDT()
函数。
特殊符号
-
.()
:代替.list()
。
-
:=
赋值符号,按引用的方式增加和修改列。
-
.N
: 行数。
-
.I
: 整数向量seq_len(nrow(x))
。
-
.SD
: 每个分组数据(除by或keyby的列)。
-
.SDcols
: 与.SD
配合使用,用来选择.SD
中的列。
-
.BY
:包含所有by分组变量的list。
-
.GRP
:分组索引,包含当前分组的整数向量,1代表第1分组,2代表第2分组,以此类推。
-
.NGRP
:分组数。
-
.EACHI
:用于by/keyby = .EACHI
,表示根据i表达式的每一行进行分组。
以上特殊符号在接下来的会有针对性的应用。
链式操作
data.table
支持链式操作,即多个操作可以串联起来,形成一个表达式。具体有点类似于tidyverse
中的管道。基本形式如下:
DT[…][…][…] 或者可以写为 DT[ …][ …][ …]`
引用语法
data.table
采用了高效的引用语法,即浅复制。
浅复制只是复制列指针对象(即对应数据框的列),而实际的数据在内存中不做物理复制。
data.table
使用:=
运算符,做整列或部分列替换时不做任何复制,因为:=
运算符是通过引用就地更新data.table
的列,而不会创建新的对象。
如果希望复制数据而不按引用处理,则可以使用copy()
函数。
键与索引
data.table
支持设置键和索引,使得选择行和做数据连接时更加方便快捷。
键
在 data.table
上设置一个或多个键可以使其执行二进制搜索(binary search),这比线性搜索要快很多数量级,尤其是对于大型数据。setkey()
函数可以设置键。
setkey(mtcars_dt, carname) # 设置键
key(mtcars_dt) # 查看键
Key: <carname>
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 22.8 4 93 3.85 2.320 18.61 1 1 4 Datsun 710
2: 14.3 8 245 3.21 3.570 15.84 0 0 3 Duster 360
3: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
4: 18.7 8 175 3.15 3.440 17.02 0 0 3 Hornet Sportabout
5: 21.0 6 110 3.90 2.620 16.46 0 1 4 Mazda RX4
6: 21.0 6 110 3.90 2.875 17.02 0 1 4 Mazda RX4 Wag
7: 22.8 4 95 3.92 3.150 22.90 1 0 4 Merc 230
8: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
9: 19.2 6 123 3.92 3.440 18.30 1 0 4 Merc 280
10: 18.1 6 105 2.76 3.460 20.22 1 0 3 Valiant
- 设置键后,
data.table
会自动对数据集进行排序,使得键值有序。
- 设置的键必须是数据集中的一列。
- 键非常有用,在后续的数据合并等操作中会有奇效((sec?)–data-man)。
- 索引可以提高查询速度,但并不是所有情况下都适用。我们多数情况是还是用键。
keyby
用法,这个函数可以进行分组统计。
output <- mtcars_dt[, .(
mean_mpg = mean(mpg),
mean_wt = mean(wt),
mean_qsec = mean(qsec)
), keyby = cyl] # 按cyl分组的同时,设定cyl为键
key(output)
数据读写
data.table
使用fread()
函数读取数据,fwrite()
函数写入数据。注意,fread()
不能读取excel文件。
读写数据
# 读入数据
flights <- fread("d:/Myblog/posts/datatable-beginners-guide-2024-06-20/tech/flights14.csv")
# 写入数据
fwrite(flights, "d:/Myblog/posts/datatable-beginners-guide-2024-06-20/tech/flights14_new.csv")
flights
year month day dep_delay arr_delay carrier origin dest air_time
<int> <int> <int> <int> <int> <char> <char> <char> <int>
1: 2014 1 1 14 13 AA JFK LAX 359
2: 2014 1 1 -3 13 AA JFK LAX 363
3: 2014 1 1 2 9 AA JFK LAX 351
4: 2014 1 1 -8 -26 AA LGA PBI 157
5: 2014 1 1 2 1 AA JFK LAX 350
---
253312: 2014 10 31 1 -30 UA LGA IAH 201
253313: 2014 10 31 -5 -14 UA EWR IAH 189
253314: 2014 10 31 -8 16 MQ LGA RDU 83
253315: 2014 10 31 -4 15 MQ LGA DTW 75
253316: 2014 10 31 -5 1 MQ LGA SDF 110
distance hour
<int> <int>
1: 2475 9
2: 2475 11
3: 2475 19
4: 1035 7
5: 2475 13
---
253312: 1416 14
253313: 1400 8
253314: 431 11
253315: 502 11
253316: 659 8
数据操作
操作行
data.table
对象知道自己的列名(与data.frame
的不同),所以在操作行时,只传递列名即可。
根据索引选择行
mtcars_dt[3:5] # 选择第3到第5行
Key: <carname>
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
2: 18.7 8 175 3.15 3.440 17.02 0 0 3 Hornet Sportabout
3: 21.0 6 110 3.90 2.620 16.46 0 1 4 Mazda RX4
mtcars_dt[!3:7] # 选择除3:7行之外的行
Key: <carname>
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 22.8 4 93 3.85 2.32 18.61 1 1 4 Datsun 710
2: 14.3 8 245 3.21 3.57 15.84 0 0 3 Duster 360
3: 24.4 4 62 3.69 3.19 20.00 1 0 4 Merc 240D
4: 19.2 6 123 3.92 3.44 18.30 1 0 4 Merc 280
5: 18.1 6 105 2.76 3.46 20.22 1 0 3 Valiant
根据逻辑表达式
mtcars_dt[mpg > 20] # 选择mpg大于20的行
Key: <carname>
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 22.8 4 93 3.85 2.320 18.61 1 1 4 Datsun 710
2: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
3: 21.0 6 110 3.90 2.620 16.46 0 1 4 Mazda RX4
4: 21.0 6 110 3.90 2.875 17.02 0 1 4 Mazda RX4 Wag
5: 22.8 4 95 3.92 3.150 22.90 1 0 4 Merc 230
6: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
mtcars_dt[carname %chin% c("AMC Javelin")]
Key: <carname>
Empty data.table (0 rows and 10 cols): mpg,cyl,hp,drat,wt,qsec...
删除行
Key: <carname>
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 22.8 4 93 3.85 2.320 18.61 1 1 4 Datsun 710
2: 14.3 8 245 3.21 3.570 15.84 0 0 3 Duster 360
3: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
4: 18.7 8 175 3.15 3.440 17.02 0 0 3 Hornet Sportabout
5: 21.0 6 110 3.90 2.620 16.46 0 1 4 Mazda RX4
6: 21.0 6 110 3.90 2.875 17.02 0 1 4 Mazda RX4 Wag
7: 22.8 4 95 3.92 3.150 22.90 1 0 4 Merc 230
8: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
9: 19.2 6 123 3.92 3.440 18.30 1 0 4 Merc 280
10: 18.1 6 105 2.76 3.460 20.22 1 0 3 Valiant
Key: <carname>
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 22.8 4 93 3.85 2.320 18.61 1 1 4 Datsun 710
2: 14.3 8 245 3.21 3.570 15.84 0 0 3 Duster 360
3: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
4: 18.7 8 175 3.15 3.440 17.02 0 0 3 Hornet Sportabout
5: 21.0 6 110 3.90 2.620 16.46 0 1 4 Mazda RX4
6: 21.0 6 110 3.90 2.875 17.02 0 1 4 Mazda RX4 Wag
7: 22.8 4 95 3.92 3.150 22.90 1 0 4 Merc 230
8: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
9: 19.2 6 123 3.92 3.440 18.30 1 0 4 Merc 280
10: 18.1 6 105 2.76 3.460 20.22 1 0 3 Valiant
行切片
mtcars_dt[sample(.N, 3)] # 随机选择3行
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
2: 19.2 6 123 3.92 3.440 18.30 1 0 4 Merc 280
3: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
mtcars_dt[sample(.N, .N * 0.5)] # 随机选择50%行
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
2: 22.8 4 95 3.92 3.150 22.90 1 0 4 Merc 230
3: 14.3 8 245 3.21 3.570 15.84 0 0 3 Duster 360
4: 19.2 6 123 3.92 3.440 18.30 1 0 4 Merc 280
5: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
mtcars_dt[frankv(-mpg, ties.method = "dense") < 2] # 选择mpg最大的行
Key: <carname>
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 24.4 4 62 3.69 3.19 20 1 0 4 Merc 240D
行排序
mtcars_dt[order(mpg)] # 按mpg排序
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 14.3 8 245 3.21 3.570 15.84 0 0 3 Duster 360
2: 18.1 6 105 2.76 3.460 20.22 1 0 3 Valiant
3: 18.7 8 175 3.15 3.440 17.02 0 0 3 Hornet Sportabout
4: 19.2 6 123 3.92 3.440 18.30 1 0 4 Merc 280
5: 21.0 6 110 3.90 2.620 16.46 0 1 4 Mazda RX4
6: 21.0 6 110 3.90 2.875 17.02 0 1 4 Mazda RX4 Wag
7: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
8: 22.8 4 93 3.85 2.320 18.61 1 1 4 Datsun 710
9: 22.8 4 95 3.92 3.150 22.90 1 0 4 Merc 230
10: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
mtcars_dt[order(-mpg)] # 按mpg逆序排序
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
2: 22.8 4 93 3.85 2.320 18.61 1 1 4 Datsun 710
3: 22.8 4 95 3.92 3.150 22.90 1 0 4 Merc 230
4: 21.4 6 110 3.08 3.215 19.44 1 0 3 Hornet 4 Drive
5: 21.0 6 110 3.90 2.620 16.46 0 1 4 Mazda RX4
6: 21.0 6 110 3.90 2.875 17.02 0 1 4 Mazda RX4 Wag
7: 19.2 6 123 3.92 3.440 18.30 1 0 4 Merc 280
8: 18.7 8 175 3.15 3.440 17.02 0 0 3 Hornet Sportabout
9: 18.1 6 105 2.76 3.460 20.22 1 0 3 Valiant
10: 14.3 8 245 3.21 3.570 15.84 0 0 3 Duster 360
setorder(mtcars_dt, -mpg, cyl) # 按mpg逆序排序,再按cyl排序(改变原数据)
其他操作
mtcars_dt[carname %like% "^M"] # carname以M开头的行
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 24.4 4 62 3.69 3.190 20.00 1 0 4 Merc 240D
2: 22.8 4 95 3.92 3.150 22.90 1 0 4 Merc 230
3: 21.0 6 110 3.90 2.620 16.46 0 1 4 Mazda RX4
4: 21.0 6 110 3.90 2.875 17.02 0 1 4 Mazda RX4 Wag
5: 19.2 6 123 3.92 3.440 18.30 1 0 4 Merc 280
mtcars_dt[mpg %between% c(10, 15)] # mpg在10到15之间的行-闭区间
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 14.3 8 245 3.21 3.57 15.84 0 0 3 Duster 360
mtcars_dt[between(mpg, 10, 15, incbounds = FALSE)] # 开区间
mpg cyl hp drat wt qsec vs am gear carname
<num> <num> <num> <num> <num> <num> <num> <num> <num> <char>
1: 14.3 8 245 3.21 3.57 15.84 0 0 3 Duster 360
操作列
有几种方法可以选择
根据列名选择列
直接输出列名进行选择
# 返回向量
ans <- flights[, arr_delay]
head(ans)
# 返回data.table
ans <- flights[, list(arr_delay)] # 选择单列
head(ans)
arr_delay
<int>
1: 13
2: 13
3: 9
4: -26
5: 1
6: 0
ans <- flights[, .(arr_delay, dep_delay)] # 选择多列
head(ans)
arr_delay dep_delay
<int> <int>
1: 13 14
2: 13 -3
3: 9 2
4: -26 -8
5: 1 2
6: 0 4
ans <- flights[, !c("arr_delay", "dep_delay")] # 反选列
head(ans)
year month day carrier origin dest air_time distance hour
<int> <int> <int> <char> <char> <char> <int> <int> <int>
1: 2014 1 1 AA JFK LAX 359 2475 9
2: 2014 1 1 AA JFK LAX 363 2475 11
3: 2014 1 1 AA JFK LAX 351 2475 19
4: 2014 1 1 AA LGA PBI 157 1035 7
5: 2014 1 1 AA JFK LAX 350 2475 13
6: 2014 1 1 AA EWR LAX 339 2454 18
-
data.table
中将列名变量封装在list()
中,从而确保返回的结果为data.table
.如果需要返回向量,则直接传入列名即可。
-
list()
可以用.()
简写代替。
- 只要
j
表达式是一个list
,这个列表中的每个元素均会转换为结果data.table
的一列。这使得j
表达式变得非常重要,也是进行更复杂操作的基础。
将所需列储存在字符向量中-..
前缀或with=FALSE
参数
# 前缀..
select_cols <- c("arr_delay", "dep_delay")
flights[, ..select_cols]
arr_delay dep_delay
<int> <int>
1: 13 14
2: 13 -3
3: 9 2
4: -26 -8
5: 1 2
---
253312: -30 1
253313: -14 -5
253314: 16 -8
253315: 15 -4
253316: 1 -5
# with=FALSE参数
flights[, select_cols, with = FALSE]
arr_delay dep_delay
<int> <int>
1: 13 14
2: 13 -3
3: 9 2
4: -26 -8
5: 1 2
---
253312: -30 1
253313: -14 -5
253314: 16 -8
253315: 15 -4
253316: 1 -5
-
with
参数与函数with()
函数类似。
-
data.table
中的列可以想变量一样被引用,所以默认的with
参数设定为TURE。
修改列名
# 方法1
setnames(
mtcars_dt,
"cyl", # 旧名
"CYL" # 新名
)
head(mtcars_dt)
carname gear CYL mpg hp drat wt qsec vs am
<char> <num> <num> <num> <num> <num> <num> <num> <num> <num>
1: Merc 240D 4 4 24.4 62 3.69 3.190 20.00 1 0
2: Datsun 710 4 4 22.8 93 3.85 2.320 18.61 1 1
3: Merc 230 4 4 22.8 95 3.92 3.150 22.90 1 0
4: Hornet 4 Drive 3 6 21.4 110 3.08 3.215 19.44 1 0
5: Mazda RX4 4 6 21.0 110 3.90 2.620 16.46 0 1
6: Mazda RX4 Wag 4 6 21.0 110 3.90 2.875 17.02 0 1
# 方法2
ans <- flights[, .(delay_arr = arr_delay, delay_dep = dep_delay)] # 新列名 = 旧列名
head(ans)
delay_arr delay_dep
<int> <int>
1: 13 14
2: 13 -3
3: 9 2
4: -26 -8
5: 1 2
6: 0 4
在j中进行计算
ans <- flights[, sum((arr_delay + dep_delay) < 0)]
head(ans)
j
不仅可以处理变量,还可以处理表达式。这样我们就可以通过调用函数来对变量进行计算。
在i子集中操作j
计算6月以”JFK”为始发机场的所有航班的平均到达和起飞的延误时间。
2014年6月,从”JFK”机场出发的飞机共有多少?
# q1
ans <- flights[
origin == "JFK" & month == 6L,
.(
m_arr = mean(arr_delay),
m_dep = mean(dep_delay)
)
]
head(ans)
m_arr m_dep
<num> <num>
1: 5.839349 9.807884
# q2
ans <- flights[
origin == "JFK" & month == 6L,
length(dest)
]
head(ans)
类似以上的,从i中筛选子集并对其中的j进行操作非常常见,所以在data.table
中专门提供了一个特殊的符号.N
,具体可见 Section 4。
修改因子水平
mtcars_dt[, setattr(gear, "levels", c("3", "4"))]
[1] 4 4 4 3 4 4 4 3 3 3
attr(,"levels")
[1] "3" "4"
增减列-加”[]“可直接输出结果
# 增加一列
mtcars_dt[, gear_new := gear * 2][] # 增加gear_new列,值为原gear的两倍
carname gear CYL mpg hp drat wt qsec vs am
<char> <num> <num> <num> <num> <num> <num> <num> <num> <num>
1: Merc 240D 4 4 24.4 62 3.69 3.190 20.00 1 0
2: Datsun 710 4 4 22.8 93 3.85 2.320 18.61 1 1
3: Merc 230 4 4 22.8 95 3.92 3.150 22.90 1 0
4: Hornet 4 Drive 3 6 21.4 110 3.08 3.215 19.44 1 0
5: Mazda RX4 4 6 21.0 110 3.90 2.620 16.46 0 1
6: Mazda RX4 Wag 4 6 21.0 110 3.90 2.875 17.02 0 1
7: Merc 280 4 6 19.2 123 3.92 3.440 18.30 1 0
8: Hornet Sportabout 3 8 18.7 175 3.15 3.440 17.02 0 0
9: Valiant 3 6 18.1 105 2.76 3.460 20.22 1 0
10: Duster 360 3 8 14.3 245 3.21 3.570 15.84 0 0
gear_new
<num>
1: 8
2: 8
3: 8
4: 6
5: 8
6: 8
7: 8
8: 6
9: 6
10: 6
# 增加多列
mtcars_dt[, c("hp_new", "wt_new", "NEW") := .(sqrt(hp), mean(wt), "x")]
删除列
cols_to_remove <- c("mpg", "cyl", "disp")
as.data.table(mtcars)[, (cols_to_remove) := NULL]
重新编码
# 一分支
mtcars_dt[mpg < 20, mpg := 0]
# 二分支
mtcars_dt[, gear := fifelse(gear > 3, -gear, gear)]
# 多分支
mtcars_dt[, hp := fcase(hp < 100, "low",
hp < 120, "mid",
default = "high"
)]
前移/后移计算
# 1,2,3转换为NA,1,2
shift(mtcars_dt[[3]], n = 1, fill = NA, type = "lag")
# 1,2,3转换为2,3,NA
shift(mtcars_dt[[4]], n = 1, fill = NA, type = "lead")
[1] 22.8 22.8 21.4 21.0 21.0 0.0 0.0 0.0 0.0 NA
.N和.I的作用
.N
:返回当前分组的观测数。
.N
的作用是保存当前group中观测值的个数,是用于j中的一个特殊内置变量,当他与by
结合使用时特别有用(Section 5)。在不进行分组操作的情况下,它只会返回i子集中的行数
# 计算6月以"JFK"为始发机场的所有航班数。
# 在之前的操作用使用了length(dest)的操作。
ans <- flights[
origin == "JFK" & month == 6L,
.N
]
ans
- 在以上代码中,
.N
没有被list()
或.()
包裹,因此返回的结果是一个向量。
- j中只使用了
.N
而没有引用其他列,即整个操作并未对原data.table
对象进行取子集的操作,这是data.table
高效的其中一个原因。
.I
:返回当前分组的索引,如果没有分组,则返回行号。
以上代码返回了所有的行号。那么如何返回特定行的行号呢?
mtcars_dt[, .I[CYL == 6]]
为什么以下两行代码返回的结果不同?
mtcars_dt[, .I[CYL == 6]]
第二行代码是先筛选CYL == 6
的行,再返回这些行的行号,但这显然不是我们想要的结果。
注意,.I
和.N
本质上是一个集合,我们可以在其基础上进行筛选操作
计算每种档位(gear)的汽车数量和平均里程。
gear gear_count gear_avg_mile
<num> <int> <num>
1: 4 12 24.53
2: 3 15 16.11
3: 5 5 21.38
分组操作
-
by
:指定分组变量。
-
keyby
:指定分组变量的同时,创建键。
flights <- fread("d:/Myblog/posts/datatable-beginners-guide-2024-06-20/tech/flights14.csv")
DT <- readxl::read_xlsx(
"D:/Myblog/posts/datatable-beginners-guide-2024-06-20/tech/ExamDatas_NAs.xlsx"
) %>%
as.data.table()
分组修改
使用by进行分组
- 计算每个始发机场对应的trip次数。
ans <- flights[, .(.N), by = .(origin)]
ans
origin N
<char> <int>
1: JFK 81483
2: LGA 84433
3: EWR 87400
-
.N
是一个特殊变量,用于保存当前组中的行数。通过 origin 分组,可以得到每个组的行数 .N
。
- 在原始数据中,始发机场的顺序为 “JFK”、“LGA”和 “EWR”。结果中保留了分组变量的原始顺序。这一点非常重要,必须牢记!。
- 由于我们没有为j中返回的列提供名称,该列通过识别特殊符号
.N
自动命名为N。当然可以对它进行重命名。
-
by
可以接受列名的字符向量。
- 当
j
和by
中只有一列或一个表达式需要引用时,可以去掉.()
符号。当然by
也可以接受多列,这时就需要.()
了。
- 计算运营商代码 “AA” 中每对 origin, dest 的总班次数
ans <- flights[carrier == "AA", .N,
by = .(origin, dest)]
ans
origin dest N
<char> <char> <int>
1: JFK LAX 3387
2: LGA PBI 245
3: EWR LAX 62
4: JFK MIA 1876
5: JFK SEA 298
6: EWR MIA 848
7: JFK SFO 1312
8: JFK BOS 1173
9: JFK ORD 432
10: JFK IAH 7
11: JFK AUS 297
12: EWR DFW 1618
13: LGA ORD 4366
14: JFK STT 229
15: JFK SJU 690
16: LGA MIA 3334
17: LGA DFW 3785
18: JFK LAS 595
19: JFK MCO 597
20: JFK EGE 85
21: JFK DFW 474
22: JFK SAN 299
23: JFK DCA 172
24: EWR PHX 121
origin dest N
使用keyby进行分组变量排序
ans <- flights[carrier == "AA",
.(mean(arr_delay), mean(dep_delay)),
keyby = .(origin, dest, month)]
ans
Key: <origin, dest, month>
origin dest month V1 V2
<char> <char> <int> <num> <num>
1: EWR DFW 1 6.427673 10.0125786
2: EWR DFW 2 10.536765 11.3455882
3: EWR DFW 3 12.865031 8.0797546
4: EWR DFW 4 17.792683 12.9207317
5: EWR DFW 5 18.487805 18.6829268
---
196: LGA PBI 1 -7.758621 0.3103448
197: LGA PBI 2 -7.865385 2.4038462
198: LGA PBI 3 -5.754098 3.0327869
199: LGA PBI 4 -13.966667 -4.7333333
200: LGA PBI 5 -10.357143 -6.8571429
- 使用
keyby
替换by
,这将自动按照分组变量的递增顺序对结果进行排序。还记得吗,by
保留了原始数据中的排序。
- 实际上,
keyby
的作用不仅仅是排序。它还通过设置名为 sorted
的 attribute
在排序后设置一个键。 我们将在 “Keys and fast binary search based subset[TODO]”小节中进一步了解 keys
;现在,您只需知道可以使用 keyby
按 by
中指定的列自动排列结果。
使用升序列 origin 和降序列 dest 对进行排序。
ans <- flights[carrier == "AA", .N, by = .(origin, dest)]
ans <- ans[order(origin, -dest)]
head(ans)
origin dest N
<char> <char> <int>
1: EWR PHX 121
2: EWR MIA 848
3: EWR LAX 62
4: EWR DFW 1618
5: JFK STT 229
6: JFK SJU 690
以上操作实现了升序列 origin 和降序列 dest 进行排序,但如果我们本来就需要进行这样的排序操作,那么命名一个ans的中间的临时变量并覆盖该结果就不太好。此时我们可以使用链式操作,即多个[]
连起来使用。
ans <- flights[carrier == "AA", .N,
by = .(origin, dest)][
order(origin, -dest)
]
head(ans, 10)
origin dest N
<char> <char> <int>
1: EWR PHX 121
2: EWR MIA 848
3: EWR LAX 62
4: EWR DFW 1618
5: JFK STT 229
6: JFK SJU 690
7: JFK SFO 1312
8: JFK SEA 298
9: JFK SAN 299
10: JFK ORD 432
分组汇总
分别对每个分组(by)进行操作(计算新列),相当于`group_by + mutate)。
计算运营商代码 “AA” 中每个月每对 origin,dest 的平均到达和出发延迟?
ans <- flights[carrier == "AA",
.(mean(arr_delay), mean(dep_delay)),
by = .(origin, dest, month)]
ans
origin dest month V1 V2
<char> <char> <int> <num> <num>
1: JFK LAX 1 6.590361 14.2289157
2: LGA PBI 1 -7.758621 0.3103448
3: EWR LAX 1 1.366667 7.5000000
4: JFK MIA 1 15.720670 18.7430168
5: JFK SEA 1 14.357143 30.7500000
---
196: LGA MIA 10 -6.251799 -1.4208633
197: JFK MIA 10 -1.880184 6.6774194
198: EWR PHX 10 -3.032258 -4.2903226
199: JFK MCO 10 -10.048387 -1.6129032
200: JFK DCA 10 16.483871 15.5161290
未分组汇总
DT[, .(math_avg = mean(math, na.rm = TRUE))]
math_avg
<num>
1: 68.04255
简单的分组汇总
DT[, .(
n = .N,
math_avg = mean(math, na.rm = TRUE),
math_med = median(math, na.rm = TRUE)
),
by = sex
]
sex n math_avg math_med
<char> <int> <num> <num>
1: 女 25 70.78261 73
2: 男 24 64.56522 69
3: <NA> 1 85.00000 85
by中适用表达式
- 可以直接在
by
中使用判断条件或表达式,特别是根据整合单位的日期或时间进行分组。
- 可以在
by
中使用表达式的同时传入列。
date <- as.IDate("2021-01-01") + 0:49 # 生成50个日期,间隔为1天
dt <- data.table(date, a = 1:50)
dt[, .(
n = .N,
mean_by_month = mean(a)
),
by = .(mon = month(date))
]
mon n mean_by_month
<int> <int> <num>
1: 1 31 16
2: 2 19 41
什么是 .SD
、.SDcols
、.EACHI
如果我们需要计算数据中所有列的平均值怎么办?难道要逐列输入mean(myCol)
,显示不现实。
要便捷的实现这一目标,我们要重新复习一下data.table
中j
表达式的特性:只要 j
表达式返回 list
, list
中的每个元素都将转换为结果 data.table
中的一列。
-
.SD
是一个data.table
,它用于保存by
定义的当前组的数据子集。.SD
只能在位置j中使用。
-
.SDcols
常于.SD
用在一起,它可以指定.SD
中所包含的列,也就是对.SD
取子集。
-
.EACHI
是一个特殊的变量,它代表了当前分组中的第i个元素。.EACHI
只能在位置j中使用。by=.EACHI
允许按每一个已知i的子集分组,在使用by=.EACHI
时需要设置键值 , 必须设置i。
DT = data.table(
ID = c("b","b","b","a","a","c"),
a = 1:6,
b = 7:12,
c = 13:18
)
DT
ID a b c
<char> <int> <int> <int>
1: b 1 7 13
2: b 2 8 14
3: b 3 9 15
4: a 4 10 16
5: a 5 11 17
6: c 6 12 18
DT[, print(.SD), by = ID]
a b c
<int> <int> <int>
1: 1 7 13
2: 2 8 14
3: 3 9 15
a b c
<int> <int> <int>
1: 4 10 16
2: 5 11 17
a b c
<int> <int> <int>
1: 6 12 18
Empty data.table (0 rows and 1 cols): ID
同时操作多列
在data.table
中,同时修改多列大都使用的是lapply()
或map_**()
以及特殊符号进行操作:
DT[, map(.SD, mean), by = ID]
ID a b c
<char> <num> <num> <num>
1: b 2.0 8.0 14.0
2: a 4.5 10.5 16.5
3: c 6.0 12.0 18.0
DT[, lapply(.SD, mean), by = ID]
ID a b c
<char> <num> <num> <num>
1: b 2.0 8.0 14.0
2: a 4.5 10.5 16.5
3: c 6.0 12.0 18.0
.SD和.SDcols
.SD
默认包含了分组变量以外的所有列,那如果只需要计算数据集中其中几列的平均值该怎么办?
-
.SDcols
:与.SD
连用,用来选择包含在.SD
中的列,支持索引、列名、连选、反选、正则表达式、条件判断函数等。
- 同时修改多列的代码,均只保留新列,如果需要保留原有列,则需使用
:=
符号,同时准备新列名,并在j表达式中使用对新列名进行定义。
现在我们使用.SD
和.SDcols
来计算origin
、 dest
和 month
分组的 arr_delay
和 dep_delay
列的 mean()
。
ans <- flights[carrier == "AA",
map(.SD, mean),
by = .(origin, dest, month),
.SDcols = c("arr_delay", "dep_delay")]
head(ans)
origin dest month arr_delay dep_delay
<char> <char> <int> <num> <num>
1: JFK LAX 1 6.590361 14.2289157
2: LGA PBI 1 -7.758621 0.3103448
3: EWR LAX 1 1.366667 7.5000000
4: JFK MIA 1 15.720670 18.7430168
5: JFK SEA 1 14.357143 30.7500000
6: EWR MIA 1 11.011236 12.1235955
获取每个组的子集
获取每个月数据的前两行。
month year day dep_delay arr_delay carrier origin dest air_time
<int> <int> <int> <int> <int> <char> <char> <char> <int>
1: 1 2014 1 14 13 AA JFK LAX 359
2: 1 2014 1 -3 13 AA JFK LAX 363
3: 2 2014 1 -1 1 AA JFK LAX 358
4: 2 2014 1 -5 3 AA JFK LAX 358
5: 3 2014 1 -11 36 AA JFK LAX 375
6: 3 2014 1 -3 14 AA JFK LAX 368
distance hour
<int> <int>
1: 2475 9
2: 2475 11
3: 2475 8
4: 2475 11
5: 2475 8
6: 2475 11
ID a b c
<char> <int> <int> <int>
1: b 1 7 13
2: b 2 8 14
3: b 3 9 15
4: a 4 10 16
5: a 5 11 17
6: c 6 12 18
DT[, .(val = c(a, b)), by = ID]
ID val
<char> <int>
1: b 1
2: b 2
3: b 3
4: b 7
5: b 8
6: b 9
7: a 4
8: a 5
9: a 10
10: a 11
11: c 6
12: c 12
总结
DT[i, j, by]
是data.table
的基本语法。
使用i
- 我们可以像
data.frame
一样对行进行子集。
- 我们还可以使用
order()
对data.table
进行排序,内部使用 data.table
的快速排序,以获得更好的性能。
- 我们还可以在
i
中做更多事,We will see this in the “Keys and fast binary search based subsets” and “Joins and rolling joins” vignette.[TODO]
使用j
选择列:DT[, .(colA, colB)]
或DT[, c("colA", "colB")]
.
-
对列进行计算:DT[, .(sum(colA), mean(colB))]
.
- 可以设定新列名:
DT[, .(sA =sum(colA), mB = mean(colB))]
和i
连用:DT[colA > value, sum(colB)]
.
使用by
我们可以通过指定列列表或列名字符向量,甚至表达式来按列分组。 j
的灵活性与 by
和 i
结合在一起,构成了一种非常强大的语法。
by
可以处理多列和表达式.
我们可以通过 keyby
分组列来自动对分组结果进行排序
-
我们可以在 j
中使用 .SD
和 .SDcols
,使用已经熟悉的基本函数对多列进行操作。下面是一些示例:
-
DT[, lapply(.SD, fun), by = ..., .SDcols = ...]
: 将 fun
应用于 .SDcols
中指定的所有列,同时按 by
中指定的列分组。
-
DT[, head(.SD, n), by = ...]
: 返回每个组的前n行。
-
DT[col > val, head(.SD, 1), by = ...]
: 将 i
与 j
和 by
合并
只要 j
返回 list
,列表中的每个元素都将成为结果 data.table
中的一列