Broom Невозможно обработать список вложенных lm-моделей, если они завернуты в Trycatch или безопасно

Я пытался подогнать линейную модель к большому набору данных (~ 45 миллионов групп по 3 точки на группу). Из-за огромного размера моего набора данных обязательно будут случаи, когда соответствие модели будет отключено. Следовательно, я получаю сообщение об ошибке, связанной с NA в моей модели lm, например:

Error in lm.fit(x, y, offset = offset, singular.ok = singular.ok, ...) : 
  0 (non-NA) cases

Проблема возникает, когда я пытаюсь обернуть lm вокруг TryCatch или Salely, чтобы поймать и обработать случайные ошибки в процессе подгонки.

Я пробовал пока несколько подходов. Здесь я даю вам фиктивный набор данных:

Чтобы обойти эту ошибку, я попробовал несколько методов. Вы можете возразить, что самый простой способ - сделать сгруппированный фильтр и исключить все наборы данных, полные НА (что я тоже пытался сделать, но, по-видимому, есть некоторые, у которых есть проблемы другого рода, из-за которых моя модель выдает ошибки).

Следовательно, поскольку я хочу иметь надежный поток, применимый к многим типам данных, я пытаюсь написать что-то довольно общее и отказоустойчивое.

Более того, я надеялся использовать multidplyr для распараллеливания, поэтому пошел по пути dplyr. Вот в чем проблема. Как только я оборачиваюсь вокруг либо tryCatch, либо safe, даже если я открою позже broom: tidy не сможет обработать результат. (что является большим обломом, так как это облегчает мою жизнь, поскольку мне не нужно все время кодировать по-лаппийски)

Случай 1: Простая пленка подходит стандартным образом плюс комбинируется с метлой. Все хорошо

library(tidyverse)

df.h= tibble(
  hour  =  factor(c("1_1","1_1","1_1")),
  price =  c(3.235536,3.205588, 3.235930),
  wind  =  c(-2.302585, 3.871201, 5.123964)
)

dfHour = df.h2 %>% group_by(hour) %>%
  do(fitHour = lm(price ~ wind, data = .))

dfHourCoef = broom::tidy(dfHour, fitHour)

Введите набор данных с ошибками, чтобы вызвать ошибку в lm:

library(tidyverse)

df.h2= tibble(
  hour  =  factor(c("1_1","1_1","1_1","1_2","1_2","1_2")),
  price =  c(3.235536,3.205588, 3.235930, 3.235536,3.205588, 3.235930),
  wind  =  c(-2.302585, 3.871201, 5.123964, NA, NA, NA)
)


dfHour2 = df.h2 %>% group_by(hour) %>%
  do(fitHour = tryCatch( lm(myy ~ myx, data = . ), error= function(e){return("FAILURE")} ) ) %>%
  filter(!is.character(fitHour)) # Exploit the fact that all good outputs are 
                                 # a list while faulty output is a character
                                 # to perform filtering

# get the coefficients by group in a tidy data_frame
dfHourCoef2 = broom::tidy(dfHour2, fitHour)

Это вызывает ошибку типа:

Error in .[[object]][[1]] : subscript out of bounds

Способ третий: безопасное завершение, чтобы улавливать сообщения об ошибках


library(tidyverse)

df.h2= tibble(
  hour  =  factor(c("1_1","1_1","1_1","1_2","1_2","1_2")),
  price =  c(3.235536,3.205588, 3.235930, 3.235536,3.205588, 3.235930),
  wind  =  c(-2.302585, 3.871201, 5.123964, NA, NA, NA)
)

test_dataset_lm <- df.h2 %>%
  mutate_if(is.factor, droplevels) %>%    # this is used to exclude leftover
                                          # factor levels from previous 
                                          # processing in the flow
  group_by( hour ) %>%
  do(fitHour = safely(lm)(price ~ wind, data = .)) %>%
  unnest() %>%
  group_by(hour) %>%
  mutate(id = str_c("fitHour_", row_number() ) ) %>%  # Exploit that the 
                                                      # $error list is always 
                                                      # in the second position of every 
                                                      # output / modelfit trial
  spread(id, fitHour) %>%
  filter( fitHour_2 == "NULL" ) %>%
  rename(fitHour = fitHour_1) %>%
  select(-fitHour_2) %>% ungroup() %>%
  broom::tidy()

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

Ошибка, которую дает этот подход в сочетании с tidy ():

Error: evaluation nested too deeply: infinite recursion / options(expressions=)?
Error during wrapup: evaluation nested too deeply: infinite recursion / options(expressions=)?

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

Не могли бы вы помочь мне понять источник моей проблемы?

Спасибо!


person Marios Barlas    schedule 05.08.2019    source источник
comment
Первый блок выдает ошибку dfHourCoef = broom::tidy(dfHour, fitHour)>Error in broom::tidy(dfHour, fitHour) : object 'dfHour' not found   -  person Grada Gukovic    schedule 05.08.2019
comment
Извините, опечатка. Приходится заменять df.h2 на df.h в исполнении lm! dfHour = df.h2 %>% group_by(hour) %>% do(fitHour = lm(price ~ wind, data = .))   -  person Marios Barlas    schedule 05.08.2019


Ответы (1)


Вот один из способов исправить метод safely

library(tidyverse)
df.h2 %>%
  mutate_if(is.factor, droplevels) %>%    # this is used to exclude leftover
  # factor levels from previous 
  # processing in the flow
  group_by( hour ) %>%
  do(fitHour = safely(lm)(price ~ wind, data = .)) %>% 
  #Create a new column to check if 'result' in each fitHour element is missing/null
  mutate(Ind_null = map_lgl(fitHour['result'], is.null)) %>%
  filter(!Ind_null) %>%
  mutate(fit = list(tidy(fitHour[['result']]))) %>% 
  unnest(fit)

# A tibble: 2 x 6
   hour  term        estimate std.error statistic p.value
  <fct> <chr>          <dbl>     <dbl>     <dbl>   <dbl>
1 1_1   (Intercept)  3.23      0.0162    200.    0.00319
2 1_1   wind        -0.00152   0.00411    -0.370 0.775  

Обновление: (более короткий путь) с использованием purrr::map_df

lm_safe <- safely(lm)
df.h2 %>%
mutate_if(is.factor, droplevels) %>%  
split( .$hour ) %>% 
map_df(~tidy(lm_safe(price ~ wind, data = .)[['result']]), .id = 'hour')
person A. Suliman    schedule 05.08.2019
comment
Спасибо! Это отличное решение :) так что, по сути, map_lgl отображает вектор списков? тогда запись fitHour ['result'] напрямую нацелена на этот вложенный список, вы в основном отменяете вложенность результата? Где я могу узнать больше о вложенных списках и какие функции их обрабатывают? (как настроить / обработать и т. д.) Еще раз спасибо! - person Marios Barlas; 05.08.2019
comment
Я нацеливаю / тестирую компонент result на каждый элемент fitHour и выбираю этот элемент, если result не равен null, также мы можем сделать map_lgl(fitHour[1], is.null) как результат всегда первый компонент. Вот простой пример, df.h2 %>% mutate_if(is.factor, droplevels) %>% group_by( hour ) %>% do(fitHour = safely(lm)(price ~ wind, data = .)) -> lst, исследуйте lst в своем совете, затем выполните map_lgl(lst$fitHour, ~is.null(.x[[1]])) / map_lgl(lst$fitHour, ~is.null(.x[['result']])), чтобы увидеть результат. - person A. Suliman; 05.08.2019
comment
У Дженни Брайан есть хорошая серия purrr пакета tidyverse для обработки и работы со списками руководств по адресу следующую ссылку - person A. Suliman; 05.08.2019
comment
@MariosBarlas, пожалуйста, проверьте мое обновление, которое, я думаю, значительно сократит ваше время вычислений. - person A. Suliman; 05.08.2019
comment
Очень интересное решение! map_df естественным образом пропустит пустые фреймы данных из гнезда $ result, поскольку они пустые, верно? Я протестировал ваше решение на распараллеленном коде с набором данных из 1,8 миллиона точек (600 тыс. Точек), и оно работает на 100% быстрее, чем стандартная версия. В 8-ядерном параллельном кластере он дает 8 минут против 14 для полного запуска. - person Marios Barlas; 07.08.2019
comment
Я рад, что это помогает, и действительно, у вас есть интересные настройки. Фактически, tidy является здесь ключом к успеху, поскольку tidy обрабатывает результаты NULL очень эффективно, создавая тиббл 0X0 -попробуй tidy(NULL)-, теперь попробуйте df.h2 %>% mutate_if(is.factor, droplevels) %>% split( .$hour ) %>% map(~tidy(lm_safe(price ~ wind, data = .)[['result']]), .id = 'hour'), поэтому map_df просто объедините или свяжите строки этих тиблей. - person A. Suliman; 07.08.2019