for (pkg in c("tidyverse", "assertthat", "cli")) {
if (!require(pkg, character.only = TRUE)) install.packages(pkg)
}
library(tidyverse)Um alle in diesem Kapitel verwendeten Pakete zu installieren und zu laden, führt man folgenden Code aus:
Einleitung
Jede Funktion macht Annahmen über ihre Inputs: ein Vektor sollte numerisch sein, ein Dataframe sollte bestimmte Spalten enthalten, ein Wert sollte positiv sein. Wenn diese Annahmen verletzt werden, wirft R entweder einen kryptischen Fehler tief in der Funktion – oder schlimmer noch – produziert stillschweigend falsche Ergebnisse. Beides kostet Debugging-Zeit und untergräbt das Vertrauen.
Man betrachte, was passiert, wenn man eine Faktor-Spalte an eine Funktion übergibt, die Zahlen erwartet:
Warning in mean.default(values): Argument ist weder numerisch noch boolesch:
gebe NA zurück
Warning in Ops.factor(values, mean(values)): '-' ist nicht sinnvoll für
Faktoren
[1] NA
Die Fehlermeldung erwähnt mean() und “not meaningful for factors”, aber das eigentliche Problem ist, dass eine nicht-numerische Spalte übergeben wurde. Mit Input-Validierung fängt man das sofort ab:
Error in summarize_column(iris, "Species"): Column 'Species' must be numeric, but is factor
Kapitel 9 hat stop(), stopifnot() und match.arg() für defensives Programmieren eingeführt. Dieses Kapitel erweitert das Toolkit: warning() und message() für nicht-fatale Signale, das assertthat-Paket für lesbare Assertions, das cli-Paket für formatierte Fehlermeldungen und tryCatch() für fehlertolerante Weiterverarbeitung.
Wiederholung: stop() und stopifnot()
Da Kapitel 9 diese Funktionen ausführlich behandelt hat, hier nur eine kurze Zusammenfassung mit Fokus auf ihre Einschränkungen.
stop() gibt volle Kontrolle über die Fehlermeldung. stopifnot() ist kompakter, aber seine automatisch generierten Meldungen sind schwer lesbar:
validate_proportion <- function(x) {
stopifnot(is.numeric(x))
stopifnot(all(x >= 0 & x <= 1, na.rm = TRUE))
x
}
validate_proportion(c(0.2, 1.5, 0.8))Error in validate_proportion(c(0.2, 1.5, 0.8)): all(x >= 0 & x <= 1, na.rm = TRUE) ist nicht TRUE
Die Meldung all(x >= 0 & x <= 1, na.rm = TRUE) is not TRUE liest sich wie Code statt wie eine Erklärung. Eine benutzerfreundliche Meldung würde sagen “Found values outside the range [0, 1]”. Diese Lesbarkeits-Lücke motiviert die später besprochenen Pakete assertthat und cli.
Als Faustregel: stopifnot() für interne Assertions verwenden, die nur Entwickler sehen, und stop() (oder cli_abort()) für benutzerseitige Validierung, wo die Qualität der Meldung wichtig ist.
warning() und message()
Nicht jedes Problem sollte die Ausführung stoppen. R bietet warning() für Situationen, in denen etwas wahrscheinlich nicht stimmt, und message() für rein informativen Output.
warning(): Etwas könnte nicht stimmen
Eine Warnung signalisiert, dass die Funktion ein Ergebnis produziert hat, der Aufrufer aber über ein mögliches Problem informiert werden sollte:
column_means <- function(data) {
numeric_cols <- data %>% select(where(is.numeric))
na_counts <- numeric_cols %>%
summarize(across(everything(), \(x) sum(is.na(x)))) %>%
pivot_longer(everything(), names_to = "column", values_to = "n_na") %>%
filter(n_na > 0)
if (nrow(na_counts) > 0) {
cols_with_na <- na_counts %>%
mutate(label = glue::glue("{column} ({n_na})")) %>%
pull(label) %>%
paste(collapse = ", ")
warning(glue::glue("NAs removed in: {cols_with_na}"), call. = FALSE)
}
numeric_cols %>%
summarize(across(everything(), \(x) mean(x, na.rm = TRUE)))
}
column_means(airquality)Warning: NAs removed in: Ozone (37), Solar.R (7)
Ozone Solar.R Wind Temp Month Day
1 42.12931 185.9315 9.957516 77.88235 6.993464 15.80392
Das Argument call. = FALSE unterdrückt den Funktionsaufruf in der Warnung, was den Output sauberer macht.
message(): Informativer Output
Eine Message ist rein informativ – nichts ist falsch, man hält nur den Nutzer auf dem Laufenden:
standardize <- function(data) {
n_cols <- sum(sapply(data, is.numeric))
n_skipped <- ncol(data) - n_cols
message(glue::glue("Standardizing {n_cols} numeric columns, skipping {n_skipped} non-numeric"))
data %>%
mutate(across(where(is.numeric), \(x) (x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)))
}
result <- standardize(iris)Standardizing 4 numeric columns, skipping 1 non-numeric
Unterdrücken und das richtige Signal wählen
Nutzer können Warnungen und Messages selektiv unterdrücken mit suppressWarnings() und suppressMessages(). Das funktioniert nur, weil warning() und message() separate Signalmechanismen verwenden – hätte man stattdessen cat() benutzt, gäbe es keine Möglichkeit, den Output programmatisch zu unterdrücken.
Die Wahl zwischen stop(), warning() und message() hängt vom Schweregrad ab:
| Signal | Fatal? | Verwenden wenn… |
|---|---|---|
stop() |
Ja | Die Funktion kann kein valides Ergebnis produzieren |
warning() |
Nein | Das Ergebnis könnte problematisch sein |
message() |
Nein | Alles ist in Ordnung, nur zur Info |
Ein praktischer Test: Wenn jemand die Funktion in suppressWarnings() einwickelt und ein falsches Ergebnis bekommt, hätte das Signal ein Fehler sein sollen, keine Warnung.
Man schreibe eine Funktion safe_mean(), die einen numerischen Vektor x entgegennimmt, prüft ob er tatsächlich numerisch ist (mit Fehler abbrechen falls nicht), eine Warnung ausgibt falls NAs vorhanden sind (mit Angabe der Anzahl), und den Mittelwert mit na.rm = TRUE zurückgibt.
Zum Testen: airquality$Ozone (37 NAs) und c(1, 2, 3) (keine NAs).
safe_mean <- function(x) {
if (!is.numeric(x)) {
stop(glue::glue("x must be numeric, not {class(x)[1]}"), call. = FALSE)
}
n_na <- sum(is.na(x))
if (n_na > 0) {
warning(glue::glue("{n_na} NA value(s) removed before computing mean"), call. = FALSE)
}
mean(x, na.rm = TRUE)
}
safe_mean(airquality$Ozone)Warning: 37 NA value(s) removed before computing mean
[1] 42.12931
safe_mean(c(1, 2, 3))[1] 2
Strukturierte Validierung mit assertthat
Das assertthat-Paket liegt zwischen stopifnot() (kompakt aber kryptisch) und stop() (lesbar aber wortreich). Seine assert_that()-Funktion funktioniert wie stopifnot(), erzeugt aber menschenlesbare Fehlermeldungen:
library(assertthat)
validate_proportion <- function(x) {
assert_that(is.numeric(x))
assert_that(all(x >= 0 & x <= 1, na.rm = TRUE))
x
}
validate_proportion("hello")Error: x is not a numeric or integer vector
Eingebaute Hilfsfunktionen
Das Paket bietet Typ-Prüfungsfunktionen mit klaren Fehlermeldungen:
assert_that(is.string("hello")) # single character string[1] TRUE
assert_that(is.number(42)) # single numeric value[1] TRUE
assert_that(is.flag(TRUE)) # single logical value[1] TRUE
assert_that(has_name(mtcars, "mpg")) # name exists in object[1] TRUE
assert_that(not_empty(c(1, 2, 3))) # non-empty[1] TRUE
# Failure produces a clear message
assert_that(has_name(mtcars, "horsepower"))Error: mtcars does not have all of these name(s): 'horsepower'
see_if() und on_failure()
see_if() prüft eine Bedingung ohne die Ausführung zu stoppen und gibt TRUE/FALSE mit einem Message-Attribut zurück. on_failure() ermöglicht es, eigene Fehlermeldungen für selbst geschriebene Prüffunktionen zu definieren.
Der on_failure()-Mechanismus nutzt Metaprogrammierung, um Fehlermeldungen anzupassen. Dies ist eine Nischenfunktion, die die meisten Anwender nicht benötigen:
is_positive <- function(x) is.numeric(x) && all(x > 0, na.rm = TRUE)
on_failure(is_positive) <- function(call, env) {
n_bad <- sum(eval(call$x, env) <= 0, na.rm = TRUE)
glue::glue("{deparse(call$x)} contains {n_bad} non-positive value(s)")
}
assert_that(is_positive(c(1, -2, 3, -4)))Error: c(1, -2, 3, -4) contains 2 non-positive value(s)
Das assertthat-Paket befindet sich im Wartungsmodus (letztes CRAN-Update 2019). Für neuen Code empfehlen sich cli::cli_abort() und rlang::abort() – sie bieten reichhaltigere Fehlermeldungen mit Inline-Formatierung und werden aktiv weiterentwickelt.
Informative Fehler mit cli
Das cli-Paket bietet modernen, formatierten Output für R. Seine Funktionen cli_abort(), cli_warn() und cli_inform() ersetzen stop(), warning() und message() mit zwei Vorteilen: Inline-Markup und automatische Wertformatierung.
Grundlegende Verwendung
Die cli-Funktionen akzeptieren einen Character-Vektor, bei dem jedes Element eine Zeile wird. Benannte Elemente bekommen spezielle Bullet-Präfixe – "x" für Probleme, "i" für Info, "!" für Warnungen:
library(cli)
validate_age <- function(age) {
if (!is.numeric(age)) {
cli_abort(c(
"{.arg age} must be numeric.",
"x" = "You supplied a {.cls {class(age)}} vector."
))
}
if (any(age < 0 | age > 150, na.rm = TRUE)) {
cli_abort(c(
"{.arg age} must be between 0 and 150.",
"i" = "Found {sum(age < 0 | age > 150, na.rm = TRUE)} out-of-range value(s)."
))
}
age
}
validate_age("twenty")Error in `validate_age()`:
! `age` must be numeric.
✖ You supplied a <character> vector.
validate_age(c(25, 30, -5, 200))Error in `validate_age()`:
! `age` must be between 0 and 150.
ℹ Found 2 out-of-range value(s).
Inline Markup
Geschweifte Klammern formatieren Werte entsprechend ihrer Rolle in der Meldung:
| Markup | Zweck | Markup | Zweck |
|---|---|---|---|
{.arg name} |
Argument | {.val value} |
Ein Wert |
{.var name} |
Variable | {.cls class} |
Klassenname |
{.code code} |
Code-Snippet | {.fn name} |
Funktionsname |
Der ?-Operator übernimmt die Pluralisierung, und R-Ausdrücke werden genau wie in glue::glue() interpoliert:
Error in post_process_plurals(pstr, values): Cannot pluralize without a quantity
cli_warn() und cli_inform()
Dasselbe Markup funktioniert auch für Warnungen und Messages:
Warning: Division by zero encountered.
ℹ 1 element of `y` is zero.
ℹ Returning Inf for those positions.
[1] 5 Inf 6
Der folgende BMI-Rechner verwendet einfache stop()-Meldungen. Man schreibe die Validierung um, sodass cli_abort() mit Inline-Markup verwendet wird. Jeder Fehler sollte eine Kopfzeile und mindestens einen "x"-Bullet haben.
calc_bmi <- function(weight_kg, height_m) {
if (!is.numeric(weight_kg)) stop("weight_kg must be numeric")
if (!is.numeric(height_m)) stop("height_m must be numeric")
if (length(weight_kg) != length(height_m)) stop("Lengths must match")
if (any(weight_kg <= 0, na.rm = TRUE)) stop("weight_kg must be positive")
if (any(height_m <= 0, na.rm = TRUE)) stop("height_m must be positive")
weight_kg / height_m^2
}Zum Testen: calc_bmi("80", 1.80), calc_bmi(c(70, 80), c(1.70, 1.75, 1.80)) und calc_bmi(c(70, -5), c(1.70, 1.80)).
calc_bmi <- function(weight_kg, height_m) {
if (!is.numeric(weight_kg)) {
cli_abort(c(
"{.arg weight_kg} must be numeric.",
"x" = "You supplied a {.cls {class(weight_kg)}} vector."
))
}
if (!is.numeric(height_m)) {
cli_abort(c(
"{.arg height_m} must be numeric.",
"x" = "You supplied a {.cls {class(height_m)}} vector."
))
}
if (length(weight_kg) != length(height_m)) {
cli_abort(c(
"{.arg weight_kg} and {.arg height_m} must have the same length.",
"x" = "{.arg weight_kg} has {length(weight_kg)} element{?s}, {.arg height_m} has {length(height_m)}."
))
}
if (any(weight_kg <= 0, na.rm = TRUE)) {
cli_abort(c(
"{.arg weight_kg} must contain only positive values.",
"x" = "Found {sum(weight_kg <= 0, na.rm = TRUE)} non-positive value{?s}."
))
}
if (any(height_m <= 0, na.rm = TRUE)) {
cli_abort(c(
"{.arg height_m} must contain only positive values.",
"x" = "Found {sum(height_m <= 0, na.rm = TRUE)} non-positive value{?s}."
))
}
weight_kg / height_m^2
}
calc_bmi("80", 1.80)Error in `calc_bmi()`:
! `weight_kg` must be numeric.
✖ You supplied a <character> vector.
Error in `calc_bmi()`:
! `weight_kg` and `height_m` must have the same length.
✖ `weight_kg` has 2 elements, `height_m` has 3.
tryCatch() und withCallingHandlers()
Die bisherigen Werkzeuge dienen dem Erzeugen von Fehlern und Warnungen. Manchmal muss man sie jedoch behandeln – Fehler abfangen und fehlertolerant weiterarbeiten, statt abzustürzen.
Grundmuster
tryCatch() wertet einen Ausdruck aus und führt eine Handler-Funktion aus, wenn eine Condition ausgelöst wird:
Praxisbeispiel: Robustes Datei-Einlesen
Warning: Could not read 'nonexistent_file.csv': 'nonexistent_file.csv' does not
exist in current working directory:
'C:/Users/PaulSchmidt-BioMathG/AppData/Local/Temp/RtmpeIZo1W/filee82c241717ec/content/r_more'.
resultNULL
Die Funktion gibt NULL zurück statt abzustürzen, was sie sicher in Pipelines verwendbar macht, in denen einige Dateien fehlen könnten.
Warnungen und mehrere Conditions behandeln
Man kann Handler für verschiedene Condition-Typen registrieren:
[1] 2.302585
carefully(log(-1))WARNING: NaNs wurden erzeugt
carefully(log("abc"))ERROR: Nicht-numerisches Argument für mathematische Funktion
Zu beachten ist, dass wenn ein warning-Handler in tryCatch() feuert, er das Abschließen des ursprünglichen Ausdrucks verhindert. Wenn man Warnungen protokollieren und trotzdem das Ergebnis erhalten möchte, verwendet man stattdessen withCallingHandlers():
Anders als tryCatch() führt withCallingHandlers() den Handler aus, ohne die ursprüngliche Berechnung abzubrechen. Das ist nützlich, wenn man Warnungen sammeln und trotzdem das Ergebnis erhalten möchte. Der <<--Operator weist einer Variablen in der übergeordneten Umgebung zu, und invokeRestart("muffleWarning") unterdrückt die Warnung nach dem Protokollieren:
logged_warnings <- character(0)
result <- withCallingHandlers(
{
x <- as.numeric(c("1", "abc", "3"))
mean(x, na.rm = TRUE)
},
warning = function(w) {
logged_warnings <<- c(logged_warnings, w$message)
invokeRestart("muffleWarning")
}
)
result[1] 2
logged_warnings[1] "NAs durch Umwandlung erzeugt"
Für die meisten Anwendungsfälle reicht tryCatch() aus. withCallingHandlers() braucht man nur, wenn die Ausführung nach einer Warnung fortgesetzt werden soll.
Brücke zu purrr
Wenn man über viele Elemente iteriert, führt Kapitel 11 safely() und possibly() aus purrr ein – Convenience-Wrapper um tryCatch(), die für die Verwendung mit map() konzipiert sind:
Man schreibe eine Funktion try_read_csv(), die einen Dateipfad entgegennimmt, versucht ihn mit read_csv() einzulesen, und NULL mit einer message() zurückgibt, falls die Datei nicht gelesen werden kann.
Zum Testen: eine temporäre CSV-Datei mit write_csv() und tempfile() erstellen, dann die Funktion sowohl mit der echten Datei als auch mit einem falschen Pfad aufrufen.
try_read_csv <- function(path) {
tryCatch(
read_csv(path, show_col_types = FALSE),
error = function(e) {
message(glue::glue("Failed to read '{path}': {e$message}"))
NULL
}
)
}
# Create a temporary test file
tmp_file <- tempfile(fileext = ".csv")
write_csv(mtcars %>% head(5), tmp_file)
# Test with existing file
try_read_csv(tmp_file)# A tibble: 5 × 11
mpg cyl disp hp drat wt qsec vs am gear carb
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 21 6 160 110 3.9 2.62 16.5 0 1 4 4
2 21 6 160 110 3.9 2.88 17.0 0 1 4 4
3 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1
4 21.4 6 258 110 3.08 3.22 19.4 1 0 3 1
5 18.7 8 360 175 3.15 3.44 17.0 0 0 3 2
# Test with non-existent file
try_read_csv("this_file_does_not_exist.csv")Failed to read 'this_file_does_not_exist.csv': 'this_file_does_not_exist.csv' does not exist in current working directory: 'C:/Users/PaulSchmidt-BioMathG/AppData/Local/Temp/RtmpeIZo1W/filee82c241717ec/content/r_more'.
NULL
# Clean up
file.remove(tmp_file)[1] TRUE
Vergleichstabelle
| Funktion | Fatal? | Eigene Meldung? | Paket | Am besten für |
|---|---|---|---|---|
stop() |
Ja | Ja (manuell) | base | Einfache, klare Fehler |
stopifnot() |
Ja | Nein (auto) | base | Schnelle interne Assertions |
warning() |
Nein | Ja (manuell) | base | Nicht-fatale Probleme |
message() |
Nein | Ja (manuell) | base | Informativer Output |
assert_that() |
Ja | Semi-auto | assertthat | Lesbare Typ-Prüfungen |
cli_abort() |
Ja | Ja (formatiert) | cli | Benutzerseitige Fehler mit Markup |
cli_warn() |
Nein | Ja (formatiert) | cli | Benutzerseitige Warnungen mit Markup |
cli_inform() |
Nein | Ja (formatiert) | cli | Benutzerseitige Messages mit Markup |
tryCatch() |
N/A | N/A | base | Fehler abfangen und recovern |
Für neuen Code empfiehlt sich eine praktische Kombination: cli_abort() / cli_warn() für benutzerseitige Funktionen, stopifnot() für interne Prüfungen und tryCatch() für fehlertolerante Weiterverarbeitung.
Best Practices
Am Funktionsrand validieren. Input-Validierung gehört an den Anfang einer Funktion, vor jegliche Berechnung. Dieses “Fail Fast”-Prinzip bedeutet, dass die Funktion sofort mit einer klaren Meldung stoppt, anstatt teure Arbeit zu verrichten und später mit einer kryptischen Meldung zu scheitern.
Spezifisch beschreiben, was schiefgegangen ist. Eine gute Fehlermeldung beantwortet zwei Fragen: was ist falsch und was wurde erwartet. Man vergleiche stop("Invalid input") mit cli_abort("{.arg weight_kg} must be numeric, not {.cls {class(weight_kg)}}.") – die zweite Variante sagt dem Aufrufer genau, wie das Problem zu beheben ist.
Den richtigen Schweregrad verwenden. Fehler für Probleme, die ein valides Ergebnis verhindern, Warnungen für Situationen, in denen das Ergebnis valide aber potenziell unerwartet ist, und Messages für rein informativen Output.
Interne Hilfsfunktionen nicht über-validieren. Gründliche Validierung sollte öffentlichen Funktionen vorbehalten bleiben. Interne Helfer, die nur aus dem eigenen validierten Code aufgerufen werden, können ihren Inputs vertrauen:
# Public function - validates inputs
calculate_stats <- function(data, col) {
if (!is.data.frame(data)) cli_abort("{.arg data} must be a data frame.")
if (!col %in% names(data)) cli_abort("Column {.val {col}} not found in {.arg data}.")
values <- data[[col]]
list(center = compute_center(values), spread = compute_spread(values))
}
# Internal helper - no validation needed
compute_center <- function(x) {
c(mean = mean(x, na.rm = TRUE), median = median(x, na.rm = TRUE))
}Für Menschen formatieren. cli für benutzerseitige Meldungen verwenden, wo Lesbarkeit zählt, und stopifnot() für entwicklerseitige Assertions, wo Kürze wichtig ist.
Zusammenfassung
Dieses Kapitel hat das Spektrum der Input-Validierungs- und Fehlerbehandlungs-Werkzeuge in R behandelt.
Signal-Schweregrad:
stop()/cli_abort()für fatale Fehler,warning()/cli_warn()für nicht-fatale Probleme,message()/cli_inform()für informativen Output.Früh validieren: Inputs am Anfang von benutzerseitigen Funktionen prüfen (“Fail Fast”). Interne Helfer, die aus bereits validiertem Code aufgerufen werden, können ihren Inputs vertrauen.
Spezifisch sein: Gute Fehlermeldungen beantworten “Was ist schiefgegangen?” und “Was wurde erwartet?”. Das cli-Paket bietet Inline-Markup (
{.arg},{.cls},{.val}) für formatierte, informative Meldungen.Fehler-Recovery:
tryCatch()fängt Fehler ab und gibt einen Fallback-Wert zurück. Man verwendet es, um Funktionen in Pipelines robust zu machen, in denen einzelne Inputs fehlschlagen können.Praktische Kombination:
cli_abort()/cli_warn()für benutzerseitige Funktionen,stopifnot()für interne Assertions,tryCatch()für fehlertolerante Weiterverarbeitung.
Zitat
@online{schmidt2026,
author = {{Dr. Paul Schmidt}},
publisher = {BioMath GmbH},
title = {10. Input-Validierung},
date = {2026-03-10},
url = {https://biomathcontent.netlify.app/de/content/r_more/10_input_validation.html},
langid = {de}
}