【问题标题】:Referencing to the previous value in the same variable, no loop, in R在 R 中引用同一变量中的前一个值,无循环
【发布时间】:2020-10-09 03:49:34
【问题描述】:

感谢您查看我的问题

我正在尝试创建一个新变量,如果满足某些条件,它将从另一个变量中获取值;否则,取前一个观测值。

我可以通过运行这样的循环来做到这一点:

data <- mtcars
data$test <- NA
data$test <- as.numeric(data$test)

a0 <- Sys.time()
for (i in 2:nrow(data)) {

  ifelse(data$carb[[i]] < 4,
         data$test[[i]] <- data[[i-1,'test']],
         data$test[[i]] <- data[[i,'mpg']]
  )
  a1 <- Sys.time()
  per_left <- (i)/nrow(data)
  print(paste("Time left is", round(as.numeric(as.difftime(a1-a0, units = "mins"))/per_left,2),"mins"))  
  
} 

但是,我的数据集超过 800 万个观测值。我觉得这不是节省时间的最佳方式。

***对于函数滞后: 不知何故,如果我使用滞后,滞后功能将使用以前记录的数据,而不是更新的数据。

例如。

df1 <- data.frame(ID = c(1, 1, 1, 1, 4, 5),
                  condition = c(FALSE,TRUE,TRUE,TRUE, TRUE, FALSE),
                  var1 = c('a', 'b', 'c', 'd', 'f', 'e'))


df2 <- df1 %>% 
  mutate(
    new_2 = '0',
    new_2 = case_when(
    ID == lag(ID) & condition == TRUE ~ lag(new_2),
    TRUE ~ var1
  ))
> df2
  ID condition var1 new_2
1  1     FALSE    a     a
2  1      TRUE    b     0
3  1      TRUE    c     0
4  1      TRUE    d     0
5  4      TRUE    f     f
6  5     FALSE    e     e

应该是

  ID condition var1 new_2
1  1     FALSE    a     a
2  1      TRUE    b     a
3  1      TRUE    c     a
4  1      TRUE    d     a
5  4      TRUE    f     f
6  5     FALSE    e     e

我运行上面的函数,第 2 行应该采用之前的值 - a,而不是默认值 0。而如果我使用 for 循环,它将采用“a”。

有没有功能呢?或者我应该如何更新我的函数以使其更快?

请指教。 谢谢!

【问题讨论】:

  • 尽管它们提供相同的输出,但您选择使用 [[ 而不仅仅是 [ 这里的任何理由:“[[i-1,'test']]”
  • 你能显示预期的输出吗
  • 你需要df1 %&gt;% mutate(new_2 = replace(var1, condition, lag(var1[condition])))
  • 很难知道您的预期输出是什么。可能是df1 %&gt;% group_by(ID) %&gt;% mutate(new_2 = case_when(condition ~ lag(var1), TRUE ~ '0'))
  • 很抱歉给您带来了困惑。让我试试你的建议。非常感谢你。我真的很感激。

标签: r


【解决方案1】:

我们可以使用lag 并且ifelse 已经被矢量化了。因此,可以使用ifelsecase_when。但是,当有多个条件时,case_when 会更通用

library(dplyr)
out <- data %>% 
    mutate(new = case_when(carb < 4 ~  lag(test), TRUE ~ mpg)) 

为了加快速度,另一个选项是 shift from data.table

library(data.table)
setDT(data)[, new := fifelse(carb < 4, shift(test), mpg)]

对于第二个数据集,也许

library(dplyr)
df1 %>%
      mutate(new_2 = replace(var1, condition, lag(var1[condition])))

-输出

#  ID condition var1 new_2
#1  1     FALSE    a     a
#2  1      TRUE    b  <NA>
#3  1      TRUE    c     b
#4  1      TRUE    d     c
#5  4      TRUE    f     d
#6  5     FALSE    e     e

也可以

df1 %>%
     group_by(ID) %>%
     mutate(new_2 = case_when(condition ~ lag(var1), TRUE ~ '0'))
# A tibble: 6 x 4
# Groups:   ID [3]
#     ID condition var1  new_2
#  <dbl> <lgl>     <chr> <chr>
#1     1 FALSE     a     0    
#2     1 TRUE      b     a    
#3     1 TRUE      c     b    
#4     1 TRUE      d     c    
#5     4 TRUE      f     <NA> 

或使用data.table

setDT(df1)[condition, new_2 := shift(var1)]

更新

基于更新后的预期输出

df1 %>% 
    group_by(ID) %>%
    mutate(new_2 = lag(var1)) %>% 
    group_by(grp = rleid(condition), .add = TRUE) %>% 
    mutate(new_2 = coalesce(first(new_2), var1)) %>% 
    ungroup %>%
    dplyr::select(-grp)
# A tibble: 6 x 4
#     ID condition var1  new_2
#  <dbl> <lgl>     <chr> <chr>
#1     1 FALSE     a     a    
#2     1 TRUE      b     a    
#3     1 TRUE      c     a    
#4     1 TRUE      d     a    
#5     4 TRUE      f     f    
#6     5 FALSE     e     e    

【讨论】:

  • 您好,感谢您的建议。我在使用滞后时遇到了一个错误,现在将其添加到我的问题中。可以帮忙看看吗?
  • 我的错,让我澄清一下上面想要的输出。但我真的很感谢你的帮助!
  • 您好,谢谢。我运行了另一个测试,但它不起作用。规则是“如果ID与上面的ID相似AND条件为TRUE,则取前一个值;否则取一个新值” df1
【解决方案2】:

您可以在基础 R 中通过获取条件 (data$carb &lt; 4) 的位置来执行此操作,并通过将这些索引值减去 -1 来获取要替换的索引。

data <- mtcars
data$test <- mtcars$mpg
inds <- which(data$carb < 4)
data$test[inds] <- data$test[inds - 1]

许多 R 函数都是矢量化的,因此您不需要显式的 for 循环。

【讨论】:

  • 如果mtcars$mpg[1] &lt; 4,我不确定这是否可行。
【解决方案3】:

base R 中的一个选项是将ifelse 替换为if() ... else。但是,使用基本 R 的更快的解决方案是使用 ifelseReduce 的组合。这比 OP 的解决方案快 3.46 / .044 ~ 78 倍。解决办法是:

v2 <- mtcars
v2$test <- ifelse(v2$carb < 4, NA_real_, v2$mpg)
v2$test <- Reduce(
  function(xprev, xnew)
    if(is.na(xnew)) xprev else xnew, 
  v2$test, accumulate = TRUE, init = v2$mpg[1])[-1]

这是与一些替代方案的比较:

# works even if the first entry does not comply with the condition
mtcars$carb[1] <- 1

# essentially the OPs solution
v0 <- mtcars
v0$test <- v0$mpg
for (i in 2:nrow(v0))
  ifelse(v0$carb[[i]] < 4,
         v0$test[[i]] <- v0[[i-1,'test']],
         v0$test[[i]] <- v0[[i,'mpg']])

# using if ... else instead of ifelse
v1 <- mtcars
v1$test <- v1$mpg
for (i in 2:nrow(v1)) 
  v1$test[i] <- if(v1$carb[i] < 4) v1$test[i - 1] else v1$test[i]

# we get the same
all.equal(v0, v1)
#R> [1] TRUE

# using ifelse + Reduce
v2 <- mtcars
v2$test <- ifelse(v2$carb < 4, NA_real_, v2$mpg)
v2$test <- Reduce(
  function(xprev, xnew)
    if(is.na(xnew)) xprev else xnew, 
  v2$test, accumulate = TRUE, init = v2$mpg[1])[-1]

# we get the same
all.equal(v0, v2)
#R> [1] TRUE

# compare the computation time
bench::mark(
  `ifelse` = {
    v0 <- mtcars
    v0$test <- v0$mpg
    for (i in 2:nrow(v0))
      ifelse(v0$carb[[i]] < 4,
             v0$test[[i]] <- v0[[i-1,'test']],
             v0$test[[i]] <- v0[[i,'mpg']])
  }, 
  `if ... else` = {
    v1 <- mtcars
    v1$test <- v1$mpg
    for (i in 2:nrow(v1)) 
      v1$test[i] <- if(v1$carb[i] < 4) v1$test[i - 1] else v1$test[i]
  }, 
  `ifelse + reduce` = {
    v2 <- mtcars
    v2$test <- ifelse(v2$carb < 4, NA_real_, v2$mpg)
    v2$test <- Reduce(
      function(xprev, xnew)
        if(is.na(xnew)) xprev else xnew, 
      v2$test, accumulate = TRUE, init = v2$mpg[1])[-1]
  }, min_time = 1, check = FALSE)
#R> # A tibble: 3 x 13
#R>   expression           min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time 
#R>   <bch:expr>      <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> 
#R> 1 ifelse            3.27ms   3.46ms      285.   50.05KB     16.8   254    15      891ms 
#R> 2 if ... else       2.72ms    2.9ms      341.    48.7KB     17.9   304    16      892ms 
#R> 3 ifelse + reduce  40.98µs  44.92µs    21803.    9.79KB     19.6  9991     9      458ms

但是,我的数据集超过 800 万个观测值。我觉得这不是节省时间的最佳方式。

ifelseReduce 解决方案在我的计算机上运行大约 9 秒,有 800 万行,我想如果只执行一次,这是可以管理的:

# simulate a large data set
set.seed(1)
n <- 8e6
dum_dat <- data.frame(var_1 = runif(n, 0, 8), var_2 = rnorm(n))
system.time({
  dum_dat$test <- ifelse(dum_dat$var_1 < 4, NA_real_, dum_dat$var_2)
  func <- compiler::cmpfun(
    function(xprev, xnew)
      if(is.na(xnew)) xprev else xnew)
  dum_dat$test <- Reduce(
    func, dum_dat$test, accumulate = TRUE, init = dum_dat$var_2[1])[-1]
})
#R>  user  system elapsed 
#R> 8.816   0.064   8.882

【讨论】:

  • 哇,这真的很好用。让我很难理解 Reduce() 但据我了解,它看起来很棒。非常感谢
  • 我很高兴它对你有用。 ?Reduce 可能会解释细节。
【解决方案4】:

我们可以将 lag() 函数添加到您的 ifelse 条件中:

data$test = ifelse(data$carb < 4, data$test <- lag(data$test), data$test <-  data$mpg)
> data
                     mpg cyl  disp  hp drat    wt  qsec vs am gear carb test
Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4 21.0
Mazda RX4 Wag       21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4 21.0
Datsun 710          22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1 21.0
Hornet 4 Drive      21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1 21.0
Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2 22.8
Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1 21.4
Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4 14.3
Merc 240D           24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2 14.3
Merc 230            22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2 14.3
Merc 280            19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4 19.2
Merc 280C           17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4 17.8
Merc 450SE          16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3 17.8
Merc 450SL          17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3 17.8
Merc 450SLC         15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3 16.4
Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4 10.4
Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4 10.4
Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4 14.7
Fiat 128            32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1 14.7
Honda Civic         30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2 14.7
Toyota Corolla      33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1 32.4
Toyota Corona       21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1 30.4
Dodge Challenger    15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2 33.9
AMC Javelin         15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2 21.5
Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4 13.3
Pontiac Firebird    19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2 13.3
Fiat X1-9           27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1 13.3
Porsche 914-2       26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2 19.2
Lotus Europa        30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2 27.3
Ford Pantera L      15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4 15.8
Ferrari Dino        19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6 19.7
Maserati Bora       15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8 15.0
Volvo 142E          21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2 15.0
> 

【讨论】:

  • 您好,感谢您的建议。我在使用滞后时遇到了一个错误,现在将其添加到我的问题中。可以帮忙看看吗?
猜你喜欢
  • 2021-12-06
  • 1970-01-01
  • 2019-05-07
  • 2020-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-25
  • 1970-01-01
相关资源
最近更新 更多