Note
Click here to download the full example code
Plot functions¶
Several auxiliary functions for plotting the examples in this documentation.
Import the required libraries¶
import os
import warnings
import matplotlib.pyplot as plt # type: ignore
import numpy as np
import numpy.typing as npt
import pandas as pd
import seaborn as sns # type: ignore
from pyclugen import Clusters
# Hide annoying warnings when building docs in CI
if os.getenv("CI") != None:
warnings.filterwarnings("ignore")
clusters2df¶
def clusters2df(
*exs: Clusters | dict[str, npt.ArrayLike], clusters_field: str = "clusters"
) -> pd.DataFrame:
"""Convert a sequence of clusters to a Pandas dataframe."""
dfs = []
iex = 1
for ex in exs:
if isinstance(ex, dict):
points = ex["points"]
clusters = ex[clusters_field]
else:
points = ex.points
clusters = ex.clusters
df = pd.DataFrame(
data=points, columns=[f"x{i}" for i in range(np.size(points, 1))]
)
df["cluster"] = clusters.tolist()
df["example"] = [iex] * clusters.size
dfs.append(df)
iex += 1
return pd.concat(dfs, ignore_index=True)
get_plot_lims¶
def get_plot_lims(df: pd.DataFrame, pmargin: float = 0.1):
"""Determine the plot limits for the cluster data given in `df`."""
# Get maximum and minimum points in each dimension
xmaxs = df.iloc[:, :-2].max()
xmins = df.iloc[:, :-2].min()
# Determine plot centers in each dimension
xcenters = (xmaxs + xmins) / 2
# Determine plots span for all dimensions
sidespan = (1 + pmargin) * np.max(np.abs(xmaxs - xmins)) / 2
# Determine final plots limits
xmaxs = xcenters + sidespan
xmins = xcenters - sidespan
return xmaxs, xmins
plot_examples_1d¶
def plot_examples_1d(*ets, ncols: int = 3, clusters_field: str = "clusters"):
"""Plot the 1D examples given in the ets parameter."""
# Get examples
ex = ets[0::2]
# Get titles
et = ets[1::2]
df = clusters2df(*ex, clusters_field=clusters_field)
# Set seaborn's dark grid style
sns.set_theme(style="darkgrid")
# Use seaborn to create the plots
g = sns.FacetGrid(df, col="example", hue="cluster", col_wrap=ncols)
# Plot the kernel density estimation plots
g.map(sns.kdeplot, "x0", multiple="layer", fill=True)
# Get a flattened view of the axes array
g_axes = g.axes.reshape(-1)
# Determine the height of the rugs in the rug plot to 5% of total height
rug_height = g_axes[0].get_ylim()[1] * 0.05
# Plot the rug markers below the kde plots
g.map(sns.rugplot, "x0", height=rug_height)
# Set titles
for ax, t in zip(g_axes, et):
ax.set_title(t)
plot_examples_2d¶
def plot_examples_2d(
*ets, pmargin: float = 0.1, ncols: int = 3, clusters_field: str = "clusters"
):
"""Plot the 2D examples given in the ets parameter."""
# Get examples
ex = ets[0::2]
# Get titles
et = ets[1::2]
df = clusters2df(*ex, clusters_field=clusters_field)
# Get limits in each dimension
xmaxs, xmins = get_plot_lims(df, pmargin=pmargin)
# Set seaborn's dark grid style
sns.set_theme(style="darkgrid")
# Use seaborn to create the plots
g = sns.FacetGrid(
df,
col="example",
hue="cluster",
xlim=(xmins.iloc[0], xmaxs.iloc[0]),
ylim=(xmins.iloc[1], xmaxs.iloc[1]),
aspect=1,
col_wrap=ncols,
)
g.map(sns.scatterplot, "x0", "x1", s=10)
# Set the plot titles and x, y labels
for ax, t in zip(g.axes, et):
ax.set_title(t)
ax.set_xlabel("x")
ax.set_ylabel("y")
plot_examples_3d¶
def plot_examples_3d(
*ets,
pmargin: float = 0.1,
ncols: int = 3,
side=350,
clusters_field: str = "clusters",
):
"""Plot the 3D examples given in the ets parameter."""
# Get examples
ex = ets[0::2]
# Get titles
et = ets[1::2]
# Number of plots and number of rows in combined plot
num_plots = len(ex)
nrows = max(1, int(np.ceil(num_plots / ncols)))
blank_plots = nrows * ncols - num_plots
df = clusters2df(*ex, clusters_field=clusters_field)
# Get limits in each dimension
xmaxs, xmins = get_plot_lims(df, pmargin=pmargin)
# Reset to default Matplotlib style, to avoid seaborn interference
sns.reset_orig()
# To convert inches to pixels afterwards
px = 1 / plt.rcParams["figure.dpi"] # pixel in inches
# Use Matplotlib to create the plots
_, axs = plt.subplots(
nrows,
ncols,
figsize=(side * px * ncols, side * px * nrows),
subplot_kw=dict(projection="3d"),
)
axs = axs.reshape(-1)
for ax, e, t in zip(axs, ex, et):
ax.set_title(t, fontsize=10)
ax.set_xlim(xmins.iloc[0], xmaxs.iloc[0])
ax.set_ylim(xmins.iloc[1], xmaxs.iloc[1])
ax.set_zlim(xmins.iloc[2], xmaxs.iloc[2])
ax.set_xlabel("$x$", labelpad=-2)
ax.set_ylabel("$y$", labelpad=-2)
ax.set_zlabel("$z$", labelpad=-2)
ax.tick_params(labelsize=8, pad=-2)
ax.scatter(
e.points[:, 0],
e.points[:, 1],
e.points[:, 2],
c=e.clusters,
depthshade=False,
edgecolor="black",
linewidths=0.2,
)
# Remaining plots are left blank
for ax in axs[len(ex) : len(ex) + blank_plots]:
ax.set_axis_off()
ax.set_facecolor(color="white")
ax.patch.set_alpha(0)
plot_examples_nd¶
def plot_examples_nd(
ex: Clusters, t: str, pmargin: float = 0.1, clusters_field: str = "clusters"
):
"""Plot the nD example given in the ex parameter."""
# How many dimensions?
nd = ex.points.shape[1]
df = clusters2df(ex, clusters_field=clusters_field)
# Get limits in each dimension
xmaxs, xmins = get_plot_lims(df, pmargin=pmargin)
# Set seaborn's dark grid style
sns.set_theme(style="darkgrid")
# Create pairwise plots with nothing on the diagonal
g = sns.PairGrid(df.iloc[:, :-1], hue="cluster", palette="deep")
g.map_offdiag(sns.scatterplot, s=10)
g.figure.suptitle(t, y=1)
# Decorate plot
for i in range(nd):
for j in range(nd):
if i == j:
# Set the x labels in the diagonal plots
xycoord = (xmaxs.iloc[i] + xmins.iloc[i]) / 2
g.axes[i, i].text(
xycoord, xycoord, f"$x{i}$", fontsize=20, ha="center", va="center"
)
else:
# Set appropriate plot intervals and aspect ratio
g.axes[i, j].set_xlim([xmins.iloc[j], xmaxs.iloc[j]])
g.axes[i, j].set_ylim([xmins.iloc[i], xmaxs.iloc[i]])
g.axes[i, j].set_aspect(1)
Total running time of the script: ( 0 minutes 0.005 seconds)
Download Python source code: plot_functions.py