Как winsorize (или удалить одномерные выбросы) в продольном наборе данных
Я пытаюсь выяснить, как проводить наблюдения, сгруппированные индивидами в продольном наборе данных.
Я начал с этого отличного ответа о том, как удалить данные > 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
Если бы я хотел удалить экстремальные наблюдения в points
внутри индивидуумов (name
), мой код:
do.call(rbind,by(df,df$name,function(x) x[!abs(scale(x$points)) > 2,]))
Но я действительно хочу исключить ИНДИВИДУАЛЬНЫЙ, который является экстремальным (в данном случае MJ
). Как мне это сделать?
(P.S.) - вставьте здесь все оговорки о том, как нельзя удалять выбросы. Это просто тест надежности!)
Ответы
Ответ 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)
Это отфильтровывает MJ как средний балл в верхних 2,5%, а Ларри - на 2,5% ниже.
Если вы хотите заменить переменную точек точками отсечения для 2,5 процентов, просто оставьте последний оператор фильтра следующим образом:
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
Вот как я могу это сделать:
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
, который вы хотите.
Ответ 3
Возможно, это не подходит для ваших данных, но я собираюсь попытаться создать общее решение, чтобы вы начали думать. Я предлагаю использовать надежную статистику, такую как медианное и медианное абсолютное отклонение (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.