【问题标题】:How to winsorize (or remove univariate outliers) in a longitudinal dataset如何在纵向数据集中对(或删除单变量异常值)进行winsorize
【发布时间】:2014-03-23 17:09:32
【问题描述】:

我试图弄清楚如何对纵向数据集中按个人分组的观察结果进行缩尾处理。

我从这个excellent answer 开始讨论如何从变量的平均值中删除>2 个标准差的数据。作者还很有帮助地展示了如何在类别中执行此操作。

我的用例略有不同:我有一个纵向数据集,我想删除随着时间的推移系统地显示为异常值的个人。与其排除受试者中的极端观察,我想要么完全排除这些个体(修剪数据),要么用截断值替换底部和顶部的 2.5%(winsorizing,参见:http://en.wikipedia.org/wiki/Winsorising)。

例如,我的长格式数据可能如下所示:

name time points
MJ   1    998
MJ   2    1000
MJ   3    998
MJ   4    3000
MJ   5    998
MJ   5    420
MJ   6    999
MJ   7    998
Lebron   1    9
Lebron   2    1
Lebron   3    3
Lebron   4    900
Lebron   5    4
Lebron   5    4
Lebron   6    3
Lebron   7    8
Kobe   1    2
Kobe   2    1
Kobe   3    4
Kobe   4    2
Kobe   5    1000
Kobe   5    4
Kobe   6    7
Kobe   7    9
Larry   1    2
Larry   2    1
Larry   3    4
Larry   4    2
Larry   5    800
Larry   5    4
Larry   6    7
Larry   7    9

如果我想在个人 (name) 中删除 points 中的极端观察,我的代码将是:

do.call(rbind,by(df,df$name,function(x) x[!abs(scale(x$points)) > 2,]))

但我真正想做的是排除极端的个人(在这种情况下,MJ)。我该怎么做呢?

(附注 - 在这里插入所有关于不应删除异常值的警告。这只是一个稳健性测试!)

【问题讨论】:

  • 嗯,作为第一步,您需要定义汇总统计数据,根据该统计数据将某人识别为异常值。该统计数据是什么,与其他值的距离应该被视为异常值吗?
  • 是的,这根本不应该是一个问题,除了我们不知道你会用什么指标来定义 MJ 是极端的。
  • @JoshO'Brien 感谢您指出这一点;我将对我的问题进行澄清。最初的目标是进行 95% 的 Winsorization,其中对底部 2.5% 的个体和顶部 2.5% 的个体的观察值将替换为切割值。 (见:en.wikipedia.org/wiki/Winsorising)。但是,只是“修剪”这些观察结果的答案同样有用,因为问题在于识别而不是删除?

标签: r


【解决方案1】:

我只会使用 dplyr:

test <- read.csv("test.csv", header=TRUE)
library(dplyr)

test <- test %.% 
  group_by(name) %.% 
  mutate(mean_points=mean(points))

cut_point_top <- quantile(test$mean_points, 0.95)
cut_point_bottom <- quantile(test$mean_points, 0.05)

test <- test %.% 
  group_by(name) %.% 
  mutate(outlier_top = (mean_points >= cut_point_top), 
         outlier_bottom = mean_points <= cut_point_bottom) %.%
  filter(!outlier_top & ! outlier_bottom)

这会过滤掉平均得分在前 2.5% 中的 MJ 和在后 2.5% 中的拉里。

如果您想用 2.5 个百分位数的切点替换 points 变量,只需像这样删除最后一个过滤器语句:

test <- test %.% 
  group_by(name) %.% 
  mutate(outlier_top = (mean_points >= cut_point_top), 
         outlier_bottom = mean_points <= cut_point_bottom) 

test$points <- ifelse(test$outlier_top, cut_point_top, 
                      ifelse(test$outlier_bottom, cut_point_bottom, test$points))

【讨论】:

  • 我喜欢你的回答,但你会如何用切点替换顶部和底部的 2.5%?
  • 谢谢。我已经修改了我的答案来处理这种情况。
  • 第二个解决方案使用pminpmax 会更容易
  • 关于如何使用 pmin 或 pmax 进行 Winsorizing 有什么想法吗?我在考虑 pmax(pmin(points, quantile(points, .95)), quantile(points, .05))。但是诀窍是将其作为新列插入数据框中。想法?
【解决方案2】:

这可能不适合您的数据,但我将尝试一个通用的解决方案让您开始思考。我建议使用稳健的统计数据,例如中位数和中位数绝对偏差 (MAD) 来定义异常值。您可以首先查看每个个体的异常值点的比例(与所有点相比):

df 成为你的数据框

library(plyr)

med <- median(df$points)
md <- mad(df$points)
outlier.factor <- 2
daply(df, .(name), function(x) {sum(abs(x$points - m) > md * outlier.factor) / nrow(x)})

最后一行输出以下内容(用于您的示例数据):

 Kobe  Larry Lebron     MJ
0.125  0.125  0.125  1.000

因此,MJ 的所有点都是异常值,而 12.5% 是所有其他个体的异常值。

您现在可以使用阈值来选择要删除的个人。例如,对于正态分布的数据,您预计大约 4.55% 会落在中位数 ± 2 x MAD 的范围之外。

【讨论】:

    【解决方案3】:

    这就是我可能会做的事情:

    means <- ddply(df, .(name), summarize, mean=mean(points))$mean
    means <- mean(means)
    
    upperBound <- 2
    
    outlierTest <- ddply(df, .(name), summarize, outlier=ifelse(sum(points) / means > upperBound, 
    TRUE, FALSE))
    
    keep <- outlierTest$name[!outlierTest$outlier]
    
    df <- df[df$name %in% keep, ]
    

    其中 df 是您的 data.frame。你可以选择任何你想要的upperBound

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-14
      • 2013-01-13
      • 2021-06-13
      • 1970-01-01
      • 2020-09-16
      • 2012-07-02
      相关资源
      最近更新 更多