R 使用高度重复的键减少data.table的内存占用
我正在写一个包来分析R 使用高度重复的键减少data.table的内存占用,r,memory,data.table,R,Memory,Data.table,我正在写一个包来分析R中的高通量动物行为数据。 数据为多变量时间序列。 我选择使用data.tables表示它们,我觉得这非常方便 对于一种动物,我会有这样的想法: one_animal_dt <- data.table(t=1:20, x=rnorm(20), y=rnorm(20)) animal_list <- list() animal_list[[1]] <- data.table(t=1:20, x=rnorm(20), y=rnorm(20),
R
中的高通量动物行为数据。
数据为多变量时间序列。
我选择使用data.tables
表示它们,我觉得这非常方便
对于一种动物,我会有这样的想法:
one_animal_dt <- data.table(t=1:20, x=rnorm(20), y=rnorm(20))
animal_list <- list()
animal_list[[1]] <- data.table(t=1:20, x=rnorm(20), y=rnorm(20),
treatment="A", date="2017-02-21 20:00:00",
animal_id=1)
animal_list[[2]] <- data.table(t=1:20, x=rnorm(20), y=rnorm(20),
treatment="B", date="2017-02-21 22:00:00",
animal_id=2)
# ...
final_dt <- rbindlist(animal_list)
setkeyv(final_dt,c("treatment", "date","animal_id"))
<代码> No.IngalaldD> p>您应该考虑使用嵌套数据帧< /P>
library(tidyverse)
使用玩具示例,其中Irbind
4份mtcars
new <- rbind(mtcars,mtcars,mtcars,mtcars) %>%
select(cyl,mpg)
object.size(new)
11384 bytes
看起来像
alt1
animal_id treatment date data
1 1 A 2017-02-21 20:00:00 <tibble [20 x 3]>
2 1 B 2017-02-21 22:00:00 <tibble [20 x 3]>
alt1
动物id治疗日期数据
1A 2017-02-21 20:00:00
21b 2017-02-21 22:00:00
这里有两种可能性(使用一种、两种或无):
# Same code as in question to generate data
animal_list <- list()
animal_list[[1]] <- data.table(t=1:20, x=rnorm(20), y=rnorm(20),
treatment="A", date="2017-02-21 20:00:00",
animal_id=1)
animal_list[[2]] <- data.table(t=1:20, x=rnorm(20), y=rnorm(20),
treatment="B", date="2017-02-21 22:00:00",
animal_id=2)
# ...
final_dt <- rbindlist(animal_list)
# Separating into treatment and animal data.tables
animals_dt <- unique(final_dt[, .(date), key = animal_id])
treatments_dt <- final_dt[, .(t, x, y, treatment), key = animal_id]
#与生成数据的代码相同
让我们假设,我们是一名数据库管理员,任务是在SQL数据库中高效地实现这一点。数据库规范化的目标之一是减少冗余
根据OP的描述,每只动物有许多(约1m)的观察结果(多变量、纵向数据),而动物的数量似乎要小得多
因此,每只动物的恒定(或不变)基础数据,例如,治疗
,日期
,应与观察
分开保存
假设animal\u id
是唯一的(顾名思义),则animal\u id
是两个表的键
(请注意,这是使用治疗
作为关键的主要区别,但不能保证其唯一性,也就是说,两种动物可能接受相同的治疗,并进一步增加冗余。)
单独的表可以节省内存
为了演示的目的,正在为10只动物创建更现实的“基准”数据,每只动物的观察值为1m:
library(data.table) # CRAN version 1.10.4 used
# create observations
n_obs <- 1E6L
n_animals <-10L
set.seed(123L)
observations <- data.table(
animal_id = rep(seq_len(n_animals), each = n_obs),
t = rep(seq_len(n_obs), n_animals),
x = rnorm(n_animals * n_obs),
y = rnorm(n_animals * n_obs))
# create animal base data
animals = data.table(
animal_id = seq_len(n_animals),
treatment = wakefield::string(n_animals),
date = wakefield::date_stamp(n_animals, random = TRUE))
组合大小约为240 MB:
让我们将此作为参考,并与OP的方法进行比较。final\u dt
:
# join both tables to create equivalent of final_dt
joined <- animals[observations, on = "animal_id"]
请注意,到目前为止没有设置数据。表键。相反,使用on
参数指定要连接的列。如果我们设置该键,连接将加速,并且可以省略on
参数:
setkey(observations, animal_id)
setkey(animals, animal_id)
joined <- animals[observations]
并与动物
animals[observations[, .(.N, mean(x), mean(y)), by = animal_id]]
OP已经指出,他不想强迫他的用户自己使用连接。诚然,键入动物[观察结果]
所需的击键次数要比键入最终结果
所需的击键次数多。因此,由OP决定是否值得保存内存
例如,如果我们想要比较具有某些特征的动物,例如
animals[observations[, .(.N, mean(x), mean(y)), by = animal_id]][date == as.Date("2017-07-02")]
OP的用例
在中,OP描述了一些用例,他希望看到这些用例对他的用户透明地实现:
- 创建新的列
final\u dt[,x2:=1-x]
:由于只涉及观察,这直接转化为观察[,x2:=1-x]
- 使用各种标准选择:这里涉及两个表的列。这可以通过
数据来实现。表
以不同的方式实现(注意,已针对实际样本数据修改了条件):
请注意,这看起来与预期的语法非常相似(只需少按一次键)
如果用户认为这是不可接受的,则可以在函数中隐藏联接操作:
# function definition
filter_dt <- function(ani_filter = "", obs_filter = "") {
eval(parse(text = stringr::str_interp(
'animals[observations[${obs_filter}]][${ani_filter}]')))
}
# called by user
filter_dt("treatment %like% 'MAD'", "t < 5L")
使用因子减少内存占用
注意事项:您的里程数可能会有所不同,因为以下结论取决于计算机上整数的内部表示形式和数据的基数。请参阅有关此主题的内容
已经提到,如果整数作为数字存储,内存可能会被浪费。这可以证明:
n <- 10000L
# integer vs numeric vs logical
test_obj_size <- data.table(
rep(1, n),
rep(1L, n),
rep(TRUE, n))
str(test_obj_size)
请注意,数值向量需要的内存是整数向量的两倍。因此,最好的编程实践是始终使用后缀字符L
限定整数常量
此外,如果强制将字符串转换为因子,则可以减少字符串的内存消耗:
# character vs factor
test_obj_size <- data.table(
rep("A", n),
rep("AAAAAAAAAAA", n),
rep_len(LETTERS, n),
factor(rep("A", n)),
factor(rep("AAAAAAAAAAA", n)),
factor(rep_len(LETTERS, n)))
str(test_obj_size)
存储为因子,只需要一半的内存
对于Date
和POSIXct
类也同样适用:
# Date & POSIXct vs factor
test_obj_size <- data.table(
rep(as.Date(Sys.time()), n),
rep(as.POSIXct(Sys.time()), n),
factor(rep(as.Date(Sys.time()), n)),
factor(rep(as.POSIXct(Sys.time()), n)))
str(test_obj_size)
请注意,data.table()
拒绝创建类POSIXlt
的列,因为它存储在40字节而不是8字节中
所以,如果你的应用程序是内存关键的,那么可以考虑在适用的情况下使用因子。p> 非常感谢您的反馈。你鼓励我回答我自己的问题,就这样
我考虑了三种不同的数据结构:
- 原始的,其中所有元数据都在同一个表中。见我的问题
- 嵌套,其中一个表包含元数据和一个特殊的
数据
列,每个动物包含一个表。看
- 两张表。一个用于metatadata,一个用于data。这两个表可以使用共享id(键)相互映射。看
原始的方法非常方便。例如,在数据和元数据之间编写操作非常高效和简单(因为它们在同一个表中)。例如,使用setkey(observations, animal_id)
setkey(animals, animal_id)
joined <- animals[observations]
observations[, .(.N, mean(x), mean(y)), by = animal_id]
animal_id N V2 V3
1: 1 1000000 -5.214370e-04 -0.0019643145
2: 2 1000000 -1.555513e-03 0.0002489457
3: 3 1000000 1.541233e-06 -0.0005317967
4: 4 1000000 1.775802e-04 0.0016212182
5: 5 1000000 -9.026074e-04 0.0015266330
6: 6 1000000 -1.000892e-03 0.0003284044
7: 7 1000000 1.770055e-04 -0.0018654386
8: 8 1000000 1.919562e-03 0.0008605261
9: 9 1000000 1.175696e-03 0.0005042170
10: 10 1000000 1.681614e-03 0.0020562628
animals[observations[, .(.N, mean(x), mean(y)), by = animal_id]]
animal_id treatment date N V2 V3
1: 1 MADxZ9c6fN 2017-07-02 1000000 -5.214370e-04 -0.0019643145
2: 2 ymoJHnvrRx 2016-10-02 1000000 -1.555513e-03 0.0002489457
3: 3 ifdtywJ4jU 2016-10-02 1000000 1.541233e-06 -0.0005317967
4: 4 Q7ZRwnQCsU 2017-02-02 1000000 1.775802e-04 0.0016212182
5: 5 H2M4V9Dfxz 2017-04-02 1000000 -9.026074e-04 0.0015266330
6: 6 29P3hFxqNY 2017-03-02 1000000 -1.000892e-03 0.0003284044
7: 7 rBxjewyGML 2017-02-02 1000000 1.770055e-04 -0.0018654386
8: 8 gQP8cZhcTT 2017-04-02 1000000 1.919562e-03 0.0008605261
9: 9 0GEOseSshh 2017-07-02 1000000 1.175696e-03 0.0005042170
10: 10 x74yDs2MdT 2017-02-02 1000000 1.681614e-03 0.0020562628
animals[observations[, .(.N, mean(x), mean(y)), by = animal_id]][date == as.Date("2017-07-02")]
animal_id treatment date N V2 V3
1: 1 MADxZ9c6fN 2017-07-02 1000000 -0.000521437 -0.001964315
2: 9 0GEOseSshh 2017-07-02 1000000 0.001175696 0.000504217
animals[observations][t < 5L & treatment %like% "MAD"]
animals[observations[t < 5L]][treatment %like% "MAD"]
# function definition
filter_dt <- function(ani_filter = "", obs_filter = "") {
eval(parse(text = stringr::str_interp(
'animals[observations[${obs_filter}]][${ani_filter}]')))
}
# called by user
filter_dt("treatment %like% 'MAD'", "t < 5L")
animal_id treatment date t x y
1: 1 MADxZ9c6fN 2017-07-02 1 -0.56047565 0.6958622
2: 1 MADxZ9c6fN 2017-07-02 2 -0.23017749 -0.5373377
3: 1 MADxZ9c6fN 2017-07-02 3 1.55870831 -3.0425688
4: 1 MADxZ9c6fN 2017-07-02 4 0.07050839 1.8488057
n <- 10000L
# integer vs numeric vs logical
test_obj_size <- data.table(
rep(1, n),
rep(1L, n),
rep(TRUE, n))
str(test_obj_size)
Classes ‘data.table’ and 'data.frame': 10000 obs. of 3 variables:
$ V1: num 1 1 1 1 1 1 1 1 1 1 ...
$ V2: int 1 1 1 1 1 1 1 1 1 1 ...
$ V3: logi TRUE TRUE TRUE TRUE TRUE TRUE ...
- attr(*, ".internal.selfref")=<externalptr>
sapply(test_obj_size, object.size)
V1 V2 V3
80040 40040 40040
# character vs factor
test_obj_size <- data.table(
rep("A", n),
rep("AAAAAAAAAAA", n),
rep_len(LETTERS, n),
factor(rep("A", n)),
factor(rep("AAAAAAAAAAA", n)),
factor(rep_len(LETTERS, n)))
str(test_obj_size)
Classes ‘data.table’ and 'data.frame': 10000 obs. of 6 variables:
$ V1: chr "A" "A" "A" "A" ...
$ V2: chr "AAAAAAAAAAA" "AAAAAAAAAAA" "AAAAAAAAAAA" "AAAAAAAAAAA" ...
$ V3: chr "A" "B" "C" "D" ...
$ V4: Factor w/ 1 level "A": 1 1 1 1 1 1 1 1 1 1 ...
$ V5: Factor w/ 1 level "AAAAAAAAAAA": 1 1 1 1 1 1 1 1 1 1 ...
$ V6: Factor w/ 26 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ...
- attr(*, ".internal.selfref")=<externalptr>
sapply(test_obj_size, object.size)
V1 V2 V3 V4 V5 V6
80088 80096 81288 40456 40464 41856
# Date & POSIXct vs factor
test_obj_size <- data.table(
rep(as.Date(Sys.time()), n),
rep(as.POSIXct(Sys.time()), n),
factor(rep(as.Date(Sys.time()), n)),
factor(rep(as.POSIXct(Sys.time()), n)))
str(test_obj_size)
Classes ‘data.table’ and 'data.frame': 10000 obs. of 4 variables:
$ V1: Date, format: "2017-08-02" "2017-08-02" "2017-08-02" "2017-08-02" ...
$ V2: POSIXct, format: "2017-08-02 18:25:55" "2017-08-02 18:25:55" "2017-08-02 18:25:55" "2017-08-02 18:25:55" ...
$ V3: Factor w/ 1 level "2017-08-02": 1 1 1 1 1 1 1 1 1 1 ...
$ V4: Factor w/ 1 level "2017-08-02 18:25:55": 1 1 1 1 1 1 1 1 1 1 ...
- attr(*, ".internal.selfref")=<externalptr>
sapply(test_obj_size, object.size)
V1 V2 V3 V4
80248 80304 40464 40480