Skip to content

API Reference

This page is auto-generated from Python docstrings.

ml_vizkit

ML VizKit public API.

Reusable visualizations for inspecting, comparing, and explaining trained machine-learning models.

compare_models

compare_models(
    scores: Mapping[str, float],
    *,
    ax: Axes | None = None,
    metric_name: str = 'Score',
    title: str = 'Model Comparison',
) -> Axes

Compare evaluation scores from already-completed model experiments.

Parameters:

Name Type Description Default
scores Mapping[str, float]

A mapping from model names to their evaluation scores.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
metric_name str

The name of the evaluation metric.

'Score'
title str

The title of the plot.

'Model Comparison'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: A consistent visual basis helps analysts compare models evaluated with the same metric and experimental conditions.

NOTE: This function assumes the caller has already established that the supplied scores are meaningfully comparable.

Source code in src/ml_vizkit/comparison.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def compare_models(
    scores: Mapping[str, float],
    *,
    ax: Axes | None = None,
    metric_name: str = "Score",
    title: str = "Model Comparison",
) -> Axes:
    """Compare evaluation scores from already-completed model experiments.

    Args:
        scores: A mapping from model names to their evaluation scores.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        metric_name: The name of the evaluation metric.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: A consistent visual basis helps analysts compare models evaluated with
    the same metric and experimental conditions.

    NOTE: This function assumes the caller has already established that the
    supplied scores are meaningfully comparable.
    """
    if not scores:
        msg = "scores must contain at least one model result."
        raise ValueError(msg)

    if ax is None:
        _, ax = plt.subplots()

    names = list(scores)
    values = [scores[name] for name in names]

    ax.bar(names, values)
    ax.set_ylabel(metric_name)
    ax.set_title(title)
    ax.tick_params(axis="x", rotation=30)
    return ax

compare_splits

compare_splits(
    splits: Sequence[SplitView],
) -> tuple[Axes, ...]

Create one train/test visualization per completed split.

Parameters:

Name Type Description Default
splits Sequence[SplitView]

A sequence of SplitView instances representing the completed splits.

required

Returns:

Type Description
tuple[Axes, ...]

A tuple of Matplotlib Axes, each containing the visualization for one split.

WHY: Comparing several partitions makes sampling variability visible.

This function does not create splits or train models. Each visualization is created in its own figure, and all Axes are returned to the caller.

Source code in src/ml_vizkit/comparison.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def compare_splits(
    splits: Sequence[SplitView],
) -> tuple[Axes, ...]:
    """Create one train/test visualization per completed split.

    Args:
        splits: A sequence of SplitView instances representing the completed splits.

    Returns:
        A tuple of Matplotlib Axes, each containing the visualization for one split.

    WHY: Comparing several partitions makes sampling variability visible.

    This function does not create splits or train models. Each visualization is
    created in its own figure, and all Axes are returned to the caller.
    """
    axes: list[Axes] = []

    for split in splits:
        _, ax = plt.subplots()

        title = split.label
        if split.score is not None:
            title = f"{title} | score={split.score:.3f}"

        show_train_test_split(
            split.X_train,
            split.X_test,
            ax=ax,
            title=title,
        )
        axes.append(ax)

    return tuple(axes)

save_chart

save_chart(
    ax: Axes,
    path: str | Path,
    *,
    bbox_inches: str | None = 'tight',
) -> None

Save chart.

Parameters:

Name Type Description Default
ax Axes

The matplotlib Axes object containing the chart to save.

required
path str | Path

The file path where the chart should be saved.

required
bbox_inches str | None

The bounding box in inches. Defaults to "tight".

'tight'

Raises:

Type Description
ValueError

If the Axes is not attached to a Figure.

Source code in src/ml_vizkit/save.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def save_chart(
    ax: Axes,
    path: str | Path,
    *,
    bbox_inches: str | None = "tight",
) -> None:
    """Save chart.

    Args:
        ax (Axes): The matplotlib Axes object containing the chart to save.
        path (str | Path): The file path where the chart should be saved.
        bbox_inches (str | None, optional): The bounding box in inches. Defaults to "tight".

    Raises:
        ValueError: If the Axes is not attached to a Figure.

    """
    output_path = Path(path)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    figure = ax.get_figure(root=True)
    if figure is None:
        raise ValueError("Axes must be attached to a Figure before saving.")

    figure.savefig(output_path, bbox_inches=bbox_inches)

show_actual_vs_predicted

show_actual_vs_predicted(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = 'Actual vs. Predicted',
) -> Axes

Show actual versus predicted regression values.

WHY: Good predictions should generally fall near the identity line.

The implementation delegates to scikit-learn's PredictionErrorDisplay.

Source code in src/ml_vizkit/regression.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def show_actual_vs_predicted(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = "Actual vs. Predicted",
) -> Axes:
    """Show actual versus predicted regression values.

    WHY: Good predictions should generally fall near the identity line.

    The implementation delegates to scikit-learn's PredictionErrorDisplay.
    """
    plot_ax = _new_axes(ax)

    PredictionErrorDisplay.from_predictions(
        y_true,
        y_pred,
        kind="actual_vs_predicted",
        ax=plot_ax,
    )
    plot_ax.set_title(title)
    return plot_ax

show_class_distribution

show_class_distribution(
    y: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = 'Class Distribution',
    x_label: str = 'Class',
) -> Axes

Show the number of observations in each target class.

Parameters:

Name Type Description Default
y Numeric1D

The target labels.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Class Distribution'
x_label str

The label for the x-axis.

'Class'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Class imbalance can affect training, evaluation, and interpretation.

Source code in src/ml_vizkit/classification.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
def show_class_distribution(
    y: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = "Class Distribution",
    x_label: str = "Class",
) -> Axes:
    """Show the number of observations in each target class.

    Args:
        y: The target labels.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.
        x_label: The label for the x-axis.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Class imbalance can affect training, evaluation, and interpretation.
    """
    counts = pd.Series(y).value_counts(dropna=False).sort_index()
    plot_ax = _new_axes(ax)

    plot_ax.bar(counts.index.astype(str), counts.to_numpy())
    plot_ax.set_xlabel(x_label)
    plot_ax.set_ylabel("Count")
    plot_ax.set_title(title)
    return plot_ax

show_confusion_matrix

show_confusion_matrix(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    labels: Sequence[Any] | None = None,
    normalize: str | None = None,
    ax: Axes | None = None,
    title: str = 'Confusion Matrix',
) -> Axes

Show a confusion matrix from completed classification predictions.

Parameters:

Name Type Description Default
y_true Numeric1D

The true labels.

required
y_pred Numeric1D

The predicted labels.

required
labels Sequence[Any] | None

Optional sequence of labels to include in the matrix.

None
normalize str | None

Normalization method for the confusion matrix.

None
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Confusion Matrix'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Overall accuracy can hide which classes are being confused.

This function delegates the matrix visualization to scikit-learn and returns the Matplotlib Axes.

Source code in src/ml_vizkit/classification.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def show_confusion_matrix(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    labels: Sequence[Any] | None = None,
    normalize: str | None = None,
    ax: Axes | None = None,
    title: str = "Confusion Matrix",
) -> Axes:
    """Show a confusion matrix from completed classification predictions.

    Args:
        y_true: The true labels.
        y_pred: The predicted labels.
        labels: Optional sequence of labels to include in the matrix.
        normalize: Normalization method for the confusion matrix.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Overall accuracy can hide which classes are being confused.

    This function delegates the matrix visualization to scikit-learn and
    returns the Matplotlib Axes.
    """
    plot_ax = _new_axes(ax)

    ConfusionMatrixDisplay.from_predictions(
        y_true,
        y_pred,
        labels=labels,
        normalize=normalize,
        ax=plot_ax,
    )
    plot_ax.set_title(title)
    return plot_ax

show_decision_boundary

show_decision_boundary(
    model: Any,
    X: DataFrame,
    y: Sequence[Any] | None = None,
    *,
    ax: Axes | None = None,
    response_method: str = 'auto',
    plot_method: str = 'contourf',
    title: str = 'Decision Boundary',
    alpha: float = 0.25,
) -> Axes

Show the decision boundary for an already-trained classifier.

Parameters:

Name Type Description Default
model Any

The trained classifier to visualize.

required
X DataFrame

A DataFrame containing exactly two numeric features.

required
y Sequence[Any] | None

Optional sequence of true labels corresponding to X.

None
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
response_method str

The response method to use for the decision boundary.

'auto'
plot_method str

The plotting method for the decision boundary.

'contourf'
title str

The title of the plot.

'Decision Boundary'
alpha float

The transparency level for the decision boundary.

0.25

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: A decision boundary makes the classifier's learned separation of feature space visible.

REQ: X must contain exactly two numeric features. REQ: model must already be fitted.

The implementation delegates boundary construction to scikit-learn's DecisionBoundaryDisplay and returns the Matplotlib Axes to the caller.

Source code in src/ml_vizkit/classification.py
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def show_decision_boundary(
    model: Any,
    X: pd.DataFrame,
    y: Sequence[Any] | None = None,
    *,
    ax: Axes | None = None,
    response_method: str = "auto",
    plot_method: str = "contourf",
    title: str = "Decision Boundary",
    alpha: float = 0.25,
) -> Axes:
    """Show the decision boundary for an already-trained classifier.

    Args:
        model: The trained classifier to visualize.
        X: A DataFrame containing exactly two numeric features.
        y: Optional sequence of true labels corresponding to X.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        response_method: The response method to use for the decision boundary.
        plot_method: The plotting method for the decision boundary.
        title: The title of the plot.
        alpha: The transparency level for the decision boundary.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: A decision boundary makes the classifier's learned separation of
    feature space visible.

    REQ: X must contain exactly two numeric features.
    REQ: model must already be fitted.

    The implementation delegates boundary construction to scikit-learn's
    DecisionBoundaryDisplay and returns the Matplotlib Axes to the caller.
    """
    if X.shape[1] != 2:
        msg = "Decision-boundary plots require exactly two features."
        raise ValueError(msg)

    plot_ax = _new_axes(ax)

    DecisionBoundaryDisplay.from_estimator(
        model,
        X,
        response_method=response_method,
        plot_method=plot_method,
        alpha=alpha,
        ax=plot_ax,
    )

    # Overlay observed samples when labels are supplied. The boundary itself is
    # produced by scikit-learn; this layer makes the result easier to inspect.
    if y is not None:
        values = np.asarray(y)
        classes, codes = np.unique(values, return_inverse=True)
        plot_ax.scatter(
            X.iloc[:, 0],
            X.iloc[:, 1],
            c=codes,
            edgecolors="black",
            alpha=0.85,
        )

        # A compact legend makes class identity available without dictating
        # styling beyond the default Matplotlib color cycle.
        handles = []
        for code, label in enumerate(classes):
            handles.append(
                plt.Line2D(
                    [],
                    [],
                    marker="o",
                    linestyle="",
                    label=str(label),
                    markerfacecolor=f"C{code % 10}",
                    markeredgecolor="black",
                )
            )
        plot_ax.legend(handles=handles, title="Class")

    plot_ax.set_title(title)
    return plot_ax

show_feature_importance

show_feature_importance(
    model: Any,
    feature_names: Sequence[str],
    *,
    ax: Axes | None = None,
    title: str = 'Feature Importance',
) -> Axes

Show model-provided feature importance values.

Parameters:

Name Type Description Default
model Any

The already-trained model exposing feature_importances_.

required
feature_names Sequence[str]

The names of the features corresponding to the importance values.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Feature Importance'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Importance values can help analysts investigate which features most influenced a fitted model.

REQ: The model must already expose feature_importances_.

Source code in src/ml_vizkit/inspection.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def show_feature_importance(
    model: Any,
    feature_names: Sequence[str],
    *,
    ax: Axes | None = None,
    title: str = "Feature Importance",
) -> Axes:
    """Show model-provided feature importance values.

    Args:
        model: The already-trained model exposing ``feature_importances_``.
        feature_names: The names of the features corresponding to the importance values.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Importance values can help analysts investigate which features most
    influenced a fitted model.

    REQ: The model must already expose ``feature_importances_``.
    """
    if not hasattr(model, "feature_importances_"):
        msg = f"{type(model).__name__} does not expose feature_importances_."
        raise ValueError(msg)

    importance = np.asarray(model.feature_importances_, dtype=float)

    if len(importance) != len(feature_names):
        msg = "feature_names must match the model's feature count."
        raise ValueError(msg)

    order = np.argsort(importance)
    ordered_names = np.asarray(feature_names, dtype=str)[order]
    ordered_values = importance[order]

    if ax is None:
        _, ax = plt.subplots()

    ax.barh(ordered_names, ordered_values)
    ax.set_xlabel("Importance")
    ax.set_title(title)
    return ax

show_prediction_errors

show_prediction_errors(
    X: DataFrame,
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = 'Classification Prediction Errors',
) -> Axes

Show correct and incorrect classifications in two-feature space.

Parameters:

Name Type Description Default
X DataFrame

A DataFrame containing exactly two numeric features.

required
y_true Numeric1D

The true labels.

required
y_pred Numeric1D

The predicted labels.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Classification Prediction Errors'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Looking directly at mistakes can reveal overlap, unusual observations, and regions where a classifier struggles.

REQ: X must contain exactly two numeric features.

Source code in src/ml_vizkit/classification.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def show_prediction_errors(
    X: pd.DataFrame,
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = "Classification Prediction Errors",
) -> Axes:
    """Show correct and incorrect classifications in two-feature space.

    Args:
        X: A DataFrame containing exactly two numeric features.
        y_true: The true labels.
        y_pred: The predicted labels.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Looking directly at mistakes can reveal overlap, unusual observations,
    and regions where a classifier struggles.

    REQ: X must contain exactly two numeric features.
    """
    if X.shape[1] != 2:
        msg = "Prediction-error plots require exactly two features."
        raise ValueError(msg)

    actual = np.asarray(y_true)
    predicted = np.asarray(y_pred)

    if len(X) != len(actual) or len(actual) != len(predicted):
        msg = "X, y_true, and y_pred must contain the same number of observations."
        raise ValueError(msg)

    correct = actual == predicted
    plot_ax = _new_axes(ax)

    plot_ax.scatter(
        X.iloc[correct, 0],
        X.iloc[correct, 1],
        marker="o",
        label="Correct",
        alpha=0.65,
    )
    plot_ax.scatter(
        X.iloc[~correct, 0],
        X.iloc[~correct, 1],
        marker="x",
        label="Incorrect",
        alpha=0.95,
    )

    plot_ax.set_xlabel(str(X.columns[0]))
    plot_ax.set_ylabel(str(X.columns[1]))
    plot_ax.set_title(title)
    plot_ax.legend()
    return plot_ax

show_residuals

show_residuals(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = 'Residuals vs. Predicted',
) -> Axes

Show regression residuals against predicted values.

WHY: Residual plots can reveal systematic error patterns, changing variance, and unusual observations.

The implementation delegates to scikit-learn's PredictionErrorDisplay.

Source code in src/ml_vizkit/regression.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def show_residuals(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = "Residuals vs. Predicted",
) -> Axes:
    """Show regression residuals against predicted values.

    WHY: Residual plots can reveal systematic error patterns, changing
    variance, and unusual observations.

    The implementation delegates to scikit-learn's PredictionErrorDisplay.
    """
    plot_ax = _new_axes(ax)

    PredictionErrorDisplay.from_predictions(
        y_true,
        y_pred,
        kind="residual_vs_predicted",
        ax=plot_ax,
    )
    plot_ax.set_title(title)
    return plot_ax

show_train_test_split

show_train_test_split(
    X_train: DataFrame,
    X_test: DataFrame,
    *,
    ax: Axes | None = None,
    title: str = 'Train/Test Split',
) -> Axes

Show which observations were assigned to training and testing.

Parameters:

Name Type Description Default
X_train DataFrame

The training feature DataFrame containing exactly two numeric features.

required
X_test DataFrame

The testing feature DataFrame containing exactly two numeric features.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Train/Test Split'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Seeing the actual partition makes random splitting concrete instead of treating the random seed as unexplained boilerplate.

REQ: Both frames must contain the same two numeric features.

Source code in src/ml_vizkit/splits.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def show_train_test_split(
    X_train: pd.DataFrame,
    X_test: pd.DataFrame,
    *,
    ax: Axes | None = None,
    title: str = "Train/Test Split",
) -> Axes:
    """Show which observations were assigned to training and testing.

    Args:
        X_train: The training feature DataFrame containing exactly two numeric features.
        X_test: The testing feature DataFrame containing exactly two numeric features.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Seeing the actual partition makes random splitting concrete instead
    of treating the random seed as unexplained boilerplate.

    REQ: Both frames must contain the same two numeric features.
    """
    if X_train.shape[1] != 2 or X_test.shape[1] != 2:
        msg = "Train/test split plots require exactly two features."
        raise ValueError(msg)

    if list(X_train.columns) != list(X_test.columns):
        msg = "Training and test data must contain the same features in the same order."
        raise ValueError(msg)

    if ax is None:
        _, ax = plt.subplots()

    feature_x, feature_y = X_train.columns

    ax.scatter(
        X_train[feature_x],
        X_train[feature_y],
        marker="o",
        label="Train",
        alpha=0.65,
    )
    ax.scatter(
        X_test[feature_x],
        X_test[feature_y],
        marker="x",
        label="Test",
        alpha=0.95,
    )

    ax.set_xlabel(str(feature_x))
    ax.set_ylabel(str(feature_y))
    ax.set_title(title)
    ax.legend()
    return ax

classification

Classification visualizations.

These functions visualize trained classifiers and completed predictions. They do not fit models or choose analytical settings.

show_class_distribution

show_class_distribution(
    y: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = 'Class Distribution',
    x_label: str = 'Class',
) -> Axes

Show the number of observations in each target class.

Parameters:

Name Type Description Default
y Numeric1D

The target labels.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Class Distribution'
x_label str

The label for the x-axis.

'Class'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Class imbalance can affect training, evaluation, and interpretation.

Source code in src/ml_vizkit/classification.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
def show_class_distribution(
    y: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = "Class Distribution",
    x_label: str = "Class",
) -> Axes:
    """Show the number of observations in each target class.

    Args:
        y: The target labels.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.
        x_label: The label for the x-axis.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Class imbalance can affect training, evaluation, and interpretation.
    """
    counts = pd.Series(y).value_counts(dropna=False).sort_index()
    plot_ax = _new_axes(ax)

    plot_ax.bar(counts.index.astype(str), counts.to_numpy())
    plot_ax.set_xlabel(x_label)
    plot_ax.set_ylabel("Count")
    plot_ax.set_title(title)
    return plot_ax

show_confusion_matrix

show_confusion_matrix(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    labels: Sequence[Any] | None = None,
    normalize: str | None = None,
    ax: Axes | None = None,
    title: str = 'Confusion Matrix',
) -> Axes

Show a confusion matrix from completed classification predictions.

Parameters:

Name Type Description Default
y_true Numeric1D

The true labels.

required
y_pred Numeric1D

The predicted labels.

required
labels Sequence[Any] | None

Optional sequence of labels to include in the matrix.

None
normalize str | None

Normalization method for the confusion matrix.

None
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Confusion Matrix'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Overall accuracy can hide which classes are being confused.

This function delegates the matrix visualization to scikit-learn and returns the Matplotlib Axes.

Source code in src/ml_vizkit/classification.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def show_confusion_matrix(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    labels: Sequence[Any] | None = None,
    normalize: str | None = None,
    ax: Axes | None = None,
    title: str = "Confusion Matrix",
) -> Axes:
    """Show a confusion matrix from completed classification predictions.

    Args:
        y_true: The true labels.
        y_pred: The predicted labels.
        labels: Optional sequence of labels to include in the matrix.
        normalize: Normalization method for the confusion matrix.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Overall accuracy can hide which classes are being confused.

    This function delegates the matrix visualization to scikit-learn and
    returns the Matplotlib Axes.
    """
    plot_ax = _new_axes(ax)

    ConfusionMatrixDisplay.from_predictions(
        y_true,
        y_pred,
        labels=labels,
        normalize=normalize,
        ax=plot_ax,
    )
    plot_ax.set_title(title)
    return plot_ax

show_decision_boundary

show_decision_boundary(
    model: Any,
    X: DataFrame,
    y: Sequence[Any] | None = None,
    *,
    ax: Axes | None = None,
    response_method: str = 'auto',
    plot_method: str = 'contourf',
    title: str = 'Decision Boundary',
    alpha: float = 0.25,
) -> Axes

Show the decision boundary for an already-trained classifier.

Parameters:

Name Type Description Default
model Any

The trained classifier to visualize.

required
X DataFrame

A DataFrame containing exactly two numeric features.

required
y Sequence[Any] | None

Optional sequence of true labels corresponding to X.

None
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
response_method str

The response method to use for the decision boundary.

'auto'
plot_method str

The plotting method for the decision boundary.

'contourf'
title str

The title of the plot.

'Decision Boundary'
alpha float

The transparency level for the decision boundary.

0.25

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: A decision boundary makes the classifier's learned separation of feature space visible.

REQ: X must contain exactly two numeric features. REQ: model must already be fitted.

The implementation delegates boundary construction to scikit-learn's DecisionBoundaryDisplay and returns the Matplotlib Axes to the caller.

Source code in src/ml_vizkit/classification.py
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def show_decision_boundary(
    model: Any,
    X: pd.DataFrame,
    y: Sequence[Any] | None = None,
    *,
    ax: Axes | None = None,
    response_method: str = "auto",
    plot_method: str = "contourf",
    title: str = "Decision Boundary",
    alpha: float = 0.25,
) -> Axes:
    """Show the decision boundary for an already-trained classifier.

    Args:
        model: The trained classifier to visualize.
        X: A DataFrame containing exactly two numeric features.
        y: Optional sequence of true labels corresponding to X.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        response_method: The response method to use for the decision boundary.
        plot_method: The plotting method for the decision boundary.
        title: The title of the plot.
        alpha: The transparency level for the decision boundary.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: A decision boundary makes the classifier's learned separation of
    feature space visible.

    REQ: X must contain exactly two numeric features.
    REQ: model must already be fitted.

    The implementation delegates boundary construction to scikit-learn's
    DecisionBoundaryDisplay and returns the Matplotlib Axes to the caller.
    """
    if X.shape[1] != 2:
        msg = "Decision-boundary plots require exactly two features."
        raise ValueError(msg)

    plot_ax = _new_axes(ax)

    DecisionBoundaryDisplay.from_estimator(
        model,
        X,
        response_method=response_method,
        plot_method=plot_method,
        alpha=alpha,
        ax=plot_ax,
    )

    # Overlay observed samples when labels are supplied. The boundary itself is
    # produced by scikit-learn; this layer makes the result easier to inspect.
    if y is not None:
        values = np.asarray(y)
        classes, codes = np.unique(values, return_inverse=True)
        plot_ax.scatter(
            X.iloc[:, 0],
            X.iloc[:, 1],
            c=codes,
            edgecolors="black",
            alpha=0.85,
        )

        # A compact legend makes class identity available without dictating
        # styling beyond the default Matplotlib color cycle.
        handles = []
        for code, label in enumerate(classes):
            handles.append(
                plt.Line2D(
                    [],
                    [],
                    marker="o",
                    linestyle="",
                    label=str(label),
                    markerfacecolor=f"C{code % 10}",
                    markeredgecolor="black",
                )
            )
        plot_ax.legend(handles=handles, title="Class")

    plot_ax.set_title(title)
    return plot_ax

show_prediction_errors

show_prediction_errors(
    X: DataFrame,
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = 'Classification Prediction Errors',
) -> Axes

Show correct and incorrect classifications in two-feature space.

Parameters:

Name Type Description Default
X DataFrame

A DataFrame containing exactly two numeric features.

required
y_true Numeric1D

The true labels.

required
y_pred Numeric1D

The predicted labels.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Classification Prediction Errors'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Looking directly at mistakes can reveal overlap, unusual observations, and regions where a classifier struggles.

REQ: X must contain exactly two numeric features.

Source code in src/ml_vizkit/classification.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def show_prediction_errors(
    X: pd.DataFrame,
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = "Classification Prediction Errors",
) -> Axes:
    """Show correct and incorrect classifications in two-feature space.

    Args:
        X: A DataFrame containing exactly two numeric features.
        y_true: The true labels.
        y_pred: The predicted labels.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Looking directly at mistakes can reveal overlap, unusual observations,
    and regions where a classifier struggles.

    REQ: X must contain exactly two numeric features.
    """
    if X.shape[1] != 2:
        msg = "Prediction-error plots require exactly two features."
        raise ValueError(msg)

    actual = np.asarray(y_true)
    predicted = np.asarray(y_pred)

    if len(X) != len(actual) or len(actual) != len(predicted):
        msg = "X, y_true, and y_pred must contain the same number of observations."
        raise ValueError(msg)

    correct = actual == predicted
    plot_ax = _new_axes(ax)

    plot_ax.scatter(
        X.iloc[correct, 0],
        X.iloc[correct, 1],
        marker="o",
        label="Correct",
        alpha=0.65,
    )
    plot_ax.scatter(
        X.iloc[~correct, 0],
        X.iloc[~correct, 1],
        marker="x",
        label="Incorrect",
        alpha=0.95,
    )

    plot_ax.set_xlabel(str(X.columns[0]))
    plot_ax.set_ylabel(str(X.columns[1]))
    plot_ax.set_title(title)
    plot_ax.legend()
    return plot_ax

comparison

Higher-level visual comparisons of completed ML experiments.

SplitView dataclass

Data needed to visualize one already-created train/test split.

Parameters:

Name Type Description Default
label str

A descriptive label for the split.

required
X_train DataFrame

The training feature set.

required
X_test DataFrame

The testing feature set.

required
score float | None

Optional evaluation score for the split.

None

Returns:

Type Description

An instance of SplitView containing the data for the split.

Source code in src/ml_vizkit/comparison.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@dataclass(frozen=True, slots=True)
class SplitView:
    """Data needed to visualize one already-created train/test split.

    Args:
        label: A descriptive label for the split.
        X_train: The training feature set.
        X_test: The testing feature set.
        score: Optional evaluation score for the split.

    Returns:
        An instance of SplitView containing the data for the split.
    """

    label: str
    X_train: pd.DataFrame
    X_test: pd.DataFrame
    score: float | None = None

compare_models

compare_models(
    scores: Mapping[str, float],
    *,
    ax: Axes | None = None,
    metric_name: str = 'Score',
    title: str = 'Model Comparison',
) -> Axes

Compare evaluation scores from already-completed model experiments.

Parameters:

Name Type Description Default
scores Mapping[str, float]

A mapping from model names to their evaluation scores.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
metric_name str

The name of the evaluation metric.

'Score'
title str

The title of the plot.

'Model Comparison'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: A consistent visual basis helps analysts compare models evaluated with the same metric and experimental conditions.

NOTE: This function assumes the caller has already established that the supplied scores are meaningfully comparable.

Source code in src/ml_vizkit/comparison.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def compare_models(
    scores: Mapping[str, float],
    *,
    ax: Axes | None = None,
    metric_name: str = "Score",
    title: str = "Model Comparison",
) -> Axes:
    """Compare evaluation scores from already-completed model experiments.

    Args:
        scores: A mapping from model names to their evaluation scores.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        metric_name: The name of the evaluation metric.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: A consistent visual basis helps analysts compare models evaluated with
    the same metric and experimental conditions.

    NOTE: This function assumes the caller has already established that the
    supplied scores are meaningfully comparable.
    """
    if not scores:
        msg = "scores must contain at least one model result."
        raise ValueError(msg)

    if ax is None:
        _, ax = plt.subplots()

    names = list(scores)
    values = [scores[name] for name in names]

    ax.bar(names, values)
    ax.set_ylabel(metric_name)
    ax.set_title(title)
    ax.tick_params(axis="x", rotation=30)
    return ax

compare_splits

compare_splits(
    splits: Sequence[SplitView],
) -> tuple[Axes, ...]

Create one train/test visualization per completed split.

Parameters:

Name Type Description Default
splits Sequence[SplitView]

A sequence of SplitView instances representing the completed splits.

required

Returns:

Type Description
tuple[Axes, ...]

A tuple of Matplotlib Axes, each containing the visualization for one split.

WHY: Comparing several partitions makes sampling variability visible.

This function does not create splits or train models. Each visualization is created in its own figure, and all Axes are returned to the caller.

Source code in src/ml_vizkit/comparison.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def compare_splits(
    splits: Sequence[SplitView],
) -> tuple[Axes, ...]:
    """Create one train/test visualization per completed split.

    Args:
        splits: A sequence of SplitView instances representing the completed splits.

    Returns:
        A tuple of Matplotlib Axes, each containing the visualization for one split.

    WHY: Comparing several partitions makes sampling variability visible.

    This function does not create splits or train models. Each visualization is
    created in its own figure, and all Axes are returned to the caller.
    """
    axes: list[Axes] = []

    for split in splits:
        _, ax = plt.subplots()

        title = split.label
        if split.score is not None:
            title = f"{title} | score={split.score:.3f}"

        show_train_test_split(
            split.X_train,
            split.X_test,
            ax=ax,
            title=title,
        )
        axes.append(ax)

    return tuple(axes)

inspection

Visual inspection of already-trained model characteristics.

show_feature_importance

show_feature_importance(
    model: Any,
    feature_names: Sequence[str],
    *,
    ax: Axes | None = None,
    title: str = 'Feature Importance',
) -> Axes

Show model-provided feature importance values.

Parameters:

Name Type Description Default
model Any

The already-trained model exposing feature_importances_.

required
feature_names Sequence[str]

The names of the features corresponding to the importance values.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Feature Importance'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Importance values can help analysts investigate which features most influenced a fitted model.

REQ: The model must already expose feature_importances_.

Source code in src/ml_vizkit/inspection.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def show_feature_importance(
    model: Any,
    feature_names: Sequence[str],
    *,
    ax: Axes | None = None,
    title: str = "Feature Importance",
) -> Axes:
    """Show model-provided feature importance values.

    Args:
        model: The already-trained model exposing ``feature_importances_``.
        feature_names: The names of the features corresponding to the importance values.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Importance values can help analysts investigate which features most
    influenced a fitted model.

    REQ: The model must already expose ``feature_importances_``.
    """
    if not hasattr(model, "feature_importances_"):
        msg = f"{type(model).__name__} does not expose feature_importances_."
        raise ValueError(msg)

    importance = np.asarray(model.feature_importances_, dtype=float)

    if len(importance) != len(feature_names):
        msg = "feature_names must match the model's feature count."
        raise ValueError(msg)

    order = np.argsort(importance)
    ordered_names = np.asarray(feature_names, dtype=str)[order]
    ordered_values = importance[order]

    if ax is None:
        _, ax = plt.subplots()

    ax.barh(ordered_names, ordered_values)
    ax.set_xlabel("Importance")
    ax.set_title(title)
    return ax

regression

Regression visualizations for completed predictions.

show_actual_vs_predicted

show_actual_vs_predicted(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = 'Actual vs. Predicted',
) -> Axes

Show actual versus predicted regression values.

WHY: Good predictions should generally fall near the identity line.

The implementation delegates to scikit-learn's PredictionErrorDisplay.

Source code in src/ml_vizkit/regression.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def show_actual_vs_predicted(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = "Actual vs. Predicted",
) -> Axes:
    """Show actual versus predicted regression values.

    WHY: Good predictions should generally fall near the identity line.

    The implementation delegates to scikit-learn's PredictionErrorDisplay.
    """
    plot_ax = _new_axes(ax)

    PredictionErrorDisplay.from_predictions(
        y_true,
        y_pred,
        kind="actual_vs_predicted",
        ax=plot_ax,
    )
    plot_ax.set_title(title)
    return plot_ax

show_residuals

show_residuals(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = 'Residuals vs. Predicted',
) -> Axes

Show regression residuals against predicted values.

WHY: Residual plots can reveal systematic error patterns, changing variance, and unusual observations.

The implementation delegates to scikit-learn's PredictionErrorDisplay.

Source code in src/ml_vizkit/regression.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def show_residuals(
    y_true: Numeric1D,
    y_pred: Numeric1D,
    *,
    ax: Axes | None = None,
    title: str = "Residuals vs. Predicted",
) -> Axes:
    """Show regression residuals against predicted values.

    WHY: Residual plots can reveal systematic error patterns, changing
    variance, and unusual observations.

    The implementation delegates to scikit-learn's PredictionErrorDisplay.
    """
    plot_ax = _new_axes(ax)

    PredictionErrorDisplay.from_predictions(
        y_true,
        y_pred,
        kind="residual_vs_predicted",
        ax=plot_ax,
    )
    plot_ax.set_title(title)
    return plot_ax

save

Save visualization outputs.

save_chart

save_chart(
    ax: Axes,
    path: str | Path,
    *,
    bbox_inches: str | None = 'tight',
) -> None

Save chart.

Parameters:

Name Type Description Default
ax Axes

The matplotlib Axes object containing the chart to save.

required
path str | Path

The file path where the chart should be saved.

required
bbox_inches str | None

The bounding box in inches. Defaults to "tight".

'tight'

Raises:

Type Description
ValueError

If the Axes is not attached to a Figure.

Source code in src/ml_vizkit/save.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def save_chart(
    ax: Axes,
    path: str | Path,
    *,
    bbox_inches: str | None = "tight",
) -> None:
    """Save chart.

    Args:
        ax (Axes): The matplotlib Axes object containing the chart to save.
        path (str | Path): The file path where the chart should be saved.
        bbox_inches (str | None, optional): The bounding box in inches. Defaults to "tight".

    Raises:
        ValueError: If the Axes is not attached to a Figure.

    """
    output_path = Path(path)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    figure = ax.get_figure(root=True)
    if figure is None:
        raise ValueError("Axes must be attached to a Figure before saving.")

    figure.savefig(output_path, bbox_inches=bbox_inches)

splits

Visualizations for already-created train/test partitions.

show_train_test_split

show_train_test_split(
    X_train: DataFrame,
    X_test: DataFrame,
    *,
    ax: Axes | None = None,
    title: str = 'Train/Test Split',
) -> Axes

Show which observations were assigned to training and testing.

Parameters:

Name Type Description Default
X_train DataFrame

The training feature DataFrame containing exactly two numeric features.

required
X_test DataFrame

The testing feature DataFrame containing exactly two numeric features.

required
ax Axes | None

The Matplotlib Axes to plot on. If None, a new Axes is created.

None
title str

The title of the plot.

'Train/Test Split'

Returns:

Type Description
Axes

The Matplotlib Axes containing the plot.

WHY: Seeing the actual partition makes random splitting concrete instead of treating the random seed as unexplained boilerplate.

REQ: Both frames must contain the same two numeric features.

Source code in src/ml_vizkit/splits.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def show_train_test_split(
    X_train: pd.DataFrame,
    X_test: pd.DataFrame,
    *,
    ax: Axes | None = None,
    title: str = "Train/Test Split",
) -> Axes:
    """Show which observations were assigned to training and testing.

    Args:
        X_train: The training feature DataFrame containing exactly two numeric features.
        X_test: The testing feature DataFrame containing exactly two numeric features.
        ax: The Matplotlib Axes to plot on. If None, a new Axes is created.
        title: The title of the plot.

    Returns:
        The Matplotlib Axes containing the plot.

    WHY: Seeing the actual partition makes random splitting concrete instead
    of treating the random seed as unexplained boilerplate.

    REQ: Both frames must contain the same two numeric features.
    """
    if X_train.shape[1] != 2 or X_test.shape[1] != 2:
        msg = "Train/test split plots require exactly two features."
        raise ValueError(msg)

    if list(X_train.columns) != list(X_test.columns):
        msg = "Training and test data must contain the same features in the same order."
        raise ValueError(msg)

    if ax is None:
        _, ax = plt.subplots()

    feature_x, feature_y = X_train.columns

    ax.scatter(
        X_train[feature_x],
        X_train[feature_y],
        marker="o",
        label="Train",
        alpha=0.65,
    )
    ax.scatter(
        X_test[feature_x],
        X_test[feature_y],
        marker="x",
        label="Test",
        alpha=0.95,
    )

    ax.set_xlabel(str(feature_x))
    ax.set_ylabel(str(feature_y))
    ax.set_title(title)
    ax.legend()
    return ax