Python 进阶教程:数据可视化

前言

“一图胜千言”——数据可视化是数据分析的最后一步,也是最关键的一步。无论多么复杂的模型、多么精妙的数据,处理结果最终都需要以直观的方式呈现给用户。

Python 生态拥有丰富的数据可视化库,从基础的 Matplotlib 到高级的 Plotly,从静态图表到交互式仪表盘,总有一款适合你的需求。

本文将系统覆盖:

  • Matplotlib:Python 可视化的基石
  • Seaborn:统计图表的高级封装
  • Plotly:交互式图表的首选
  • 实战场景:从数据到图表的完整流程
  • 最佳实践:配色、布局、排版

可视化库对比

主流库一览

定位图表类型交互性学习曲线适用场景
Matplotlib基础层几乎所有❌ 弱⭐⭐⭐定制化图表、论文出版
Seaborn统计层统计图表❌ 弱⭐⭐统计分析、快速探索
Plotly交互层多种类型✅ 强⭐⭐仪表盘、Web 应用
Altair声明层Vega-Lite✅ 强⭐⭐快速探索、JSON 驱动
Bokeh交互层Web 图表✅ 强⭐⭐⭐Web 应用、大数据
PyEcharts交互层ECharts✅ 强⭐⭐中文图表、Dashboards

选型指南

# 快速生成统计图表 → Seaborn
import seaborn as sns
sns.boxplot(data=df, x='category', y='value')
​
# 高度定制化图表 → Matplotlib
import matplotlib.pyplot as plt
plt.plot(x, y, color='red', linewidth=2)
​
# 交互式仪表盘 → Plotly Dash
import dash
import plotly.express as px
px.scatter(df, x='x', y='y', color='category')
​
# Web 应用嵌入 → Bokeh / PyEcharts
# 中文场景 → PyEcharts

Matplotlib 基础

安装与导入

# 安装
pip install matplotlib numpy pandas
​
# Jupyter 魔法命令(自动显示图表)
%matplotlib inline
# 或交互模式
%matplotlib widget
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
​
# 设置中文字体(重要!)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

图表结构详解

fig, ax = plt.subplots(figsize=(10, 6))
​
# fig = Figure(画布,整个图形)
# ax = Axes(坐标系,一个 fig 可以有多个 ax)
​
# 图表各部分名称
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
​
# 图表组成
# - Figure: 整个画布
# - Axes: 坐标系(包含 X/Y 轴)
# - Axis: X 轴和 Y 轴
# - Title: 标题
# - Labels: 轴标签
# - Tick: 刻度
# - Legend: 图例
# - Grid: 网格线
​
plt.show()  # 显示图表
fig.savefig('chart.png', dpi=300, bbox_inches='tight')  # 保存图表

基本图表类型

import matplotlib.pyplot as plt
import numpy as np
​
x = np.linspace(0, 10, 100)
y = np.sin(x)
​
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
​
# 1. 折线图
axes[0, 0].plot(x, y, 'b-', linewidth=2, label='sin(x)')
axes[0, 0].set_title('折线图')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
​
# 2. 散点图
np.random.seed(42)
x_scatter = np.random.randn(50)
y_scatter = np.random.randn(50)
colors = np.random.choice(['red', 'blue', 'green'], 50)
sizes = np.random.randint(50, 200, 50)
axes[0, 1].scatter(x_scatter, y_scatter, c=colors, s=sizes, alpha=0.6)
axes[0, 1].set_title('散点图')
​
# 3. 柱状图
categories = ['A', 'B', 'C', 'D']
values = [23, 45, 56, 78]
bars = axes[1, 0].bar(categories, values, color=['#3498db', '#2ecc71', '#e74c3c', '#f39c12'])
axes[1, 0].set_title('柱状图')
# 添加数值标签
for bar, val in zip(bars, values):
    axes[1, 0].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, 
                    str(val), ha='center', va='bottom')
​
# 4. 直方图
data_normal = np.random.normal(0, 1, 1000)
axes[1, 1].hist(data_normal, bins=30, color='#9b59b6', edgecolor='white', alpha=0.7)
axes[1, 1].set_title('直方图(正态分布)')
axes[1, 1].set_xlabel('值')
axes[1, 1].set_ylabel('频数')
​
plt.tight_layout()
plt.show()

颜色与样式

# 颜色指定方式
plt.plot(x, y, color='red')           # 颜色名称
plt.plot(x, y, color='#FF5733')       # HEX
plt.plot(x, y, color=(1, 0.34, 0.2)) # RGB 元组 (0-1)
plt.plot(x, y, color='C0')            # Tableau 颜色 C0-C9
​
# 颜色映射
plt.imshow(data, cmap='viridis')      # viridis, plasma, coolwarm, RdYlGn
plt.colorbar()                        # 显示颜色条
​
# 线型
plt.plot(x, y, linestyle='--')        # --, :, -., solid
plt.plot(x, y, linewidth=2)           # 线宽
​
# 标记
plt.plot(x, y, marker='o')            # o, s, D, ^, v, <, >
plt.plot(x, y, markersize=10)
plt.plot(x, y, markerfacecolor='red')
​
# 样式简写
# 'b-' 蓝色实线
# 'ro' 红色圆点
# 'g--' 绿色虚线
# 'ks:' 黑色方形点线

Matplotlib 进阶

多子图布局

# 方法1:subplots
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
axes = axes.flatten()  # 展平便于索引
​
# 方法2:subplot2grid(更灵活的布局)
fig = plt.figure(figsize=(12, 6))
ax1 = plt.subplot2grid((2, 3), (0, 0), colspan=2)  # 第1行,占2列
ax2 = plt.subplot2grid((2, 3), (0, 2))               # 第1行,第3列
ax3 = plt.subplot2grid((2, 3), (1, 0), colspan=3)    # 第2行,占3列
​
# 方法3:GridSpec(更精细的控制)
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize=(12, 8))
gs = GridSpec(3, 3, figure=fig, hspace=0.3, wspace=0.3)
​
ax1 = fig.add_subplot(gs[0, :])       # 第1行,占整行
ax2 = fig.add_subplot(gs[1, :-1])     # 第2行,前2列
ax3 = fig.add_subplot(gs[1:, -1])     # 第2-3行,最后1列
ax4 = fig.add_subplot(gs[2, 0])       # 第3行,第1列

双 Y 轴与共享轴

# 双 Y 轴
fig, ax1 = plt.subplots(figsize=(10, 6))
​
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.exp(x)
​
ax1.plot(x, y1, 'b-', label='sin(x)')
ax1.set_xlabel('X')
ax1.set_ylabel('sin(x)', color='blue')
​
ax2 = ax1.twinx()  # 创建共享 X 轴的双 Y 轴
ax2.plot(x, y2, 'r-', label='exp(x)')
ax2.set_ylabel('exp(x)', color='red')
​
# 添加图例
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
​
plt.title('双 Y 轴图表')
plt.show()
​
# 共享 X 轴
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(10, 8))
ax1.plot(x, y1)
ax2.plot(x, y2)
plt.show()

注释与标注

fig, ax = plt.subplots(figsize=(10, 6))
​
x = np.linspace(0, 10, 100)
y = x ** 2
​
ax.plot(x, y, 'b-', linewidth=2)
​
# 文本标注
ax.text(5, 50, 'y = x²', fontsize=14, ha='center')
​
# 带箭头的标注
ax.annotate(
    '最大值点',
    xy=(10, 100),           # 箭头指向点
    xytext=(7, 70),         # 文本位置
    fontsize=12,
    arrowprops=dict(
        arrowstyle='->',
        color='red',
        lw=2
    ),
    color='red'
)
​
# 水平/垂直线
ax.axhline(y=50, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=5, color='gray', linestyle='--', alpha=0.5)
​
# 填充区域
ax.fill_between(x, 0, y, alpha=0.3, color='blue')
ax.fill_between(x, y, y + 20, alpha=0.3, color='green')
​
plt.show()

保存与导出

# 保存各种格式
fig.savefig('chart.png', dpi=300, bbox_inches='tight')
fig.savefig('chart.pdf', bbox_inches='tight')           # 矢量图,适合出版
fig.savefig('chart.svg', bbox_inches='tight')          # SVG,可编辑
fig.savefig('chart.jpg', dpi=150, quality=95)          # JPEG

# 透明背景
fig.savefig('chart_transparent.png', transparent=True)

# 不含白边
plt.savefig('chart.png', dpi=300, bbox_inches='tight', pad_inches=0)

# 保存数据
np.savetxt('data.csv', np.column_stack([x, y]), delimiter=',', header='x,y')

Seaborn 统计可视化

Seaborn 基础

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
​
# 设置样式
sns.set_theme(style='whitegrid')  # darkgrid, whitegrid, dark, white, ticks
sns.set_palette('husl')          # 配色方案
​
# 内置数据集
tips = sns.load_dataset('tips')   # 小费数据集
iris = sns.load_dataset('iris')   # 鸢尾花数据集
flights = sns.load_dataset('flights')  # 航班数据
​
print(tips.head())
#    total_bill  tip   sex smoker  day    time  size
# 0       16.99  1.01  Female     No  Sun  Dinner     2
# 1       10.34  1.66    Male     No  Sun  Dinner     3

关系图

# 散点图 + 回归线
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
​
# 左图:简单散点图
sns.scatterplot(data=tips, x='total_bill', y='tip', ax=axes[0])
axes[0].set_title('小费 vs 账单金额')
​
# 右图:带回归线的散点图
sns.regplot(data=tips, x='total_bill', y='tip', ax=axes[1])
axes[1].set_title('带回归线的小费分析')
​
# 增强散点图(带分类)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
​
# 左图:按性别分类
sns.scatterplot(data=tips, x='total_bill', y='tip', 
                hue='sex', style='smoker', ax=axes[0])
axes[0].set_title('按性别和吸烟状态分类')
​
# 右图:带边缘分布
sns.jointplot(data=tips, x='total_bill', y='tip', 
              kind='reg', marginal_kws=dict(bins=30))
plt.suptitle('联合分布图', y=1.02)
​
plt.tight_layout()
plt.show()

分布图

fig, axes = plt.subplots(2, 2, figsize=(14, 10))
​
# 1. 直方图 + KDE
sns.histplot(data=tips, x='total_bill', kde=True, ax=axes[0, 0])
axes[0, 0].set_title('账单金额分布')
​
# 2. 分类直方图
sns.histplot(data=tips, x='total_bill', hue='sex', 
             multiple='stack', kde=True, ax=axes[0, 1])
axes[0, 1].set_title('按性别分类的账单分布')
​
# 3. KDE 图
sns.kdeplot(data=tips, x='total_bill', hue='day', 
            multiple='layer', ax=axes[1, 0])
axes[1, 0].set_title('各天账单金额密度')
​
# 4. 箱线图
sns.boxplot(data=tips, x='day', y='total_bill', 
            hue='sex', palette='Set2', ax=axes[1, 1])
axes[1, 1].set_title('各天账单金额箱线图')
​
plt.tight_layout()
plt.show()

分类图

fig, axes = plt.subplots(2, 3, figsize=(18, 10))
​
# 1. 柱状图
sns.barplot(data=tips, x='day', y='total_bill', 
            hue='sex', estimator='mean', capsize=0.1, ax=axes[0, 0])
axes[0, 0].set_title('各天平均账单(带误差棒)')
​
# 2. 计数图
sns.countplot(data=tips, x='day', hue='sex', ax=axes[0, 1])
axes[0, 1].set_title('各天顾客数量')
​
# 3. 箱线图
sns.boxplot(data=tips, x='day', y='total_bill', 
            hue='smoker', palette='Set3', ax=axes[0, 2])
axes[0, 2].set_title('吸烟者与非吸烟者账单对比')
​
# 4. 小提琴图
sns.violinplot(data=tips, x='day', y='total_bill', 
               hue='sex', split=True, palette='Set2', ax=axes[1, 0])
axes[1, 0].set_title('小提琴图(显示分布形态)')
​
# 5. 点图
sns.pointplot(data=tips, x='day', y='total_bill', 
              hue='sex', markers=['o', 's'], errorbar='sd', ax=axes[1, 1])
axes[1, 1].set_title('点图(显示趋势)')
​
# 6. 热力图(相关性)
numeric_cols = tips.select_dtypes(include=[np.number]).columns
corr = tips[numeric_cols].corr()
sns.heatmap(corr, annot=True, cmap='coolwarm', center=0, 
            fmt='.2f', ax=axes[1, 2])
axes[1, 2].set_title('账单数据相关性热力图')
​
plt.tight_layout()
plt.show()

高级:FacetGrid

# 多维度分类可视化
g = sns.FacetGrid(tips, col='time', row='sex', margin_titles=True, height=4)
g.map_dataframe(sns.scatterplot, x='total_bill', y='tip', hue='smoker')
g.add_legend()
g.set_axis_labels('账单金额', '小费')
g.set_titles(col_template='{col_name}', row_template='{row_name}')
plt.suptitle('多维度分类散点图', y=1.02)
plt.show()

Plotly 交互式图表

Plotly 基础

pip install plotly kaleido  # kaleido 用于导出静态图
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
​
# 内置数据集
df = px.data.iris()  # 鸢尾花数据集
print(df.head())

散点图

# 简单散点图
fig = px.scatter(df, x='sepal_width', y='sepal_length', 
                 color='species', title='鸢尾花散点图')
​
# 添加趋势线
fig = px.scatter(df, x='sepal_width', y='sepal_length', 
                 color='species', trendline='ols')  # OLS 回归
​
# 3D 散点图
fig = px.scatter_3d(df, x='sepal_width', y='sepal_length', 
                     z='petal_width', color='species',
                     title='3D 散点图')
​
# 气泡图(大小映射到数值)
fig = px.scatter(df, x='sepal_width', y='sepal_length',
                 size='petal_length', color='species',
                 hover_name='species_id',
                 title='气泡图')
​
fig.show()

其他常见图表

# 折线图
df_line = pd.DataFrame({
    'x': range(10),
    'y': [1, 3, 2, 5, 4, 6, 5, 7, 6, 8],
    'category': ['A']*5 + ['B']*5
})
fig = px.line(df_line, x='x', y='y', color='category', 
              markers=True, title='折线图')
​
# 柱状图
fig = px.bar(df, x='species', y='sepal_width', 
             color='species', title='柱状图',
             barmode='group')  # group, stack, relative
​
# 饼图
df_pie = df.groupby('species').size().reset_index(name='count')
fig = px.pie(df_pie, values='count', names='species', 
             title='物种占比', hole=0.4)  # hole 变为环形图
​
# 箱线图
fig = px.box(df, x='species', y='sepal_length', 
             color='species', title='箱线图',
             points='all')  # points: all, outliers, False
​
# 热力图
fig = px.density_heatmap(df, x='sepal_width', y='sepal_length',
                          title='密度热力图')
​
fig.show()

地图可视化

# 地图示例
df_map = pd.DataFrame({
    'city': ['北京', '上海', '广州', '深圳'],
    'lat': [39.9042, 31.2304, 23.1291, 22.5431],
    'lon': [116.4074, 121.4737, 113.2644, 114.0579],
    'value': [100, 120, 80, 90]
})
​
fig = px.scatter_geo(df_map, lat='lat', lon='lon', 
                     size='value', color='value',
                     hover_name='city',
                     projection='natural earth',
                     title='城市数据地图')
​
# 美国地图
df_usa = px.data.election()
fig = px.scatter_geo(df_usa, locations='county', locationmode='USA-states',
                     color='winner', scope='usa')
​
fig.show()

子图与布局

# 创建子图
from plotly.subplots import make_subplots
​
fig = make_subplots(
    rows=2, cols=2,
    subplot_titles=('散点图', '柱状图', '折线图', '饼图'),
    specs=[[{'type': 'scatter'}, {'type': 'bar'}],
           [{'type': 'scatter'}, {'type': 'pie'}]]
)
​
# 添加图表
fig.add_trace(
    go.Scatter(x=[1, 2, 3], y=[4, 5, 6], name='散点'),
    row=1, col=1
)
fig.add_trace(
    go.Bar(x=['A', 'B', 'C'], y=[3, 5, 2], name='柱状'),
    row=1, col=2
)
​
# 更新布局
fig.update_layout(
    title='多子图布局',
    showlegend=True,
    height=600,
    width=900
)
​
fig.show()

导出与保存

# 保存为 HTML(交互式,可嵌入网页)
fig.write_html('interactive_chart.html')
​
# 保存为静态图片
fig.write_image('chart.png', width=1200, height=800, scale=2)
fig.write_image('chart.pdf')
​
# 保存为 JSON
fig.write_json('chart.json')

常见图表类型

选型指南

目的推荐图表示例场景
比较柱状图、条形图产品销量对比
趋势折线图、面积图股价走势、用户增长
分布直方图、KDE、箱线图用户年龄分布
构成饼图、堆叠柱状图市场份额占比
关系散点图、热力图身高体重相关性
地理地图、 Choropleth各省销售额

对比图表

# 分组柱状图
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
​
categories = ['Q1', 'Q2', 'Q3', 'Q4']
product_a = [100, 120, 110, 130]
product_b = [80, 90, 100, 95]
​
# 方案1:分组柱状图
x = np.arange(len(categories))
width = 0.35
axes[0].bar(x - width/2, product_a, width, label='产品 A')
axes[0].bar(x + width/2, product_b, width, label='产品 B')
axes[0].set_xlabel('季度')
axes[0].set_ylabel('销量')
axes[0].set_title('分组柱状图')
axes[0].set_xticks(x)
axes[0].set_xticklabels(categories)
axes[0].legend()
​
# 方案2:堆叠柱状图
axes[1].bar(categories, product_a, label='产品 A')
axes[1].bar(categories, product_b, bottom=product_a, label='产品 B')
axes[1].set_xlabel('季度')
axes[1].set_ylabel('销量')
axes[1].set_title('堆叠柱状图')
axes[1].legend()
​
plt.tight_layout()
plt.show()

趋势图表

# 面积图
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
​
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
​
# 普通面积图
axes[0].fill_between(x, y1, alpha=0.3, label='sin')
axes[0].fill_between(x, y2, alpha=0.3, label='cos')
axes[0].set_title('面积图')
​
# 堆叠面积图
df_area = pd.DataFrame({
    'x': x,
    'A': y1 * 10 + 50,
    'B': y2 * 10 + 30,
    'C': np.random.randint(10, 30, 100)
})
axes[1].stackplot(df_area['x'], df_area['A'], df_area['B'], df_area['C'],
                  labels=['A', 'B', 'C'], alpha=0.8)
axes[1].set_title('堆叠面积图')
axes[1].legend(loc='upper left')
​
plt.tight_layout()
plt.show()

实战案例

案例1:股票数据可视化

import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
import mplfinance as mpf
​
# 下载股票数据
stock = yf.download('AAPL', start='2025-01-01', end='2026-05-15')
print(stock.head())
​
# K 线图(需要 mplfinance)
mpf.plot(stock, type='candle', style='charles', 
         title='Apple Inc. K线图',
         ylabel='价格',
         volume=True,
         mav=(5, 10, 20),  # 移动平均线
         savefig='candlestick.png')
​
# 普通折线图
fig, axes = plt.subplots(2, 1, figsize=(14, 10), 
                         gridspec_kw={'height_ratios': [3, 1]})
​
# 价格走势
axes[0].plot(stock.index, stock['Close'], label='收盘价')
axes[0].plot(stock.index, stock['MA5'], label='MA5', alpha=0.7)
axes[0].plot(stock.index, stock['MA20'], label='MA20', alpha=0.7)
axes[0].fill_between(stock.index, stock['Low'], stock['High'], alpha=0.3)
axes[0].set_title('Apple Inc. 价格走势')
axes[0].set_ylabel('价格 (USD)')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
​
# 成交量
colors = ['green' if stock['Close'].iloc[i] >= stock['Open'].iloc[i] 
          else 'red' for i in range(len(stock))]
axes[1].bar(stock.index, stock['Volume'], color=colors, alpha=0.7)
axes[1].set_ylabel('成交量')
axes[1].set_xlabel('日期')
axes[1].grid(True, alpha=0.3)
​
plt.tight_layout()
plt.savefig('stock_analysis.png', dpi=150, bbox_inches='tight')
plt.show()

案例2:用户行为分析仪表盘

import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
​
# 模拟用户行为数据
np.random.seed(42)
dates = pd.date_range('2026-01-01', '2026-05-15', freq='D')
df = pd.DataFrame({
    'date': dates,
    'DAU': np.random.randint(1000, 5000, len(dates)),  # 日活用户
    'new_users': np.random.randint(100, 500, len(dates)),
    'retention_rate': np.random.uniform(0.3, 0.8, len(dates)),
    'avg_session_time': np.random.uniform(5, 30, len(dates)),  # 分钟
})
​
# 创建仪表盘
fig = make_subplots(
    rows=2, cols=2,
    subplot_titles=('日活用户趋势', '新增用户趋势', '留存率分布', '平均使用时长'),
    specs=[[{'type': 'scatter'}, {'type': 'scatter'}],
           [{'type': 'histogram'}, {'type': 'box'}]]
)
​
# 日活趋势
fig.add_trace(
    go.Scatter(x=df['date'], y=df['DAU'], 
               mode='lines', name='DAU', fill='tozeroy'),
    row=1, col=1
)
​
# 新增用户趋势
fig.add_trace(
    go.Bar(x=df['date'], y=df['new_users'], name='新增用户'),
    row=1, col=2
)
​
# 留存率直方图
fig.add_trace(
    go.Histogram(x=df['retention_rate'], nbinsx=20, name='留存率'),
    row=2, col=1
)
​
# 使用时长箱线图
fig.add_trace(
    go.Box(y=df['avg_session_time'], name='使用时长'),
    row=2, col=2
)
​
fig.update_layout(
    title='用户行为分析仪表盘',
    height=800,
    showlegend=False
)
​
fig.show()
fig.write_html('user_dashboard.html')

案例3:电商数据分析

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
​
# 模拟电商数据
np.random.seed(42)
df = pd.DataFrame({
    'category': np.random.choice(['电子产品', '服装', '食品', '图书', '家居'], 500),
    'price': np.random.lognormal(4.5, 1, 500),  # 价格分布
    'rating': np.random.uniform(1, 5, 500),       # 评分
    'sales': np.random.randint(10, 1000, 500),  # 销量
    'discount': np.random.uniform(0, 0.5, 500)   # 折扣率
})
​
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
​
# 1. 各类别销售额占比(饼图)
sales_by_cat = df.groupby('category')['sales'].sum().sort_values(ascending=False)
colors = sns.color_palette('husl', len(sales_by_cat))
axes[0, 0].pie(sales_by_cat, labels=sales_by_cat.index, autopct='%1.1f%%',
               colors=colors, explode=[0.05] * len(sales_by_cat))
axes[0, 0].set_title('各类别销售额占比')
​
# 2. 价格 vs 评分(带回归)
sns.regplot(data=df, x='price', y='rating', ax=axes[0, 1], 
            scatter_kws={'alpha': 0.3}, line_kws={'color': 'red'})
axes[0, 1].set_title('价格与评分关系')
​
# 3. 各类别评分箱线图
sns.boxplot(data=df, x='category', y='rating', ax=axes[1, 0],
            palette='Set2')
axes[1, 0].set_title('各类别评分分布')
axes[1, 0].tick_params(axis='x', rotation=30)
​
# 4. 销售额 TOP10 商品(条形图)
top10 = df.nlargest(10, 'sales')[['category', 'sales']]
colors = [colors[sales_by_cat.index.get_loc(c)] for c in top10['category']]
axes[1, 1].barh(range(10), top10['sales'].values, color=colors)
axes[1, 1].set_yticks(range(10))
axes[1, 1].set_yticklabels(top10['category'].values)
axes[1, 1].set_xlabel('销量')
axes[1, 1].set_title('销量 TOP10 商品')
axes[1, 1].invert_yaxis()
​
plt.tight_layout()
plt.savefig('ecommerce_analysis.png', dpi=150, bbox_inches='tight')
plt.show()

图表美化与主题

配色方案

import matplotlib.pyplot as plt
import seaborn as sns
​
# Seaborn 调色板
sns.palettes.SEABORN_PALETTES
# ['deep', 'muted', 'pastel', 'bright', 'dark', 'colorblind']
​
# 使用调色板
plt.figure(figsize=(10, 6))
current_palette = sns.color_palette('pastel')
sns.barplot(x=['A', 'B', 'C', 'D'], y=[3, 5, 2, 4], palette=current_palette)
​
# 自定义颜色列表
custom_colors = ['#E74C3C', '#3498DB', '#2ECC71', '#F39C12', '#9B59B6']
sns.barplot(x=['A', 'B', 'C', 'D', 'E'], y=[3, 5, 2, 4, 6], palette=custom_colors)
​
# 渐变色
from matplotlib.colors import LinearSegmentedColormap
colors = ['#FF6B6B', '#FFE66D', '#4ECDC4']  # 红 -> 黄 -> 青
cmap = LinearSegmentedColormap.from_list('my_cmap', colors)

Seaborn 主题

# 可用主题
sns.set_theme(style='darkgrid')   # 深色网格
sns.set_theme(style='whitegrid')  # 白色网格(推荐)
sns.set_theme(style='dark')       # 深色背景
sns.set_theme(style='white')      # 纯白背景
sns.set_theme(style='ticks')      # 带刻度线
​
# 全局设置
plt.rcParams.update({
    'figure.facecolor': 'white',
    'axes.facecolor': 'white',
    'axes.edgecolor': '.9',
    'axes.grid': True,
    'grid.color': '.9',
    'axes.spines.top': False,
    'axes.spines.right': False,
    'font.family': 'sans-serif',
    'font.sans-serif': ['Arial', 'Helvetica'],
    'axes.titlesize': 14,
    'axes.labelsize': 12,
    'xtick.labelsize': 10,
    'ytick.labelsize': 10,
    'legend.fontsize': 10,
})

自定义样式

# 创建自定义样式
plt.style.use('default')  # 重置
​
# 自定义 Matplotlib 参数
import matplotlib as mpl
​
# 设置全局样式
mpl.rcParams['figure.figsize'] = (10, 6)
mpl.rcParams['figure.dpi'] = 100
mpl.rcParams['savefig.dpi'] = 300
mpl.rcParams['font.size'] = 12
mpl.rcParams['font.family'] = 'serif'
mpl.rcParams['lines.linewidth'] = 2
mpl.rcParams['axes.labelsize'] = 12
mpl.rcParams['axes.titlesize'] = 14
mpl.rcParams['xtick.labelsize'] = 10
mpl.rcParams['ytick.labelsize'] = 10
mpl.rcParams['legend.fontsize'] = 10
mpl.rcParams['axes.grid'] = True
mpl.rcParams['grid.alpha'] = 0.3
mpl.rcParams['axes.spines.top'] = False
mpl.rcParams['axes.spines.right'] = False
​
# 保存自定义样式
# matplotlib.rc_file('custom_style.mplstyle')
# matplotlib.rc_context({'font.size': 14})

性能优化

大数据可视化

# 问题:数据量过大时渲染慢
# 解决方案1:数据采样
def sample_data(df, n=1000):
    if len(df) > n:
        return df.sample(n=n)
    return df
​
# 解决方案2:使用 Datashader(Bokeh生态)
# pip install datashader
import datashader as ds
import datashader.transfer_functions as tf
​
# 百万级散点图
cvs = ds.Canvas(plot_width=800, plot_height=600)
agg = cvs.points(df, 'x', 'y')
img = tf.shade(agg, cmap=['lightblue', 'darkblue'], how='log')
​
# 解决方案3:Plotly 的 WebGL 渲染
fig = px.scatter(df, x='x', y='y', render_mode='webgl')
​
# 解决方案4:Hexbin 代替散点图
plt.hexbin(x, y, gridsize=30, cmap='YlOrRd')

渲染优化

# 1. 避免创建过多对象
# ❌ 循环中频繁创建 Figure
for i in range(100):
    fig, ax = plt.subplots()
    ax.plot(x, y)
    plt.savefig(f'frame_{i}.png')
    plt.close(fig)
​
# ✅ 使用子图,分批保存
fig, axes = plt.subplots(10, 10, figsize=(50, 50))
# ... 绘制 ...
fig.savefig('grid.png', dpi=100)
plt.close(fig)
​
# 2. 缓存不变的图表
# 如果图表数据不变,可以保存为 SVG 后续修改
​
# 3. 使用面向对象的 API
# ❌ pyplot 方式(全局状态)
plt.figure()
plt.plot(x, y)
plt.savefig('chart.png')
​
# ✅ 面向对象方式(更高效)
fig, ax = plt.subplots()
ax.plot(x, y)
fig.savefig('chart.png')
plt.close(fig)

排错指南

常见错误与解决方案

错误1:中文字体不显示

# 问题:中文显示为方块
# 解决1:设置中文字体
import matplotlib.pyplot as plt
​
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
​
# 解决2:下载中文字体
# 下载 Noto Sans CJK 或 Source Han Sans
# 放到 ~/.matplotlib/fontdir/
import matplotlib.font_manager as fm
fm.fontManager.addfont('/path/to/NotoSansCJK-Regular.ttc')
​
# 解决3:使用英文标签
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.title('Distribution Chart')

错误2:负号显示为方块

# 解决
plt.rcParams['axes.unicode_minus'] = False  # 使用 ASCII 负号
# 或
plt.rcParams['axes.unicode_minus'] = True
# 并确保字体支持

错误3:图例遮挡数据

# 问题:图例位置不当
# 解决1:调整位置
plt.legend(loc='upper right', bbox_to_anchor=(1.15, 1))
​
# 解决2:放在图外
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
​
# 解决3:放在图表下方
plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1), ncol=3)
​
# 解决4:放在空白区域
fig.tight_layout(rect=[0, 0, 0.85, 1])

错误4:刻度标签重叠

# 问题:X 轴标签太挤
# 解决1:旋转
plt.xticks(rotation=45, ha='right')
​
# 解决2:每隔一个显示
plt.xticks(x[::2], labels[::2])
​
# 解决3:自动调整
plt.gcf().autofmt_xdate()
​
# 解决4:调整间距
plt.subplots_adjust(bottom=0.2)

错误5:子图间距不当

# 问题:子图太挤
# 解决1:自动调整
plt.tight_layout()  # 推荐
​
# 解决2:手动调整
plt.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.1, 
                    wspace=0.3, hspace=0.3)
​
# 解决3:GridSpec 精确控制
from matplotlib.gridspec import GridSpec
gs = GridSpec(2, 2, hspace=0.3, wspace=0.3)

错误6:图例颜色与实际不符

# 问题:多条线共用同一颜色
# 解决1:指定颜色
colors = plt.cm.viridis(np.linspace(0, 1, len(lines)))
for i, line in enumerate(lines):
    ax.plot(x, y[i], color=colors[i])
​
# 解决2:确保颜色一致
sns.lineplot(data=df, x='x', y='y', hue='category', palette='tab10')
​
# 解决3:刷新绑定
plt.gca().set_prop_cycle(None)  # 重置颜色循环

Jupyter 特定问题

# 问题1:图表不显示
%matplotlib inline  # 在 notebook 开头添加
​
# 问题2:交互模式
%matplotlib widget  # 交互式图表(需要 ipympl)
​
# 问题3:图表太大
plt.figure(figsize=(10, 6), dpi=80)
​
# 问题4:清除缓存
%reset -f
import matplotlib.pyplot as plt
plt.close('all')

总结

核心要点

  1. 选对工具:Matplotlib 定制化、Seaborn 统计分析、Plotly 交互展示
  2. 理解图表类型:比较用柱状图、趋势用折线图、分布用直方图
  3. 配色要专业:使用专业配色方案,避免彩虹色滥用
  4. 标注要清晰:标题、轴标签、图例、单位缺一不可
  5. 避免过度设计:简洁是美,数据说话

速查表

┌─────────────────────────────────────────────────────────────────┐
│                    图表类型速查表                                │
├──────────────────┬──────────────────────────────────────────────┤
│     目的         │                   图表                       │
├──────────────────┼──────────────────────────────────────────────┤
│ 单一变量分布      │ 直方图、KDE、箱线图                          │
│ 多变量关系        │ 散点图、热力图                               │
│ 类别比较         │ 柱状图、条形图、饼图                         │
│ 时间趋势         │ 折线图、面积图                               │
│ 占比构成         │ 堆叠柱状图、环形图                           │
│ 地理分布         │ 地图、Choropleth                            │
│ 相关性分析        │ 相关热力图、PairPlot                         │
└──────────────────┴──────────────────────────────────────────────┘

常用代码模板

# 基础图表模板
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, y, label='数据')
ax.set_xlabel('X 轴')
ax.set_ylabel('Y 轴')
ax.set_title('标题')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
​
# Seaborn 快速图表
sns.set_theme(style='whitegrid')
sns.relplot(data=df, x='x', y='y', col='category', kind='scatter')
plt.show()
​
# Plotly 交互图表
fig = px.scatter(df, x='x', y='y', color='category', title='标题')
fig.update_layout(template='plotly_white')
fig.show()

💡 提示: 好的数据可视化应该让读者一眼就能理解数据的故事。选择合适的图表类型比花哨的样式更重要。 📚 扩展阅读:


原创内容,版权所有。未经授权,禁止转载。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容