【r<-包】R-数据操作(三):高效的data.table

【r<-基础】R-数据操作(二)

使用data.table包操作数据

data.table包提供了一个加强版的data.frame,它运行效率极高,而且能够处理适合内存的大数据集,它使用[]实现了一种自然地数据操作语法。使用下面命令进行安装:

install.packages("data.table")

载入包:

library(data.table)
#> 
#> 载入程辑包:'data.table'
#> The following objects are masked from 'package:reshape2':
#> 
#>     dcast, melt

注意,data.table包提供了加强版的dcast()melt(),它们的功能更强大、性能更高,内存使用也更高效。

创建data.table与创建data.frame类似:

dt = data.table(x = 1:3, y = rnorm(3), z = letters[1:3])
dt
#>    x      y z
#> 1: 1  0.906 a
#> 2: 2 -0.154 b
#> 3: 3  0.608 c

检查它的结构:

str(dt)
#> Classes 'data.table' and 'data.frame':   3 obs. of  3 variables:
#>  $ x: int  1 2 3
#>  $ y: num  0.906 -0.154 0.608
#>  $ z: chr  "a" "b" "c"
#>  - attr(*, ".internal.selfref")=<externalptr>

可以看到,dt的类是data.tabledata.frame,也就是说data.table继承了data.frame的一些行为,但增强了其他部分。

**data.table的基本语法是dt[i, j, by],简单说就是使用i选择行,用by分组,然后计算j**。接下来我们看看data.table`继承了什么,增强了什么。

首先,我们仍然载入之前用到的产品数据,不过这里我们使用data.table包提供的fread()函数,它非常高效和智能,默认返回data.table

product_info = fread("../../R/dataset/product-info.csv")
product_stats = fread("../../R/dataset/product-stats.csv")
product_tests = fread("../../R/dataset/product-tests.csv")
toy_tests = fread("../../R/dataset/product-toy-tests.csv")

如果查看表格信息,你会发现它和data.frame没什么两样:

product_info
#>     id      name  type   class released
#> 1: T01    SupCar   toy vehicle      yes
#> 2: T02  SupPlane   toy vehicle       no
#> 3: M01     JeepX model vehicle      yes
#> 4: M02 AircraftX model vehicle      yes
#> 5: M03    Runner model  people      yes
#> 6: M04    Dancer model  people       no

再看结构:

str(product_info)
#> Classes 'data.table' and 'data.frame':   6 obs. of  5 variables:
#>  $ id      : chr  "T01" "T02" "M01" "M02" ...
#>  $ name    : chr  "SupCar" "SupPlane" "JeepX" "AircraftX" ...
#>  $ type    : chr  "toy" "toy" "model" "model" ...
#>  $ class   : chr  "vehicle" "vehicle" "vehicle" "vehicle" ...
#>  $ released: chr  "yes" "no" "yes" "yes" ...
#>  - attr(*, ".internal.selfref")=<externalptr>

data.frame不同,如果只提供一个参数用来构建子集,data.table是选择行而不是列:

product_info[1]
#>     id   name type   class released
#> 1: T01 SupCar  toy vehicle      yes

product_info[1:3]
#>     id     name  type   class released
#> 1: T01   SupCar   toy vehicle      yes
#> 2: T02 SupPlane   toy vehicle       no
#> 3: M01    JeepX model vehicle      yes

如果提供的是负数,那么将删除指定的行:

product_info[-1]
#>     id      name  type   class released
#> 1: T02  SupPlane   toy vehicle       no
#> 2: M01     JeepX model vehicle      yes
#> 3: M02 AircraftX model vehicle      yes
#> 4: M03    Runner model  people      yes
#> 5: M04    Dancer model  people       no

data.table提供了许多特殊符号,它们是data.table的重要组成.N是最常用的符号之一,它表示当前分组中,对象的数目(就不用调用nrow函数啦)。在[]使用它指提取最后一行。

product_info[.N]
#>     id   name  type  class released
#> 1: M04 Dancer model people       no
product_info[c(1, .N)]
#>     id   name  type   class released
#> 1: T01 SupCar   toy vehicle      yes
#> 2: M04 Dancer model  people       no

在对data.table构建子集时,能够自动根据语义计算表达式,因此可以直接使用列名,像with()subset()那样。

比如:

product_info[released == "yes"]
#>     id      name  type   class released
#> 1: T01    SupCar   toy vehicle      yes
#> 2: M01     JeepX model vehicle      yes
#> 3: M02 AircraftX model vehicle      yes
#> 4: M03    Runner model  people      yes

方括号内的第1个参数是行筛选器,第2个则对筛选后的数据进行适当的计算。

例如提取列:

product_info[released == "yes", id]
#> [1] "T01" "M01" "M02" "M03"

在这里使用"id"结果不同,返回的必然是个data.table。

product_info[released == "yes", "id"]
#>     id
#> 1: T01
#> 2: M01
#> 3: M02
#> 4: M03

第二个参数可以是表达式,例如生成一张表,反应每种typeclass组合中releasedyes的数量:

product_info[released == "yes", table(type, class)]
#>        class
#> type    people vehicle
#>   model      1       2
#>   toy        0       1

要注意,给第2个参数提供list(),结果仍然转换为data.table

product_info[released == "yes", list(id, name)]
#>     id      name
#> 1: T01    SupCar
#> 2: M01     JeepX
#> 3: M02 AircraftX
#> 4: M03    Runner

我们可以替换原有列,生成新的data.table:

product_info[, list(id, name, released = released == "yes")]
#>     id      name released
#> 1: T01    SupCar     TRUE
#> 2: T02  SupPlane    FALSE
#> 3: M01     JeepX     TRUE
#> 4: M02 AircraftX     TRUE
#> 5: M03    Runner     TRUE
#> 6: M04    Dancer    FALSE

还可以创建新列:

product_stats[, list(id, material, size, weight, density = size/weight)]
#>     id material size weight density
#> 1: T01    Metal  120   10.0   12.00
#> 2: T02    Metal  350   45.0    7.78
#> 3: M01 Plastics   50     NA      NA
#> 4: M02 Plastics   85    3.0   28.33
#> 5: M03     Wood   15     NA      NA
#> 6: M04     Wood   16    0.6   26.67

为了简化,data.table使用.()作为list()的缩写,这两者等价

product_info[, .(id, name, type, class)]
#>     id      name  type   class
#> 1: T01    SupCar   toy vehicle
#> 2: T02  SupPlane   toy vehicle
#> 3: M01     JeepX model vehicle
#> 4: M02 AircraftX model vehicle
#> 5: M03    Runner model  people
#> 6: M04    Dancer model  people

product_info[released == "yes", .(id, name)]
#>     id      name
#> 1: T01    SupCar
#> 2: M01     JeepX
#> 3: M02 AircraftX
#> 4: M03    Runner

提供排序索引可以对记录排序:

product_stats[order(size, decreasing = TRUE)]
#>     id material size weight
#> 1: T02    Metal  350   45.0
#> 2: T01    Metal  120   10.0
#> 3: M02 Plastics   85    3.0
#> 4: M01 Plastics   50     NA
#> 5: M04     Wood   16    0.6
#> 6: M03     Wood   15     NA

前面都是在构建子集后,又创建新的data.table。这样挺麻烦的,因此data.table包提供了对列进行原地赋值的符号:=,例如product_stats开始是这样的:

product_stats
#>     id material size weight
#> 1: T01    Metal  120   10.0
#> 2: T02    Metal  350   45.0
#> 3: M01 Plastics   50     NA
#> 4: M02 Plastics   85    3.0
#> 5: M03     Wood   15     NA
#> 6: M04     Wood   16    0.6

使用:=直接在上面数据框创建新列:

product_stats[, density := size / weight]

虽然没有任何返回,但数据已经被修改了:

product_stats
#>     id material size weight density
#> 1: T01    Metal  120   10.0   12.00
#> 2: T02    Metal  350   45.0    7.78
#> 3: M01 Plastics   50     NA      NA
#> 4: M02 Plastics   85    3.0   28.33
#> 5: M03     Wood   15     NA      NA
#> 6: M04     Wood   16    0.6   26.67

使用:=替换已有的列:

product_info[, released := released == "yes"]
product_info
#>     id      name  type   class released
#> 1: T01    SupCar   toy vehicle     TRUE
#> 2: T02  SupPlane   toy vehicle    FALSE
#> 3: M01     JeepX model vehicle     TRUE
#> 4: M02 AircraftX model vehicle     TRUE
#> 5: M03    Runner model  people     TRUE
#> 6: M04    Dancer model  people    FALSE

使用键获取值

索引支持是data.table另一个独特功能,即我们可以创建键(key),使用键获取记录及其高效。

例如,使用setkey()id设置为product_info中的一个键:

setkey(product_info, id)

同样的,函数无任何返回,但我们已经为原始数据设置了键,而且原来的数据看起来也没变化:

product_info
#>     id      name  type   class released
#> 1: M01     JeepX model vehicle     TRUE
#> 2: M02 AircraftX model vehicle     TRUE
#> 3: M03    Runner model  people     TRUE
#> 4: M04    Dancer model  people    FALSE
#> 5: T01    SupCar   toy vehicle     TRUE
#> 6: T02  SupPlane   toy vehicle    FALSE

但键已生成:

key(product_info)
#> [1] "id"

现在我们可以用它来获取数据了,比如提供一个id值:

product_info["M01"]
#>     id  name  type   class released
#> 1: M01 JeepX model vehicle     TRUE

也可以使用setkeyv()来设置键,但它只接受字符向量:

setkeyv(product_stats, "id")

当key是一个动态变化的向量时,这个函数会非常好用

product_stats["M02"]
#>     id material size weight density
#> 1: M02 Plastics   85      3    28.3

如果两个表格有相同的键,我们可以轻松把他们连接到一起:

product_info[product_stats]
#>     id      name  type   class released material size weight density
#> 1: M01     JeepX model vehicle     TRUE Plastics   50     NA      NA
#> 2: M02 AircraftX model vehicle     TRUE Plastics   85    3.0   28.33
#> 3: M03    Runner model  people     TRUE     Wood   15     NA      NA
#> 4: M04    Dancer model  people    FALSE     Wood   16    0.6   26.67
#> 5: T01    SupCar   toy vehicle     TRUE    Metal  120   10.0   12.00
#> 6: T02  SupPlane   toy vehicle    FALSE    Metal  350   45.0    7.78

data.table的键可以不止一个。例如使用iddate定位toy_tests中的记录:

setkey(toy_tests, id, date)

现在提供key中的两个元素就可以获取记录了

toy_tests[.("T01", 20160201)]
#>     id     date sample quality durability
#> 1: T01 20160201    100       9          9

如果提供第一个元素,会返回匹配的多个值:

toy_tests["T01"]
#>     id     date sample quality durability
#> 1: T01 20160201    100       9          9
#> 2: T01 20160302    150      10          9
#> 3: T01 20160405    180       9         10
#> 4: T01 20160502    140       9          9

key不能错序,因此不能单独提供第2个元素以及反序排列。

toy_tests[20160201]
#>      id date sample quality durability
#> 1: <NA>   NA     NA      NA         NA

toy_tests[.(20160202,"T01")]
#> Error in bmerge(i, x, leftcols, rightcols, io, xo, roll, rollends, nomatch, : x.'id' is a character column being joined to i.'V1' which is type 'double'. Character columns must join to factor or character columns.

对数据进行分组汇总

by是data.table中另一个重要参数(即方括号内的第3个参数),它可以将数据按照by值进行分组,并对分组计算第2个参数。

接下来,我们学习如何通过by以简便的方式实现数据的分组汇总。

最简单的用法是计算每组的记录条数:

product_info[, .N, by = released]
#>    released N
#> 1:     TRUE 4
#> 2:    FALSE 2

分组的变量可以不止一个,例如由typeclass确定一个分组:

product_info[, .N, by = .(type, class)]
#>     type   class N
#> 1: model vehicle 2
#> 2: model  people 2
#> 3:   toy vehicle 2

可以对每个分组进行统计计算,这里计算防水和非防水产品的质量得分均值:

product_tests[, mean(quality, na.rm = TRUE), by = .(waterproof)]
#>    waterproof    V1
#> 1:         no 10.00
#> 2:        yes  5.75

可以看到结果存储在V1列中,我们可以手动指定列名:

product_tests[, .(mean_quality = mean(quality, na.rm = TRUE)), by = .(waterproof)]
#>    waterproof mean_quality
#> 1:         no        10.00
#> 2:        yes         5.75

注意操作需要�放在list中进行(.())。

我们可以将多个[]按顺序连接起来,形成工作流(类似管道%>%)。

下面的例子中,首先使用通用键id将product_info和product_tests连接起来,然后筛选已发布的产品,再按type和class进行分组,最后计算每组的quality和durability的均值。

type_class_test0 = product_info[product_tests][released == TRUE,
                                               .(mean_quality = mean(quality, na.rm=TRUE),
                                                 mean_durability = mean(durability, na.rm=TRUE)),
                                               by = .(type, class)]

type_class_test0
#>     type   class mean_quality mean_durability
#> 1:   toy vehicle          NaN            10.0
#> 2: model vehicle            6             4.5
#> 3: model  people            5             NaN

在返回的data.table中,by所对应的组合中的值是唯一的,虽然实现了目标,但结果中没有设置键:

key(type_class_test0)
#> NULL

这种情况下,我们可以使用keyby来确保结果的data.table自动将keyby对应的分组向量设置为键。一般data.table会保持原来的顺序返回,有时候我们想要设定排序,keyby也可以实现,所以是一举两得:

type_class_test = product_info[product_tests][released == TRUE, 
                                              .(mean_quality = mean(quality, na.rm = TRUE),
                                                mean_durability = mean(durability, na.rm = TRUE)),
                                              keyby = .(type, class)]
type_class_test
#>     type   class mean_quality mean_durability
#> 1: model  people            5             NaN
#> 2: model vehicle            6             4.5
#> 3:   toy vehicle          NaN            10.0
key(type_class_test)
#> [1] "type"  "class"

下面可以直接用键来获取值:

type_class_test[.("model", "vehicle"), mean_quality]
#> [1] 6

对大数据集使用键进行搜索,能够比迭代使用逻辑比较快得多,因为键搜索利用了二进制搜索,而迭代在不必要的计算上浪费了时间

下面举例说明,首先创建有1000万行的数据,其中一列是索引列id,其他两列是随机数:

n = 10000000
test1 = data.frame(id = 1:n, x = rnorm(n), y = rnorm(n))

现在查找id为876543的行,看要花多少时间:

system.time(row <- test1[test1$id == 876543, ])
#>  用户  系统  流逝 
#> 0.132 0.018 0.150

作为对比,我们使用data.table来完成这个任务,使用setDT()将数据框转换为data.table,该函数可以原地转换,不需要复制,并可以设定键。

setDT(test1, key = "id")
class(test1)
#> [1] "data.table" "data.frame"

现在我们搜索相同的元素:

system.time(row <- test1[.(876543)])
#>  用户  系统  流逝 
#> 0.001 0.000 0.000

结果一致,但data.table用的时间要少得多。

重塑data.table

data.table扩展包为data.table对象提供了更强更快得dcast()melt()函数。

例如将toy_tests的每个产品质量得分按照年和月进行对齐

toy_tests[, ym := substr(date, 1, 6)]
toy_quality = dcast(toy_tests, ym ~ id, value.var = "quality")
toy_quality
#>        ym T01 T02
#> 1: 201602   9   7
#> 2: 201603  10   8
#> 3: 201604   9   9
#> 4: 201605   9  10

data.table::dcast()提供了更强大的多变量支持:

toy_tests2 = data.table::dcast(toy_tests, ym ~ id, value.var = c("quality", "durability"))
toy_tests2
#>        ym quality_T01 quality_T02 durability_T01 durability_T02
#> 1: 201602           9           7              9              9
#> 2: 201603          10           8              9              8
#> 3: 201604           9           9             10              8
#> 4: 201605           9          10              9              9

看到没,data.table可以自动将id值与质量分类连接起来。

此时ym是键:

key(toy_tests2)
#> [1] "ym"

我们可以利用它提取数据:

toy_tests2["201602"]
#>        ym quality_T01 quality_T02 durability_T01 durability_T02
#> 1: 201602           9           7              9              9

使用原地设置函数

我们知道R存在复制修改机制,这在进行大数据计算时开销很大,data.table提供了一系列支持语义的set函数,它们可以原地修改data.table,因此避免不必要的复制。

仍以product_stats为例,我们可以使用setDF()函数不要任何复制就可以将data.table变成data.frame。

product_stats
#>     id material size weight density
#> 1: M01 Plastics   50     NA      NA
#> 2: M02 Plastics   85    3.0   28.33
#> 3: M03     Wood   15     NA      NA
#> 4: M04     Wood   16    0.6   26.67
#> 5: T01    Metal  120   10.0   12.00
#> 6: T02    Metal  350   45.0    7.78

setDF(product_stats)

class(product_stats)
#> [1] "data.frame"

setDT()可以将任意的data.frame转换为data.table,并设置键。

setDT(product_stats, key = "id")
class(product_stats)
#> [1] "data.table" "data.frame"

使用setnames()可以对列重命名:

setnames(product_stats, "size", "volume")

product_stats
#>     id material volume weight density
#> 1: M01 Plastics     50     NA      NA
#> 2: M02 Plastics     85    3.0   28.33
#> 3: M03     Wood     15     NA      NA
#> 4: M04     Wood     16    0.6   26.67
#> 5: T01    Metal    120   10.0   12.00
#> 6: T02    Metal    350   45.0    7.78

如果给行添加索引,使用:

product_stats[, i := .I]
product_stats
#>     id material volume weight density i
#> 1: M01 Plastics     50     NA      NA 1
#> 2: M02 Plastics     85    3.0   28.33 2
#> 3: M03     Wood     15     NA      NA 3
#> 4: M04     Wood     16    0.6   26.67 4
#> 5: T01    Metal    120   10.0   12.00 5
#> 6: T02    Metal    350   45.0    7.78 6

为方便,索引一般在第1列,所以我们要修改列的顺序:

setcolorder(product_stats, c("i", "id", "material", "weight", "volume", "density"))
product_stats
#>    i  id material weight volume density
#> 1: 1 M01 Plastics     NA     50      NA
#> 2: 2 M02 Plastics    3.0     85   28.33
#> 3: 3 M03     Wood     NA     15      NA
#> 4: 4 M04     Wood    0.6     16   26.67
#> 5: 5 T01    Metal   10.0    120   12.00
#> 6: 6 T02    Metal   45.0    350    7.78

data.table的动态作用域

我们不仅可以直接使用列,也可以提前定义注入.N.I.SD来指代数据中的重要部分。

为演示,我们先创建新的data.table,命名为market_data,其中date列是连续的。

market_data = data.table(date = as.Date("2015-05-01") + 0:299)
head(market_data)
#>          date
#> 1: 2015-05-01
#> 2: 2015-05-02
#> 3: 2015-05-03
#> 4: 2015-05-04
#> 5: 2015-05-05
#> 6: 2015-05-06

向调用函数一样,我们给data.table添加数据列:

set.seed(123)

market_data[, `:=`(
    price = round(30 * cumprod(1 + rnorm(300, 0.001, 0.05)), 2),
    volume = rbinom(300, 5000, 0.8)
)]

注意这里的price和volumn都是服从正态分布的随机数:

head(market_data)
#>          date price volume
#> 1: 2015-05-01  29.2   4021
#> 2: 2015-05-02  28.9   4000
#> 3: 2015-05-03  31.2   4033
#> 4: 2015-05-04  31.3   4036
#> 5: 2015-05-05  31.5   3995
#> 6: 2015-05-06  34.3   3955

我们以图形的方式展示数据:

plot(price ~ date, data = market_data,
     type = "l",
     main = "Market data")

img

数据准备好后,我们看看动态作用域如何让事情变得简单。

看下时间范围:

market_data[, range(date)]
#> [1] "2015-05-01" "2016-02-24"

将数据整合缩减为月度数据:

monthly = market_data[,
                      .(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 29.2 37.7 26.1  28.4
#> 2: 2015     6 28.1 37.6 28.1  37.2
#> 3: 2015     7 36.3 41.0 32.1  41.0
#> 4: 2015     8 41.5 50.0 30.9  30.9
#> 5: 2015     9 30.5 34.5 22.9  27.0
#> 6: 2015    10 25.7 33.2 24.6  29.3

计算过程为:先根据by表达式将原始数据分割,分割后的每个部分都是原始数据的一个子集,并且原始数据和子集都是data.table。然后在每个子集data.table的语义中计算j表达式

下面代码没有按组聚合数据,而是画了每年的价格图:

oldpar = par(mfrow = c(1, 2))
market_data[, {
    plot(price ~ date, type = "l",
         main = sprintf("Market data (%d)", year))
}, by = .(year = year(date))]
par(oldpar)

这里我们没有为plot()设定data参数,图像也成功绘制,这是因为该操作是在data.table的语义中进行的。

此外,j表达式还可以用于构建模型的代码,下面是一个批量拟合线性模型的例子。这里使用diamonds数据集。

data("diamonds", package = "ggplot2")

setDT(diamonds)
head(diamonds)
#>    carat       cut color clarity depth table price    x    y    z
#> 1:  0.23     Ideal     E     SI2  61.5    55   326 3.95 3.98 2.43
#> 2:  0.21   Premium     E     SI1  59.8    61   326 3.89 3.84 2.31
#> 3:  0.23      Good     E     VS1  56.9    65   327 4.05 4.07 2.31
#> 4:  0.29   Premium     I     VS2  62.4    58   334 4.20 4.23 2.63
#> 5:  0.31      Good     J     SI2  63.3    58   335 4.34 4.35 2.75
#> 6:  0.24 Very Good     J    VVS2  62.8    57   336 3.94 3.96 2.48

该数据集包含超过5万条钻石信息的记录,每条记录了钻石的10个属性,现在我们队cut列中的每种切割类型都你拟合一个线性回归模型,由此观察每种切割类型中carat与depth是如何反映log(price)的信息。

diamonds[, {
    m = lm(log(price) ~ carat + depth)
    as.list(coef(m))
}, keyby = .(cut)]
#>          cut (Intercept) carat    depth
#> 1:      Fair        7.73  1.26 -0.01498
#> 2:      Good        7.08  1.97 -0.01460
#> 3: Very Good        6.29  2.09 -0.00289
#> 4:   Premium        5.93  1.85  0.00594
#> 5:     Ideal        8.50  2.13 -0.03808

动态作用域允许我们组合使用data.table内部或外部预定义的符号。举例,我们定义一个函数,计算market_data中由用户定义的列的年度均值:

average = function(column){
    market_data[, .(average = mean(.SD[[column]])),
                by = .(year = year(date))]
}

这里我们使用.SD[[x]]提取x列的值,这跟通过名字从列表中提取成分或元素相同。

下面计算每年的平均价格:

average("price")
#>    year average
#> 1: 2015    32.3
#> 2: 2016    32.4

每年平均数量:

average("volume")
#>    year average
#> 1: 2015    4000
#> 2: 2016    4003

我们可以利用此包专门的语法创造一个列数动态变化的组合,并且组合中的列是由动态变化的名称决定的。

这里我们假设添加额外的3列数据,每一列都是原始价格加了随机噪声生成的。不用重复调用market_date[, price1 := ...],而是使用market_data[, (columns) := list(...)]来动态设定列,其中columns是一个包含列名的字符向量,list(...)是每个列对应的值:

price_cols = paste0("price", 1:3)
market_data[, (price_cols) := lapply(1:3,
                                     function(i) round(price + rnorm(.N, 0, 5), 2))]
head(market_data)
#>          date price volume price1 price2 price3
#> 1: 2015-05-01  29.2   4021   30.6   27.4   33.2
#> 2: 2015-05-02  28.9   4000   29.7   20.4   36.0
#> 3: 2015-05-03  31.2   4033   34.3   26.9   27.2
#> 4: 2015-05-04  31.3   4036   29.3   29.0   28.0
#> 5: 2015-05-05  31.5   3995   36.0   32.1   34.8
#> 6: 2015-05-06  34.3   3955   30.1   31.0   35.2

另一方面,如果表格有很多列,并且需要对它们的子集进行一些计算,也可以用类似的语法来解决。

举例,我们现在需要对每个价格列调用na.locf()以去掉缺失值,先获取所有的价格列:

cols = colnames(market_data)

price_cols = cols[grep("^price", cols)]

price_cols
#> [1] "price"  "price1" "price2" "price3"

然后我们用类似的语法,并添加一个参数.SDcols = price_cols,这是为了让.SD中的列只是我们想要的那些价格列。

market_data[, (price_cols) := lapply(.SD, zoo::na.locf), .SDcols =  price_cols]

head(market_data)
#>          date price volume price1 price2 price3
#> 1: 2015-05-01  29.2   4021   30.6   27.4   33.2
#> 2: 2015-05-02  28.9   4000   29.7   20.4   36.0
#> 3: 2015-05-03  31.2   4033   34.3   26.9   27.2
#> 4: 2015-05-04  31.3   4036   29.3   29.0   28.0
#> 5: 2015-05-05  31.5   3995   36.0   32.1   34.8
#> 6: 2015-05-06  34.3   3955   30.1   31.0   35.2

最后,更多操作请前往https://github.com/Rdatatable/data.table/wiki查看完整功能列表。


data.table包功能快速学习见文章https://www.jianshu.com/p/ed499763a3a9

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343