Skip to content

数据可视化实战

用 matplotlib + seaborn 把数据变成洞察——从基础图表到专业报告


🎯 图表选择速查

先想清楚你要表达什么,再选图表。

你要表达什么? 推荐图表 示例
比较数值大小 柱状图 / 条形图 各渠道收入对比
展示变化趋势 折线图 月度用户增长
显示分布形态 直方图 / 箱线图 用户消费金额分布
展示比例构成 饼图 / 堆叠柱状图 流量来源占比
发现变量关系 散点图 / 热力图 年龄与消费的相关性
比较多组分布 小提琴图 / 箱线图 不同城市的收入分布

📊 基础图表

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

# 全局设置
plt.rcParams.update({
    'figure.figsize': (10, 6),
    'font.size': 12,
    'axes.titlesize': 14,
    'axes.labelsize': 12,
})

# 模拟数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=12, freq='ME')
categories = ['搜索', '社交', '直接访问', '邮件', '广告']

1. 折线图 — 展示趋势

fig, ax = plt.subplots(figsize=(10, 5))

revenue = [120, 135, 148, 162, 155, 178, 190, 205, 198, 215, 230, 250]
ax.plot(dates, revenue, marker='o', linewidth=2, markersize=8,
        color='#2196F3', markerfacecolor='white', markeredgewidth=2)

ax.set_title('月度收入趋势', fontweight='bold', pad=15)
ax.set_xlabel('月份')
ax.set_ylabel('收入 (万元)')
ax.grid(True, alpha=0.3)
ax.fill_between(dates, revenue, alpha=0.1, color='#2196F3')

# 标注最高点
max_idx = np.argmax(revenue)
ax.annotate(f'最高: ¥{revenue[max_idx]}万',
            xy=(dates[max_idx], revenue[max_idx]),
            xytext=(dates[max_idx], revenue[max_idx] + 15),
            arrowprops=dict(arrowstyle='->', color='gray'),
            ha='center')

plt.tight_layout()

2. 柱状图 — 比较大小

fig, ax = plt.subplots(figsize=(10, 5))

values = [350, 280, 200, 150, 120]
colors = ['#2196F3', '#4CAF50', '#FF9800', '#9C27B0', '#F44336']
bars = ax.bar(categories, values, color=colors, edgecolor='white', linewidth=0.5)

# 数值标签
for bar, val in zip(bars, values):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5,
            f{val}万', ha='center', fontweight='bold')

ax.set_title('各渠道收入对比', fontweight='bold', pad=15)
ax.set_ylabel('收入 (万元)')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.set_ylim(0, max(values) * 1.15)

plt.tight_layout()

3. 直方图 — 看分布

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# 生成偏态数据(消费金额)
spending = np.random.lognormal(mean=4.5, sigma=0.8, size=10000)

# 左:直方图
axes[0].hist(spending, bins=50, color='#2196F3', alpha=0.7, edgecolor='white')
axes[0].axvline(np.median(spending), color='red', linestyle='--',
                linewidth=2, label=f'中位数: ¥{np.median(spending):.0f}')
axes[0].axvline(np.mean(spending), color='orange', linestyle='--',
                linewidth=2, label=f'均值: ¥{np.mean(spending):.0f}')
axes[0].set_title('消费金额分布')
axes[0].legend()

# 右:箱线图
axes[1].boxplot(spending, vert=True, patch_artist=True,
                boxprops=dict(facecolor='#2196F3', alpha=0.5))
axes[1].set_title('消费金额箱线图')
axes[1].set_ylabel('金额 (元)')

plt.tight_layout()

4. 散点图 — 看关系

fig, ax = plt.subplots(figsize=(8, 6))

# 模拟:年龄 vs 消费
age = np.random.normal(35, 12, 500)
spend = 500 + 20 * age + np.random.normal(0, 200, 500)

scatter = ax.scatter(age, spend, c=spend, cmap='Blues',
                      alpha=0.6, edgecolors='white', linewidth=0.3)

# 添加趋势线
z = np.polyfit(age, spend, 1)
p = np.poly1d(z)
x_line = np.linspace(age.min(), age.max(), 100)
ax.plot(x_line, p(x_line), color='red', linewidth=2, linestyle='--',
        label=f'趋势线 (斜率={z[0]:.1f})')

ax.set_xlabel('年龄')
ax.set_ylabel('月消费 (元)')
ax.set_title('年龄与消费的关系', fontweight='bold')
ax.legend()
plt.colorbar(scatter, label='消费金额')

plt.tight_layout()

5. 热力图 — 相关性矩阵

fig, ax = plt.subplots(figsize=(10, 8))

# 模拟特征矩阵
np.random.seed(42)
data = pd.DataFrame({
    'age': np.random.normal(35, 10, 1000),
    'income': np.random.lognormal(4, 0.5, 1000),
    'spending': np.random.lognormal(5, 0.8, 1000),
    'frequency': np.random.poisson(5, 1000),
    'tenure': np.random.exponential(12, 1000),
})
corr = data.corr()

# 热力图
mask = np.triu(np.ones_like(corr, dtype=bool))
sns.heatmap(corr, mask=mask, annot=True, fmt='.2f',
            cmap='RdBu_r', center=0, square=True,
            linewidths=1, cbar_kws={'shrink': 0.8},
            ax=ax)

ax.set_title('特征相关性热力图', fontweight='bold', pad=15)
plt.tight_layout()

🎨 专业报告美化技巧

技巧 1:统一的配色方案

# 推荐配色
COLORS = {
    'primary': '#2196F3',
    'success': '#4CAF50',
    'warning': '#FF9800',
    'danger': '#F44336',
    'purple': '#9C27B0',
}

# 色盲友好配色
CBB_PALETTE = ['#0072B2', '#009E73', '#F0E442',
               '#D55E00', '#CC79A7', '#56B4E9']

技巧 2:多子图排版

fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle('用户行为分析仪表盘', fontsize=16, fontweight='bold', y=1.02)

# (0,0): 月度趋势
axes[0, 0].plot(dates, revenue, 'o-', color=COLORS['primary'])
axes[0, 0].set_title('月度收入')

# (0,1): 渠道占比
axes[0, 1].pie(values, labels=categories, autopct='%1.1f%%',
               colors=[COLORS[k] for k in ['primary', 'success',
                       'warning', 'purple', 'danger']])

# (1,0): 分布
axes[1, 0].hist(spending, bins=40, color=COLORS['primary'], alpha=0.7)

# (1,1): Top N
top10_idx = np.argsort(values)[::-1]
axes[1, 1].barh(range(len(values)), [values[i] for i in top10_idx],
                color=[COLORS['primary']])
axes[1, 1].set_yticks(range(len(values)))

plt.tight_layout()

技巧 3:保存高清图

# 保存为 PNG(适合网页/PPT)
plt.savefig('report.png', dpi=150, bbox_inches='tight',
            facecolor='white', edgecolor='none')

# 保存为 SVG(适合进一步编辑)
plt.savefig('report.svg', format='svg', bbox_inches='tight')

📈 进阶:Seaborn 一行出图

Seaborn 是 matplotlib 的高级封装,适合快速探索而非精细定制。

# 分类对比
sns.boxplot(data=df, x='category', y='spending')

# 分布 + 分类
sns.violinplot(data=df, x='gender', y='spending', split=True)

# 多变量关系
sns.pairplot(df[['age', 'income', 'spending', 'frequency']],
             diag_kind='kde', plot_kws={'alpha': 0.5})

# 分类散点
sns.stripplot(data=df, x='day_of_week', y='spending',
              jitter=True, alpha=0.3)

# 回归 + 分布
sns.jointplot(data=df, x='age', y='spending', kind='reg')

⚠️ 常见错误

错误 为什么不好 正确做法
3D 饼图 视觉扭曲,比例难读 用条形图替代
双 Y 轴 容易误导(调整刻度=改变结论) 两个并排图
颜色太多 分散注意力 2-3 种颜色足够
不标注坐标轴 信息不完整 始终标注 + 单位
堆叠面积图太多层 无法比较中间层的趋势 最多 3-4 层

🔗 相关资源