Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Collate:
'report.stanreg.R'
'report.brmsfit.R'
'report.character.R'
'report.check_outliers.R'
'report.compare.loo.R'
Comment on lines 117 to 121
'report.compare_performance.R'
'report.coxph.R'
Expand All @@ -135,6 +136,7 @@ Collate:
'report.test_performance.R'
'report.zeroinfl.R'
'report_ai.R'
'report_assumptions.R'
'report_effectsize.R'
'report_htest_chi2.R'
'report_htest_cor.R'
Expand Down Expand Up @@ -162,3 +164,4 @@ Collate:
'zzz.R'
Roxygen: list(markdown = TRUE)
Config/roxygen2/version: 8.0.0
RoxygenNote: 7.3.3
4 changes: 3 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ S3method(report,bayesfactor_inclusion)
S3method(report,bayesfactor_models)
S3method(report,brmsfit)
S3method(report,character)
S3method(report,check_outliers)
S3method(report,compare.loo)
S3method(report,compare_performance)
S3method(report,coxph)
Expand Down Expand Up @@ -247,6 +248,7 @@ S3method(report_text,bayesfactor_inclusion)
S3method(report_text,bayesfactor_models)
S3method(report_text,brmsfit)
S3method(report_text,character)
S3method(report_text,check_outliers)
S3method(report_text,compare_performance)
S3method(report_text,coxph)
S3method(report_text,data.frame)
Expand Down Expand Up @@ -305,7 +307,7 @@ export(is.report)
export(print_html)
export(print_md)
export(report)
export(report_ai)
export(report_assumptions)
export(report_date)
Comment thread
DominiqueMakowski marked this conversation as resolved.
export(report_effectsize)
export(report_info)
Expand Down
14 changes: 14 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
# report (devel)

New features

* `report_assumptions()`: new function that reports model assumption checks for a model, covering influential observations (via `performance::check_outliers()`) and homoskedasticity (via `performance::check_heteroskedasticity()`). Calling `summary()` on the result returns a compact one-sentence version integrating all checks. Supports `audience = "ai"` for a compact token-efficient structured output.
Comment thread
DominiqueMakowski marked this conversation as resolved.
Outdated

* `report()` / `report_text()` for linear models: assumption checks (from `report_assumptions()`) are now automatically included at the end of the report. For human audience, the compact one-sentence summary is appended; for AI audience, a structured `## Assumptions` section is added to the output.
Comment thread
DominiqueMakowski marked this conversation as resolved.
Outdated

Bug fixes

* Fixed missing `@exportS3Method` roxygen2 tags on `report_ai` S3 methods (`default`, `lm`, `glm`, `merMod`, `glmmTMB`), which caused warnings during package loading.

* `report_text()`: new method for `performance::check_outliers()` output, producing a brief sentence such as "2 observations (5.88%) were detected as potential outliers based on Cook's distance (threshold = 0.806)."

Bug fixes

* Fixed an issue in `report()` where the reference level for logical predictors was incorrectly displayed as `[?]` instead of `FALSE` for the intercept (@M-Colley, #598).

* Fixed the *AI-Optimized Reports* vignette to no longer refer to the internal `report_ai()` function; all examples now use `report(x, audience = "ai")`.

# report 0.6.4

New features
Expand Down
112 changes: 112 additions & 0 deletions R/report.check_outliers.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#' Reporting outlier detection results
#'
#' Create a brief textual report for outlier detection results from
#' [performance::check_outliers()].
#'
#' @param x Object of class `check_outliers` as returned by
#' [performance::check_outliers()].
#' @inheritParams report
#'
#' @inherit report return seealso
#'
#' @examplesIf requireNamespace("performance", quietly = TRUE)
#' \donttest{
#' library(report)
#' library(performance)
#'
#' mt2 <- rbind(
#' mtcars[, c("mpg", "disp", "hp")],
#' data.frame(mpg = c(37, 40), disp = c(300, 400), hp = c(110, 120))
#' )
#' model <- lm(disp ~ mpg + hp, data = mt2)
#' rez <- performance::check_outliers(model)
#' report(rez)
#' }
#'
#' @return An object of class [report_text()].
#' @export
report_text.check_outliers <- function(x, ...) {
n_outliers <- sum(x)
n_total <- length(x)
pct <- n_outliers / n_total

methods <- attr(x, "method")
thresholds <- attr(x, "threshold")

method_labels <- .format_outlier_method_names(methods)

method_parts <- vapply(
seq_along(methods),
function(i) {
m <- methods[i]
thresh <- thresholds[[m]]
if (!is.null(thresh) && length(thresh) == 1L && !is.na(thresh)) {
paste0(
method_labels[i],
" (threshold = ",
insight::format_value(thresh, digits = 3),
")"
)
} else {
method_labels[i]
}
},
character(1L)
)

method_str <- paste(method_parts, collapse = " and ")

if (n_outliers == 0L) {
text <- "No observations were detected as potential outliers."
} else if (n_outliers == 1L) {
text <- paste0(
"1 observation (",
insight::format_value(pct, as_percent = TRUE),
") was detected as a potential outlier based on ",
method_str,
"."
)
} else {
text <- paste0(
n_outliers,
" observations (",
insight::format_value(pct, as_percent = TRUE),
") were detected as potential outliers based on ",
method_str,
"."
)
}

as.report_text(text)
}


#' @rdname report_text.check_outliers
#' @export
report.check_outliers <- function(x, ...) {
result_text <- report_text(x, ...)
as.report(text = result_text, ...)
}


# Helper ------------------------------------------------------------------

.format_outlier_method_names <- function(methods) {
method_map <- c(
cook = "Cook's distance",
mahalanobis = "Mahalanobis distance",
mahalanobis_robust = "robust Mahalanobis distance",
zscore = "z-score",
zscore_robust = "robust z-score",
iqr = "IQR",
ci = "confidence intervals",
optics = "OPTICS",
iforest = "Isolation Forest",
lof = "Local Outlier Factor",
mcd = "MCD"
)
labels <- method_map[methods]
# Fall back to the raw method name for any unknown method
labels[is.na(labels)] <- methods[is.na(labels)]
unname(labels)
}
27 changes: 25 additions & 2 deletions R/report.lm.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#' @param include_diagnostic If `FALSE`, won't include diagnostic related
#' indices for Bayesian models (ESS, Rhat).
#' @param include_intercept If `FALSE`, won't include the intercept.
#' @param assumptions If `FALSE`, assumption checks (see
#' [report_assumptions()]) are skipped and not included in the report.
#' Defaults to `TRUE`.
#' @param effectsize_method See documentation for
#' [effectsize::effectsize()].
#' @param parameters Provide the output of `report_parameters()` to avoid
Expand Down Expand Up @@ -55,6 +58,7 @@ report.lm <- function(
x,
include_effectsize = TRUE,
effectsize_method = "refit",
assumptions = TRUE,
...
) {
result_table <- report_table(
Expand All @@ -63,7 +67,12 @@ report.lm <- function(
effectsize_method = effectsize_method,
...
)
result_text <- report_text(x, table = result_table, ...)
result_text <- report_text(
x,
table = result_table,
assumptions = assumptions,
...
)

as.report(result_text, table = result_table, ...)
}
Expand Down Expand Up @@ -729,7 +738,7 @@ report_info.lm <- function(

#' @rdname report.lm
#' @export
report_text.lm <- function(x, table = NULL, ...) {
report_text.lm <- function(x, table = NULL, assumptions = TRUE, ...) {
params <- report_parameters(x, table = table, include_intercept = FALSE, ...)
report_table_data <- attributes(params)$table

Expand All @@ -753,6 +762,18 @@ report_text.lm <- function(x, table = NULL, ...) {
)
}

# --- Assumptions (requires performance) ---------------------------------
assumptions_summary <- ""
if (isTRUE(assumptions) && requireNamespace("performance", quietly = TRUE)) {
assumptions_obj <- tryCatch(
report_assumptions(x),
error = function(e) NULL
)
if (!is.null(assumptions_obj)) {
assumptions_summary <- as.character(summary(assumptions_obj))
}
}

# Helpers
sep_after <- function(x) {
x <- trimws(x)
Expand All @@ -773,6 +794,7 @@ report_text.lm <- function(x, table = NULL, ...) {
"We fitted a ",
model,
". ",
if (nzchar(assumptions_summary)) paste0(assumptions_summary, " ") else "",
perf,
sep_after(perf),
intercept,
Expand All @@ -787,6 +809,7 @@ report_text.lm <- function(x, table = NULL, ...) {
"We fitted a ",
summary(model),
". ",
if (nzchar(assumptions_summary)) paste0(assumptions_summary, " ") else "",
summary(perf),
sep_after(summary(perf)),
summary(intercept),
Expand Down
18 changes: 17 additions & 1 deletion R/report_ai.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ report_ai <- function(x, ...) {
UseMethod("report_ai")
}

#' @exportS3Method
report_ai.default <- function(x, ...) {
insight::format_warning(
paste0(
Expand All @@ -14,16 +15,22 @@ report_ai.default <- function(x, ...) {
report(x, ..., audience = "humans")
}

#' @exportS3Method
report_ai.lm <- function(x, ...) {
.report_ai_models(x, ...)
}

report_ai.glm <- report_ai.lm
#' @exportS3Method
report_ai.glm <- function(x, ...) {
.report_ai_models(x, ...)
}

#' @exportS3Method
report_ai.merMod <- function(x, ...) {
.report_ai_models(x, ...)
}

#' @exportS3Method
report_ai.glmmTMB <- function(x, ...) {
.report_ai_models(x, ...)
}
Expand Down Expand Up @@ -79,6 +86,8 @@ report_ai.glmmTMB <- function(x, ...) {
} else {
desc_str <- "- No variables found."
}
# Remove any leading blank lines introduced by the report() header
desc_str <- sub("^\\n+", "", desc_str)

params <- parameters::model_parameters(x, ...)

Expand Down Expand Up @@ -153,6 +162,12 @@ report_ai.glmmTMB <- function(x, ...) {
perf_markdown <- insight::export_table(perf_table, format = "markdown")
perf_str <- paste(perf_markdown, collapse = "\n")

# Assumptions (requires performance; silently skipped if unsupported)
assumptions_str <- tryCatch(
as.character(report_assumptions(x, audience = "ai")),
error = function(e) NULL
)

if ("p" %in% names(fixed_params) && "Parameter" %in% names(fixed_params)) {
sig_effects <- fixed_params$Parameter[
!is.na(fixed_params$p) &
Expand Down Expand Up @@ -221,6 +236,7 @@ report_ai.glmmTMB <- function(x, ...) {
"\n\n",
"## Variables\n",
desc_str,
if (!is.null(assumptions_str)) paste0("\n\n", assumptions_str) else "",
"\n\n",
param_section,
"\n\n",
Expand Down
Loading
Loading