【问题标题】:Custom function to create an index of results用于创建结果索引的自定义函数
【发布时间】:2011-06-11 17:05:03
【问题描述】:

我正在尝试创建一个函数来创建一个索引(从 100 开始),然后根据投资结果调整该索引。因此,简而言之,如果第一次投资的利润为 5%,那么该指数将为 105,如果第二个结果为 -7%,则该指数为 97.65。在这个问题中,当我使用“索引”一词时,我不是指的是zoo 包的index 函数。

除了创建这个索引之外,我的目标也是创建一个可以应用于我的完整数据集的各种子集的函数(即使用sapply 和它的朋友)。

这是我目前拥有的功能(本问题末尾的数据):

CalculateIndex <- function(x){
    totalAccount <- accountValueStart
    if(x$TradeResult.Currency == head(x$TradeResult.Currency., n = 1)){
        indexedValues <- 100 + ( 100 *((((x$Size.Units. * x$EntryPrice) / totalAccount) * x$TradeResult.Percent.) / 100))
        # Update the accountvalue
        totalAccount <- totalAccount + x$TradeResult.Currency.
    }   
    else{ # the value is not the first
        indexedValues <- c(indexedValues, 
                indexedValues[-1] + (indexedValues[-1] *(((x$Size.Units. * x$EntryPrice) / totalAccount) * x$TradeResult.Percent.) / 100)
                )
            # Update the accountvalue
            totalAccount <- totalAccount + x$TradeResult.Currency.      
    }
    return(indexedValues)
}

函数执行(阅读:旨在执行)以下操作: 如果值是第一个,则使用100 作为索引的起点。如果该值不是第一个,则以之前计算的索引值作为计算新索引值的起点。除此之外,该函数还取单个结果的权重(与totalAccount值相比) ) 考虑在内。

问题:theData 数据帧上使用此 CalculateIndex 函数会产生以下错误输出:

> CalculateIndex(theData)
 [1]  99.97901  99.94180  99.65632 101.88689 100.89309  98.92878 102.02911 100.49159  98.52955 102.02243  98.43655 100.76502  99.34869 100.76401 101.18014  99.75136  97.90130
[18] 100.39935  99.81311 101.34961
Warning message:
In if (x$TradeResult.Currency == head(x$TradeResult.Currency., n = 1)) { :
  the condition has length > 1 and only the first element will be used

编辑: 哇,我已经投了反对票,尽管我认为我的问题已经太长了。抱歉,我认为/认为问题出在我的循环中,所以我不想让你厌烦细节,我认为这些细节只会给出更少的答案。抱歉,我的判断有误。

问题是,对于CalculateIndex 的上述输出,结果与 Excel 大不相同。尽管这可能是由于舍入错误造成的(正如 Joris 在下面提到的那样),但我对此表示怀疑。与 Excel 的结果相比,R 的结果有很大的不同:

R output    Excel calculate values  
99,9790085700   99,97900857 
99,9418035700   99,92081189 
99,6563228600   99,57713687 
101,8868850000  101,4639947 
100,8930864300  102,3570786 
98,9287771400   101,2858564 
102,0291071400  103,3149664 
100,4915864300  103,806556  
98,5295542900   102,3361186 
102,0224285700  104,3585552 
98,4365550000   102,795089  
100,7650171400  103,5601228 
99,3486857100   102,9087897 
100,7640057100  103,6728077 
101,1801400000  104,8529634 
99,7513600000   104,6043164 
97,9013000000   102,5055298 
100,3993485700  102,9048999 
99,8131085700   102,7179995 
101,3496071400  104,0676555 

我认为公平地说,输出的差异不是 R 与 Excel 问题的结果,而是我的函数中的错误。所以,让我们专注于函数。

函数的手动计算 该函数使用不同的变量:

  • Size.Units.;这是在EntryPrice 购买的单位数量。
  • EntryPrice:买入股票的价格,
  • TradeResult.Percent.:投资产生的收益或损失百分比,
  • TradeResult.Currency.:投资收益或损失的货币价值(美元),

这些变量用于函数的以下部分:

100 + ( 100 *((((x$Size.Units. * x$EntryPrice) / totalAccount) * x$TradeResult.Percent.) / 100))

indexedValues[-1] + (indexedValues[-1] *(((x$Size.Units. * x$EntryPrice) / totalAccount) * x$TradeResult.Percent.) / 100)

两个公式基本相同,不同之处在于第一个从100 开始,第二个使用previous value 计算新的索引值。

公式可以分为不同的步骤:

首先,x$Size.Units. * x$EntryPrice 确定所持有的总头寸,即以 48.98 的价格购买 100 股股票会获得 4898 美元的头寸。

得到的总头寸然后除以总账户规模(即totalAccount)。这是纠正一个头寸相对于整个投资组合的影响所必需的。例如,如果我们以 48.98 买入的 100 股下跌 10%,则计算出的指数(即CalculateIndex 函数)不一定必须下跌 10%,因为当然不是所有的钱都在 @ 987654344@投资一只股票。因此,通过将总头寸除以totalAccount,我们得到一个比率,它告诉我们投资了多少资金。例如,如果股票下跌 10%,则规模为 4898 美元(总账户为 14000)的头寸导致总账户损失为 3.49%。 (即4898 / 14000 = 0.349857. 0.349857 * 10% = 3.49857%

这个比率(投资金额与总金额的比率)然后在公式中乘以x$TradeResult.Percent.,从而得到对总账户的百分比影响(参见上一段中的计算示例) .

作为最后一步,总账户的百分比损失应用于指数值(从100 开始)。在这种情况下,第一次投资 100 股以 48.89 美元买入,让指数从 100 的起点跌至 99.97901,反映亏损交易对总账户的影响。

编辑结束

将函数剥离干净,然后一次添加一部分公式,为了发现错误,我来到了错误所在的以下步骤:

CalculateIndex <- function(x){
    totalAccount <- accountValueStart
    if(x$TradeResult.Currency == head(x$TradeResult.Currency., n = 1)){
        indexedValues <- totalAccount
        # Update the accountvalue
        totalAccount <- totalAccount + x$TradeResult.Currency.
    }   
    else{ # the value is not the first
        indexedValues <- c(indexedValues, totalAccount)         
            # Update the accountvalue
            totalAccount <- totalAccount + x$TradeResult.Currency.      
    }
    return(indexedValues)
}
> CalculateIndex(theData)
[1] 14000
Warning message:
In if (x$TradeResult.Currency == head(x$TradeResult.Currency., n = 1)) { :
  the condition has length > 1 and only the first element will be used

所以,如果我只使用 totalAccount 变量,函数似乎无法正确更新。这似乎表明if else 语句的基础存在一些错误,因为它只输出第一个值。

如果我从函数中删除else 语句,我会得到theData 中每一行的值。然而,这些然后被错误地计算。所以,在我看来,这个函数如何更新totalAccount 变量存在一些错误。我看不出我在哪里犯了错误,所以任何建议都会受到高度赞赏。我做错了什么?


数据

我的数据如下所示:

> theData
   Size.Units. EntryPrice TradeResult.Percent. TradeResult.Currency.
1          100      48.98                -0.06                    -3
11         100      32.59                -0.25                    -8
12         100      32.51                -1.48                   -48
2          100      49.01                 5.39                   264
13         100      32.99                 3.79                   125
14         100      34.24                -4.38                  -150
3          100      51.65                 5.50                   284
4          100      48.81                 1.41                    69
15         100      35.74                -5.76                  -206
5          100      49.50                 5.72                   283
6          100      46.67                -4.69                  -219
16         100      33.68                 3.18                   107
7          100      44.48                -2.05                   -91
17         100      32.61                 3.28                   107
8          100      45.39                 3.64                   165
9          100      47.04                -0.74                   -35
10         100      47.39                -6.20                  -294
18         100      33.68                 1.66                    56
19         100      33.12                -0.79                   -26
20         100      32.86                 5.75                   189

theData <- structure(list(X = c(1L, 11L, 12L, 2L, 13L, 14L, 3L, 4L, 15L, 
    5L, 6L, 16L, 7L, 17L, 8L, 9L, 10L, 18L, 19L, 20L), Size.Units. = c(100L, 
    100L, 100L, 100L, 100L, 100L, 100L, 100L, 100L, 100L, 100L, 100L, 
    100L, 100L, 100L, 100L, 100L, 100L, 100L, 100L), EntryPrice = c(48.98, 
    32.59, 32.51, 49.01, 32.99, 34.24, 51.65, 48.81, 35.74, 49.5, 
    46.67, 33.68, 44.48, 32.61, 45.39, 47.04, 47.39, 33.68, 33.12, 
    32.86), TradeResult.Percent. = c(-0.06, -0.25, -1.48, 5.39, 3.79, 
    -4.38, 5.5, 1.41, -5.76, 5.72, -4.69, 3.18, -2.05, 3.28, 3.64, 
    -0.74, -6.2, 1.66, -0.79, 5.75), TradeResult.Currency. = c(-3L, 
    -8L, -48L, 264L, 125L, -150L, 284L, 69L, -206L, 283L, -219L, 
    107L, -91L, 107L, 165L, -35L, -294L, 56L, -26L, 189L)), .Names = c("X", 
    "Size.Units.", "EntryPrice", "TradeResult.Percent.", "TradeResult.Currency."
    ), class = "data.frame", row.names = c(NA, -20L))

# Set the account start @ 14000
> accountValueStart <- 14000

【问题讨论】:

  • @Jura25:我尝试通过添加数据对象的 ASCII 表示(使用:dput())来编辑您的问题,以便能够更轻松地读取 R 中的数据框。
  • @Jura - 您需要确保您的数据正确排序吗?我没有关注您的索引的所有详细信息,但 indexedValues[k] 的值依赖于 indexedValues[k - 1] 上的计算值。您在上面发布的数据似乎表明行顺序可能不符合..即1、11、12、2、13、14 等...另外,为什么不将 accountStartValue 作为第二个参数传递给您的函数?
  • @Jura 您用文字描述的内容与您尝试编写的代码几乎没有关系。有一个货币变量,一些您没有描述但似乎与您尝试做的事情不可或缺的单位和价格。您的代码也很难阅读。你能解释一下Size.Units.EntryPriceTradeResult.Currency.是什么以及它们是如何进入问题的。
  • @ 反对者:没有理由反对这个问题。这并不是因为 OP 出现了一些编码错误,这不是一个坏问题。他提供了足够的信息,说明他拥有什么、他想做什么以及他的数据。我怎么能在 10 分钟内解决这个问题?
  • @Gavin:在 R 编程考试中提问会是一个很好的难题:优化以下代码 :-)

标签: function r if-statement


【解决方案1】:

警告信息来自这一行:

if(x$TradeResult.Currency == head(x$TradeResult.Currency., n = 1)){

原因很容易理解; x$TradeResult.Currency 是一个向量,因此与head(x$TradeResult.Currency., n = 1) 的比较产生一个逻辑的向量。 (顺便说一句,为什么不用x$TradeResult.Currency[1] 而不是head() 调用呢?)。 if() 需要单个逻辑而不是逻辑向量,这就是警告的含义。 ifelse() 如果您想根据给出逻辑向量的条件做两件事中的一件,这很有用。

实际上,您所做的只是输入语句的if() 部分并且它只执行一次,因为x$TradeResult.Currency == head(x$TradeResult.Currency., n = 1) 的第一个元素是TRUE 而R 忽略了其他元素。

> if(c(TRUE, FALSE)) {
+ print("Hi")
+ } else {
+ print("Bye")
+ }
[1] "Hi"
Warning message:
In if (c(TRUE, FALSE)) { :
  the condition has length > 1 and only the first element will be used
> ifelse(c(TRUE, FALSE), print("Hi"), print("Bye"))
[1] "Hi"
[1] "Bye"
[1] "Hi"  "Bye"

至于解决你真正的问题:

CalculateIndex2 <- function(x, value, start = 100) {
    rowSeq <- seq_len(NROW(x))
    totalAc <- cumsum(c(value, x$TradeResult.Currency.))[rowSeq]
    idx <- numeric(length = nrow(x))
    interm <- (((x$Size.Units. * x$EntryPrice) / totalAc) *
               x$TradeResult.Percent.) / 100
    for(i in rowSeq) {
        idx[i] <- start + (start * interm[i])
        start <- idx[i]
    }
    idx
}

theData 上使用时会给出:

> CalculateIndex2(theData, 14000)
 [1]  99.97901  99.92081  99.57714 101.46399 102.35708 101.28586 103.31497
 [8] 103.80656 102.33612 104.35856 102.79509 103.56012 102.90879 103.67281
[15] 104.85296 104.60432 102.50553 102.90490 102.71800 104.06766

你想要的是一个递归函数(IIRC);当前索引是前一个索引的一些函数。这些很难在 R 中以矢量化方式解决,因此是循环。

【讨论】:

  • 感谢您的回答加文。我想说的是向量的一个基本错误,所以谢谢你的例子,这让它更清楚了。
  • @Jura25 我添加了一个解决方案,在我不得不离开参加研讨会之前正在研究它。与此同时,我看到@Joris Meys 以同样的方式回答了我。两者的关键特征是我们尽可能地向量化,并且只在我们必须做的地方进行递归。
  • 很好的递归指针。 @Jura,您可能想检查一下。递归和 R,一个非常有趣的组合。
  • 很好的答案加文,看起来很复杂,但我可以按照你在这个函数中所做的事情(事后看来它看起来容易多了;))。递归函数是否也是总结函数似乎不起作用的原因?
  • [我不能直接在这个上面编辑我的评论,但我成功地用函数总结了,所以这个问题现在无关紧要。]
【解决方案2】:

您的代码看起来很奇怪,而且您似乎对来自另一种编程语言的 R 有很多误解。 Gavin 和 Gillespie 已经指出了您收到警告的原因。让我添加一些更优化编码的技巧:

  • [-1] 并不意味着:删除最后一个。它的意思是“保留除第一个值之外的所有内容”,这也解释了为什么会得到错误的结果。

  • 一开始就计算常见的东西,以整理你的代码。

  • head(x$TradeResult.Currency., n = 1)x$TradeResult.Currency.[1] 相同。

  • 密切关注您的向量。代码中的大多数错误来自于忘记使用向量。

  • 如果您需要一个值作为向量中的第一个值,请将其放在您要使用的任何循环之外,切勿在函数中添加 if 子句。

  • 预定义尽可能多的向量/矩阵,这样在处理大数据时速度会快很多并且减少内存问题。

  • 矢量化,矢量化,矢量化。我有提到矢量化吗?

  • 学习使用debug()debugonce()browser() 来检查你的函数在做什么。您的许多问题都可以通过在函数内操作时检查对象来解决。

这说并考虑到,你的功能变成:

CalculateIndex <- function(x,accountValueStart){
  # predifine your vector
  indexedValues <- vector("numeric",nrow(x))
  # get your totalAccount calculated FAST. This is a VECTOR!!!
  totalAccount <- cumsum(c(accountValueStart,x$TradeResult.Currency.))
  #adjust length:
  totalAccount <- totalAccount[-(nrow(x)+1)]

  # only once this calculation. This is a VECTOR!!!!
  totRatio <- 1+(((x$Size.Units. * x$EntryPrice)/totalAccount) *
                 x$TradeResult.Percent.)/100

  # and now the calculations
  indexedValues[1] <- 100 * totRatio[1]
  for(i in 2:nrow(x)){
      indexedValues[i] <- indexedValues[i-1]*totRatio[i]
  }
  return(indexedValues)
}

然后返回

> CalculateIndex(theData,14000)
[1]  99.97901  99.92081  99.57714 101.46399 102.35708 101.28586 103.31497 
 103.80656 102.33612 104.35856 102.79509 103.56012
[13] 102.90879 103.67281 104.85296 104.60432 102.50553 102.90490 102.71800 
 104.06766

所以现在你可以这样做了:

 invisible(replicate(10,print("I will never forget about vectorization any more!")))

【讨论】:

  • 感谢 Joris,这完全符合预期。除此之外,我很感谢我(严重)缺乏 R 知识的编码技巧和想法。我已经把它们打印出来了,所以我不会重复它们。 ;) 哈哈,再次感谢!
  • +1 表示努力阅读和解决一个长问题并提供详细提示!
  • +1 我出去参加午餐时间的研讨会,你去提供一个很好的解决方案,这实际上是我在接受教育之前所拥有的(但略有不同)!该死的学术生活;它打断了这样的回答,所以......
  • @Gavin : 别担心,你只需多花一天时间就能超越我进入顶级用户名单 ;-)
  • @Jura25 : 使用函数by()
【解决方案3】:

对于您到底想做什么,我仍然有些困惑,但希望以下内容会有所帮助。

您的 R 脚本针对第一个值给出与 Excel 函数相同的答案。您会看到差异,因为 R 不会打印出所有数字。

> tmp = CalculateIndex(thedata)
Warning message:
In if (x$TradeResult.Currency == head(x$TradeResult.Currency., n = 1)) { :
  the condition has length > 1 and only the first element will be used
> print(tmp, digits=10)
 [1]  99.97900857  99.94180357  99.65632286 101.88688500 100.89308643
 <snip>

警告消息的原因是因为x$TradeResult.Currency 是一个与单个数字进行比较的向量。

该警告消息也是您的错误所在。在您的 if 语句中,您从不执行 else 部分,因为只使用了 x$TradeResult.Currency 的值。正如警告消息所述,仅使用了x$TradeResult.Currency 的第一个元素。

【讨论】:

  • @Joris-Meys:我从你的(非常好的)回答中看到了这一点。
  • 感谢 Csgillespie,您对向量是正确的。可悲的是,我的功能还有很多问题。 ;) 不过还是感谢您的回复!
猜你喜欢
  • 1970-01-01
  • 2014-06-10
  • 2011-01-06
  • 2013-08-30
  • 1970-01-01
  • 1970-01-01
  • 2012-02-29
  • 2013-11-27
  • 1970-01-01
相关资源
最近更新 更多