在宽数据框中检测异常值

4

x

Team Date       Score
A    1-1-2012   80
A    1-2-2012   90
A    1-3-2012   50
A    1-4-2012   40   
B    1-1-2012   100
B    1-2-2012   60
B    1-3-2012   30
B    1-4-2012   70
etc

我可以将该数据框转换为宽数据框,每个团队一行,所有观测和日期作为标题:

xx

Team 1-1-2012 1-2-2012  1-3-2012 1-4-2012
A    80       90        50        40
B    100     60         30        70  

我需要为每一行计算平均值和标准差,这可以通过以下方式完成:

xx

Team 1-1-2012 1-2-2012  1-3-2012 1-4-2012  mean   sd
A    80       90        50        40       75    20
B    100     60         30        70       55    10 

考虑到我在数据框xx中有成千上万行。我想对每个单元格进行如下计算:
如果abs(xx-Mean) > 3*SD,则创建一个计数器列名并增加值。这个想法是将每个观察结果与平均值和标准差进行比较,如果给定团队的每个观察结果都符合此条件 - abs(xx-Mean) > 3*SD,则增加计数器。检查完每个单元格后,我想查看每个团队的每个计数器,并获取具有最高计数器值的前十个高团队。基本上,我试图检测最多的异常值。一旦获得前10个团队名称,我想在数据框x上绘制它们的时间序列数据。
我希望我没有把这变得比应该更复杂。不确定R是否已经有函数来对每个单元格进行计算。如何完成这个任务的任何想法都会受到赞赏?
2个回答

6

我建议您保留数据的长格式,并使用plyrdata.table或其他分离-应用-合并工具来计算统计信息。以下是我如何使用plyr完成此任务:

#Your data
dat <- read.table(text = "Team Date       Score
A    1-1-2012   80
A    1-2-2012   90
A    1-3-2012   50
A    1-4-2012   40   
B    1-1-2012   100
B    1-2-2012   60
B    1-3-2012   30
B    1-4-2012   70", header = TRUE)

library(plyr)

#Compute mean and sd by team
dat <- ddply(dat, .(Team), transform, mean = mean(Score), sd = sd(Score))
#Your outlier threshold
dat <- transform(dat, outlier = abs(Score - mean) > 3*sd)
#Cumulative sum by team
dat <- ddply(dat, .(Team), transform, cumsumOutlier = cumsum(outlier))

给你输出了这个结果(与您的示例不符,但可能您的真实数据是):
 Team     Date Score mean       sd outlier cumsumOutlier
1    A 1-1-2012    80   65 23.80476   FALSE             0
2    A 1-2-2012    90   65 23.80476   FALSE             0
3    A 1-3-2012    50   65 23.80476   FALSE             0
4    A 1-4-2012    40   65 23.80476   FALSE             0
5    B 1-1-2012   100   65 28.86751   FALSE             0
6    B 1-2-2012    60   65 28.86751   FALSE             0
7    B 1-3-2012    30   65 28.86751   FALSE             0
8    B 1-4-2012    70   65 28.86751   FALSE             0

2
等有人拿出一些计时结果来...data.table将会赢得这场战斗,这里有一些证据 - Chase
我很好奇您如何使用cumsumOutlier列。 我猜它会累计每个团队发现的异常值数量。 您将如何在实际应用中利用这些信息? - Beaker

5
一个长格式、data.table方法。
DT <- read.table( 'clipboard', header = T)
library(data.table)
DT <- as.data.table(DT)
DT[, mean.score := mean(Score), by = Team]
##    Team     Date Score mean.score
## 1:    A 1-1-2012    80         65
## 2:    A 1-2-2012    90         65
## 3:    A 1-3-2012    50         65
## 4:    A 1-4-2012    40         65
## 5:    B 1-1-2012   100         65
## 6:    B 1-2-2012    60         65
## 7:    B 1-3-2012    30         65
## 8:    B 1-4-2012    70         65
DT[, sd.score := sd(Score), by = Team]
##    Team     Date Score mean.score sd.score
## 1:    A 1-1-2012    80         65 23.80476
## 2:    A 1-2-2012    90         65 23.80476
## 3:    A 1-3-2012    50         65 23.80476
## 4:    A 1-4-2012    40         65 23.80476
## 5:    B 1-1-2012   100         65 28.86751
## 6:    B 1-2-2012    60         65 28.86751
## 7:    B 1-3-2012    30         65 28.86751
## 8:    B 1-4-2012    70         65 28.86751
DT[, outlier := abs(Score-mean.score) > 3 * sd.score, by = Team]
##    Team     Date Score mean.score sd.score outlier
## 1:    A 1-1-2012    80         65 23.80476   FALSE
## 2:    A 1-2-2012    90         65 23.80476   FALSE
## 3:    A 1-3-2012    50         65 23.80476   FALSE
## 4:    A 1-4-2012    40         65 23.80476   FALSE
## 5:    B 1-1-2012   100         65 28.86751   FALSE
## 6:    B 1-2-2012    60         65 28.86751   FALSE
## 7:    B 1-3-2012    30         65 28.86751   FALSE
## 8:    B 1-4-2012    70         65 28.86751   FALSE

或者,一步到位。
DT[, outlier := abs(Score-mean(Score)) > 3 *  sd(Score), by = Team]

要增加异常值的数量(逻辑变量上的求和会强制转换为0或1)

DT[, sum.outlier := sum(outlier), by = Team]

非常感谢。我明天早上第一件事就会尝试这个。我认为这会起作用。与其使用真假值,我可以给它一个整数,然后执行聚合(outlier〜Team,sum,DT),并获取每个团队的总和值,然后绘制前十名。 - user1471980
@user - 对于大多数情况,R会将TRUE/FALSE分别视为1/0。 - Chase
@mnel,我执行了这个操作,它的性能比plyr好得多。 - user1471980
@mnel,我有一个问题。与其查看绝对值,我只对此计算的正值感兴趣:outlier := abs(Score-mean(Score)) > 3 * sd(Score),按Team分组。有没有简单的方法来实现这个? - user1471980

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