Day 44:Matplotlib 进阶------统计图表与3D绘图
柱状图、折线图、饼图已经熟练了。今天学更有"分析味"的图表:箱线图(看分布)、小提琴图(看分布形状)、六边形热力图(大数据散点)、以及 Matplotlib 的 3D 功能。
一、箱线图 boxplot()------一眼看清数据分布
箱线图是数据分析和论文中最常用的统计图之一,能同时展示中位数、四分位数、异常值。
python
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
np.random.seed(42)
# 模拟数据:四个班级的考试成绩
class_A = np.random.normal(75, 10, 50) # 平均75,标准差10
class_B = np.random.normal(80, 8, 50) # 平均80,标准差8
class_C = np.random.normal(70, 15, 50) # 平均70,标准差15(离散大)
class_D = np.random.normal(85, 12, 50) # 平均85,标准差12
data = [class_A, class_B, class_C, class_D]
labels = ["一班", "二班", "三班", "四班"]
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# --- 图1:基础箱线图 ---
bp = axes[0].boxplot(data, labels=labels, patch_artist=True, showmeans=True)
# 配色
colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4"]
for patch, color in zip(bp["boxes"], colors):
patch.set_facecolor(color)
patch.set_alpha(0.7)
axes[0].set_ylabel("考试成绩", fontsize=12)
axes[0].set_title("班级成绩箱线图", fontsize=14, fontweight="bold")
axes[0].grid(True, alpha=0.3, axis="y")
# --- 图2:横向箱线图 + 散点叠加 ---
bp2 = axes[1].boxplot(data, labels=labels, patch_artist=True,
vert=False, showmeans=True,
meanprops=dict(marker="D", markerfacecolor="red", markersize=8))
for patch, color in zip(bp2["boxes"], colors):
patch.set_facecolor(color)
patch.set_alpha(0.5)
# 叠加原始数据点
for i, (d, color) in enumerate(zip(data, colors), 1):
jitter = np.random.normal(0, 0.04, len(d)) # 加抖动避免重叠
axes[1].scatter(d, i + jitter, alpha=0.4, s=20, color=color, edgecolor="white", zorder=3)
axes[1].set_xlabel("考试成绩", fontsize=12)
axes[1].set_title("横向箱线图 + 原始数据点", fontsize=14, fontweight="bold")
axes[1].grid(True, alpha=0.3, axis="x")
plt.tight_layout()
plt.show()
箱线图怎么读?
┬ ─── 最大值(上边缘)
│
│ ┬ ─── 上四分位数 Q3(75%)
│ │
│ ───┼─── ─── 中位数(50%)
│ │
│ ┴ ─── 下四分位数 Q1(25%)
│
┴ ─── 最小值(下边缘)
○ ─── 异常值(离群点)
二、小提琴图 violinplot()------箱线图的升级版
小提琴图 = 箱线图 + 密度曲线。既能看到统计量,又能看到数据分布形状。
python
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# --- 图1:基础小提琴图 ---
vp = axes[0].violinplot(data, positions=[1, 2, 3, 4], showmeans=True, showmedians=True)
for i, body in enumerate(vp["bodies"]):
body.set_facecolor(colors[i])
body.set_alpha(0.7)
axes[0].set_xticks([1, 2, 3, 4])
axes[0].set_xticklabels(labels)
axes[0].set_ylabel("考试成绩", fontsize=12)
axes[0].set_title("小提琴图", fontsize=14, fontweight="bold")
axes[0].grid(True, alpha=0.3, axis="y")
# --- 图2:箱线图 vs 小提琴图对比 ---
# 同数据,一半箱线图一半小提琴图
data_combined = np.concatenate(data)
labels_combined = np.repeat(labels, [len(d) for d in data])
axes[1].boxplot(data, labels=labels, patch_artist=True, widths=0.3,
positions=np.arange(1, 5) - 0.15, showfliers=False)
vp2 = axes[1].violinplot(data, positions=np.arange(1, 5) + 0.15, showmedians=True)
for i, body in enumerate(vp2["bodies"]):
body.set_facecolor(colors[i])
body.set_alpha(0.5)
axes[1].set_xticks([1, 2, 3, 4])
axes[1].set_xticklabels(labels)
axes[1].set_title("箱线图(左) + 小提琴图(右) 同框对比", fontsize=14, fontweight="bold")
axes[1].grid(True, alpha=0.3, axis="y")
plt.tight_layout()
plt.show()
三、hexbin()------海量散点的高效可视化
普通散点图数据一多就糊成一片。hexbin() 用六边形格子统计每个区域的数据密度。
python
np.random.seed(42)
n = 10000 # 一万个点
# 模拟有相关性的双变量数据
mean = [0, 0]
cov = [[1, 0.7], [0.7, 1]]
x, y = np.random.multivariate_normal(mean, cov, n).T
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
# --- 普通散点图 ---
axes[0].scatter(x, y, s=1, alpha=0.3, color="#5B9BD5")
axes[0].set_title(f"普通散点图 (n={n})\n糊成一片")
axes[0].set_xlabel("X")
axes[0].set_ylabel("Y")
axes[0].grid(True, alpha=0.3)
# --- hexbin 六边形热力图 ---
hb = axes[1].hexbin(x, y, gridsize=40, cmap="YlOrRd", mincnt=1)
plt.colorbar(hb, ax=axes[1], label="数据点数", shrink=0.8)
axes[1].set_title("hexbin 六边形密度图\n密度分布一目了然")
axes[1].set_xlabel("X")
axes[1].set_ylabel("Y")
# --- 带等高线的 hexbin ---
axes[2].hexbin(x, y, gridsize=40, cmap="Blues", mincnt=1)
# 叠加 KDE 等高线(用 scipy)
from scipy.stats import gaussian_kde
kde = gaussian_kde(np.vstack([x, y]))
xi, yi = np.mgrid[-4:4:100j, -4:4:100j]
zi = kde(np.vstack([xi.flatten(), yi.flatten()])).reshape(xi.shape)
axes[2].contour(xi, yi, zi, levels=6, colors="red", linewidths=1.5, alpha=0.7)
axes[2].set_title("hexbin + KDE 等高线")
axes[2].set_xlabel("X")
axes[2].set_ylabel("Y")
plt.tight_layout()
plt.show()
四、3D 绘图入门
Matplotlib 可以画 3D 图,虽然不如专业工具强大,但日常够用。
python
# 导入 3D 模块
from mpl_toolkits.mplot3d import Axes3D # 虽未显式用但必须导入
4.1 3D 曲线
python
fig = plt.figure(figsize=(14, 5))
# --- 3D 螺旋线 ---
ax1 = fig.add_subplot(1, 2, 1, projection="3d")
t = np.linspace(0, 4 * np.pi, 200)
x = np.sin(t)
y = np.cos(t)
z = t
ax1.plot(x, y, z, "b-", linewidth=2)
ax1.set_xlabel("X")
ax1.set_ylabel("Y")
ax1.set_zlabel("Z")
ax1.set_title("3D 螺旋线", fontsize=12, fontweight="bold")
# --- 3D 散点图 ---
ax2 = fig.add_subplot(1, 2, 2, projection="3d")
np.random.seed(42)
n = 300
xs = np.random.randn(n)
ys = np.random.randn(n)
zs = xs**2 + ys**2 + np.random.randn(n) * 0.5
sc = ax2.scatter(xs, ys, zs, c=zs, cmap="viridis", s=20, alpha=0.7)
plt.colorbar(sc, ax=ax2, shrink=0.6, label="Z值")
ax2.set_xlabel("X")
ax2.set_ylabel("Y")
ax2.set_zlabel("Z = X² + Y² + noise")
ax2.set_title("3D 散点图", fontsize=12, fontweight="bold")
plt.tight_layout()
plt.show()
4.2 3D 曲面图
python
# 曲面图 plot_surface
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2)) # 涟漪函数
fig = plt.figure(figsize=(14, 5))
# --- 曲面图 ---
ax1 = fig.add_subplot(1, 2, 1, projection="3d")
surf = ax1.plot_surface(X, Y, Z, cmap="viridis", alpha=0.85,
linewidth=0, antialiased=True)
plt.colorbar(surf, ax=ax1, shrink=0.6, label="Z值")
ax1.set_xlabel("X")
ax1.set_ylabel("Y")
ax1.set_zlabel("Z")
ax1.set_title("plot_surface 曲面图", fontsize=12, fontweight="bold")
# --- 线框图 ---
ax2 = fig.add_subplot(1, 2, 2, projection="3d")
ax2.plot_wireframe(X, Y, Z, rstride=5, cstride=5, color="steelblue", alpha=0.6, linewidth=0.5)
ax2.set_xlabel("X")
ax2.set_ylabel("Y")
ax2.set_zlabel("Z")
ax2.set_title("plot_wireframe 线框图", fontsize=12, fontweight="bold")
plt.tight_layout()
plt.show()
4.3 3D 柱状图
python
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection="3d")
# 模拟 5天的4种产品销售数据
days = np.arange(5)
products = np.arange(4)
data_3d = np.random.randint(10, 100, (5, 4))
colors_3d = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#FFEAA7"]
_x = np.arange(5) # 天数
_y = np.arange(4) # 产品
_xx, _yy = np.meshgrid(_x, _y)
x_flat, y_flat = _xx.ravel(), _yy.ravel()
z_flat = np.zeros_like(x_flat)
dx = dy = 0.6
dz = data_3d.T.ravel()
# 每个柱子按产品颜色
bar_colors = np.repeat(colors_3d, 5)
ax.bar3d(x_flat, y_flat, z_flat, dx, dy, dz, color=bar_colors, alpha=0.7, edgecolor="white")
ax.set_xlabel("天数")
ax.set_ylabel("产品")
ax.set_zlabel("销量")
ax.set_xticks(_x)
ax.set_yticks(_y)
ax.set_yticklabels(["手机", "电脑", "平板", "耳机"])
ax.set_title("3D 柱状图:5天 × 4产品", fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()
五、踩坑记录 🐛
| 坑 | 现象 | 解决方案 |
|---|---|---|
| 箱线图中文乱码 | 标签出现方块 | 确保 rcParams 字体设置在最前面 |
| violinplot 位置偏移 | 图和标签对不齐 | 手动指定 positions=[1,2,3...] |
| hexbin 颜色全一样 | 密度差异看不出 | 调整 gridsize(越小格子越大,越容易区分) |
| 3D 图交互卡顿 | 旋转视角很慢 | 减少数据点,或用 rstride/cstride 降低精度 |
| 3D 图视角不对 | 默认角度看不清 | ax.view_init(elev=30, azim=45) 调整仰角和方位角 |
| scipy 未安装 | gaussian_kde 报错 | pip install scipy 或跳过等高线部分 |
六、总结
| 图表类型 | 函数 | 一句话用途 |
|---|---|---|
| 箱线图 | boxplot() |
比较组间分布差异 |
| 小提琴图 | violinplot() |
看分布形状+统计量 |
| 六边形热力图 | hexbin() |
海量散点的密度分布 |
| 3D曲线 | plot() with projection="3d" |
空间曲线 |
| 3D曲面 | plot_surface() |
函数z=f(x,y)可视化 |
| 3D柱状 | bar3d() |
三维数据对比 |
今天学完,Matplotlib 能画的图表类型你已经掌握了 90%。明天是 Matplotlib 的最终篇------综合实战,把前面 4 天的知识全部用在一个完整项目中。
第44天,打卡完成!📊
本系列是个人学习笔记,如有错误欢迎在评论区指正交流。