簡體   English   中英

如何加快計算取決於上一行中的值的值?

[英]How to speed up calculation of a value that depends on the value in the previous row?

我試圖加快這個計算速度,至少擺脫內部循環,因為它在很多 id 上真的很慢。 對於每個 ID,我都有一個“x”值向量,並試圖計算“y”值,這些值取決於“x”的當前值和“y”的先前值。 換句話說,對於每個 ID y[1] = x[1] 和后續行,y[i]=y[i-1]*x[i]。

誰能幫我加快速度? 我試圖找到解決方案,但無法弄清楚。

這是一些可重現的代碼,可以滿足我的要求,但速度很慢:

#Calculate y[i]=y[i-1]*x[i]

#Make some dummy data
  dat<-data.frame(id=c(rep(1,10), rep(2,10)), 
                  x=c(0.95,1,1,1,0.98,0.96,0.93,1,0.94,0.9, 
                  0.97,1,1,0.94,0.93,1,1,0.97,0.99,0.95),
                  y=rep(0,20))
                


#Calculate y[i]=x[i]*y[i-1]
    idx<-1
    #Loop over individuals 
    ids<-unique(dat$id)
      for (j in 1:length(ids)){
        #Extract data for individual i
        temp<-dat[dat$id==ids[j],]
        #Initialize y[1]=x[1]
        temp$y[1]<-temp$x[1]
        #Calculate y[i]=x[i]*y[i-1] for remaining rows starting with row 2
        for (i in 2:nrow(temp)){
          temp$y[i]<-temp$x[i]*temp$y[i-1]
        }  
        #Store results in "dat"
        dat$y[idx:(nrow(temp)+idx-1)]<-temp$y
        idx<-nrow(temp)+1
        rm(temp)
      }
  

這是一個選項,您可以更廣泛地旋轉數據,然后可以一次對所有 ID 進行計算,因此您只需遍歷每個 ID 中的觀察數,而不是每個 ID。

xmat <- dat %>% 
  group_by(id) %>% 
  mutate(obs=seq_along(id)) %>% 
  select(-y) %>% 
  pivot_wider(names_from="id", values_from="x", names_sep="_") %>% 
  as.matrix() %>%
  .[,-1]
ymat <- xmat[1, , drop=FALSE]
for(i in 2:nrow(xmat)){
  ymat <- rbind(ymat, xmat[i,]*ymat[(i-1), ])
}

colnames(ymat) <- paste0("y_", colnames(ymat))
colnames(xmat) <- paste0("x_", colnames(xmat))
mats <- as.data.frame(cbind(xmat, ymat))
mats %>% pivot_longer(everything(), names_pattern="(.*)_(.*)", names_to=c(".value", "id"))

這是否以及加快速度取決於您擁有多少數據。 這里有一些基准。 在下面的代碼中,標記為循環的一個是您的原始代碼,而標記為樞軸的一個是我的代碼。 對於具有 20 行的原始數據,旋轉速度要慢得多:

microbenchmark(pivot=f1(), loops=f2(), times = 250)
#Unit: microseconds
#  expr      min       lq     mean   median       uq       max neval cld
# pivot 8861.625 9032.000 9734.104 9164.167 9768.792 25119.918   250   b
# loops  223.293  235.334  258.029  261.334  271.876   398.293   250  a 

但是,隨着您獲得更多數據,它相對於循環變得更快。 這里有 100 個 ID,每個 ID 有 20 個觀察值:

microbenchmark(pivot=f1(), loops=f2(), times = 25)
# Unit: milliseconds
#  expr      min       lq     mean   median       uq      max neval cld
# pivot 11.13217 12.22788 14.35621 12.58250 13.08083 35.59029    25  a 
# loops 17.23871 18.42488 18.60614 18.54304 19.08754 20.24063    25   b

在這里,沒有太大的收獲,但平均而言,旋轉方式略快一些。 使用 1000 個 ID 和每個 ID 20 個 obs,旋轉方式要快得多:

microbenchmark(pivot=f1(), loops=f2(), times = 25)
#Unit: milliseconds
#  expr       min        lq      mean    median        uq       max neval cld
# pivot  30.25879  31.13842  32.52211  31.31271  31.97708  46.16475    25  a 
# loops 270.93421 292.02204 310.56463 293.39604 306.38221 605.06092    25   b

對於 1000 個 ID,旋轉方式比循環快大約 10 倍。 所有這一切都是說,如果你有大量數據,那么旋轉方式可能是有意義的。

library(tidyverse)

dat <- data.frame(id=c(rep(1,10), rep(2,10)), 
                  x=c(0.95,1,1,1,0.98,0.96,0.93,1,0.94,0.9, 
                      0.97,1,1,0.94,0.93,1,1,0.97,0.99,0.95),
                  y=rep(0,20)) %>% 
  as_tibble()

dat %>%
  group_by(id) %>% 
  mutate(y = case_when(row_number() == 1 ~ x, 
                       TRUE ~ cumprod(y + x)))
#> # A tibble: 20 × 3
#> # Groups:   id [2]
#>       id     x     y
#>    <dbl> <dbl> <dbl>
#>  1     1  0.95 0.95 
#>  2     1  1    0.95 
#>  3     1  1    0.95 
#>  4     1  1    0.95 
#>  5     1  0.98 0.931
#>  6     1  0.96 0.894
#>  7     1  0.93 0.831
#>  8     1  1    0.831
#>  9     1  0.94 0.781
#> 10     1  0.9  0.703
#> 11     2  0.97 0.97 
#> 12     2  1    0.97 
#> 13     2  1    0.97 
#> 14     2  0.94 0.912
#> 15     2  0.93 0.848
#> 16     2  1    0.848
#> 17     2  1    0.848
#> 18     2  0.97 0.823
#> 19     2  0.99 0.814
#> 20     2  0.95 0.774

reprex 包於 2022-06-29 創建 (v2.0.1)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM