在数据中计算连续的序列

10
我正在尝试计算数据集中的最大连续获胜或失败次数(即连续正值或负值的最高数量)。我在StackOverflow上找到了一个相关问题,尽管那个问题给了我一些好建议,但是那个问题的角度不同,而且我还没有足够的经验来将那些信息翻译并应用到这个问题上。所以我希望你能帮我解决这个问题,即使只有一个建议也会很棒。
我的数据集看起来像这样:
> subRes
   Instrument TradeResult.Currency.
1         JPM                    -3
2         JPM                   264
3         JPM                   284
4         JPM                    69
5         JPM                   283
6         JPM                  -219
7         JPM                   -91
8         JPM                   165
9         JPM                   -35
10        JPM                  -294
11        KFT                    -8
12        KFT                   -48
13        KFT                   125
14        KFT                  -150
15        KFT                  -206
16        KFT                   107
17        KFT                   107
18        KFT                    56
19        KFT                   -26
20        KFT                   189
> split(subRes[,2],subRes[,1])
$JPM
 [1]   -3  264  284   69  283 -219  -91  165  -35 -294
$KFT
 [1]   -8  -48  125 -150 -206  107  107   56  -26  189

在这种情况下,JPM的最大(获胜)连胜次数为四次(即连续264、284、69和283次正结果),而KFT的该值为3次(107、107、56)。
我的目标是创建一个函数,以每个工具的最大连胜次数为依据(即JPM:4,KFT:3)。为了实现这一点:
R需要将当前结果与先前结果进行比较,如果更高,则至少有2个连续的正结果。然后R需要查看下一个值,如果这个值也更高:则在已找到的2个值上加1。如果这个值不高,则R需要移动到下一个值,同时将2作为中间最大值记住。
我尝试了cumsum和cummax来符合条件求和(例如cumsum(c(TRUE,diff(subRes [,2])> 0))),但没有成功。还使用rle与lapply相结合(例如lapply(rle(subRes $ TradeResult.Currency.),function(x) diff(x)> 0)),但也没有成功。
我该如何使其工作?
2021年1月19日编辑
计算连胜次数大小 除了连胜次数之外,我还想将连胜次数的大小纳入我的分析。根据以下提供的答案,我认为我能够自己完成它,但遗憾的是我犯了以下问题:
> subRes
   Instrument TradeResult.Currency.
1         JPM                    -3
2         JPM                   264
3         JPM                   284
4         JPM                    69
5         JPM                   283
6         JPM                  -219
7         JPM                   -91
8         JPM                   165
9         JPM                   -35
10        JPM                  -294
11        KFT                    -8
12        KFT                   -48
13        KFT                   125
14        KFT                  -150
15        KFT                  -206
16        KFT                   107
17        KFT                   107
18        KFT                    56
19        KFT                   -26
20        KFT                   189
> lapply(split(subRes[,2], subRes[,1]), function(x) {
+             df.rle <- ifelse(x > 0, 1, 0)
+             df.rle <- rle(df.rle)
+ 
+             wh <- which(df.rle$lengths == max(df.rle$lengths))
+             mx <- df.rle$lengths[wh]
+             suma <- df.rle$lengths[1:wh]
+             out <- x[(sum(suma) - (suma[length(suma)] - 1)):sum(suma)]
+             return(out)
+         })
$JPM
[1] 264 284  69 283

$KFT
[1] 107 107  56

这个结果是正确的,将最后一行改为return(sum(out)),我可以得到连续出现的总大小:

$JPM
[1] 900

$KFT
[1] 270

然而,当更改 ifelse 条件时,该函数似乎没有计算连续失败的情况:
lapply(split(subRes[,2], subRes[,1]), function(x) {
            df.rle <- ifelse(x < 0, 1, 0)
            df.rle <- rle(df.rle)

            wh <- which(df.rle$lengths == max(df.rle$lengths))
            mx <- df.rle$lengths[wh]
            suma <- df.rle$lengths[1:wh]
            out <- x[(sum(suma) - (suma[length(suma)] - 1)):sum(suma)]
            return(out)
        })
$JPM
[1] 264 284  69 283

$KFT
[1] 107 107  56

我不知道我需要改变这个函数的什么地方才能最终得出输球连败的总和。无论我如何调整/更改这个函数,我都得到相同的结果或错误。ifelse 函数让我感到困惑,因为它似乎是要更改的函数的明显部分,但并没有导致任何变化。我错过了什么明显的点吗?


2
请为您的编辑开始一个新问题。 - Shane
1
@Shane:好的,也许那确实更容易。在我尝试了一些新想法之后,我会这样做的。 - Jos
3个回答

11

这样做是可行的:

FUN <- function(x, negate = FALSE, na.rm = FALSE) {
    rles <- rle(x > 0)
    if(negate) {
        max(rles$lengths[!rles$values], na.rm = na.rm)
    } else {
        max(rles$lengths[rles$values], na.rm = na.rm)
    }
}
wins <- lapply(split(subRes[,2],subRes[,1]), FUN)
loses <- lapply(split(subRes[,2],subRes[,1]), FUN, negate = TRUE)
给定以下内容:
> wins
$JPM
[1] 4

$KFT
[1] 3
> loses
$JPM
[1] 2

$KFT
[1] 2

或者:

> sapply(split(subRes[,2],subRes[,1]), FUN)
JPM KFT 
  4   3
> sapply(split(subRes[,2],subRes[,1]), FUN, negate = TRUE)
JPM KFT 
  2   2 
你已经接近了正确答案,但你需要对列表中的每个元素分别应用rle()函数,并将TradeResult.Currency.转换为一个逻辑向量,表示是否大于0。我们的FUN函数仅返回rle函数返回对象的lengths组件。我们将max()应用于这个长度向量来查找最长获胜次数。
请注意,在这里不需要使用split,你可以使用其他子集按因子和应用函数的函数(例如tapplyaggregate等)。
> with(subRes, aggregate(`TradeResult.Currency.`, 
+                        by = list(Instrument = Instrument), FUN))
  Instrument x
1        JPM 4
2        KFT 3
> with(subRes, tapply(`TradeResult.Currency.`, Instrument, FUN))
JPM KFT 
  4   3
先前版本不正确的原因是,如果您连续输多于赢(连续负值较长),将选择损失系列的长度。
修改后的函数添加了一个“否定”参数来交换测试的含义。如果我们想要获胜,我们将保留 TRUE FALSE $values 中。如果我们想要输掉比赛,我们交换 TRUE FALSE 。然后,我们可以使用这个 $values 组件来选择仅对应于获胜( negate = TRUE )或对应于失败的运行( negate = FALSE )。

谢谢Gavin!这解决了我的问题,而且函数也比我预期的要短得多。谢谢 :) 不过我想知道,为什么你在函数中选择使用na.rm?搜索这个语句会得到很多代码,但没有真正的文档。你能告诉我在这个例子中它的用途是什么吗?因为如果在函数中没有na.rm,我会得到相同的输出(JPM 4 KFT 3)。 - Jos
1
@Jura25 我在为所有人写FUN时,为了一个 'na.rm' 参数而进行防御性编程,以便在计算过程中出现缺失数据时 max 函数能够正常工作。在这个例子中,您不需要它,因为没有缺失的可能性,但是一旦您开始使用它,如果任何长度为 NAmax 将返回 NA。经过更仔细地查看 rlelengths 组件永远不会包含 NA,所以我过于谨慎了,您可以省略 sapplylapply 调用中的 na.rm = TRUE 部分。 - Gavin Simpson
感谢Gavin进一步的解释! - Jos
只是好奇,但如果我理解正确的话,我不能使用这个解决方案来计算连败的长度(即小于零的值)吗?更改明显的参数会得到相同的结果(使用rle(x <= 0)),或者出现错误(使用inverse.rle)。我有什么遗漏的吗? - Jos
1
@Jura25 这个函数有问题,因为会选择比赢多的连续输。我已经更正了这个函数,使其做正确的事情,并通过使用新参数“'negate'”来扩展它以返回输或赢。关于原始函数为什么不起作用以及为什么不能交换条件x <0以在我的答案末尾包含输运行的一些解释。 - Gavin Simpson
谢谢 Gavin,这个非常有效,你的额外解释也很有启发性。再次感谢! - Jos

3

这里提供的方案不如Gavin的方案那么流畅,但是我会尽力让内容更加易懂。我的函数会返回最长连续字符串的实际序列。

inst.split <- split(inst[, 2], inst[, 1])

inst <- lapply(inst.split, function(x) {
            df.rle <- ifelse(x > 0, 1, 0)
            df.rle <- rle(df.rle)

            wh <- which(df.rle$lengths == max(df.rle$lengths))
            mx <- df.rle$lengths[wh]
            suma <- df.rle$lengths[1:wh]
            out <- x[(sum(suma) - (suma[length(suma)] - 1)):sum(suma)]
            return(out)
        })

$JPM
[1] 264 284  69 283

$KFT
[1] 107 107  56

如果您想了解每种乐器的最长连续时间,请执行以下操作:
lapply(inst, length)

$JPM
[1] 4

$KFT
[1] 3

对于负数值

请注意,KFT有一个漫长的连败记录。我已经保留了JPM(摩根大通?)的价值。

> inst
   Instrument TradeResult.Currency.
1         JPM                    -3
2         JPM                   264
3         JPM                   284
4         JPM                    69
5         JPM                   283
6         JPM                  -219
7         JPM                   -91
8         JPM                   165
9         JPM                   -35
10        JPM                  -294
11        KFT                    -8
12        KFT                   -48
13        KFT                  -125
14        KFT                  -150
15        KFT                  -206
16        KFT                  -107
17        KFT                  -107
18        KFT                    56
19        KFT                   -26
20        KFT                   189

这是通过上述函数运行分割data.frame的结果。

$JPM
[1] 264 284  69 283

$KFT
[1]   -8  -48 -125 -150 -206 -107 -107

@Roman,这是一个好的解决方案,并建议返回类似于rle()的内容将在此处很有用,其中包括lengthsvalues组件。几个风格要点:您不需要ifelse部分,只需直接将rle()应用于x>0(逻辑被解释为0,1)。您还可以使用which.max代替您的whichwhich.max(df.rle$lengths) - Gavin Simpson
谢谢Roman,看起来很复杂(而且完美运作!)。除此之外,在你的函数中进行了一些小改动,我能够计算出连胜的总价值(这将是我的下一步分析)。谢谢! :) - Jos
@Gavin,我在看到你的答案时已经写好了所有内容。我留下它是为了多样化和展示Jura25可能不熟悉的额外功能。我不知道x > 0,但我喜欢它。 - Roman Luštrik
@Roman:我使用了你的脚本来计算连胜的总大小,但是我似乎无法让它同样适用于连败。我尝试在函数中使用negate和反向(!),但结果要么相同,要么是错误的值加上警告消息。更改ifelse似乎是最明显的方法,但奇怪的是,这仍然会产生相同的结果。你能否修改你的函数版本以显示连败的相同结果? - Jos
1
我已将几个 FKT 的值改为负数,以便获得漫长的连败,并使函数直接能用。你确定你没有使用一些奇怪(冗余)的对象吗? - Roman Luštrik
显示剩余3条评论

1
我已经编写了一个循环来计算任意长度数据的连胜和连败次数(在本例中,x是您感兴趣的数字向量)。这个问题的问题在于最大的连胜或连败可能不会与最长的连胜次数重合。因此,需要进行单独/独立的计算:
rout <- rle (x>=0) # In this calculation, 0 is considered a "win"

losel <- max(rout$lengths[!rout$values]) # Length of max losing streak
winl <- max(rout$lengths[rout$values]) # Length of max winning streak

xpostemp <- cumsum(rout$lengths)
xpos <- c(0,xpostemp)
looplength <- length(xpos)-1
tot <- rep (0,looplength)

for(j in 1:looplength){
    start <- xpos[j]+1
    end <- xpos[j+1]
    tot[j] <- sum(x[start:end])                
}
winmax <- max(tot) # Sum of largest winning steak
losemax <- min(tot) # Sum of largest losing streak

非常抱歉,看起来有些繁琐,我不是全职程序员,但我认为这个应该能够正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接