Есть несколько вариантов, доступных при работе с гетероскедастическими данными. К сожалению, ни один из них не гарантированно всегда работает. Вот несколько вариантов, с которыми я знаком:
- преобразования
- Уэлч АНОВА
- взвешенные наименьшие квадраты
- устойчивая регрессия
- гетероскедастичность соответствует стандартным ошибкам
- начальная загрузка
- Тест Крускала-Уоллиса
- порядковая логистическая регрессия
Обновление: Вот демонстрация R
некоторых способов подгонки линейной модели (например, ANOVA или регрессии), когда у вас есть гетероскедастичность / гетерогенность дисперсии.
Давайте начнем с просмотра ваших данных. Для удобства я загрузил их в два названных фрейма данных my.data
(который структурирован, как указано выше, с одним столбцом на группу) и stacked.data
(который имеет два столбца: values
с числами и ind
с индикатором группы).
Мы можем официально проверить на гетероскедастичность с помощью теста Левена:
library(car)
leveneTest(values~ind, stacked.data)
# Levene's Test for Homogeneity of Variance (center = median)
# Df F value Pr(>F)
# group 2 8.1269 0.001153 **
# 38
Конечно же, у вас есть гетероскедастичность. Мы проверим, каковы дисперсии групп. Эмпирическое правило заключается в том, что линейные модели достаточно устойчивы к неоднородности дисперсии, если максимальная дисперсия не более чем в Раза превышает минимальную дисперсию, поэтому мы также найдем это соотношение: 4×
apply(my.data, 2, function(x){ var(x, na.rm=T) })
# A B C
# 0.01734578 0.33182844 0.06673060
var(my.data$B, na.rm=T) / var(my.data$A, na.rm=T)
# [1] 19.13021
Ваши дисперсии существенно отличаются, с самым большим, B
, будучи самый маленький . Это проблемный уровень гетероскедсатичности. 19×A
Вы думали использовать преобразования, такие как лог или квадратный корень, чтобы стабилизировать дисперсию. В некоторых случаях это будет работать, но преобразования типа Бокса-Кокса стабилизируют дисперсию, сжимая данные асимметрично, либо сжимая их вниз с максимальными сжимаемыми данными, либо сжимая их вверх с наименьшими сжимаемыми данными наиболее. Таким образом, вам нужно, чтобы дисперсия ваших данных изменялась со средним значением для оптимальной работы. Ваши данные имеют огромную разницу в дисперсии, но относительно небольшую разницу между средними и средними значениями, т. Е. Распределения в основном перекрываются. В качестве учебного упражнения мы можем создать некоторые parallel.universe.data
, добавив ко всем значениям и к.72.7B
.7C
Чтобы показать, как это будет работать:
parallel.universe.data = with(my.data, data.frame(A=A, B=B+2.7, C=C+.7))
apply(parallel.universe.data, 2, function(x){ var(x, na.rm=T) })
# A B C
# 0.01734578 0.33182844 0.06673060
apply(log(parallel.universe.data), 2, function(x){ var(x, na.rm=T) })
# A B C
# 0.12750634 0.02631383 0.05240742
apply(sqrt(parallel.universe.data), 2, function(x){ var(x, na.rm=T) })
# A B C
# 0.01120956 0.02325107 0.01461479
var(sqrt(parallel.universe.data$B), na.rm=T) /
var(sqrt(parallel.universe.data$A), na.rm=T)
# [1] 2.074217
Использование преобразования квадратного корня довольно хорошо стабилизирует эти данные. Вы можете увидеть улучшение для данных параллельной вселенной здесь:
Вместо того, чтобы просто пытаться использовать разные преобразования, более систематический подход заключается в оптимизации параметра Бокса-Кокса (хотя обычно рекомендуется округлить его до ближайшего интерпретируемого преобразования). В вашем случае допустимы либо квадратный корень , либо лог , хотя на самом деле ни один из них не работает. Для данных параллельной вселенной лучше использовать квадратный корень: λ = .5 λ = 0λλ=.5λ=0
boxcox(values~ind, data=stacked.data, na.action=na.omit)
boxcox(values~ind, data=stacked.pu.data, na.action=na.omit)
Поскольку этот случай представляет собой ANOVA (т. Е. Не имеет непрерывных переменных), один из способов справиться с неоднородностью - это использовать поправку Уэлча к знаменателям степеней свободы в тесте (nb , вместо дробного значения ): Fdf = 19.445
df = 38
oneway.test(values~ind, data=stacked.data, na.action=na.omit, var.equal=FALSE)
# One-way analysis of means (not assuming equal variances)
#
# data: values and ind
# F = 4.1769, num df = 2.000, denom df = 19.445, p-value = 0.03097
Более общий подход заключается в использовании взвешенных наименьших квадратов . Поскольку некоторые группы ( B
) разбросаны больше, данные в этих группах предоставляют меньше информации о расположении среднего значения, чем данные в других группах. Мы можем позволить модели включить это, предоставляя вес для каждой точки данных. Обычной системой является использование обратной величины групповой дисперсии в качестве веса:
wl = 1 / apply(my.data, 2, function(x){ var(x, na.rm=T) })
stacked.data$w = with(stacked.data, ifelse(ind=="A", wl[1],
ifelse(ind=="B", wl[2], wl[3])))
w.mod = lm(values~ind, stacked.data, na.action=na.omit, weights=w)
anova(w.mod)
# Response: values
# Df Sum Sq Mean Sq F value Pr(>F)
# ind 2 8.64 4.3201 4.3201 0.02039 *
# Residuals 38 38.00 1.0000
Это дает немного отличающиеся значения и от невзвешенного ANOVA ( , ), но оно хорошо учитывает неоднородность: pFp4.5089
0.01749
Однако взвешенные наименьшие квадраты не являются панацеей. Один неприятный факт заключается в том, что весовые коэффициенты верны, и это означает, что они известны априори. Он также не учитывает ненормальность (например, перекос) или выбросы. Использование весов, оцененных по вашим данным, часто будет работать нормально, особенно если у вас достаточно данных для оценки отклонения с разумной точностью (это аналогично идее использования таблицы вместо таблицы, когда у вас есть илиt 50 100 Nzt50100степеней свободы), ваши данные достаточно нормальны, и у вас, похоже, нет никаких выбросов. К сожалению, у вас относительно мало данных (13 или 15 на группу), некоторые искажены и, возможно, некоторые выбросы. Я не уверен, что они достаточно плохи, чтобы из них что-то сделать, но вы могли бы смешать взвешенные наименьшие квадраты с помощью надежных методов. Вместо того, чтобы использовать дисперсию в качестве меры разброса (которая чувствительна к выбросам, особенно с низким ), вы можете использовать обратную величину интерквартильного диапазона (на которую не влияют до 50% выбросов в каждой группе). Эти веса можно затем объединить с устойчивой регрессией, используя другую функцию потерь, такую как биквадрат Тьюки: N
1 / apply(my.data, 2, function(x){ var(x, na.rm=T) })
# A B C
# 57.650907 3.013606 14.985628
1 / apply(my.data, 2, function(x){ IQR(x, na.rm=T) })
# A B C
# 9.661836 1.291990 4.878049
rw = 1 / apply(my.data, 2, function(x){ IQR(x, na.rm=T) })
stacked.data$rw = with(stacked.data, ifelse(ind=="A", rw[1],
ifelse(ind=="B", rw[2], rw[3])))
library(robustbase)
w.r.mod = lmrob(values~ind, stacked.data, na.action=na.omit, weights=rw)
anova(w.r.mod, lmrob(values~1,stacked.data,na.action=na.omit,weights=rw), test="Wald")
# Robust Wald Test Table
#
# Model 1: values ~ ind
# Model 2: values ~ 1
# Largest model fitted by lmrob(), i.e. SM
#
# pseudoDf Test.Stat Df Pr(>chisq)
# 1 38
# 2 40 6.6016 2 0.03685 *
Вес здесь не такой экстремальный. Прогнозируемые средства группы незначительно отличаются ( A
: WLS 0.36673
, надежным 0.35722
; B
: ВМНК 0.77646
, надежным 0.70433
; C
: WLS 0.50554
, надежный 0.51845
), с помощью B
и C
того меньше тянут экстремальных значения.
В эконометрике очень популярна стандартная ошибка Хубера-Уайта («сэндвич») . Как и поправка Уэлча, это не требует, чтобы вы знали априорные отклонения, и не требует, чтобы вы оценивали веса по вашим данным и / или зависели от модели, которая может быть неверной. С другой стороны, я не знаю, как включить это в ANOVA, а это означает, что вы получаете их только для тестов отдельных фиктивных кодов, что в данном случае кажется мне менее полезным, но я все равно продемонстрирую их:
library(sandwich)
mod = lm(values~ind, stacked.data, na.action=na.omit)
sqrt(diag(vcovHC(mod)))
# (Intercept) indB indC
# 0.03519921 0.16997457 0.08246131
2*(1-pt(coef(mod) / sqrt(diag(vcovHC(mod))), df=38))
# (Intercept) indB indC
# 1.078249e-12 2.087484e-02 1.005212e-01
Функция vcovHC
рассчитывает гетероскедастичную согласованную дисперсионно-ковариационную матрицу для ваших бета-версий (фиктивных кодов), то есть то, что обозначают буквы в функции. Чтобы получить стандартные ошибки, вы извлекаете основную диагональ и берете квадратные корни. Чтобы получить тесты для ваших бета-версий, вы делите оценки коэффициентов на SE и сравниваете результаты с соответствующим распределением (а именно, распределением с вашими остаточными степенями свободы). т тttt
R
Специально для пользователей @TomWenseleers отмечает в комментариях ниже, что функция ? Anova в car
пакете может принимать white.adjust
аргумент для получения значения для фактора, использующего ошибки, согласованные с гетероскедастичностью. p
Anova(mod, white.adjust=TRUE)
# Analysis of Deviance Table (Type II tests)
#
# Response: values
# Df F Pr(>F)
# ind 2 3.9946 0.02663 *
# Residuals 38
# ---
# Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Вы можете попытаться получить эмпирическую оценку того, как выглядит фактическое распределение выборки вашей тестовой статистики с помощью начальной загрузки . Во-первых, вы создаете истинный ноль, делая все группы равными. Затем вы производите повторную выборку с заменой и рассчитываете свою статистику теста ( ) для каждой начальной загрузки, чтобы получить эмпирическую оценку распределения выборки под нулевым значением с вашими данными независимо от их статуса в отношении нормальности или однородности. Доля того распределения выборки, которое является таким же экстремальным или более экстремальным, чем наблюдаемая вами статистика теста, является значением: F pFFp
mod = lm(values~ind, stacked.data, na.action=na.omit)
F.stat = anova(mod)[1,4]
# create null version of the data
nullA = my.data$A - mean(my.data$A)
nullB = my.data$B - mean(my.data$B, na.rm=T)
nullC = my.data$C - mean(my.data$C, na.rm=T)
set.seed(1)
F.vect = vector(length=10000)
for(i in 1:10000){
A = sample(na.omit(nullA), 15, replace=T)
B = sample(na.omit(nullB), 13, replace=T)
C = sample(na.omit(nullC), 13, replace=T)
boot.dat = stack(list(A=A, B=B, C=C))
boot.mod = lm(values~ind, boot.dat)
F.vect[i] = anova(boot.mod)[1,4]
}
1-mean(F.stat>F.vect)
# [1] 0.0485
В некотором смысле, самозагрузка является окончательным подходом с уменьшенным допущением для проведения анализа параметров (например, средних), но она предполагает, что ваши данные являются хорошим представлением совокупности, то есть у вас есть разумный размер выборки. Так как ваши маленькие, это может быть менее надежным. Вероятно, окончательная защита от ненормальности и неоднородности состоит в использовании непараметрического теста. Основной непараметрической версией ANOVA является тест Крускала-Уоллиса : n
kruskal.test(values~ind, stacked.data, na.action=na.omit)
# Kruskal-Wallis rank sum test
#
# data: values by ind
# Kruskal-Wallis chi-squared = 5.7705, df = 2, p-value = 0.05584
Хотя тест Крускала-Уоллиса, безусловно, является лучшей защитой от ошибок типа I, его можно использовать только с одной категориальной переменной (т. Е. Без непрерывных предикторов или факторных планов), и он имеет наименьшую мощность из всех обсуждаемых стратегий. Другой непараметрический подход заключается в использовании порядковой логистической регрессии . Многим это кажется странным, но вам нужно только предположить, что ваши ответные данные содержат достоверную порядковую информацию, что они, безусловно, делают, иначе любая другая стратегия, указанная выше, также недействительна:
library(rms)
olr.mod = orm(values~ind, stacked.data)
olr.mod
# Model Likelihood Discrimination Rank Discrim.
# Ratio Test Indexes Indexes
# Obs 41 LR chi2 6.63 R2 0.149 rho 0.365
# Unique Y 41 d.f. 2 g 0.829
# Median Y 0.432 Pr(> chi2) 0.0363 gr 2.292
# max |deriv| 2e-04 Score chi2 6.48 |Pr(Y>=median)-0.5| 0.179
# Pr(> chi2) 0.0391
Это может быть не ясно из результатов, но проверка модели в целом, которая в данном случае является проверкой ваших групп, является chi2
ниже Discrimination Indexes
. Перечислены две версии: тест отношения правдоподобия и тест оценки. Тест отношения правдоподобия обычно считается лучшим. Это дает значение . p0.0363