使用 dplyr::do 按组拟合多个模型时处理错误的正确方法

Proper way of dealing with errors when fitting many models by group with dplyr::do

使用dplyr::do,可以非常简单地按组拟合多个模型,如下所示:

library(tidyverse)
set.seed(100)
tbl <- tibble(
  group_id = rep(1:3, each = 10),
  y1 = rnorm(30),
  y2 = runif(30),
  x1 = rnorm(30),
  x2 = runif(30)
)

tbl %>%
  group_by(group_id) %>%
  do(
    model1 = lm(y1 ~ x1 + x2, data = .),
    model2 = lm(y2 ~ x1 + x2, data = .)
  )
#> Source: local data frame [3 x 3]
#> Groups: <by row>
#> 
#> # A tibble: 3 x 3
#>   group_id model1   model2  
#> *    <int> <list>   <list>  
#> 1        1 <S3: lm> <S3: lm>
#> 2        2 <S3: lm> <S3: lm>
#> 3        3 <S3: lm> <S3: lm>

这是用于 broom::tidybroom::glance 以按组提取 r.squared 和系数的理想格式。但是,当一组 group_id == 3 具有所有缺失值时会出现问题:

tbl2 <- mutate(tbl, y2 = c(runif(20), rep(NA, 10)))

tbl2 %>%
  group_by(group_id) %>%
  do(
    model1 = lm(y1 ~ x1 + x2, data = .),
    model2 = lm(y2 ~ x1 + x2, data = .)
  )
#> Error in lm.fit(x, y, offset = offset, singular.ok = singular.ok, ...): 0 (non-NA) cases

正如预期的那样,因为 group_id == 3 没有 y2 的非缺失值,model2 无法拟合任何内容。我发现的其他问题建议在拟合之前简单地删除具有 NA 值的行,但是我不想这样做,因为那样我会失去 model1 的成功拟合。我想到的另一种方法是用 try 捕获错误,但我无法只用缺失值替换错误。我尝试了以下使用 purrr::modify_if 的代码的许多变体,但不知道为什么不替换该值(例如,

modify_if(list(1, "a", TRUE), ~ inherits(., "numeric"), `is.na<-`)

工作正常。)您可以看到使用 mapinherits 正确地发现了哪个单元格是 class try-error,但将其包裹在 modify_if 让它不再被发现。

tbl2 %>%
  group_by(group_id) %>%
  do(
    model1 = lm(y1 ~ x1 + x2, data = .),
    model2 = try(
      lm(y2 ~ x1 + x2, data = .),
      silent = TRUE
    )
  ) %>%
  ungroup() %>%
  mutate_all(
    function(col) map_lgl(col, function(cell) inherits(cell, "try-error"))
  )
#> # A tibble: 3 x 3
#>   group_id model1 model2
#>   <lgl>    <lgl>  <lgl> 
#> 1 FALSE    FALSE  FALSE 
#> 2 FALSE    FALSE  FALSE 
#> 3 FALSE    FALSE  TRUE

tbl2 %>%
  group_by(group_id) %>%
  do(
    model1 = lm(y1 ~ x1 + x2, data = .),
    model2 = try(
      lm(y2 ~ x1 + x2, data = .),
      silent = TRUE
    )
  ) %>%
  ungroup() %>%
  mutate_at(
    .vars = vars(starts_with("model_")),
    .funs = function(col) {
      modify_if(
        .x = col,
        .p = function(cell) inherits(cell, "try-error"),
        .f = function(cell) unclass(`is.na<-`(cell)))
    }
  )
#> # A tibble: 3 x 3
#>   group_id model1   model2         
#> *    <int> <list>   <list>         
#> 1        1 <S3: lm> <S3: lm>       
#> 2        2 <S3: lm> <S3: lm>       
#> 3        3 <S3: lm> <S3: try-error>

reprex package (v0.2.0) 创建于 2018-04-17。

我的实际数据有~80k组和~10个模型供参考。非常感谢任何改进此代码的建议或更好的捕获错误的方法。

我认为这是我找到的处理此问题的最佳方法。与其使用 modify 尝试替换错误模型,不如将它们过滤掉并替换 glance 之后缺失的行。这是因为 glance 无论如何都不能很好地处理格式错误的 lm 输出。

tbl2 %>%
  group_by(group_id) %>%
  do(
    model1 = lm(y1 ~ x1 + x2, data = .),
    model2 = try(
      lm(y2 ~ x1 + x2, data = .),
      silent = TRUE
    )
  ) %>%
  ungroup() %>%
  gather(model, lm, starts_with("model")) %>%
  mutate(error = map_lgl(lm, ~inherits(., "try-error"))) %>%
  filter(error == FALSE) %>%
  rowwise() %>%
  glance(lm) %>%
  ungroup() %>%
  complete(group_id = 1:3, model = c("model1", "model2"))
#> # A tibble: 6 x 14
#>   group_id model  error r.squared adj.r.squared  sigma statistic p.value
#>      <int> <chr>  <lgl>     <dbl>         <dbl>  <dbl>     <dbl>   <dbl>
#> 1        1 model1 FALSE    0.0215       -0.258   0.629    0.0769   0.927
#> 2        1 model2 FALSE    0.107        -0.149   0.329    0.418    0.674
#> 3        2 model1 FALSE    0.208        -0.0184  0.868    0.919    0.442
#> 4        2 model2 FALSE    0.0808       -0.182   0.362    0.308    0.745
#> 5        3 model1 FALSE    0.0707       -0.195   0.738    0.266    0.774
#> 6        3 model2 NA      NA            NA      NA       NA       NA    
#> # ... with 6 more variables: df <int>, logLik <dbl>, AIC <dbl>, BIC <dbl>,
#> #   deviance <dbl>, df.residual <int>