Есть ли более простой способ вручную рассчитать оценочные средние значения группы с помощью model.matrix?

Я хочу рассчитать предполагаемые средние баллы группы в гауссовой регрессии 2x2 после получения коэффициентов регрессии. Вот данные игрушки. По 100 наблюдений региона - a и b - и пола - m и f. Я разработал оценки таким образом, чтобы разница между регионами a и b в среднем составляла 5 баллов, но между m и f разницы не было.

set.seed(1234)

d <- data.frame(region = factor(rep(letters[1:2],each=100)),
                sex = factor(rep(c("m", "f"),times=100)),
                score = round(x = c(rnorm(100, mean = 5, sd = 1),
                                    rnorm(100, mean = 10, sd = 1)),
                              digits = 1))

Теперь я буду использовать функцию model.matrix() для получения коэффициентов контраста для каждого наблюдения в зависимости от его принадлежности к группе. Я буду использовать кодирование лечения, то есть [0,1] с указанием региона a и пола m в качестве контрольных уровней для каждого из них.

model.matrix(object = score ~ region*sex,
             data = d,
             contrasts.arg = list(region = contr.treatment(nlevels(d$region)),
                                  sex = contr.treatment(nlevels(d$region)))) -> cmTreat

Теперь мы можем использовать матрицу модели непосредственно в регрессии с помощью функции lm(). Мы указываем 0 + terms, потому что матрица модели уже содержит перехват.

(lm(d$score ~ 0 + cmTreat) -> lmTreat)

# output
# Call:
#   lm(formula = d$score ~ 0 + cmTreat)
# 
# Coefficients:
# cmTreat(Intercept)       cmTreatregion2          cmTreatsex2  cmTreatregion2:sex2  
#              4.814                5.132                0.056                0.140 

Регрессия выявила основные эффекты и взаимодействия. Но что, если мы хотим получить оценочные предельные средние значения, а именно оценочное среднее значение в каждой «ячейке» 2 x 2: регион a — женский, регион a — мужской, регион b — женский, регион b — мужской.

Мы можем сделать это вручную с помощью атрибутов матрицы модели.

treatCoefs <- coef(lmTreat) # assign the vector of coefficients a name

# mean in region a female: intercept[1] + region[0] + sex[0] + region[0]*sex[0]
regionA_f <- treatCoefs[1] + treatCoefs[2]*attr(cmTreat, which = "contrasts")$region[,1][1] + treatCoefs[3]*attr(cmTreat, which = "contrasts")$sex[,1][1] + treatCoefs[4]*attr(cmTreat, which = "contrasts")$region[,1][1]*attr(cmTreat, which = "contrasts")$sex[,1][1]

# mean in region a male: intercept[1] + region[0] + sex[1] + region[0]*sex[1]
regionA_m <- treatCoefs[1] + treatCoefs[2]*attr(cmTreat, which = "contrasts")$region[,1][1] + treatCoefs[3]*attr(cmTreat, which = "contrasts")$sex[,1][2] + treatCoefs[4]*attr(cmTreat, which = "contrasts")$region[,1][1]*attr(cmTreat, which = "contrasts")$sex[,1][2]

# mean in region b female: : intercept[1] + region[1] + sex[0] + region[1]*sex[0]
regionB_f <- treatCoefs[1] + treatCoefs[2]*attr(cmTreat, which = "contrasts")$region[,1][2] + treatCoefs[3]*attr(cmTreat, which = "contrasts")$sex[,1][1] + treatCoefs[4]*attr(cmTreat, which = "contrasts")$region[,1][2]*attr(cmTreat, which = "contrasts")$sex[,1][1]

# mean in group b male: intercept[1] + region[1] + sex[1] + region[1]*sex[1]
regionB_m <-treatCoefs[1] + treatCoefs[2]*attr(cmTreat, which = "contrasts")$region[,1][2] + treatCoefs[3]*attr(cmTreat, which = "contrasts")$sex[,1][2] + treatCoefs[4]*attr(cmTreat, which = "contrasts")$region[,1][2]*attr(cmTreat, which = "contrasts")$sex[,1][2]

Теперь, если мы сравним фактические средние значения группы с расчетными средними (извиняюсь, люди, не занимающиеся этим вопросом)…

(library(dplyr)
d %>%
  group_by(region, sex) %>%
    summarise(actualMean = mean(score)) %>%
      add_column(estMeans = c(regionA_f, regionA_m, regionB_f, regionB_m))

# # A tibble: 4 × 4
# # Groups:   region [2]
# region  sex    actualMean estMeans
# <fct>   <fct>        <dbl>    <dbl>
# 1 a      f           4.81     4.81
# 2 a      m           4.87     4.87
# 3 b      f           9.95     9.95
# 4 b      m           10.1     10.1

Так что это отлично работает. "В чем проблема?" Я слышал, ты спрашиваешь. Итак, вы видели, сколько кода потребовалось для получения оценочных средних значений для каждой группы. И я могу это сделать. Но мне было интересно: «Есть ли более простой способ сделать это вручную?».

Я знаю, что могу использовать превосходный пакет emmeans Расса Лента и использую его часто, но мне хотелось научиться делать это вручную более элегантным способом. Я ничего не знаю о матричной алгебре и мало что знаю о контрастных матрицах. Я просто не могу избавиться от ощущения, что существует лучший способ (тот, метод которого может лучше адаптироваться к различным конструкциям и уровням).

п.с. этот вопрос, возможно, лучше подходил для перекрестной проверки, но я решил сначала попробовать здесь, поскольку он достаточно специфичен, чтобы гарантировать публикацию на SO.

Что вы подразумеваете под более простым способом сделать это вручную? Я думаю, вы ищете predict(), хотя использовать его немного сложнее, когда модель включает в себя матрицу.

lroha 05.08.2024 05:42

Спасибо за ответ @lroha. Проблема в том, что я не знаю того, чего я не знаю. Возможно, нет более простого способа сделать это; всегда предполагается, что все делают это проще. Мне просто интересно, есть ли какая-нибудь матричная алгебра, которую я мог бы использовать для решения подобных задач, когда вам нужно включать и выключать индикаторные переменные. Очевидно, это звучит очень невежественно, но я невежественен. Возможно, пора получить степень по биостатистике

llewmills 05.08.2024 09:24

Для этого простого примера: res <- unique(d[, c("sex", "region")]); res$emm <- predict(lm(score ~ sex * region, data = d), newdata = res)

Roland 05.08.2024 09:34
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как я уже упоминал в комментариях выше, функция predict() — это то, что вам, кажется, нужно, но когда вы писали о выполнении действий вручную, было не совсем ясно, хотите ли вы избегать использования прогнозирования или нет.

Вы можете использовать expand.grid(), чтобы создать набор данных комбинаций факторов для использования с predict() (убедитесь, что уровни факторов находятся в том же порядке, что и в модели):

(grid <- expand.grid(region = factor(c("a", "b")), sex = factor(c("m", "f"))))

  region sex
1      a   m
2      b   m
3      a   f
4      b   f

lm(score ~ region * sex, d) |> 
  predict(newdata = grid) |> 
  cbind(grid, pred = _)

  region sex   pred
1      a   m  4.870
2      b   m 10.142
3      a   f  4.814
4      b   f  9.946

Однако в вашем примере вы использовали матрицу проектирования непосредственно в модели, поэтому нам нужно убедиться, что мы также передаем фрейм данных, содержащий матрицу той же ширины, в predict(). Мы можем создать это с помощью model.matrix().

(design <- model.matrix(~ region * sex, grid))

  (Intercept) regionb sexm regionb:sexm
1           1       0    1            0
2           1       1    1            1
3           1       0    0            0
4           1       1    0            0
attr(,"assign")
[1] 0 1 2 3
attr(,"contrasts")
attr(,"contrasts")$region
[1] "contr.treatment"

attr(,"contrasts")$sex
[1] "contr.treatment"

cbind(grid,
      pred = predict(lmTreat, data.frame(cmTreat = I(design))))

  region sex   pred
1      a   m  4.870
2      b   m 10.142
3      a   f  4.814
4      b   f  9.946

Мы также можем рассчитать это без использования predict() через:

cbind(grid, pred = rowSums(design * coef(lmTreat)[col(design)]))

  region sex   pred
1      a   m  4.870
2      b   m 10.142
3      a   f  4.814
4      b   f  9.946

Другие вопросы по теме