如何在 R 中生成自引用变量(例如,给定的索引级别 returns)?

How to produce a self-referencing variable in R (e.g., index levels given returns)?

我必须生成一个自引用变量 (ind),它按 id 分组并且必须满足特定条件(例如,时间 >1)。这是一个玩具示例:

set.seed(13)
dt <- data.frame(id = rep(letters[1:2], each = 4), time = rep(1:4, 2), ret = rnorm(8)/100)
dt$ind <- if_else(dt$time == 1, 100, as.numeric(NA))
dt

dt <- dt %>%
  group_by(id) %>%
  mutate(
    ind = if_else(time > 1, lag(ind, 1)*(1+ret), ind)
  )

这是输出:

显然我不能在此设置中使用 mutate,因为它引用 ind 的 initial 值并且在新时不更新计算值。

我想避免 运行 循环。有什么想法可以最有效地计算所有时间段的 ind 吗?


编辑:

感谢大家的热心解答!我对上述问题有一个稍微复杂的扩展。

如何处理更高的延迟?例如,滞后 = 2,这样

index_{t} = index_{t-2}*(1+ret_{t})

这是我使用 Excel:

生成的示例数据框和示例结果
set.seed(13)
dt <- data.frame(id = rep(letters[1:2], each = 5), time = rep(1:5, 2), ret = rnorm(10)/100)
dt$ind <- if_else(dt$time == 1, 120, if_else(dt$time == 2, 125, as.numeric(NA)))

更新二 我问了一个 as your new requirements made for an interesting case and I thought it would be a great opportunity to grapple with the issue to learn new stuff. Hopefully Mr. Grothendieck 教我们一个巧妙的解决方法。让我先告诉你:

  • 对于这种情况,我们使用复数结构 (a + bi)。您可能已经知道 a 是实部,b 是虚部,而 i 是不确定部分。所以我们以一种方式重组我们的 ind 输出 ind 的前一个值是实部 a,倒数第二个值是虚部 b 例如,我们的第一个值可以重组为 120 + 0i,第二个作为 125 + 120i
  • 我们这样做是因为我们需要在上一次迭代中保留这两个值,以便我们可以提取我们需要的那个。我们使用Re函数提取实部,Im提取虚部
  • 关于ret变量,我们只需要省略前两行并使用与每次迭代对应的其余行
  • 最后,我们编写自定义函数的方式是从前面的 ind 中提取虚部,这实际上是我们之前的两个值,并将其分配给 (1 + 当前值 ret) 还有一个微妙的地方:为了在下一次迭代中保持相同的结构,我们还添加了上一次迭代的实部作为当前值的虚部(实际上是下一次迭代的实部)&最后我们只提取实部

我知道这里可能有太多内容要讲,但如果有什么我可以解释的,请告诉我,谢谢你提出这个很好的问题。

library(dplyr)
library(purrr)

dt %>%
  group_by(id) %>%
  mutate(ind = c(ind[1], 
                 Re(unlist(accumulate(ret[3:n()], .init = ind[2] + ind[1] * 1i,
                                      ~ Im(..1) * (1 + ..2) + Re(..1) * 1i)))))

# A tibble: 10 x 4
# Groups:   id [2]
   id     time      ret   ind
   <chr> <int>    <dbl> <dbl>
 1 a         1  0.00554  120 
 2 a         2 -0.00280  125 
 3 a         3  0.0178   122.
 4 a         4  0.00187  125.
 5 a         5  0.0114   124.
 6 b         1  0.00416  120 
 7 b         2  0.0123   125 
 8 b         3  0.00237  120.
 9 b         4 -0.00365  125.
10 b         5  0.0111   122.

根据@AnilGoyal 的绝妙想法更新一个

library(dplyr)
library(purrr)

dt %>%
  group_by(id) %>%
  group_by(d = seq(n()) %% 2, .add = TRUE) %>%
  mutate(ind = accumulate(ret[-1], .init = ind[1], ~ (..2 + 1) * ..1)) %>%
  select(-d)

# A tibble: 10 x 5
# Groups:   id, d [4]
       d id     time      ret   ind
   <dbl> <chr> <int>    <dbl> <dbl>
 1     1 a         1  0.00554  120 
 2     0 a         2 -0.00280  125 
 3     1 a         3  0.0178   122.
 4     0 a         4  0.00187  125.
 5     1 a         5  0.0114   124.
 6     0 b         1  0.00416  120 
 7     1 b         2  0.0123   125 
 8     0 b         3  0.00237  120.
 9     1 b         4 -0.00365  125.
10     0 b         5  0.0111   122.

或者在 base R 中我们可以这样做:

do.call(rbind, lapply(split(dt, dt$id), function(x) {
  x$ind <- c(x$ind[1], Re(Reduce(function(a, b) Im(a) * (1 + b) + Re(a) * 1i,
                                 init = x$ind[2] + x$ind[1] * 1i, 
                                 x$ret[3:nrow(x)], accumulate = TRUE)))
  x
}))

     id time          ret      ind
a.1   a    1  0.005543269 120.0000
a.2   a    2 -0.002802719 125.0000
a.3   a    3  0.017751634 122.1302
a.4   a    4  0.001873201 125.2342
a.5   a    5  0.011425261 123.5256
b.6   b    1  0.004155261 120.0000
b.7   b    2  0.012295066 125.0000
b.8   b    3  0.002366797 120.2840
b.9   b    4 -0.003653828 124.5433
b.10  b    5  0.011051443 121.6133

Anoushiravan 的建议解决了我的问题。这是满足我所有要求的最终代码:(i) 按 id 分组,(ii) 按时间条件(此处,时间>=2),(iii) 起始值不是 100(此处,ind = 150):

library(dplyr)
library(purrr)


set.seed(13)
dt <- data.frame(id = rep(letters[1:2], each = 4), time = rep(1:4, 2), ret = rnorm(8)/100)
dt$ind <- if_else(dt$time == 2, 150, as.numeric(NA))
dt

dt_tmp <- dt %>%
  group_by(id) %>%
  filter(time>=2) %>%
  mutate(
    ind =  accumulate(ret[-1], .init = ind[1], ~ (..2 + 1) * ..1)
         )

dt_tmp <- dt_tmp %>% select(id, time, ind)

dt <- dt %>% left_join(dt_tmp, by = c("id", "time"))
dt <- rename(dt, ind_orig = ind.x)
dt <- rename(dt, ind = ind.y)
rm(dt_tmp) 

最终输出:


编辑:

最后,我使用循环解决了延迟较高的问题(我最初想避免):

my_projection <- function(index, ret, lag) {
  if (length(index) != length(ret)) {
    print("error: length of vectors does not match")
    break;
  }
  if (lag < 0) {
    print("error: lag < 0")
    break;
  }
  else {
    for(i in 1:length(index)){
      if (i<=lag){
        print(index[i])
      }
      else {
        print(index[i-lag]*(1+ret[i]))
        index[i] = index[i-lag]*(1+ret[i])
    }
  }
  }
  return(index)
}


dt <- dt %>% group_by(id) %>%
  mutate(ind = my_projection(ind, ret, 2))

输出:

作为变通方法,您可以在经过编辑的情况下使用以下技巧。 请注意,您可以针对任意数量的同步系列更改此设置

  • 我刚刚使用 seq(n()) %% 2
  • 基于所需变量数的模序列添加了一个额外的 group_by 语句
set.seed(13)
dt <- data.frame(id = rep(letters[1:2], each = 5), time = rep(1:5, 2), ret = rnorm(10)/100)
dt$ind <- ifelse(dt$time == 1, 120, ifelse(dt$time == 2, 125, as.numeric(NA)))
library(dplyr, warn.conflicts = F)

dt %>% group_by(id) %>%
  group_by(d = seq(n()) %% 2, .add = TRUE) %>%
  mutate(ind = cumprod(1 + duplicated(id) * ret)* ind[1])
#> # A tibble: 10 x 5
#> # Groups:   id, d [4]
#>    id     time      ret   ind     d
#>    <chr> <int>    <dbl> <dbl> <dbl>
#>  1 a         1  0.00554  120      1
#>  2 a         2 -0.00280  125      0
#>  3 a         3  0.0178   122.     1
#>  4 a         4  0.00187  125.     0
#>  5 a         5  0.0114   124.     1
#>  6 b         1  0.00416  120      0
#>  7 b         2  0.0123   125      1
#>  8 b         3  0.00237  120.     0
#>  9 b         4 -0.00365  125.     1
#> 10 b         5  0.0111   122.     0

旧答案:不使用 purrr

library(tidyverse)

set.seed(13)
dt <- data.frame(id = rep(letters[1:2], each = 4), time = rep(1:4, 2), ret = rnorm(8)/100)
dt$ind <- if_else(dt$time == 1, 100, as.numeric(NA))
dt
#>   id time          ret ind
#> 1  a    1  0.005543269 100
#> 2  a    2 -0.002802719  NA
#> 3  a    3  0.017751634  NA
#> 4  a    4  0.001873201  NA
#> 5  b    1  0.011425261 100
#> 6  b    2  0.004155261  NA
#> 7  b    3  0.012295066  NA
#> 8  b    4  0.002366797  NA

dt %>% group_by(id) %>%
  mutate(ind = cumprod(1 + duplicated(id) * ret)* ind[1])
#> # A tibble: 8 x 4
#> # Groups:   id [2]
#>   id     time      ret   ind
#>   <chr> <int>    <dbl> <dbl>
#> 1 a         1  0.00554 100  
#> 2 a         2 -0.00280  99.7
#> 3 a         3  0.0178  101. 
#> 4 a         4  0.00187 102. 
#> 5 b         1  0.0114  100  
#> 6 b         2  0.00416 100. 
#> 7 b         3  0.0123  102. 
#> 8 b         4  0.00237 102.

reprex package (v2.0.0)

于 2021-07-27 创建