A parallel coordinates plot visualizes multivariate data by representing each variable as a vertical axis and each observation as a line connecting values across all axes. This technique is powerful for identifying patterns, clusters, and outliers in high-dimensional datasets where traditional 2D plots fall short. It enables simultaneous comparison of multiple variables for each data point.

""" anyplot.ai
parallel-basic: Basic Parallel Coordinates Plot
Library: matplotlib 3.11.0 | Python 3.13.14
Quality: 88/100 | Updated: 2026-07-24
"""
import os
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.collections import LineCollection
# Theme tokens (see prompts/default-style-guide.md "Background" + "Theme-adaptive Chrome")
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
# Imprint palette — 8 hues, theme-independent, hybrid-v3 sort. First 3 positions
# used in canonical order for the 3 species (no semantic color cue applies here).
IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3"]
# Data - Iris dataset for multivariate demonstration (embedded for reproducibility)
np.random.seed(42)
# Create Iris-like dataset with realistic measurements
data = {
"sepal_length": np.concatenate(
[
np.random.normal(5.0, 0.35, 50), # Setosa
np.random.normal(5.9, 0.52, 50), # Versicolor
np.random.normal(6.6, 0.64, 50), # Virginica
]
),
"sepal_width": np.concatenate(
[
np.random.normal(3.4, 0.38, 50), # Setosa
np.random.normal(2.8, 0.31, 50), # Versicolor
np.random.normal(3.0, 0.32, 50), # Virginica
]
),
"petal_length": np.concatenate(
[
np.random.normal(1.5, 0.17, 50), # Setosa
np.random.normal(4.3, 0.47, 50), # Versicolor
np.random.normal(5.5, 0.55, 50), # Virginica
]
),
"petal_width": np.concatenate(
[
np.random.normal(0.2, 0.11, 50), # Setosa
np.random.normal(1.3, 0.20, 50), # Versicolor
np.random.normal(2.0, 0.27, 50), # Virginica
]
),
"species": ["setosa"] * 50 + ["versicolor"] * 50 + ["virginica"] * 50,
}
df = pd.DataFrame(data)
# Ensure realistic bounds
df["sepal_length"] = df["sepal_length"].clip(4.3, 7.9)
df["sepal_width"] = df["sepal_width"].clip(2.0, 4.4)
df["petal_length"] = df["petal_length"].clip(1.0, 6.9)
df["petal_width"] = df["petal_width"].clip(0.1, 2.5)
# Define numeric columns and normalize to [0, 1] for fair comparison
numeric_cols = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
df_norm = df.copy()
for col in numeric_cols:
min_val = df[col].min()
max_val = df[col].max()
df_norm[col] = (df[col] - min_val) / (max_val - min_val)
# Plot — see default-style-guide.md "Visual Sizing Defaults" for canvas + sizing values
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)
colors = {"setosa": IMPRINT_PALETTE[0], "versicolor": IMPRINT_PALETTE[1], "virginica": IMPRINT_PALETTE[2]}
# Vertical reference line at each dimension's axis position - anchors the
# "each variable is a vertical axis" metaphor that parallel coordinates rely on
x = np.arange(len(numeric_cols))
for xi in x:
ax.axvline(xi, color=INK_SOFT, alpha=0.3, linewidth=0.8, zorder=0)
# Plot parallel coordinates - vectorized via LineCollection instead of a per-row loop
segments = np.stack([np.tile(x, (len(df_norm), 1)), df_norm[numeric_cols].to_numpy()], axis=2)
line_colors = df_norm["species"].map(colors).to_numpy()
lc = LineCollection(segments, colors=line_colors, alpha=0.4, linewidths=2, zorder=2)
ax.add_collection(lc)
ax.set_xlim(x.min() - 0.15, x.max() + 0.15)
# Axis labels with original scale ranges
ax.set_xticks(x)
labels = [
f"Sepal Length\n({df['sepal_length'].min():.1f}-{df['sepal_length'].max():.1f} cm)",
f"Sepal Width\n({df['sepal_width'].min():.1f}-{df['sepal_width'].max():.1f} cm)",
f"Petal Length\n({df['petal_length'].min():.1f}-{df['petal_length'].max():.1f} cm)",
f"Petal Width\n({df['petal_width'].min():.1f}-{df['petal_width'].max():.1f} cm)",
]
ax.set_xticklabels(labels, fontsize=8, color=INK_SOFT)
ax.set_ylabel("Normalized Value", fontsize=10, color=INK)
ax.set_title("parallel-basic · python · matplotlib · anyplot.ai", fontsize=12, fontweight="medium", color=INK)
ax.tick_params(axis="y", labelsize=8, colors=INK_SOFT)
# Add legend for species
legend_handles = [
plt.Line2D([0], [0], color=colors["setosa"], linewidth=3, label="Setosa"),
plt.Line2D([0], [0], color=colors["versicolor"], linewidth=3, label="Versicolor"),
plt.Line2D([0], [0], color=colors["virginica"], linewidth=3, label="Virginica"),
]
leg = ax.legend(handles=legend_handles, fontsize=8, loc="upper right")
leg.get_frame().set_facecolor(ELEVATED_BG)
leg.get_frame().set_edgecolor(INK_SOFT)
plt.setp(leg.get_texts(), color=INK_SOFT)
# Styling
ax.set_ylim(-0.05, 1.05)
ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
for spine in ("left", "bottom"):
ax.spines[spine].set_color(INK_SOFT)
plt.tight_layout()
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) # no bbox_inches='tight'
Part of Basic Parallel Coordinates Plot on anyplot.ai.