| Title: | Interpretable Civic-Accountable and Responsible Machine Learning |
|---|---|
| Description: | A general-purpose framework for Interpretable Civic-Accountable and Responsible Machine Learning (ICARM). Works with any clean tabular data and automatically detects whether a task is binary classification, multi-class classification, or regression from the target variable type. Provides a single unified entry point civic_fit() alongside tidy interfaces for global and local model explanations, group-level fairness auditing, probability calibration, multi-model comparison, threshold analysis, and reproducible audit trails. Designed to support the DataCitizen-Pro research agenda at Ludwigsburg University of Education: developing data literacy, statistical reasoning, and democratic judgment formation in civic and political teacher education. References: Biecek (2018) <doi:10.18637/jss.v085.i04>, Kuhn (2008) <doi:10.18637/jss.v028.i05>, Awe (2025) <https://github.com/Olawaleawe/civic.icarm>. |
| Authors: | Olushina Olawale Awe [aut, cre], Ludwigsburg University of Education [fnd] |
| Maintainer: | Olushina Olawale Awe <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.2.0 |
| Built: | 2026-06-18 17:04:46 UTC |
| Source: | https://github.com/olawaleawe/civic.icarm |
Produces a structured JSON audit record for any 'civic_model': provenance metadata (data hash, seed, analyst, timestamp), performance metrics, equity summary, and DataCitizen-Pro annotations.
civic_audit( object, metrics = NULL, fairness = NULL, notes = NULL, analyst = NULL, path = NULL )civic_audit( object, metrics = NULL, fairness = NULL, notes = NULL, analyst = NULL, path = NULL )
object |
A 'civic_model'. |
metrics |
Named numeric vector from [civic_metrics()] (optional). |
fairness |
A 'civic_fairness' from [civic_fairness()] (optional). |
notes |
Character analyst notes (optional). |
analyst |
Character analyst name (optional). |
path |
File path to write the JSON (optional). |
Invisibly, the JSON character string.
m <- civic_fit(voted ~ age + education, civic_voting) trail <- civic_audit(m, analyst = "O. O. Awe", notes = "Baseline") cat(trail)m <- civic_fit(voted ~ age + education, civic_voting) trail <- civic_audit(m, analyst = "O. O. Awe", notes = "Baseline") cat(trail)
Evaluates whether predicted probabilities are well-calibrated: a model predicting 0.7 should be correct ~70 Returns Brier score and Expected Calibration Error (ECE).
civic_calibrate(object, data, outcome, positive = NULL, n_bins = 10L)civic_calibrate(object, data, outcome, positive = NULL, n_bins = 10L)
object |
A 'civic_model' (binary classification only). |
data |
A data frame for evaluation. |
outcome |
Character. Outcome column name. |
positive |
Character. Positive class level. |
n_bins |
Integer. Number of probability bins (default '10'). |
An object of class 'civic_calibration' (a list) with: 'bins' (tibble), 'brier_score', 'ece', 'positive', 'outcome', 'model'.
m <- civic_fit(voted ~ age + education, civic_voting) cal <- civic_calibrate(m, civic_voting, "voted", "yes") print(cal) civic_plot_calibration(cal)m <- civic_fit(voted ~ age + education, civic_voting) cal <- civic_calibrate(m, civic_voting, "voted", "yes") print(cal) civic_plot_calibration(cal)
Evaluates a named list of 'civic_model' objects on a common test set and returns a tidy comparison table of performance, fairness, and interpretability. All models must have the same task type.
civic_compare( models, test_data, outcome, protected = NULL, positive = NULL, threshold = 0.5 )civic_compare( models, test_data, outcome, protected = NULL, positive = NULL, threshold = 0.5 )
models |
A **named** list of 'civic_model' objects, e.g. 'list(CART = m1, Logistic = m2)'. |
test_data |
A data frame used for all evaluations. |
outcome |
Character. Outcome column name. |
protected |
Character. Protected attribute column (optional). Pass 'NULL' to skip fairness metrics. |
positive |
Positive class level (binary only). |
threshold |
Decision threshold (binary only, default '0.5'). |
A tibble of class 'civic_comparison'.
splits <- civic_split(iris, stratify = "Species") m1 <- civic_fit(Species ~ ., splits$train, model = "cart") m2 <- civic_fit(Species ~ ., splits$train, model = "multinomial") cmp <- civic_compare(list(CART = m1, Multinomial = m2), splits$test, outcome = "Species") civic_plot_comparison(cmp)splits <- civic_split(iris, stratify = "Species") m1 <- civic_fit(Species ~ ., splits$train, model = "cart") m2 <- civic_fit(Species ~ ., splits$train, model = "multinomial") cmp <- civic_compare(list(CART = m1, Multinomial = m2), splits$test, outcome = "Species") civic_plot_comparison(cmp)
Synthetic civic education outcomes data.
civic_educationcivic_education
A tibble with 800 rows and 9 variables.
Synthetic data generated by civic.icarm team.
Compute equalized odds curves across thresholds (binary only)
civic_equalized_odds_curve( object, data, outcome, protected, positive = NULL, thresholds = seq(0.05, 0.95, 0.05) )civic_equalized_odds_curve( object, data, outcome, protected, positive = NULL, thresholds = seq(0.05, 0.95, 0.05) )
object |
A 'civic_model' (binary classification). |
data |
A data frame. |
outcome |
Character outcome column name. |
protected |
Character protected attribute column name. |
positive |
Positive class level. |
thresholds |
Numeric vector of thresholds. |
A tibble with columns: 'threshold', 'group', 'tpr', 'fpr', 'tnr'.
Summarise fairness into scalar equity indicators
civic_equity_summary(fairness)civic_equity_summary(fairness)
fairness |
A 'civic_fairness' from [civic_fairness()]. |
A named list of scalar equity indicators.
Creates a civic_explainer containing feature importance and optionally a DALEX explainer for PDP/ICE and local explanations. Works for all task types: binary, multiclass, and regression.
civic_explain(object, data = NULL, label = NULL)civic_explain(object, data = NULL, label = NULL)
object |
A civic_model from civic_fit(). |
data |
Optional data frame for DALEX explainer. |
label |
Optional character label for the DALEX explainer. |
An object of class civic_explainer.
m <- civic_fit(Species ~ ., iris) ex <- civic_explain(m) print(ex)m <- civic_fit(Species ~ ., iris) ex <- civic_explain(m) print(ex)
Explains why the model made a specific prediction for one or more individual observations. Uses DALEX break-down if available, falls back to coefficient contributions for GLM and LM models.
civic_explain_local(explainer, newdata, n_features = 10L)civic_explain_local(explainer, newdata, n_features = 10L)
explainer |
A civic_explainer from civic_explain(). |
newdata |
A data frame of observations to explain. |
n_features |
Integer. Maximum features to show. Default 10. |
A list of tibbles, one per row of newdata.
m <- civic_fit(Species ~ ., iris) ex <- civic_explain(m) civic_explain_local(ex, iris[1, ])m <- civic_fit(Species ~ ., iris) ex <- civic_explain(m) civic_explain_local(ex, iris[1, ])
Evaluates a 'civic_model' across levels of a protected attribute, computing standard algorithmic fairness metrics. Works for binary classification, multi-class classification, and regression.
**Binary classification metrics (per group):** 'n', 'acc', 'tpr', 'tnr', 'fpr', 'fnr', 'ppv', 'rate_pos', 'mean_prob', 'acc_gap', 'tpr_gap', 'fpr_gap', 'dp_ratio' (disparate impact), 'eo_gap' (equalized odds gap).
**Multi-class metrics (per group):** 'n', 'acc', 'balanced_acc', 'acc_gap'.
**Regression metrics (per group):** 'n', 'mae', 'rmse', 'mae_gap', 'rmse_gap'.
civic_fairness( object, data, outcome, protected, positive = NULL, threshold = 0.5 )civic_fairness( object, data, outcome, protected, positive = NULL, threshold = 0.5 )
object |
A 'civic_model' from [civic_fit()]. |
data |
A 'data.frame' containing features, outcome, and protected column. |
outcome |
Character. Name of the outcome/target column. |
protected |
Character. Name of the protected attribute column (e.g., '"gender"', '"ethnicity"', '"age_group"'). |
positive |
Character. Positive class for binary classification. Defaults to 'object$positive'. |
threshold |
Decision threshold for binary classification (default '0.5'). |
A tibble of class 'civic_fairness' with one row per group.
# Binary classification m <- civic_fit(voted ~ age + education, civic_voting) civic_fairness(m, civic_voting, outcome = "voted", protected = "gender", positive = "yes") # Regression m2 <- civic_fit(mpg ~ cyl + wt + hp, mtcars) mtcars$gear_grp <- factor(mtcars$gear) civic_fairness(m2, mtcars, outcome = "mpg", protected = "gear_grp") # Any data — works with iris too m3 <- civic_fit(Sepal.Length ~ Sepal.Width + Petal.Length, iris) civic_fairness(m3, iris, outcome = "Sepal.Length", protected = "Species")# Binary classification m <- civic_fit(voted ~ age + education, civic_voting) civic_fairness(m, civic_voting, outcome = "voted", protected = "gender", positive = "yes") # Regression m2 <- civic_fit(mpg ~ cyl + wt + hp, mtcars) mtcars$gear_grp <- factor(mtcars$gear) civic_fairness(m2, mtcars, outcome = "mpg", protected = "gear_grp") # Any data — works with iris too m3 <- civic_fit(Sepal.Length ~ Sepal.Width + Petal.Length, iris) civic_fairness(m3, iris, outcome = "Sepal.Length", protected = "Species")
Single unified entry point for all civic.icarm modelling. Automatically detects the prediction task from your target variable — you do not need to choose between classification and regression upfront.
**Task auto-detection rules:** | Target type | Task | Default model | |—|—|—| | 'factor' / 'character', 2 levels | Binary classification | '"cart"' | | 'factor' / 'character', 3+ levels | Multi-class classification | '"cart"' | | 'numeric' / 'integer' | Regression | '"cart"' |
**Supported models:**
*Binary classification:* - '"cart"' — Classification tree (rpart). Fully inspectable. - '"logistic"' — Logistic regression (stats::glm). Coefficient-interpretable. - '"logistic_l1"' — L1-penalised logistic (glmnet). Requires 'glmnet'.
*Multi-class classification:* - '"cart"' — Classification tree (rpart). Handles any number of classes. - '"multinomial"' — Multinomial logistic regression (nnet). Requires 'nnet'.
*Regression:* - '"cart"' — Regression tree (rpart). - '"linear"' — Ordinary least squares (stats::lm). - '"gam"' — Generalised Additive Model (mgcv). Requires 'mgcv'.
civic_fit( formula, data, task = "auto", model = "auto", seed = 2025L, cart_control = NULL, positive = NULL, ... )civic_fit( formula, data, task = "auto", model = "auto", seed = 2025L, cart_control = NULL, positive = NULL, ... )
formula |
A model formula. Use '.' for all columns: 'target ~ .' or 'target ~ x1 + x2 + x3'. |
data |
A 'data.frame' or 'tibble' of training data. |
task |
One of '"auto"' (default), '"binary"', '"multiclass"', or '"regression"'. Use '"auto"' to let the package detect the task. |
model |
Character. Model type. Use '"auto"' to let the package pick a sensible default, or specify one explicitly (see above). |
seed |
Integer. Random seed recorded for reproducibility (default 2025). |
cart_control |
A [rpart::rpart.control()] list for tuning CART trees. Default: 'cp = 0.01', 'minsplit = 20'. |
positive |
Character. For binary classification: which factor level is the "positive" class. If 'NULL', uses the first factor level. |
... |
Additional arguments passed to the underlying model fitter. |
An S3 object of class 'civic_model' containing:
The underlying fitted model object.
Detected/specified task: '"binary"', '"multiclass"', or '"regression"'.
Model type string.
The model formula used.
Name of the target/outcome variable.
Factor levels (classification only).
Positive class (binary classification only).
Random seed used.
Number of training rows.
SHA-256 digest of training data for provenance.
POSIXct timestamp.
Number of predictor features.
Names of predictor features.
# Binary classification (auto-detected from factor target) data(civic_voting) m <- civic_fit(voted ~ age + education + political_interest, data = civic_voting) print(m) # Regression (auto-detected from numeric target) data(civic_education) m2 <- civic_fit(civic_knowledge_score ~ age + stats_course + news_consumption, data = civic_education) # Explicit model choice m3 <- civic_fit(voted ~ ., data = civic_voting, model = "logistic") # Works on any data frame — here using the built-in iris dataset m4 <- civic_fit(Species ~ ., data = iris) # multi-class m5 <- civic_fit(Sepal.Length ~ ., data = iris) # regression# Binary classification (auto-detected from factor target) data(civic_voting) m <- civic_fit(voted ~ age + education + political_interest, data = civic_voting) print(m) # Regression (auto-detected from numeric target) data(civic_education) m2 <- civic_fit(civic_knowledge_score ~ age + stats_course + news_consumption, data = civic_education) # Explicit model choice m3 <- civic_fit(voted ~ ., data = civic_voting, model = "logistic") # Works on any data frame — here using the built-in iris dataset m4 <- civic_fit(Species ~ ., data = iris) # multi-class m5 <- civic_fit(Sepal.Length ~ ., data = iris) # regression
Synthetic German credit scoring fairness benchmark.
civic_german_creditcivic_german_credit
A tibble with 1000 rows and 8 variables.
Synthetic data generated by civic.icarm team.
Returns a named numeric vector of performance metrics appropriate for the task. Task is inferred automatically unless 'type' is given.
**Binary / multi-class classification metrics:** 'accuracy', 'balanced_acc', 'f1', 'precision', 'recall', 'specificity' (binary only), 'auc' (binary only, requires 'pROC').
**Regression metrics:** 'mae', 'rmse', 'r2'.
civic_metrics(y_true, y_pred, y_prob = NULL, positive = NULL, type = "auto")civic_metrics(y_true, y_pred, y_prob = NULL, positive = NULL, type = "auto")
y_true |
True outcome values (factor or numeric). |
y_pred |
Predicted values (factor/character for classification, numeric for regression). |
y_prob |
Numeric probability vector for the **positive** class (binary classification only). Used to compute AUC. |
positive |
Character. Positive class level (binary classification). Defaults to first factor level. |
type |
One of '"auto"' (default), '"binary"', '"multiclass"', or '"regression"'. |
A named numeric vector of metrics.
# Classification y <- factor(c("yes","no","yes","yes","no","no")) yhat <- factor(c("yes","no","no","yes","no","yes")) civic_metrics(y, yhat, positive = "yes") # Regression (any numeric target) y2 <- c(10, 20, 30, 40, 50) yhat2 <- c(12, 18, 33, 39, 48) civic_metrics(y2, yhat2) # Works with iris m <- civic_fit(Species ~ ., iris) yhat3 <- predict(m, iris) civic_metrics(iris$Species, yhat3)# Classification y <- factor(c("yes","no","yes","yes","no","no")) yhat <- factor(c("yes","no","no","yes","no","yes")) civic_metrics(y, yhat, positive = "yes") # Regression (any numeric target) y2 <- c(10, 20, 30, 40, 50) yhat2 <- c(12, 18, 33, 39, 48) civic_metrics(y2, yhat2) # Works with iris m <- civic_fit(Species ~ ., iris) yhat3 <- predict(m, iris) civic_metrics(iris$Species, yhat3)
A family of ggplot2-based visualisation functions. All return ggplot2 objects that can be further customised.
A ggplot2 object that can be further customised
with standard ggplot2 syntax.
Synthesises model provenance, interpretability rating, performance, and equity into a printed civic accountability scorecard, with an optional JSON output. Works for all task types.
civic_scorecard( object, test_data, outcome, protected = NULL, positive = NULL, analyst = NULL, project = "civic.icarm", path = NULL )civic_scorecard( object, test_data, outcome, protected = NULL, positive = NULL, analyst = NULL, project = "civic.icarm", path = NULL )
object |
A 'civic_model'. |
test_data |
Data frame of held-out test data. |
outcome |
Character. Outcome column name. |
protected |
Character. Protected attribute column (optional). |
positive |
Positive class (binary only). |
analyst |
Character analyst name. |
project |
Character project name. |
path |
Optional file path for JSON output. |
Invisibly, a named list (the scorecard structure).
splits <- civic_split(civic_voting, stratify = "voted") m <- civic_fit(voted ~ age + education + political_interest, splits$train) civic_scorecard(m, splits$test, outcome = "voted", protected = "gender", positive = "yes", project = "DataCitizen-Pro")splits <- civic_split(civic_voting, stratify = "voted") m <- civic_fit(voted ~ age + education + political_interest, splits$train) civic_scorecard(m, splits$test, outcome = "voted", protected = "gender", positive = "yes", project = "DataCitizen-Pro")
Splits a data frame into training and test sets. The seed is always stored in the returned object so the split is fully reproducible. Optional stratification preserves class proportions.
civic_split(data, prop = 0.75, seed = 2025L, stratify = NULL)civic_split(data, prop = 0.75, seed = 2025L, stratify = NULL)
data |
A 'data.frame' or 'tibble'. |
prop |
Proportion for training (default '0.75'). |
seed |
Integer random seed (default '2025'). |
stratify |
Optional column name (character) to stratify on. Ensures class proportions are preserved in both splits. Works for both factor (classification) and numeric targets (stratifies by quartile). |
A named list with elements 'train', 'test', 'seed', and 'prop'.
# Any data frame works splits <- civic_split(iris, prop = 0.8, stratify = "Species") nrow(splits$train) # ~120 nrow(splits$test) # ~30 # Numeric stratification (by quartile) splits2 <- civic_split(mtcars, prop = 0.75, stratify = "mpg")# Any data frame works splits <- civic_split(iris, prop = 0.8, stratify = "Species") nrow(splits$train) # ~120 nrow(splits$test) # ~30 # Numeric stratification (by quartile) splits2 <- civic_split(mtcars, prop = 0.75, stratify = "mpg")
Computes performance metrics across a grid of decision thresholds. Essential for understanding the accuracy-vs-fairness tradeoffs that arise when choosing a classification cutoff — a DataCitizen-Pro democratic judgment teaching tool.
civic_thresholds( y_true, y_prob, positive = NULL, thresholds = seq(0.1, 0.9, by = 0.05) )civic_thresholds( y_true, y_prob, positive = NULL, thresholds = seq(0.1, 0.9, by = 0.05) )
y_true |
Factor of true class labels. |
y_prob |
Numeric vector of predicted probabilities for the positive class. |
positive |
Character. Positive class level. |
thresholds |
Numeric vector of thresholds to evaluate. Default: 'seq(0.1, 0.9, by = 0.05)'. |
A tibble with one row per threshold and columns: 'threshold', 'accuracy', 'balanced_acc', 'precision', 'recall', 'specificity', 'f1', 'rate_positive'.
y <- factor(sample(c("yes","no"), 200, replace = TRUE)) p <- runif(200) thr <- civic_thresholds(y, p, positive = "yes") civic_plot_thresholds(thr)y <- factor(sample(c("yes","no"), 200, replace = TRUE)) p <- runif(200) thr <- civic_thresholds(y, p, positive = "yes") civic_plot_thresholds(thr)
Synthetic civic voting participation data.
civic_votingcivic_voting
A tibble with 1000 rows and 10 variables.
Synthetic data generated by civic.icarm team.
Generates predictions from a fitted 'civic_model' object for any task type.
## S3 method for class 'civic_model' predict(object, newdata, type = c("class", "prob"), threshold = 0.5, ...)## S3 method for class 'civic_model' predict(object, newdata, type = c("class", "prob"), threshold = 0.5, ...)
object |
A 'civic_model'. |
newdata |
A 'data.frame' for prediction. Must contain the same feature columns used during training. |
type |
For classification: '"class"' (default) returns predicted class labels as a factor; '"prob"' returns a matrix of class probabilities (one column per class). For regression: ignored — always returns a numeric vector. |
threshold |
Decision threshold for **binary** classification only (default 0.5). Ignored for multi-class and regression. |
... |
Ignored. |
For classification with 'type = "class"': a factor vector. For classification with 'type = "prob"': a numeric matrix. For regression: a numeric vector.
data(civic_voting) m <- civic_fit(voted ~ age + education, data = civic_voting) predict(m, civic_voting[1:5, ], type = "class") predict(m, civic_voting[1:5, ], type = "prob")data(civic_voting) m <- civic_fit(voted ~ age + education, data = civic_voting) predict(m, civic_voting[1:5, ], type = "class") predict(m, civic_voting[1:5, ], type = "prob")
Print a civic_model
## S3 method for class 'civic_model' print(x, ...)## S3 method for class 'civic_model' print(x, ...)
x |
A civic_model object. |
... |
Further arguments passed to or from other methods. |
Invisibly returns the civic_model object x.
Called for its side effect of printing a formatted summary to the console.
Summary of a civic_model
## S3 method for class 'civic_model' summary(object, ...)## S3 method for class 'civic_model' summary(object, ...)
object |
A civic_model object. |
... |
Further arguments passed to or from other methods. |
Invisibly returns the summary of the underlying fitted model. Called for its side effect of printing a detailed model summary to the console.