如何在 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 创建
我必须生成一个自引用变量 (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)))
更新二
我问了一个
- 对于这种情况,我们使用复数结构 (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 创建