PARALLEL PLOT
绘图 · 第 29 / 36 章
70 字约 1 分钟
基础平行坐标图

基础平行坐标图
Python52 行
import matplotlib.pyplot as plt
import numpy as np
COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"
np.random.seed(42)
n = 60
n_dims = 5
# 模拟 5 维数据
X = np.random.normal(0, 1, (n, n_dims))
# 第 2 维与第 1 维相关
X[:, 1] = X[:, 0] * 0.7 + np.random.normal(0, 0.5, n)
dims = [f"Dim {i+1}" for i in range(n_dims)]
# 归一化到 0~1
X_norm = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
fig, ax = plt.subplots(figsize=(11, 6))
x_positions = np.arange(n_dims)
# 画每条数据线
for i in range(n):
ax.plot(x_positions, X_norm[i],
color=COLORS[i % len(COLORS)],
linewidth=1.2, alpha=0.5)
# 坐标轴(竖线)
for x in x_positions:
ax.axvline(x, color=COLORS[3], linewidth=1.2,
alpha=0.6, zorder=0)
# 刻度标签
ax.set_xticks(x_positions)
ax.set_xticklabels(dims, fontsize=11)
ax.set_yticks([]) # 平行坐标图通常隐藏 y 刻度
ax.set_ylim(-0.05, 1.05)
ax.set_xlim(-0.3, n_dims - 0.7)
ax.set_title("Parallel Plot — Basic",
fontsize=15, color=COLORS[3], pad=12)
ax.tick_params(axis="x", colors=COLORS[3])
for s in ("top", "right", "left"):
ax.spines[s].set_visible(False)
ax.spines["bottom"].set_visible(False)
plt.tight_layout()
plt.show()按分类着色

按分类着色
Python64 行
import matplotlib.pyplot as plt
import numpy as np
COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"
np.random.seed(0)
n_per_class = 30
n_dims = 6
# 三类数据,各有不同的多维模式
class_data = {
"Class A": np.random.normal(0, 0.5, (n_per_class, n_dims)),
"Class B": np.random.normal(0.8, 0.5, (n_per_class, n_dims)),
"Class C": np.random.normal(-0.6, 0.5, (n_per_class, n_dims)),
}
# 给各类加特征
class_data["Class A"][:, 1] += 1.0
class_data["Class B"][:, 3] -= 1.2
class_data["Class C"][:, 4] += 1.5
X = np.vstack(list(class_data.values()))
dims = [f"Dim {i+1}" for i in range(n_dims)]
# 归一化
X_norm = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
fig, ax = plt.subplots(figsize=(12, 6.5))
x_positions = np.arange(n_dims)
# 按类画线
start = 0
for (name, data), c in zip(class_data.items(), COLORS):
end = start + len(data)
for i in range(start, end):
ax.plot(x_positions, X_norm[i],
color=c, linewidth=1.4, alpha=0.55,
label=name if i == start else None)
start = end
# 竖轴
for x in x_positions:
ax.axvline(x, color=COLORS[3], linewidth=1.2,
alpha=0.6, zorder=0)
ax.set_xticks(x_positions)
ax.set_xticklabels(dims, fontsize=11)
ax.set_yticks([])
ax.set_ylim(-0.05, 1.05)
ax.set_xlim(-0.3, n_dims - 0.7)
ax.set_title("Parallel Plot by Class",
fontsize=15, color=COLORS[3], pad=12)
ax.tick_params(axis="x", colors=COLORS[3])
ax.legend(frameon=False, fontsize=11, labelcolor=COLORS[3],
loc="upper right", ncol=3)
for s in ("top", "right", "left"):
ax.spines[s].set_visible(False)
ax.spines["bottom"].set_visible(False)
plt.tight_layout()
plt.show()按数值渐变着色

按数值渐变着色
Python62 行
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.cm import ScalarMappable
BG = "#F7F3EC"
CMAP = LinearSegmentedColormap.from_list(
"theme", ["#D0A64F", "#A8552E", "#3F2E18"], N=256
)
np.random.seed(1)
n = 80
n_dims = 6
X = np.random.normal(0, 1, (n, n_dims))
# 用一个连续变量(这里用第 1 维)映射颜色
color_var = X[:, 0]
norm = (color_var - color_var.min()) / (color_var.max() - color_var.min())
X_norm = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
dims = [f"Dim {i+1}" for i in range(n_dims)]
fig, ax = plt.subplots(figsize=(12, 6.5))
x_positions = np.arange(n_dims)
# 先画浅色的线在底层
order = np.argsort(norm) # 按颜色值排序,让高值在上层
for i in order:
ax.plot(x_positions, X_norm[i],
color=CMAP(norm[i]),
linewidth=1.6, alpha=0.7)
for x in x_positions:
ax.axvline(x, color="#3F2E18", linewidth=1.2,
alpha=0.6, zorder=0)
ax.set_xticks(x_positions)
ax.set_xticklabels(dims, fontsize=11)
ax.set_yticks([])
ax.set_ylim(-0.05, 1.05)
ax.set_xlim(-0.3, n_dims - 0.7)
# 色条
sm = ScalarMappable(cmap=CMAP, norm=plt.Normalize(0, 1))
sm.set_array([])
cbar = fig.colorbar(sm, ax=ax, shrink=0.7, pad=0.02)
cbar.set_label("Color Variable", fontsize=11, color="#3F2E18")
cbar.ax.tick_params(colors="#3F2E18")
cbar.outline.set_edgecolor("#3F2E18")
ax.set_title("Parallel Plot — Continuous Color",
fontsize=15, color="#3F2E18", pad=12)
ax.tick_params(axis="x", colors="#3F2E18")
for s in ("top", "right", "left"):
ax.spines[s].set_visible(False)
ax.spines["bottom"].set_visible(False)
plt.tight_layout()
plt.show()按维度独立归一化(真实量纲)

按维度独立归一化(真实量纲)
Python58 行
import matplotlib.pyplot as plt
import numpy as np
COLORS = ["#A8552E", "#35666E", "#D0A64F", "#3F2E18", "#7E8F52"]
BG = "#F7F3EC"
np.random.seed(2)
n = 50
# 各维度量纲差异大
X = np.column_stack([
np.random.normal(170, 10, n), # 身高 cm
np.random.normal(70, 12, n), # 体重 kg
np.random.normal(25, 5, n), # BMI
np.random.normal(100, 15, n), # 血压
np.random.normal(5.5, 1, n), # 血糖
])
dims = ["Height\n(cm)", "Weight\n(kg)", "BMI", "BP\n(mmHg)", "Glucose"]
# 每列独立归一化
X_norm = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
fig, ax = plt.subplots(figsize=(12, 6.5))
x_positions = np.arange(len(dims))
for i in range(n):
ax.plot(x_positions, X_norm[i],
color=COLORS[0], linewidth=1.2, alpha=0.35)
# 竖轴 + min/max 标注
for x, dim, col in zip(x_positions, dims, range(len(dims))):
ax.axvline(x, color=COLORS[3], linewidth=1.2,
alpha=0.6, zorder=0)
# 顶部标 max
ax.text(x, 1.04, f"{X[:, col].max():.1f}",
ha="center", va="bottom",
fontsize=9, color=COLORS[3])
# 底部标 min
ax.text(x, -0.04, f"{X[:, col].min():.1f}",
ha="center", va="top",
fontsize=9, color=COLORS[3])
ax.set_xticks(x_positions)
ax.set_xticklabels(dims, fontsize=11)
ax.set_yticks([])
ax.set_ylim(-0.1, 1.1)
ax.set_xlim(-0.3, len(dims) - 0.7)
ax.set_title("Parallel Plot — Per-Axis Scale",
fontsize=15, color=COLORS[3], pad=12)
ax.tick_params(axis="x", colors=COLORS[3])
for s in ("top", "right", "left"):
ax.spines[s].set_visible(False)
ax.spines["bottom"].set_visible(False)
plt.tight_layout()
plt.show()