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)
使用玩具示例,其中I
rbind
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

这里有两种可能性(使用一种、两种或无):

  • 确保所有列类型都是内存效率最高的。如果将整数存储为数字,则会占用大量内存
  • 因为你不希望你的用户必须自己做连接,所以写一个简短的函数,根据他们想要的动物为他们做连接。只需将动物信息放在一个data.table中,将治疗信息放在另一个data.table中,然后在函数中进行合并
  • 首先,我将把这些表分开:

    # 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