想知道在这种情况下是否可以在此处使用APPLY而不是使用FOR LOOP进行优化?

时间:2019-07-17 03:25:05

标签: r algorithm

我正在建立一个方程,以获取股票组合的净值。

原始方程式:

股票净值*重量。库存/股票类型数量+债券*重量。债券/债券类型数量+现金*重量。现金/现金类型数量

获取日期序列。

匹配方法:例如,BackTest.table中日期在2008-7-15之前的任何行都将与Weight.table中的第一行(涵盖从2008-5-01到2008-7- 15。)

这里是BackTest.table的一部分,以简化想象。该表的标题为:日期,股票类型1,股票类型2,...,债券类型1,债券类型2,...,现金类型4。(这只是类型编号的示例)它实际上与重量表

         Date s1         s2           s3 s4 s5 b1 b2 b3 b4 b5 b6 b7 c1 c2 c3 c4
2  2008-07-01  0 -3.0158124 -0.055652040  1  0  0  0  0  0  0  0  0  0  0  0  0
3  2008-07-02  0  0.3838345 -0.119046476  1  0  0  0  0  0  0  0  0  0  0  0  0
4  2008-07-03  0  2.7602604  0.009611965  1  0  0  0  0  0  0  0  0  0  0  0  0
5  2008-07-04  0 -0.5370067 -0.009611041  1  0  0  0  0  0  0  0  0  0  0  0  0
6  2008-07-05  0  0.0000000  0.000000000  1  0  0  0  0  0  0  0  0  0  0  0  0
7  2008-07-06  0  0.0000000  0.000000000  1  0  0  0  0  0  0  0  0  0  0  0  0
8  2008-07-07  0  5.1583803  0.032680681  1  0  0  0  0  0  0  0  0  0  0  0  0
9  2008-07-08  0  0.8500539  0.048044124  1  0  0  0  0  0  0  0  0  0  0  0  0
10 2008-07-09  0  3.6352579  0.048981473  1  0  0  0  0  0  0  0  0  0  0  0  0
11 2008-07-10  0 -1.5689846  0.052797297  1  0  0  0  0  0  0  0  0  0  0  0  0
12 2008-07-11  0 -0.6688334  0.045093882  1  0  0  0  0  0  0  0  0  0  0  0  0
13 2008-07-12  0  0.0000000  0.000000000  1  0  0  0  0  0  0  0  0  0  0  0  0
14 2008-07-13  0  0.0000000  0.000000000  1  0  0  0  0  0  0  0  0  0  0  0  0
15 2008-07-14  0  1.0436299  0.033565414  1  0  0  0  0  0  0  0  0  0  0  0  0
16 2008-07-15  0 -3.8589001  0.004793450  1  0  0  0  0  0  0  0  0  0  0  0  0
17 2008-07-16  0 -4.0513392  0.034511187  1  0  0  0  0  0  0  0  0  0  0  0  0
18 2008-07-17  0 -1.0070062  0.009583134  1  0  0  0  0  0  0  0  0  0  0  0  0
19 2008-07-18  0  3.5303394  0.014373323  1  0  0  0  0  0  0  0  0  0  0  0  0
20 2008-07-19  0  0.0000000  0.000000000  1  0  0  0  0  0  0  0  0  0  0  0  0
21 2008-07-20  0  0.0000000  0.000000000  1  0  0  0  0  0  0  0  0  0  0  0  0

这是Weight.table的另一块内容,用于简化想象。

Date          Stock  Numbers1 Bond  Number2 Cash     Number3
1 2008-04-30 0.0642        5 0.7858       2 0.1500       2
2 2008-07-15 0.0801        5 0.7699       2 0.1500       2
3 2008-07-31 0.0727        6 0.7773       2 0.1500       1
4 2008-10-31 0.1373        4 0.7127       2 0.1500       1
5 2008-11-30 0.1457        3 0.7144       2 0.1399       2
6 2009-01-31 0.1791        5 0.7242       2 0.0967       1

这是Weight.table和BackTest.table标头的输出

dput(head(Weight.table))
structure(list(Date = structure(c(13999, 14075, 14091, 14183, 
14213, 14275), class = "Date"), Stock = c(0.0642, 0.0801, 0.0727, 
0.1373, 0.1457, 0.1791), Numbers1 = c(5L, 5L, 6L, 4L, 3L, 5L), 
    Bond = c(0.7858, 0.7699, 0.7773, 0.7127, 0.7144, 0.7242), 
    Number2 = c(2L, 2L, 2L, 2L, 2L, 2L), 现金 = c(0.15, 0.15, 
    0.15, 0.15, 0.1399, 0.0967), Number3 = c(2L, 2L, 1L, 1L, 
    2L, 1L)), row.names = c(NA, 6L), class = "data.frame")
dput(head(BackTest.table))
structure(list(Date = structure(c(14061, 14062, 14063, 14064, 
14065, 14066), class = "Date"), s1 = c(0, 0, 0, 0, 0, 0), s2 = c(-3.01581241943634, 
0.383834486785705, 2.76026041158503, -0.537006711952127, 0, 0
), s3 = c(-0.0556520404148886, -0.119046476128297, 0.00961196497399089, 
-0.00961104116408056, 0, 0), s4 = c(1, 1, 1, 1, 1, 1), s5 = c(0, 
0, 0, 0, 0, 0), b1 = c(0, 0, 0, 0, 0, 0), b2 = c(0, 0, 0, 0, 
0, 0), b3 = c(0, 0, 0, 0, 0, 0), b4 = c(0, 0, 0, 0, 0, 0), b5 = c(0, 
0, 0, 0, 0, 0), b6 = c(0, 0, 0, 0, 0, 0), b7 = c(0, 0, 0, 0, 
0, 0), c1 = c(0, 0, 0, 0, 0, 0), c2 = c(0, 0, 0, 0, 0, 0), c3 = c(0, 
0, 0, 0, 0, 0), c4 = c(0, 0, 0, 0, 0, 0)), row.names = 2:7, class = "data.frame")

但是,要得到我想要的东西要花一些时间。所以我尝试使用sapply,但是结果却不同。似乎申请者没有经过IFELSE流程?

要获取值,每当Backtest.table中的日期与Weight.table中的日期匹配时,我就设置一个常数k,k = k + 1,因此它将移至下一行并使用新的权重进行计算净值。

有效的原始代码:

k <- 1

for (t in 1:nrow(BackTest.table)) {
if (BackTest.table[t, 1] %in% Weight.table[, 1] == FALSE) {
    NetReturnPt.table[t, 2] <- sum(BackTest.table[t, 2: ncol(BackTest.table)]* 
    c(rep(Weight.table[k, 2]/ Weight.table[k, 3], Weight.table[k, 3]),
      rep(Weight.table[k, 4]/ Weight.table[k, 5], Weight.table[k, 5]),
      rep(Weight.table[k, 6]/ Weight.table[k, 7], Weight.table[k, 7])
      ), na.rm = TRUE)
  } 
  else {NetReturnPt.table[t, 2] <- sum(BackTest.table[t, 2: ncol(BackTest.table)]* 
        c(rep(Weight.table[k, 2]/ Weight.table[k, 3], Weight.table[k, 3]),
          rep(Weight.table[k, 4]/ Weight.table[k, 5], Weight.table[k, 5]),
          rep(Weight.table[k, 6]/ Weight.table[k, 7], Weight.table[k, 7])
          ), na.rm = TRUE)
k <- k + 1
  }
}

dput(head(NetReturnPt.table[, 2]))
[1] -0.026597604  0.016239878  0.048405161  0.005821428  0.012840000  0.012840000
dput(NetReturnPt.table[20:25, 2])
[1]  0.016020000  0.073282388  0.014539880  0.003858773  0.065490672 -0.003378064

APPLY函数在前几个数据之后没有给出正确的值:

k <- 1

TestApply <- function(t) {
if (BackTest.table[t, 1] %in% Weight.table[, 1] == FALSE) {
    NetReturnPt.table[t, 2] <- sum(BackTest.table[t, 2: ncol(BackTest.table)] * 
    c(rep(Weight.table[k, 2]/ Weight.table[k, 3], Weight.table[k, 3]),
      rep(Weight.table[k, 4]/ Weight.table[k, 5], Weight.table[k, 5]),
      rep(Weight.table[k, 6]/ Weight.table[k, 7], Weight.table[k, 7])
      ), na.rm = TRUE)
  } 
  else { NetReturnPt.table[t, 2] <- sum(BackTest.table[t, 2:ncol(BackTest.table)] * 
       c(rep(Weight.table[k, 2]/ Weight.table[k, 3], Weight.table[k, 3]),
         rep(Weight.table[k, 4]/ Weight.table[k, 5], Weight.table[k, 5]),
         rep(Weight.table[k, 6]/ Weight.table[k, 7], Weight.table[k, 7])
         ), na.rm = TRUE)
k <- k + 1
  }
}

test.result <- sapply(1: nrow(BackTest.table), function(t) TestApply(t))
dput(head(test.result))
[1] -0.026597604  0.016239878  0.048405161  0.005821428  0.012840000  0.012840000
dput(test.result[20:25])
[1]  0.012840000  0.058735697  0.011653687  0.003092799  0.052490651 -0.002707512

您可以看到前几个值与使用FORLOOP的值相同。因此,我想知道它是否没有通过IFELSE流程。

感谢您的宝贵时间,我还要感谢史蒂文·李(Steven Lee)告诉我更好的方式来显示代码。

2 个答案:

答案 0 :(得分:1)

OMG,这令人困惑。看来,如果您正确设置了数据结构,则根本不需要循环,而只需对适当数据帧的列进行除法或乘以运算即可。

您的代码无法正常工作的原因是,k现在是sapply函数参数的内部变量。 sapply对BackTest.table的每一行重复调用TestApply,但是每次调用TestApply时,k仍为1,因为k <- k + 1在{之外不起作用{1}}。

一种解决方法是使用TestApply,它在父环境(特别是定义了k <<- k + 1变量的第一个父环境)中进行分配。虽然我认为这会起作用,但这既不是一个优雅的解决方案也不是一个安全的解决方案。通常,函数只能通过返回值来影响其环境(更改k被称为“副作用”,通常不鼓励这样做)。

更好的方法是考虑如何清理,匹配和提取数据帧,然后以一种简单明了的方式将其用于计算。在这里,我无法为您提供帮助:您需要向我们提供有关您的表格的更多信息(也许在另一个问题中)。

另一种方法是从以下代码开始:

k

此代码是邪恶的。这很令人困惑,您不知道它的作用(它似乎在尝试获取加权均值,但是这样做的方法要快得多,例如sum(BackTest.table[t, 2: ncol(BackTest.table)] * c(rep(Weight.table[k, 2]/ Weight.table[k, 3], Weight.table[k, 3]), rep(Weight.table[k, 4]/ Weight.table[k, 5], Weight.table[k, 5]), rep(Weight.table[k, 6]/ Weight.table[k, 7], Weight.table[k, 7]) ),这取决于BackTest.table中的列数。我会努力先清理这个烂摊子。

还要注意,带有重复项的向量需要计算weighted.mean次,但实际上可以只复制一次Weight.table的列一次,因为此过程对于每一行都是相同的。

编辑:好的,所以现在借助数据,我可以仔细看看这个邪恶代码的作用。基本上,这些列是分组的,对于每个组,您都有一个权重,您应根据该权重将该组中列的值相除。因此,为节省空间,代码作者使用了一种游程长度编码(0.0642重复5次,0.7858重复2次,等等)。

但是,t的行具有不同的重复次数。这真是一团糟,我真的会非常鼓励您考虑如何做以及如何做,以最终得到一个最佳的数据帧用于计算。

答案 1 :(得分:0)

这是一种建议的方法。正如一月份所建议的,如果您重组数据,这应该很简单。

在这里,我重塑了Weight.table的形状,以便每一行显示每个班级和日期的权重和数字。请注意,我更改了示例数据中的前两个日期,以使其与另一个表匹配。

library(tidyverse)
Weight.table.tidy <- Weight.table %>%
  # Renaming here so the first character represents the class, and the second
  #  character represents (w)eight or (n)number.
  rename(s_w = "Stock", s_n = "Numbers1",
         b_w = "Bond",  b_n = "Number2",
         c_w = "现金",  c_n = "Number3") %>%
  gather(col, val, -Date) %>% 
  separate("col", c("class", "stat")) %>%
  spread(stat, val)

head(Weight.table.tidy)
#         Date class n      w
#1  2008-07-01     b 2 0.7858
#2  2008-07-01     c 2 0.1500
#3  2008-07-01     s 5 0.0642
#4  2008-07-04     b 2 0.7699
#5  2008-07-04     c 2 0.1500
#6  2008-07-04     s 5 0.0801

现在,我对BackTest.table做类似的事情,重塑为长形,以便每一行包含一个Dateclassnum,并从原始表。然后,我们可以将其加入Weight.table.tidy并根据每个类+数字组中的先前日期填写所有不适用的日期。现在,我们有了一长串的值,以及将用来加权每个值的变量。

BackTest.table.tidy <- BackTest.table %>%
  gather(type, val, -Date) %>%
  separate("type", c("class", "num"), sep = 1) %>% 
  left_join(Weight.table.tidy) %>%
  group_by(class, num) %>%
  fill(n, w) %>% ungroup()

head(BackTest.table.tidy)
## A tibble: 6 x 6
#  Date       class num     val     n     w
#  <date>     <chr> <chr> <dbl> <dbl> <dbl>
#1 2008-07-01 b     1         0     2 0.786
#2 2008-07-02 b     1         0     2 0.786
#3 2008-07-03 b     1         0     2 0.786
#4 2008-07-04 b     1         0     2 0.770
#5 2008-07-05 b     1         0     2 0.770
#6 2008-07-06 b     1         0     2 0.770

一整形,我们可以使用一行来加权值,另一行可以对每个Date求和。作为矢量化解决方案,我希望它会比原始解决方案快得多。

BackTest.table.tidy %>% 
  mutate(val_wtd = val * w / n) %>%
  count(Date, wt = val_wtd)