如何通过替换" for-loop"来提高大型数据集的性能。和" if-else"条款

时间:2015-01-19 13:14:52

标签: r performance if-statement for-loop apply

亲爱的stackoverflow社区,

我在一个特定的数据集上安静了一段时间,这个数据集相当庞大(nrow =约5亿)。经过一系列数据操作后,基本上数据集包括以下重要列:“ParticleId”,“flag”,“Volume”和“reduction”。

  • ParticleId是独一无二的,通过时间和空间表达移动的粒子。
  • 该标志表示粒子是否在特定区域内(是/否)
  • 每个ParticleId都有一个先前的体积(注入时),如果粒子在此特定区域内或之外,则该时间取决于时间
  • 如果粒子位于特定区域内,则前一卷必须减少相应的缩减值

我写了一个带有2个if-else子句的for循环来减少每行的音量。该循环经过测试并完美地用于测试目的,最多可达20k行的子集。不幸的是,当应用于孔数据集(500微米行)时,性能呈指数下降。我尝试应用几种矢量化方法,但似乎我遗漏了一些东西。我非常感谢您对这个特定问题进行矢量化的帮助和想法。

请在下面找到for循环和测试数据集:

dataset <- data.frame(1:20)
dataset$ParticleId        <- c(1,1,1,1,2,2,2,2,2,3,3,4,4,4,4,4,4,4,4,4)
dataset$flag      <- c(T,T,T,F,T,T,F,F,T,T,T,T,T,T,F,F,F,F,T,T)
dataset$Volume    <- 0.01
dataset$reduction <- c(1.21e-03,1.21e-04,1.21e-03,1.21e-06,1.21e-03,1.21e-03,1.21e-04,1.21e-03,1.21e-06,1.21e-03,1.21e-03,1.21e-04,1.21e-03,1.21e-06,1.21e-03,1.21e-03,1.21e-04,1.21e-03,1.21e-06,1.21e-03)

for(i in 2:nrow(dataset)){
  if(dataset[i,]$flag == TRUE & dataset[i,]$ParticleId == dataset[i-1,]$ParticleId){
    dataset[i,]$Volume <- dataset[i-1,]$Volume - dataset[i-1,]$reduction
  }else{
    if(dataset[i,]$flag == FALSE & dataset[i,]$ParticleId == dataset[i-1,]$ParticleId){
      dataset[i,]$Volume <- dataset[i-1,]$Volume
    }else{
      dataset[i,]$Volume <- dataset[i,]$Volume
    }
   }
 }

如果需要,我可以提供更大的原始数据子集。测试数据集的创建只提供了数据的外观......

1 个答案:

答案 0 :(得分:3)

这会产生您想要的输出,并且应该比使用for - 循环和if .. else ..语句的初始方法快一点:

library(dplyr)
dataset %>% 
  group_by(ParticleId) %>% 
  mutate(Volume = Volume[1L] - cumsum(lag(reduction, default = 0L)*flag))

#Source: local data frame [20 x 5]
#Groups: ParticleId
#
#   X1.20 ParticleId  flag     Volume reduction
#1      1          1  TRUE 0.01000000  1.21e-03
#2      2          1  TRUE 0.00879000  1.21e-04
#3      3          1  TRUE 0.00866900  1.21e-03
#4      4          1 FALSE 0.00866900  1.21e-06
#5      5          2  TRUE 0.01000000  1.21e-03
#6      6          2  TRUE 0.00879000  1.21e-03
#7      7          2 FALSE 0.00879000  1.21e-04
#8      8          2 FALSE 0.00879000  1.21e-03
#9      9          2  TRUE 0.00758000  1.21e-06
#10    10          3  TRUE 0.01000000  1.21e-03
#11    11          3  TRUE 0.00879000  1.21e-03
#12    12          4  TRUE 0.01000000  1.21e-04
#13    13          4  TRUE 0.00987900  1.21e-03
#14    14          4  TRUE 0.00866900  1.21e-06
#15    15          4 FALSE 0.00866900  1.21e-03
#16    16          4 FALSE 0.00866900  1.21e-03
#17    17          4 FALSE 0.00866900  1.21e-04
#18    18          4 FALSE 0.00866900  1.21e-03
#19    19          4  TRUE 0.00745900  1.21e-06
#20    20          4  TRUE 0.00745779  1.21e-03

这是做什么的:

  • 获取数据“dataset”
  • 通过ParticleId对数据进行分组(然后对每个组执行以下操作)
  • mutate用于修改/添加数据列。在这种情况下,我们修改现有列“Volume”。我们在每个组中使用Volume的第一个元素(Volume[1L]),并从该值中减去reduction*flag的累积和。因为我们将reductionflag相乘,这是一个逻辑列,每当flagTRUE时,减少量乘以1,并且只要flag,它就会乘以0是FALSE。这意味着,如果flagFALSE,我们会从“卷”列中减去0(无),即它保持不变。另外,我们使用lag(Volume, default = 0)因为我们想要在每一行中减去前一个(滞后)行中存在的reduction - 值。 default = 0确保,如果组中没有前一行,即我们在组的第一行上操作,则先前的减少值假定为0 - 因此,我们不会减去任何内容从第一行卷值。
  • 如果你想知道我为什么在数字之后使用L(比如在default = 0L中):那用来表示integer - 使用更少内存的值,因此可以帮助加快代码的速度因为你正在处理大量的数据。

我尝试使用data.table中的相同代码(可能会更快一些):

library(data.table)
setkey(setDT(dataset), ParticleId)[,
      Volume:=Volume[1L]-cumsum(c(0L, head(reduction, -1L))*flag), ParticleId]

我认为在最新版本的data.table(1.9.5)中,您可以使用shift来创建滞后缩减。

这种方法与dplyr解决方案基本相同。但在开始之前,我们使用setDT()将data.frame转换为data.table对象,并使用setkey()设置密钥。其余的非常相似,除了data.table通过引用更新数据(使用:=时)而不是lag(..., default = 0)我们使用c(0, head(reduction, -1))