data.table 是 data.frame 的扩展,兼容 dplyr 包中的函数,被其操作后,返回值仍为 data.table。故今后凡是操作数据框都可以默认先转换为 data.table. 需要保留原始数据时,使用 dplyr 系列函数;不需要保留时,使用 data.table 的象牙操作符,追求速度和简洁。
data.table 的高性能来源于内存管理(引用语法)、并行化和大量精细优化。
data.table()
<- data.table(
dt a = c("A", "B", "C", "A", "A", "B"),
b = 1:6
)class(dt)
#> [1] "data.table" "data.frame"
as.data.table()
和setDT()
,前者创建一份拷贝并转换,后者原地转换。
<- data.frame(
df a = c("A", "B", "C", "A", "A", "B"),
b = 1:6
)%>%
df setDT() %>%
class()
#> [1] "data.table" "data.frame"
library(sqldf)
options(sqldf.driver = "SQLite")
<- dbConnect(
channel SQLite(),
dbname = "C:/Users/humoo/OneDrive/ICT/DataBase/SQLite/trade.db"
)<- dbReadTable(channel, "total") %>%
total select(-time) %>%
setDT()
class(total)
#> [1] "data.table" "data.frame"
dbDisconnect(channel)
set*()
函数族不复制数据,节省时间
setnames(total, "trade", "total") # 将列名trade改为total
head(total)
#> year month export import total surplus
#> 1: 2018 1 1994.9 1810.7 3805.6 184.2
#> 2: 2018 2 1706.4 1383.4 3089.8 323.0
#> 3: 2018 3 1739.1 1796.6 3535.7 -57.5
#> 4: 2018 4 1989.5 1726.7 3716.2 262.8
#> 5: 2018 5 2116.1 1881.8 3997.9 234.3
#> 6: 2018 6 2156.2 1747.0 3903.2 409.2
:= .I] # .I意为1:.N,这句为total加了一个序号索引列
total[, id
setcolorder(total, neworder = "id")
# setcolorder() 列的重排序
# 默认第二个参数是一个列名向量。如果只写出了一部分列,则其他列按照原来的顺序
head(total)
#> id year month export import total surplus
#> 1: 1 2018 1 1994.9 1810.7 3805.6 184.2
#> 2: 2 2018 2 1706.4 1383.4 3089.8 323.0
#> 3: 3 2018 3 1739.1 1796.6 3535.7 -57.5
#> 4: 4 2018 4 1989.5 1726.7 3716.2 262.8
#> 5: 5 2018 5 2116.1 1881.8 3997.9 234.3
#> 6: 6 2018 6 2156.2 1747.0 3903.2 409.2
set*() 函数族 |
|
---|---|
setDT() , setDF() |
|
setattr() setnames() |
Set attributes of objects by reference |
setkey() setkeyv() setindex()
setindexv() key() indices()
haskey() |
Create key on a data.table |
setcolorder() |
Fast column reordering of a data.table by reference |
setorder(dt, a, -b) setorderv() |
Fast row reordering of a data.table by reference |
fread()
可以将能直接将.csv .txt 等格式的文件读为
data.table
fwrite()
可以写 data.table 到文件
在处理大数据时,它们的速度非常快。建议读写文件统一使用这两个函数
= list(), ...] dt[i, j, by
很像 SQL 语句
特殊符号 | 说明 |
---|---|
.() | 代替list() |
.N | 总行数 |
.SD | 代表分组后每个组的数据框 |
.SDcols | 在 by 处,与.SD连用,用来选择哪些列包含在.SD中 |
.BY | 包含所有by分组变量的list |
.I | 整数向量 1:.N |
.GRP | 分组索引 |
.NGRP | 分组数 |
.EACHI | 用于by/keyby = .EACHI,表示根据i表达式的每一行分组 |
DT[...][...][...]
或写为:
DT[
...
][
...
][
... ]
%>%
配合的链式操作由于 R
中所有的操作符都是函数,因此可以将`[`作为一个函数,贯穿于%>%
中,获得代码风格的一致性。
%>%
mtcars as.data.table() %>%
`[`(, .(mpg = mean(mpg)), by = cyl) %>%
`[`(.N,)
#> cyl mpg
#> 1: 8 15.1
选择行可以接受的运算符除了常用的逻辑运算符,还包括
%like%
(匹配模式,有点像正则表达式) 和
%between%
data.table[] 方括号中只有一个参数时,默认为 i 参数,表示对行操作。
<- data.table(
dt3 a = 1:6,
b = c("A", "B", "C", "A", "A", "B")
)
1:2, ] dt3[
#> a b
#> 1: 1 A
#> 2: 2 B
# 选出最后一行
dt3[.N]
#> a b
#> 1: 6 B
# 选出第一行和最后一行
c(1, .N)] dt3[
#> a b
#> 1: 1 A
#> 2: 6 B
# 选出满足特定条件的行
== "A", ] dt3[b
#> a b
#> 1: 1 A
#> 2: 4 A
#> 3: 5 A
# 行排序,负号表示降序
order(-month)] %>% head() total[
#> year month export import trade surplus
#> 1: 2018 12 2212.5 1641.9 3854.4 570.6
#> 2: 2018 11 2243.3 1824.7 4068.0 418.6
#> 3: 2019 11 2217.4 1830.1 4047.5 387.3
#> 4: 2018 10 2148.0 1816.7 3964.7 331.3
#> 5: 2019 10 2129.3 1701.2 3830.5 428.1
#> 6: 2018 9 2254.3 1949.9 4204.2 304.4
%>% head() total
#> year month export import trade surplus
#> 1: 2018 1 1994.9 1810.7 3805.6 184.2
#> 2: 2018 2 1706.4 1383.4 3089.8 323.0
#> 3: 2018 3 1739.1 1796.6 3535.7 -57.5
#> 4: 2018 4 1989.5 1726.7 3716.2 262.8
#> 5: 2018 5 2116.1 1881.8 3997.9 234.3
#> 6: 2018 6 2156.2 1747.0 3903.2 409.2
c(2)] # 提取第2列 dt[,
#> b
#> 1: 1
#> 2: 2
#> 3: 3
#> 4: 4
#> 5: 5
#> 6: 6
:=
3这个操作符有点像 SQL 语句中的 AS
关键词,但左为列名,右为表达式。
data.table[, colname := var1]
data.table[, colname := NULL]
data.table[condition, colname := value]
单个列名不用加引号,多个列名组成的向量,必须加引号,如c('d1', 'd2')
<- data.table(
dt a = c("A", "B", "C", "A", "A", "B"),
b = 1:6
)
# 1列,不必加引号
:= b + 2] # (b + 2) AS c
dt[, c dt
#> a b c
#> 1: A 1 3
#> 2: B 2 4
#> 3: C 3 5
#> 4: A 4 6
#> 5: A 5 7
#> 6: B 6 8
# 增加2列,第1种写法
c('d1', 'd2') := list(1:6, 2:7)] # := 左边是一个name向量,右边是一个value列表
dt[, dt
#> a b c d1 d2
#> 1: A 1 3 1 2
#> 2: B 2 4 2 3
#> 3: C 3 5 3 4
#> 4: A 4 6 4 5
#> 5: A 5 7 5 6
#> 6: B 6 8 6 7
# 增加2列,第2种写法
`:=`(c1 = 1:6, c2 = 2:7)] # := 很像一个update函数
dt[, dt
#> a b c d1 d2 c1 c2
#> 1: A 1 3 1 2 1 2
#> 2: B 2 4 2 3 2 3
#> 3: C 3 5 3 4 3 4
#> 4: A 4 6 4 5 4 5
#> 5: A 5 7 5 6 5 6
#> 6: B 6 8 6 7 6 7
# 删除c1列
:= NULL]
dt[, c1 dt
#> a b c d1 d2 c2
#> 1: A 1 3 1 2 2
#> 2: B 2 4 2 3 3
#> 3: C 3 5 3 4 4
#> 4: A 4 6 4 5 5
#> 5: A 5 7 5 6 6
#> 6: B 6 8 6 7 7
# 同时删除d1,d2列
c("d1", "d2") := NULL]
dt[, dt
#> a b c c2
#> 1: A 1 3 2
#> 2: B 2 4 3
#> 3: C 3 5 4
#> 4: A 4 6 5
#> 5: A 5 7 6
#> 6: B 6 8 7
# 给b赋值为30
:= 30]
dt[, b dt
#> a b c c2
#> 1: A 30 3 2
#> 2: B 30 4 3
#> 3: C 30 5 4
#> 4: A 30 6 5
#> 5: A 30 7 6
#> 6: B 30 8 7
# 对a列值为B、c2列值大于3的行,其b列赋值为100
== "B" & c2 > 3, b := 100]
dt[a dt
#> a b c c2
#> 1: A 30 3 2
#> 2: B 30 4 3
#> 3: C 30 5 4
#> 4: A 30 6 5
#> 5: A 30 7 6
#> 6: B 100 8 7
# 另一种写法
:= ifelse(a == "B" & c2 > 3, 50, b)]
dt[, b dt
#> a b c c2
#> 1: A 30 3 2
#> 2: B 30 4 3
#> 3: C 30 5 4
#> 4: A 30 6 5
#> 5: A 30 7 6
#> 6: B 50 8 7
# 更新列
:= as.character(b)]
dt[, b dt
#> a b c c2
#> 1: A 30 3 2
#> 2: B 30 4 3
#> 3: C 30 5 4
#> 4: A 30 6 5
#> 5: A 30 7 6
#> 6: B 50 8 7
=
而非象牙操作符不是在原来的数据框基础上改动,而是新创建一个数据框。
此时,j
中的列不能写成向量,而要写成列表,用.(列名...)
或list(列名...)
ratio = export / import)] total[, .(year, month, export, import,
#> year month export import ratio
#> 1: 2018 1 1994.9 1810.7 1.1017286
#> 2: 2018 2 1706.4 1383.4 1.2334827
#> 3: 2018 3 1739.1 1796.6 0.9679951
#> 4: 2018 4 1989.5 1726.7 1.1521978
#> 5: 2018 5 2116.1 1881.8 1.1245084
#> 6: 2018 6 2156.2 1747.0 1.2342301
#> 7: 2018 7 2144.2 1869.8 1.1467537
#> 8: 2018 8 2169.6 1907.1 1.1376435
#> 9: 2018 9 2254.3 1949.9 1.1561106
#> 10: 2018 10 2148.0 1816.7 1.1823636
#> 11: 2018 11 2243.3 1824.7 1.2294076
#> 12: 2018 12 2212.5 1641.9 1.3475242
#> 13: 2019 1 2179.7 1788.0 1.2190716
#> 14: 2019 2 1351.2 1317.0 1.0259681
#> 15: 2019 3 1979.0 1657.3 1.1941109
#> 16: 2019 4 1934.8 1797.8 1.0762042
#> 17: 2019 5 2138.5 1721.2 1.2424471
#> 18: 2019 6 2128.4 1618.6 1.3149635
#> 19: 2019 7 2215.3 1764.8 1.2552697
#> 20: 2019 8 2148.0 1799.7 1.1935323
#> 21: 2019 9 2181.2 1784.7 1.2221662
#> 22: 2019 10 2129.3 1701.2 1.2516459
#> 23: 2019 11 2217.4 1830.1 1.2116278
#> year month export import ratio
# 原始数据不变 total
#> year month export import trade surplus
#> 1: 2018 1 1994.9 1810.7 3805.6 184.2
#> 2: 2018 2 1706.4 1383.4 3089.8 323.0
#> 3: 2018 3 1739.1 1796.6 3535.7 -57.5
#> 4: 2018 4 1989.5 1726.7 3716.2 262.8
#> 5: 2018 5 2116.1 1881.8 3997.9 234.3
#> 6: 2018 6 2156.2 1747.0 3903.2 409.2
#> 7: 2018 7 2144.2 1869.8 4014.0 274.4
#> 8: 2018 8 2169.6 1907.1 4076.7 262.5
#> 9: 2018 9 2254.3 1949.9 4204.2 304.4
#> 10: 2018 10 2148.0 1816.7 3964.7 331.3
#> 11: 2018 11 2243.3 1824.7 4068.0 418.6
#> 12: 2018 12 2212.5 1641.9 3854.4 570.6
#> 13: 2019 1 2179.7 1788.0 3967.7 391.7
#> 14: 2019 2 1351.2 1317.0 2668.2 34.2
#> 15: 2019 3 1979.0 1657.3 3636.3 321.7
#> 16: 2019 4 1934.8 1797.8 3732.6 137.0
#> 17: 2019 5 2138.5 1721.2 3859.7 417.3
#> 18: 2019 6 2128.4 1618.6 3747.0 509.8
#> 19: 2019 7 2215.3 1764.8 3980.1 450.5
#> 20: 2019 8 2148.0 1799.7 3947.7 348.3
#> 21: 2019 9 2181.2 1784.7 3965.9 396.5
#> 22: 2019 10 2129.3 1701.2 3830.5 428.1
#> 23: 2019 11 2217.4 1830.1 4047.5 387.3
#> year month export import trade surplus
data("diamonds", package = "ggplot2")
setDT(diamonds)
# 按 cut 分组做回归
diamonds[,
{<- lm(log(price) ~ carat + depth)
m as.list(coef(m))
},= cut
keyby ]
#> cut (Intercept) carat depth
#> 1: Fair 7.730010 1.264588 -0.014982439
#> 2: Good 7.077469 1.973600 -0.014601101
#> 3: Very Good 6.293642 2.087957 -0.002890208
#> 4: Premium 5.934310 1.852778 0.005939651
#> 5: Ideal 8.495409 2.125605 -0.038080022
by 分组
keyby 不仅分组且立刻定义键,并按照键对行排序
<- data.table(
dt a = c("A", "B", "C", "A", "A", "B"),
b = 1:6
)
# 对整个b列数据求和
sum(b)] dt[,
#> [1] 21
# 按a列分组,对b列按分组求和,运算结果自动命名为 V1
sum(b), by = a] dt[,
#> a V1
#> 1: A 10
#> 2: B 8
#> 3: C 3
# 提取每一组的第一行,组成新数据框
1], by = a] dt[, .SD[
#> a b
#> 1: A 1
#> 2: B 2
#> 3: C 3
# 提取每一组的最后一行,组成新数据框
= a] dt[, .SD[.N], by
#> a b
#> 1: A 5
#> 2: B 6
#> 3: C 3
# 先分组,再显示每组行数
= year] total[, .N, by
#> year N
#> 1: 2018 12
#> 2: 2019 11
= .(year, month)] # year 和 month 是有先后顺序的 total[, .N, by
#> year month N
#> 1: 2018 1 1
#> 2: 2018 2 1
#> 3: 2018 3 1
#> 4: 2018 4 1
#> 5: 2018 5 1
#> 6: 2018 6 1
#> 7: 2018 7 1
#> 8: 2018 8 1
#> 9: 2018 9 1
#> 10: 2018 10 1
#> 11: 2018 11 1
#> 12: 2018 12 1
#> 13: 2019 1 1
#> 14: 2019 2 1
#> 15: 2019 3 1
#> 16: 2019 4 1
#> 17: 2019 5 1
#> 18: 2019 6 1
#> 19: 2019 7 1
#> 20: 2019 8 1
#> 21: 2019 9 1
#> 22: 2019 10 1
#> 23: 2019 11 1
#> year month N
avg_ex = mean(export)), by = year] # 即使只有一列,也要用列表 total[, .(
#> year avg_ex
#> 1: 2018 2072.842
#> 2: 2019 2054.800
setorder(dt, a, -b)
重新排列行,先a按升序,再b按降序
unique(dt, by = c("a","b"))
检查by的列,提取不重复的行
uniqueN(dt, by = c("a","b"))
检查by的列,计算每个不重复的行出现了多少次
setnames(dt, c("a","b"), c("x","y")
列重命名
:= shift(a, 1)] # 默认 lag
dt[, c dt
#> a b c
#> 1: A 1 <NA>
#> 2: B 2 A
#> 3: C 3 B
#> 4: A 4 C
#> 5: A 5 A
#> 6: B 6 A
:= shift(a, 1, type = "lead")]
dt[, c dt
#> a b c
#> 1: A 1 B
#> 2: B 2 C
#> 3: C 3 A
#> 4: A 4 A
#> 5: A 5 B
#> 6: B 6 <NA>
这样可以很容易地加入提前或滞后列,进行同比增长率等计算
<- data.table(a = c(1, 2, 3), b = c(4, 5, 6))
dt # 遍历a和b两列,都求平均值
map(.SD, mean), .SDcols = c("a", "b")] dt[,
#> a b
#> 1: 2 5
<- c("a", "b")
cols # 遍历 cols 各列,求平均值,且赋给新加入的a_m列
str_c(cols, "_m") := map(.SD, mean), .SDcols = cols]
dt[, dt
#> a b a_m b_m
#> 1: 1 4 2 5
#> 2: 2 5 2 5
#> 3: 3 6 2 5
setkey(d1, v1, v3, ...)
将某些列定义为 keys后,就可以通过键选择行,以及进行关系数据连接,比无键时快170倍。同时,可以用 mult 参数对查询结果增加过滤条件,使代码更高效。
<- data.table(a = c("A", "B", "C", "A", "A", "B"),
dt b = 1:6)
# 设置a列为主键
setkey(dt, a)
# setkeyv(dt, 'a') 函数仅接受字符串作为第二个参数
key(dt)
#> [1] "a"
# 打印dt对象,发现数据已经按照a列字母对应ASCII码值进行了排序。
dt
#> a b
#> 1: A 1
#> 2: A 4
#> 3: A 5
#> 4: B 2
#> 5: B 6
#> 6: C 3
# 取a列中值为B的行,设置主键后可省略`a==`
"B"] dt[
#> a b
#> 1: B 2
#> 2: B 6
# 取a列中值为B的行,并保留查询结果的第一行
"B", mult = "first"] dt[
#> a b
#> 1: B 2
# 取a列中值为B的行,并保留查询结果的最后一行
"B", mult = "last"] dt[
#> a b
#> 1: B 6
# 取a列中值为b的行,没有数据则为NA
"b"] dt[
#> a b
#> 1: b NA
setkey(total, year, month)
2019, 3)] # 省略了 year == 2019 & month == 3 total[.(
#> id year month export import total surplus
#> 1: 15 2019 3 1979 1657.3 3636.3 321.7
2019)] # 省略了 year == 2019,可以只根据一列 subset total[.(
#> id year month export import total surplus
#> 1: 13 2019 1 2179.7 1788.0 3967.7 391.7
#> 2: 14 2019 2 1351.2 1317.0 2668.2 34.2
#> 3: 15 2019 3 1979.0 1657.3 3636.3 321.7
#> 4: 16 2019 4 1934.8 1797.8 3732.6 137.0
#> 5: 17 2019 5 2138.5 1721.2 3859.7 417.3
#> 6: 18 2019 6 2128.4 1618.6 3747.0 509.8
#> 7: 19 2019 7 2215.3 1764.8 3980.1 450.5
#> 8: 20 2019 8 2148.0 1799.7 3947.7 348.3
#> 9: 21 2019 9 2181.2 1784.7 3965.9 396.5
#> 10: 22 2019 10 2129.3 1701.2 3830.5 428.1
#> 11: 23 2019 11 2217.4 1830.1 4047.5 387.3
# 不能写total[list(3)],得不出有意义的结果
基本语法为dt1[dt2, on = .(b = y)]
,其中b为dt1的列,y为dt2的列
若连个表都定义了 key, 第二个参数不用填
若没有定义key,…要写 on = “commonColumn” 或 by.dt1 = “c1”, by.dt2 = “c2”
例:学生考试的场景。按照ER设计方法,我们通常会按照实体进行数据划分。这里存在2个实体,一个是学生,一个是成绩。学生实体包括学生姓名等基本资料,而成绩实体包括考试的科目和成绩。通过设置两个主键,对2个数据集进行连接。
# 6个学生
<- data.table(
student id = 1:6,
name = c("Dan", "Mike", "Ann", "Yang", "Li", "Kate")
) student
#> id name
#> 1: 1 Dan
#> 2: 2 Mike
#> 3: 3 Ann
#> 4: 4 Yang
#> 5: 5 Li
#> 6: 6 Kate
# 分别参加A和B两门考试
<- data.table(
score id = 1:12, stuId = rep(1:6, 2),
score = runif(12, 60, 99),
course = c(rep("A", 6), rep("B", 6))
) score
#> id stuId score course
#> 1: 1 1 95.12819 A
#> 2: 2 2 80.36217 A
#> 3: 3 3 60.48816 A
#> 4: 4 4 88.57715 A
#> 5: 5 5 79.29332 A
#> 6: 6 6 89.29934 A
#> 7: 7 1 92.13274 B
#> 8: 8 2 97.45311 B
#> 9: 9 3 72.97279 B
#> 10: 10 4 84.86577 B
#> 11: 11 5 82.29908 B
#> 12: 12 6 61.60280 B
# 设置student数据集的key
setkey(student, "id")
# 设置score数据集的key
setkey(score, "stuId")
# 连接
= NA, mult = "all"] student[score, nomatch
#> id name i.id score course
#> 1: 1 Dan 1 95.12819 A
#> 2: 1 Dan 7 92.13274 B
#> 3: 2 Mike 2 80.36217 A
#> 4: 2 Mike 8 97.45311 B
#> 5: 3 Ann 3 60.48816 A
#> 6: 3 Ann 9 72.97279 B
#> 7: 4 Yang 4 88.57715 A
#> 8: 4 Yang 10 84.86577 B
#> 9: 5 Li 5 79.29332 A
#> 10: 5 Li 11 82.29908 B
#> 11: 6 Kate 6 89.29934 A
#> 12: 6 Kate 12 61.60280 B
rm(list = ls())
fintersect(dt1, dt2)
fsetdiff(dt1, dt2)
funion(dt1, dt2)
fsetequal(dt1, dt2)
## 月K线图数据的整理
# 1. 产生日期序列
<- data.table(date = (as.Date("2015-05-01") + 0:299))
market_data set.seed(125)
# 2. 产生成交价格和数量序列
# 股市价格是一个随机游走过程,将300天的变化率累乘起来
# 成交量 volume 取随机数
`:=`(
market_data[, price = ((1 + rnorm(300, 0.001, 0.05)) %>% cumprod() * 30) %>% round(2),
volume = rbinom(300, 5000, 0.8)
)]head(market_data)
#> date price volume
#> 1: 2015-05-01 31.43 4000
#> 2: 2015-05-02 30.64 4022
#> 3: 2015-05-03 33.45 4006
#> 4: 2015-05-04 33.62 3976
#> 5: 2015-05-05 34.32 4044
#> 6: 2015-05-06 30.59 4010
# 3. 股价走势图
plot(price ~ date,
data = market_data,
type = "l",
main = "Market data"
)
# 4. 获取横轴 domain
range(date)] # 没有:=,所以这不是update,而是summarize market_data[,
#> [1] "2015-05-01" "2016-02-24"
# 5. 按月分组,每组统计最大、最小、最早、最晚价格,这就是月K线图!!!
<- market_data[,
monthly # 这样写 list 就很像键值对了
.(open = price[[1]],
high = max(price),
low = min(price),
close = price[[.N]]
),= .(
keyby year = year(date),
month = month(date)
)
]head(monthly)
#> year month open high low close
#> 1: 2015 5 31.43 39.36 30.03 38.13
#> 2: 2015 6 42.74 50.54 39.08 39.47
#> 3: 2015 7 37.21 46.96 31.70 43.42
#> 4: 2015 8 46.98 57.06 42.24 42.24
#> 5: 2015 9 45.79 47.19 34.02 37.09
#> 6: 2015 10 34.75 58.65 34.75 56.38
# 6. 自定义函数,针对不同的列可以分组计算平均值
<- function(column) {
average
market_data[,
.(average = mean(.SD[[column]])
),= .(year = year(date))
by
]
}average("price")
#> year average
#> 1: 2015 42.13637
#> 2: 2016 24.56982
average("volume")
#> year average
#> 1: 2015 4003.596
#> 2: 2016 3996.564