import numpy as np
import pandas as pd
from plotnine import (
aes,
geom_line,
geom_point,
geom_tile,
ggplot,
labs,
theme,
theme_bw,
)
from plotnine.data import penguins as penguins_raw
from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.svm import SVC
# Palmer Penguins data used in the motivating examples.
penguins = (
penguins_raw
.assign(
gentoo=lambda df: np.where(df["species"] == "Gentoo", "A", "B")
)
.dropna()
.copy()
)
# Linear support-vector-classifier simulation.
rng_linear = np.random.default_rng(2434)
linear_x = rng_linear.normal(size=(20, 2))
linear_y = np.repeat([-1, 1], 10)
linear_x[linear_y == 1] += 1
linear_dat = pd.DataFrame(
{
"x1": linear_x[:, 0],
"x2": linear_x[:, 1],
"y": linear_y,
"class_label": linear_y.astype(str),
}
)
# Test observations for the linear classifier.
test_x = rng_linear.normal(size=(20, 2))
test_y = rng_linear.choice([-1, 1], size=20, replace=True)
test_x[test_y == 1] += 1
testdat = pd.DataFrame(
{
"x1": test_x[:, 0],
"x2": test_x[:, 1],
"y": test_y,
"class_label": test_y.astype(str),
}
)
# Nonlinear simulation used for the radial-kernel SVM.
rng_radial = np.random.default_rng(1)
radial_x = rng_radial.normal(size=(200, 2))
radial_x[:100] += 2
radial_x[100:150] -= 2
radial_y = np.concatenate([np.repeat(1, 150), np.repeat(2, 50)])
radial_dat = pd.DataFrame(
{
"x1": radial_x[:, 0],
"x2": radial_x[:, 1],
"y": radial_y,
"class_label": radial_y.astype(str),
}
)
train_indices = rng_radial.choice(radial_dat.index, size=100, replace=False)
radial_train = radial_dat.loc[train_indices].copy()
def make_prediction_grid(model, data, points=180, padding=0.5):
"""Create a dense two-dimensional grid and classify each grid point."""
x1_values = np.linspace(
data["x1"].min() - padding,
data["x1"].max() + padding,
points,
)
x2_values = np.linspace(
data["x2"].min() - padding,
data["x2"].max() + padding,
points,
)
x1_grid, x2_grid = np.meshgrid(x1_values, x2_values)
grid = pd.DataFrame(
{
"x1": x1_grid.ravel(),
"x2": x2_grid.ravel(),
}
)
grid["prediction"] = model.predict(grid[["x1", "x2"]]).astype(str)
return grid, x1_values, x2_values
def find_boundary_points(grid, x1_values, x2_values):
"""Find grid cells whose predicted class differs from a neighbor."""
predictions = grid["prediction"].to_numpy().reshape(
len(x2_values),
len(x1_values),
)
boundary = np.zeros_like(predictions, dtype=bool)
horizontal_change = predictions[:, 1:] != predictions[:, :-1]
vertical_change = predictions[1:, :] != predictions[:-1, :]
boundary[:, 1:] |= horizontal_change
boundary[:, :-1] |= horizontal_change
boundary[1:, :] |= vertical_change
boundary[:-1, :] |= vertical_change
return grid.loc[boundary.ravel(), ["x1", "x2"]].copy()