我正在以一种广泛的格式处理不同个人的时间序列数据.不同个体的时间点数量不同.现在,问题是我需要每个人的最后一个元素.

我正在考虑在我的Tibble中使用列表作为列来存储时间序列. (将每个时间点放在不同的列中可能不是一个好主意,因为可能有数百个可能的时间点,但一个人可能只有几个时间点的数据,然而,每个人的数据总是针对连续的时间点进行测量的.)

我们称它为Column1,即:

library(tibble)
# Create an example dataframe
df <- tibble(
  column1 = list(1:3, 1:4, 4:8)
)

现在,为了效率和速度,我想使用矢量化,但对于给定的数据 struct ,这是否可能呢?Purrr包中有一个名为map()的函数,其操作如下:

library(purrr)

# Use the map function to select the last element of each vector
last_elements <- map(df$column1, ~ .x[length(.x)])

但这不是向量化,而是循环遍历列表的元素(存储为column1),对吗?

对于数据 struct ,会有比将列表作为列更好(即更快/更高效)的 Select 吗?或者,这通常是处理这种情况的最佳方式吗?

推荐答案

重要的是要理解,R中的vectorization意味着你不需要循环,B/C有人已经为你做了循环.重要的是,最终会有某种循环.

"经常"依赖现成的向量化比自己编写循环更快的原因是,前一种情况下的循环是在C级上完成的,can级更快,但有时差异甚至不明显:

library(microbenchmark)
x <- seq_len(10000000L)
 
microbenchmark(
  vec = sum(x),
  lp = {sm <- 0; for(i in seq_along(x)) sm <- sm + x[i]; sm}
)
# Unit: nanoseconds
#  expr       min        lq      mean    median        uq       max neval cld
#   vec       100       200      2745       350      4450     19300   100  a 
#    lp 327515400 340921350 367242236 351582100 383396050 603997400   100   b

看起来很多,但要注意单位,所以基本上循环结束在0.32sec,而R循环需要0.0000001sec.从相对值来看,这是一个巨大的进步,但从绝对值来看,你几乎看不出有什么不同.

底线是,你不应该害怕R级循环,b/c它们比它们的声誉要好得多.如果循环有自然的替代品,那么一定要try ,但如果避免它们的代价是数据 struct 过于臃肿,请毫不犹豫地使用它们.

现在来谈谈你手头的问题.

could将数据转换为长格式,并逐个获取最后一行.您可以更进一步,使用带有适当密钥的data.table:

library(microbenchmark)
library(tibble)
library(tidyr)
library(dplyr)
library(data.table)

create_data <- function(nr_indiv, max_per_indiv) {
  l <- replicate(nr_indiv, sample(10000L, sample(max_per_indiv, 1L)))
  tibble(id = seq_len(nr_indiv), load = l)
}

set.seed(20022024)
list_data <- create_data(10000, 1000)
lng_data <- unnest(list_data, cols = load)
dt_data <- copy(lng_data)
setDT(dt_data)
setkey(dt_data, "id")

my_check <- function(x) {
  isTRUE(all.equal(x$lst, x$lng %>% pull(load), check.attributes = FALSE)) &&
  isTRUE(all.equal(x$lst, x$dt[, load], check.attributes = FALSE)) &&
  isTRUE(all.equal(x$lst, c(x$ta), check.attributes = FALSE)) &&
  isTRUE(all.equal(x$lst, x$base, check.attributes = FALSE)) &&
  isTRUE(all.equal(x$lst, x$flt %>% pull(load), check.attributes = FALSE))
}

microbenchmark(
  lst = map_int(list_data$load, tail, n = 1L),
  lng = slice_tail(lng_data, n = 1L, by = id), 
  dt = dt_data[, .(load = tail(load, 1L)), "id"], 
  ta = tapply(lng_data$load, lng_data$id, tail, n = 1L),
  base = vapply(list_data$load, tail, integer(1L), n = 1L),
  flt = lng_data %>% filter(ord == 1L),
  check = my_check
)

# Unit: milliseconds
#  expr      min        lq       mean     median         uq       max neval   cld
#   lst  70.5403  77.40405   93.87981   87.87985  101.53635  280.5060   100 a    
#   lng 916.2937 971.14420 1053.53934 1004.19125 1133.12665 1434.2933   100  b   
#    dt  18.4768  19.47870   28.12581   20.64565   37.49055   64.8045   100   c  
#    ta 315.0710 357.06000  407.04557  368.03110  453.28390  687.4518   100    d 
#  base  65.5528  75.40010   85.31279   85.87180   94.14265  116.5428   100 a    
#   flt  44.4467  47.46310   55.71809   50.85525   57.62820  244.5270   100     e

tidyverse长格式解决方案比purrr循环差,而data.table长格式解决方案更好(不包括创建数据 struct 的开销),Filter方法介于两者之间,而基循环与purrr相当,因此即使purrr的轻微开销也可以忽略不计.

将会有更多的解决方案,但从第一次快速测试来看,在我看来,list解决方案总体上并不那么糟糕,在我看来,同样(如果不是更重要的话),相当容易阅读和理解.

不要害怕循环,如果没有直接的自然向量化方法,一定要使用它们.

R相关问答推荐

查找满足SpatRaster中条件的单元格位置

从开始时间和结束时间导出时间

在R中创建一个包含转换和转换之间的时间的列

如何使用按钮切换轨迹?

如何通过Docker部署我的shiny 应用程序(多个文件)

如何直接从R中的风险分数计算c指数?

将数字转换为分钟和秒

R中插入符号训练函数的中心因子和尺度因子预测

将饼图插入条形图

使用带有OR条件的grepl过滤字符串

仅 Select 超过9行的CSV文件

解析R函数中的变量时出现的问题

在带有`R`中的`ggmosaic`的马赛克图中使用图案而不是 colored颜色

如何使用同比折线图中的个别日

如何使用字符串从重复的模式中提取多个数字?

随机 Select 的非NA列的行均数

在使用SliderInput In Shiny(R)设置输入数据的子集时,保留一些情节痕迹

将数据从一列转换为按组累计计数的单个虚拟变量

有没有办法将勾选/审查标记添加到R中的累积关联图中?

将y轴格式更改为R中的百分比