easystats / insight

:crystal_ball: Easy access to model information for various model objects
https://easystats.github.io/insight/
GNU General Public License v3.0
404 stars 39 forks source link

Overflow `export_table(format = "text")` #951

Closed mattansb closed 3 weeks ago

mattansb commented 1 month ago

Currently, very-wide tables just get printed as is, which causes ugly over-flows in the console.

E.g.,:

library(performance)

data(HolzingerSwineford1939, package = "lavaan")
structure <- " visual  =~ x1 + x2 + x3
               textual =~ x4 + x5 + x6
               speed   =~ x7 + x8 + x9 "
model1 <- lavaan::cfa(structure, data = HolzingerSwineford1939)
model2 <- lavaan::cfa(structure, data = HolzingerSwineford1939)

(cp <- compare_performance(model1, model2))
#> When comparing models, please note that probably not all models were fit
#>   from same data.
#> # Comparison of Model Performance Indices
#> 
#> Name   |  Model | Chi2(24) | p (Chi2) | Baseline(36) | p (Baseline) |   GFI |  AGFI |   NFI |  NNFI |   CFI | RMSEA |    RMSEA  CI | p (RMSEA) |   RMR |  SRMR |   RFI |  PNFI |   IFI |   RNI | Loglikelihood |  AIC (weights) |  BIC (weights) | BIC_adjusted
#> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#> model1 | lavaan |   85.306 |   < .001 |      918.852 |       < .001 | 0.943 | 0.894 | 0.907 | 0.896 | 0.931 | 0.092 | [0.07, 0.11] |    < .001 | 0.082 | 0.065 | 0.861 | 0.605 | 0.931 | 0.931 |     -3737.745 | 7517.5 (0.500) | 7595.3 (0.500) |     7528.739
#> model2 | lavaan |   85.306 |   < .001 |      918.852 |       < .001 | 0.943 | 0.894 | 0.907 | 0.896 | 0.931 | 0.092 | [0.07, 0.11] |    < .001 | 0.082 | 0.065 | 0.861 | 0.605 | 0.931 | 0.931 |     -3737.745 | 7517.5 (0.500) | 7595.3 (0.500) |     7528.739

Created on 2024-10-30 with reprex v2.1.1

Which actually looks like this:

image

It would be nice if export_table(format = "text") could premptivly detect that this overflow will happen and "split" the table nicly.

An output like this perhaps:

{
  cp2 <- cbind(row = seq_len(nrow(cp)), cp)

  insight::export_table(cp2[,c(1, 2:8)]) |> print()
  cat("\n")
  insight::export_table(cp2[,c(1, 9:16)]) |> print()
  cat("\n")
  insight::export_table(cp2[,c(1, 17:24)]) |> print()
  cat("\n")
  insight::export_table(cp2[,c(1, 25:30)]) |> print()
}
#> row |   Name |  Model |  Chi2 | Chi2_df |   p_Chi2 | Baseline | Baseline_df
#> ---------------------------------------------------------------------------
#>   1 | model1 | lavaan | 85.31 |      24 | 8.50e-09 |   918.85 |          36
#>   2 | model2 | lavaan | 85.31 |      24 | 8.50e-09 |   918.85 |          36
#> 
#> row | p_Baseline |  GFI | AGFI |  NFI | NNFI |  CFI | RMSEA | RMSEA_CI_low
#> --------------------------------------------------------------------------
#>   1 |          0 | 0.94 | 0.89 | 0.91 | 0.90 | 0.93 |  0.09 |         0.07
#>   2 |          0 | 0.94 | 0.89 | 0.91 | 0.90 | 0.93 |  0.09 |         0.07
#> 
#> row | RMSEA_CI_high |  p_RMSEA |  RMR | SRMR |  RFI | PNFI |  IFI |  RNI
#> ------------------------------------------------------------------------
#>   1 |          0.11 | 6.61e-04 | 0.08 | 0.07 | 0.86 | 0.60 | 0.93 | 0.93
#>   2 |          0.11 | 6.61e-04 | 0.08 | 0.07 | 0.86 | 0.60 | 0.93 | 0.93
#> 
#> row | Loglikelihood |     AIC | AIC_wt |     BIC | BIC_wt | BIC_adjusted
#> ------------------------------------------------------------------------
#>   1 |      -3737.74 | 7517.49 |   0.50 | 7595.34 |   0.50 |      7528.74
#>   2 |      -3737.74 | 7517.49 |   0.50 | 7595.34 |   0.50 |      7528.74
strengejacke commented 1 month ago

See argument table_width.

mattansb commented 1 month ago

😱 Why isn't table_width = "auto" the default?

Also, it seems to "give up" after 3 "rows"?

library(performance)

data(HolzingerSwineford1939, package = "lavaan")
structure <- " visual  =~ x1 + x2 + x3
               textual =~ x4 + x5 + x6
               speed   =~ x7 + x8 + x9 "
model1 <- lavaan::cfa(structure, data = HolzingerSwineford1939)
model2 <- lavaan::cfa(structure, data = HolzingerSwineford1939)

compare_performance(model1, model2) |> 
  insight::export_table(table_width = "auto")
#> When comparing models, please note that probably not all models were fit
#>   from same data.
#> Name   |  Model |  Chi2 | Chi2_df |   p_Chi2 | Baseline | Baseline_df
#> ---------------------------------------------------------------------
#> model1 | lavaan | 85.31 |      24 | 8.50e-09 |   918.85 |          36
#> model2 | lavaan | 85.31 |      24 | 8.50e-09 |   918.85 |          36
#> 
#> Name   | p_Baseline |  GFI | AGFI |  NFI | NNFI |  CFI | RMSEA | RMSEA_CI_low
#> -----------------------------------------------------------------------------
#> model1 |          0 | 0.94 | 0.89 | 0.91 | 0.90 | 0.93 |  0.09 |         0.07
#> model2 |          0 | 0.94 | 0.89 | 0.91 | 0.90 | 0.93 |  0.09 |         0.07
#> 
#> Name   | RMSEA_CI_high |  p_RMSEA |  RMR | SRMR |  RFI | PNFI |  IFI |  RNI | Loglikelihood |     AIC | AIC_wt |     BIC | BIC_wt | BIC_adjusted
#> ------------------------------------------------------------------------------------------------------------------------------------------------
#> model1 |          0.11 | 6.61e-04 | 0.08 | 0.07 | 0.86 | 0.60 | 0.93 | 0.93 |      -3737.74 | 7517.49 |   0.50 | 7595.34 |   0.50 |      7528.74
#> model2 |          0.11 | 6.61e-04 | 0.08 | 0.07 | 0.86 | 0.60 | 0.93 | 0.93 |      -3737.74 | 7517.49 |   0.50 | 7595.34 |   0.50 |      7528.74

Created on 2024-10-30 with reprex v2.1.1

strengejacke commented 1 month ago

It's hard-coded at the moment: https://github.com/easystats/insight/blob/3092f82b446fb0f13e94c76512389bc02bd4dc88/R/export_table.R#L548

It's not set to auto, because sometimes a horizontal scrollbar is better than a split table, when it's just one column too wide.

mattansb commented 1 month ago

Got it!

library(performance)

data(HolzingerSwineford1939, package = "lavaan")
structure <- " visual  =~ x1 + x2 + x3
               textual =~ x4 + x5 + x6
               speed   =~ x7 + x8 + x9 "
model1 <- lavaan::cfa(structure, data = HolzingerSwineford1939)
model2 <- lavaan::cfa(structure, data = HolzingerSwineford1939)

compare_performance(model1, model2) |> 
  insight::export_table(table_width = 50)
#> When comparing models, please note that probably not all models were fit
#>   from same data.
#> Name   |  Model |  Chi2 | Chi2_df |   p_Chi2
#> --------------------------------------------
#> model1 | lavaan | 85.31 |      24 | 8.50e-09
#> model2 | lavaan | 85.31 |      24 | 8.50e-09
#> 
#> Baseline | Baseline_df | p_Baseline |  GFI | AGFI
#> -------------------------------------------------
#>   918.85 |          36 |          0 | 0.94 | 0.89
#>   918.85 |          36 |          0 | 0.94 | 0.89
#> 
#>  NFI | NNFI |  CFI | RMSEA | RMSEA_CI_low
#> -----------------------------------------
#> 0.91 | 0.90 | 0.93 |  0.09 |         0.07
#> 0.91 | 0.90 | 0.93 |  0.09 |         0.07
#> 
#> RMSEA_CI_high |  p_RMSEA |  RMR | SRMR |  RFI
#> ---------------------------------------------
#>          0.11 | 6.61e-04 | 0.08 | 0.07 | 0.86
#>          0.11 | 6.61e-04 | 0.08 | 0.07 | 0.86
#> 
#> PNFI |  IFI |  RNI | Loglikelihood |     AIC
#> --------------------------------------------
#> 0.60 | 0.93 | 0.93 |      -3737.74 | 7517.49
#> 0.60 | 0.93 | 0.93 |      -3737.74 | 7517.49
#> 
#> AIC_wt |     BIC | BIC_wt | BIC_adjusted
#> ----------------------------------------
#>   0.50 | 7595.34 |   0.50 |      7528.74
#>   0.50 | 7595.34 |   0.50 |      7528.74

Created on 2024-10-30 with reprex v2.1.1

strengejacke commented 3 weeks ago

There seems to be a critical bug: your code crashes this example:

library(parameters)
 data("attitude")
model <- lm(rating ~ ., data = attitude)
newmodel <- reduce_parameters(model)
out <- model_parameters(newmodel)
insight::export_table(out)

(I found a similar example today, let me see if I can reproduce it)

I think it has to do with special / non-syntactic column names.

strengejacke commented 3 weeks ago

I'm not sure, but it seems that in this code:

    if (row_width > line_width) {
      final_extra <- list(final)
      e <- 1
      while (nchar(paste(utils::tail(final_extra, 1)[[1]][1, ], collapse = sep), type = "width") > line_width && e <= length(final_extra)) { # nolint
        .final_temp <- final_extra[[e]]

        i <- 1
        # determine how many columns fit into the first line
        while (nchar(paste(.final_temp[1, 1:i], collapse = sep), type = "width") < line_width) {
          i <- i + 1
        }
        # copy first column, and all columns that did not fit into the first line
        # into the second table matrix
        if (i < ncol(.final_temp)) {
          final_extra[[e]] <- .final_temp[, 1:(i - 1), drop = FALSE]
          final_extra[[e + 1]] <- .final_temp[, c(1, i:ncol(.final_temp)), drop = FALSE]
        }
        e <- e + 1
      }

      final <- final_extra[[1]]
      if (length(final_extra) > 1) {
        final_extra <- final_extra[-1]
      } else {
        final_extra <- NULL
      }
    }
  }

e is always increasing by 1 (as intended), but the length of final_extra, too. Thus, this condition to stop the loop: e <= length(final_extra) is never reached.

strengejacke commented 3 weeks ago

Ok, it happens when the first column is so wide that the first and second column don't fit into one row. Then, the second to last columns are always moved into a new table (infinite loop), because it never happens that the 2nd to last columns will be cut from the final_extra.