Другое

Исправление ошибки модели случайного перехвата в nlme

Узнайте, как исправить ошибку «subscript out of bounds» при подгонке моделей случайного перехвата с nlme в R. Полное руководство с примерами кода и решениями.

Как исправить ошибку с моделью случайного перехвата, используя nlme и пользовательскую функцию в R?

Я работаю с пользовательской логистической функцией в R:

r
logistic.fcn <- function(x, y0, y100B, dy100.F, a, x0B, dx0.F, isF_num) {
  y0 + ((y100B + isF_num * dy100.F) - y0) /
    (1 + exp(-a * (x - (x0B + isF_num * dx0.F))))
}

logistic.model <- deriv(
  body(logistic.fcn)[[2]],
  namevec = c("y0", "y100B", "dy100.F", "a", "x0B", "dx0.F"),
  function.arg = logistic.fcn
)

Используя nlme, я хочу подстроить четыре модели с разными случайными эффектами и выбрать лучшую на основе AIC и BIC:

x0B | ID
x0B | ID + y100B | ID
y100B | ID
1 | ID   # модель случайного перехвата

Первые три модели сходятся без проблем, но я получаю ошибку при модели случайного перехвата. Правильно ли я использую синтаксис 1 | ID для модели случайного перехвата в nlme, или мне нужно использовать другой подход? Ошибка, которую я получаю:

Error in fitted.model[[i]] : subscript out of bounds

Ниже приведён мой полный код для подгонки моделей:

r
vars <- c("x0B", "y100B")
label <- c()
for (i in 1:length(vars)) {
  label.i <- vars[i]
  label <- c(label, label.i)
  for (j in (i + 1):length(vars)) {
    if (j > length(vars)) break
    label.j <- sprintf("%s.%s", label.i, vars[j])
    label <- c(label, label.j)
  }
}

label <- c(label, "1")

fitted.model <- list()
frm <- list()
frm.fixed <- "ptlt_logit ~ logistic.model(visibility, y0, y100B, dy100.F, a, x0B, dx0.F, isF_num)"
args.start <- c(
  y0 = y0,
  y100B = y100,
  dy100.F = 0,
  a = a,
  x0B = 0.30,
  dx0.F = 0
)

for (i in seq(1, length(label))) {
  frm[[i]] <- sprintf(
    "%s ~ %s",
    frm.fixed,
    paste(
      sapply(
        strsplit(label[i], "[.]")[[1]],
        function(x) sprintf("(%s|ID)", x),
        USE.NAMES = FALSE
      ),
      collapse = " + "
    )
  )

  cat(sprintf("Model: %s\n", frm[[i]]))

  tol <- .0001
  while (TRUE) {
    if (tol > 10) break
    tryCatch(
      {
        fitted.model[[i]] <- nlmer(
          as.formula(frm[[i]]),
          data = looking_times,
          start = args.start,
          control = nlmerControl(tolPwrss = tol)
        )
        break
      },
      error = function(cond) {
        tol <<- 1.1 * tol
        cat(sprintf("Tolerance: %f\n", tol))
      }
    )
  }

  args.start <- fixef(fitted.model[[i]])
}

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

При подгонке модели со случайным интерцептом с использованием nlme и пользовательской функции ошибка «ошибка индекса за пределами диапазона» обычно возникает из‑за несоответствия спецификации случайных эффектов или проблем с обработкой пользовательской функции. Синтаксис 1 | ID корректен для случайного интерцепта в lme4, но в nlme требуется иной синтаксис и обработка.


Содержание


Понимание ошибки

Ошибка «ошибка индекса за пределами диапазона» в смешанных моделях обычно возникает, когда алгоритм оптимизации пытается обратиться к элементам массива, которые не существуют. Это может произойти по нескольким причинам:

  • Несоответствие спецификации модели: когда структура случайных эффектов не соответствует ожидаемой структуре данных
  • Проблемы с пользовательской функцией: ошибки в том, как deriv() функции обрабатываются в оптимизации
  • Проблемы с сходимостью: оптимизатор выходит за пределы допустимых индексов во время подгонки
  • Проблемы с выделением памяти: недостаточно памяти для сложных структур случайных эффектов

Согласно исследованиям, эта ошибка особенно частая при работе с комплексными пользовательскими функциями в nlme, как отмечено в обсуждении Stack Overflow о нелинейных параметрах, где оптимизатор выходит за пределы диапазона[^1].


Правильный синтаксис случайного интерцепта в nlme vs lme4

Основная проблема в том, что nlme и lme4 используют разный синтаксис для случайных эффектов:

Синтаксис lme4 (что вы используете)

r
# Только случайный интерцепт
1 | ID

# Случайный интерцепт и наклон
(1 + predictor | ID)

Синтаксис nlme (необходимый для nlme/nlmer)

r
# Только случайный интерцепт
random = ~ 1 | ID

# Случайный интерцепт и наклон  
random = ~ predictor | ID

Для вашей модели со случайным интерцептом следует использовать:

r
fitted.model[[i]] <- nlmer(
  ptlt_logit ~ logistic.model(visibility, y0, y100B, dy100.F, a, x0B, dx0.F, isF_num),
  data = looking_times,
  random = ~ 1 | ID,  # Это синтаксис nlme
  start = args.start,
  control = nlmerControl(tolPwrss = tol)
)

Ошибка возникает, потому что nlme ожидает, чтобы аргумент random был указан отдельно, а не внутри строки формулы, как в lme4.


Исправление подхода с пользовательской функцией

Ваш подход с пользовательской функцией требует нескольких корректировок, чтобы корректно работать с nlme:

1. Упростите построение формулы

Вместо сложной конкатенации строк используйте правильную структуру синтаксиса nlme:

r
# Для модели со случайным интерцептом
frm[[i]] <- ptlt_logit ~ logistic.model(visibility, y0, y100B, dy100.F, a, x0B, dx0.F, isF_num)
random_formula <- ~ 1 | ID

fitted.model[[i]] <- nlmer(
  frm[[i]],
  data = looking_times,
  random = random_formula,
  start = args.start,
  control = nlmerControl(tolPwrss = tol)
)

2. Правильно обрабатывайте фиксированные и случайные параметры

В nlme необходимо явно разделять фиксированные и случайные параметры в спецификации. Исследования показывают, что шесть нелинейных параметров могут вызывать проблемы с nlme[^1].

Рассмотрите упрощение модели, например:

  • Сначала обрабатывать меньше параметров как случайные
  • Использовать аргумент fixed явно в nlme::nlme для лучшего контроля

3. Улучшите стартовые значения

Подход args.start <- fixef(fitted.model[[i]]) может вызвать проблемы, если модели не удалось подогнать. Вместо этого:

r
# Используйте стартовые значения предыдущей модели только при успешном выполнении
if (!exists("fitted.model[[i-1]]") || is.null(fitted.model[[i-1]])) {
  args.start <- c(y0 = y0, y100B = y100, dy100.F = 0, a = a, x0B = 0.30, dx0.F = 0)
} else {
  args.start <- fixef(fitted.model[[i-1]])
}

Альтернативные решения и обходные пути

1. Используйте lme4 Вместо

Поскольку вы уже знакомы с синтаксисом lme4, рассмотрите миграцию на lme4::nlmer, который использует тот же синтаксис, что и lmer:

r
library(lme4)

# Модель со случайным интерцептом
fitted.model[[i]] <- nlmer(
  ptlt_logit ~ logistic.model(visibility, y0, y100B, dy100.F, a, x0B, dx0.F, isF_num),
  data = looking_times,
  random = ~ 1 | ID,  # синтаксис lme4 работает здесь тоже
  start = args.start,
  control = nlmerControl(tolPwrss = tol)
)

2. Используйте nlme с правильным синтаксисом

Если вы предпочитаете nlme, синтаксис отличается:

r
library(nlme)

# Модель со случайным интерцептом  
fitted.model[[i]] <- nlme(
  ptlt_logit ~ logistic.model(visibility, y0, y100B, dy100.F, a, x0B, dx0.F, isF_num),
  data = looking_times,
  fixed = y0 + y100B + dy100.F + a + x0B + dx0.F ~ 1,
  random = y0 + y100B + dy100.F + a + x0B + dx0.F ~ 1 | ID,
  start = args.start,
  control = nlmeControl(tolPwrss = tol)
)

3. Упростите структуру случайных эффектов

Исследования показывают, что сложные структуры случайных эффектов с пользовательскими функциями часто вызывают проблемы с сходимостью[^2]. Рассмотрите:

r
# Начните с более простой структуры случайных эффектов
random = ~ 1 | ID  # Только случайный интерцепт

# Затем постепенно добавляйте сложность
random = ~ x0B | ID  # Добавьте случайный наклон для x0B

Лучшие практики для пользовательских функций nlme

1. Проверьте вашу пользовательскую функцию

Тестируйте вашу функцию deriv() чтобы убедиться, что она работает корректно:

r
# Тестируем функцию
test_params <- c(y0 = 0, y100B = 1, dy100.F = 0, a = 1, x0B = 0.5, dx0.F = 0)
test_x <- seq(0, 1, length.out = 10)
test_result <- logistic.model(test_x, test_params[1], test_params[2], test_params[3], 
                             test_params[4], test_params[5], test_params[6], 1)
print(test_result)

2. Используйте подходящие параметры управления

Параметр tolPwrss в nlmerControl может быть слишком агрессивным для сложных моделей:

r
control <- nlmerControl(
  tolPwrss = 1e-6,  # Менее агрессивный порог
  msMaxIter = 100,  # Больше итераций
  pnlsMaxIter = 50, # Больше итераций для PQL
  opt = "nlminb"    # Другой оптимизатор
)

3. Обрабатывайте проблемы структуры данных

Убедитесь, что сгруппированные данные правильно структурированы для nlme:

r
# Преобразуйте в groupedData при необходимости
looking_times_grouped <- groupedData(
  ptlt_logit ~ visibility | ID,
  data = looking_times,
  labels = list(x = "Visibility", y = "Logit Probability")
)

Рекомендации по миграции в lme4

Учитывая сложность вашей пользовательской функции и проблемы с nlme, рассмотрите миграцию на lme4:

1. Установите необходимые пакеты

r
install.packages(c("lme4", "nlme"))

2. Переведите ваш код

Миграция проста, поскольку lme4::nlmer использует похожий синтаксис, как и lme4::lmer:

r
library(lme4)

# Структура цикла остаётся аналогичной
for (i in seq(1, length(label))) {
  # Построим формулу как раньше
  frm[[i]] <- sprintf(
    "ptlt_logit ~ logistic.model(visibility, y0, y100B, dy100.F, a, x0B, dx0.F, isF_num) + %s",
    paste(
      sapply(
        strsplit(label[i], "[.]")[[1]],
        function(x) sprintf("(1 + %s | ID)", x),
        USE.NAMES = FALSE
      ),
      collapse = " + "
    )
  )
  
  # Подгонка с nlmer (lme4)
  fitted.model[[i]] <- nlmer(
    as.formula(frm[[i]]),
    data = looking_times,
    start = args.start,
    control = nlmerControl(tolPwrss = tol)
  )
}

3. Сравнение моделей

Используйте функции AIC() и BIC() для сравнения моделей, которые работают одинаково в обоих пакетах:

r
# Сравнение моделей
model_comparison <- data.frame(
  Model = paste("Model", 1:length(fitted.model)),
  AIC = sapply(fitted.model, AIC),
  BIC = sapply(fitted.model, BIC),
  Converged = sapply(fitted.model, function(x) !is.null(x))
)

print(model_comparison)

Заключение

Ошибка «ошибка индекса за пределами диапазона» в вашей модели со случайным интерцептом возникает из‑за использования синтаксиса lme4 с nlme. Ключевые выводы:

  1. Коррекция синтаксиса: используйте random = ~ 1 | ID вместо включения его в строку формулы для nlme
  2. Выбор пакета: рассмотрите миграцию на lme4::nlmer, если вам удобнее синтаксис lme4
  3. Упрощение модели: сложные случайные эффекты с пользовательскими функциями часто вызывают проблемы с сходимостью
  4. Настройка параметров управления: корректируйте пороги и оптимизаторы для сложных моделей
  5. Корректная структура данных: убедитесь, что данные правильно форматированы для выбранного пакета

Для вашего конкретного случая рекомендую либо:

  • использовать nlme с правильным аргументом random, либо
  • мигрировать на lme4::nlmer, который использует знакомый вам синтаксис

Оба подхода должны устранить ошибку «ошибка индекса за пределами диапазона» и позволить успешно подгонять и сравнивать модели со случайным интерцептом.


Источники

  1. Stack Overflow - Ошибка с моделью случайного интерцепта в nlme и пользовательской функцией
  2. Stack Overflow - Ошибка с nlme
  3. R-help Mailing List - nlme Error: Subscript out of bounds
  4. Cross Validated - Crossed random effects with autoregression
Авторы
Проверено модерацией
Модерация