【Python】数据可视化

Python 数据可视化:从底层原理到企业级实战全解

引言:数据可视化的力量与 Python 生态

数据可视化是将数据、信息和知识转化为视觉形式的过程,旨在通过图形化手段清晰有效地传递信息、揭示规律、辅助决策。在数据驱动的时代,数据可视化不仅仅是美化报告的工具,更是数据探索、模型解释、业务洞察不可或缺的关键环节。

Python 凭借其强大的数据科学生态系统,成为了数据可视化领域的主流语言之一。其丰富的可视化库为不同需求层次的用户提供了从静态图表到高度交互式应用的全面支持。本指南将深入探索这些库的奥秘,助您掌握 Python 数据可视化的精髓。


第一部分:Matplotlib —— Python 可视化的基石

Matplotlib 是 Python 中最为基础和广泛使用的绘图库。它提供了一个类似 MATLAB 的绘图接口,非常灵活,几乎可以绘制任何类型的静态、动态和交互式图表。虽然有时其 API 被认为较为底层和复杂,但正是这种底层性赋予了它无与伦比的控制力和定制性。Seaborn、Pandas plotting 等更高级的库通常都是在 Matplotlib 的基础上构建的。

1.1 Matplotlib 核心概念与架构

理解 Matplotlib 的核心架构是高效使用它的前提。

1.1.1 Figure、Axes 与 Artist 对象

Matplotlib 的绘图体系可以概括为三个主要层次的对象:

Figure (画布/图表):

顶层容器,可以看作是所有绘图元素的“画布”或整个“图表窗口”。
一个 Figure 对象可以包含一个或多个 Axes 对象 (子图)。
它负责管理图表的全局属性,如图表大小、分辨率 (DPI)、背景色、标题等。
通过 plt.figure()fig = Figure() (面向对象方式) 创建。

Axes (子图/坐标轴):

实际进行数据绘制的区域,代表一个“子图”或一组“坐标轴”。
包含了数据的大部分可视化元素,如线条、标记、文本、图例、坐标轴刻度、网格线等。
一个 Figure 中可以有多个 Axes,形成网格布局或其他复杂布局。
通过 fig.add_subplot()fig.add_axes()plt.subplots() 创建。
AxesAxis 的区别:

Axes (注意末尾的 ‘e’) 是一个二维 (或三维) 的绘图区域,它包含两个 (或三个) Axis 对象。
Axis (末尾无 ‘e’) 对象代表单个坐标轴 (如 X 轴、Y 轴),负责管理该轴的刻度、标签、范围等。可以通过 axes.xaxisaxes.ax.yaxis 访问。

Artist (绘图元素/艺术家):

FigureAxes 内部的所有可见元素都是 Artist 对象的实例或其子类。
例如,线条 (Line2D)、文本 (Text)、矩形 (Rectangle)、图像 (AxesImage)、图例 (Legend)、坐标轴标签 (XLabel, YLabel) 等都是 Artist
FigureAxes 本身也是 Artist 的容器 (Figure 是顶层 Artist 容器,Axes 是其子容器)。
Matplotlib 的渲染引擎最终会将这些 Artist 对象绘制到输出设备上。
用户通常通过 Axes 对象的方法来创建和操作这些 Artist (例如 ax.plot() 返回一个 Line2D 列表)。

层级关系图示 (概念性):

Figure (画布)
  ├── Axes (子图1, 坐标系1)
  │     ├── Line2D (线条)
  │     ├── Text (文本标签)
  │     ├── XAxis (X轴对象)
  │     │    ├── Tick (刻度线)
  │     │    └── Label (轴标签)
  │     ├── YAxis (Y轴对象)
  │     │    ├── Tick (刻度线)
  │     │    └── Label (轴标签)
  │     └── Legend (图例)
  │
  ├── Axes (子图2, 坐标系2)
  │     ├── BarContainer (柱状图容器)
  │     │    └── Rectangle (单个柱子)
  │     └── ...
  │
  └── Figure Title (Text对象, 属于Figure)
  └── ...
1.1.2 两种绘图接口:Pyplot API 与面向对象 (OO) API

Matplotlib 提供了两种主要的编程接口:

Pyplot API (基于状态的接口):

通过 matplotlib.pyplot 模块 (通常导入为 plt) 提供了一系列便捷的函数,如 plt.plot(), plt.title(), plt.xlabel() 等。
这种接口在内部隐式地维护当前的 FigureAxes 对象。当你调用 plt.plot() 时,它会在“当前” Axes 上绘图。如果没有,它会自动创建一个。
优点:简单快捷,对于快速绘图和交互式探索非常方便,语法类似 MATLAB。
缺点:当处理多个图表或复杂的子图布局时,对当前状态的管理容易混淆,不够灵活和 Pythonic。

面向对象 (OO) API (更 Pythonic 的接口):

显式地创建和操作 FigureAxes 对象,然后调用这些对象的方法进行绘图。
例如:fig, ax = plt.subplots(),然后使用 ax.plot(), ax.set_title(), ax.set_xlabel() 等。
优点:代码更清晰、结构化,对图表的控制更精细,推荐用于编写脚本、函数和更复杂的应用程序。
缺点:初学时可能感觉比 Pyplot API 稍显繁琐。

强烈推荐在大多数情况下(尤其是编写可重用代码或复杂图表时)使用面向对象的 API。 本指南将主要侧重于 OO API,同时也会在适当时候提及 Pyplot 的便捷用法。

import matplotlib.pyplot as plt
import numpy as np

# --- Pyplot API 示例 ---
plt.figure(figsize=(5, 3)) # 创建一个新的 Figure (隐式)
x_py = np.linspace(0, 10, 100) # 生成 x 数据
y_py = np.sin(x_py) # 生成 y 数据
plt.plot(x_py, y_py, label='sin(x) - Pyplot') # 在当前 Axes 上绘图 (如果不存在则创建)
plt.title('Pyplot API Example') # 设置当前 Axes 的标题
plt.xlabel('X-axis') # 设置当前 Axes 的 X 轴标签
plt.ylabel('Y-axis') # 设置当前 Axes 的 Y 轴标签
plt.legend() # 显示图例
plt.grid(True) # 显示网格
# plt.show() # 在脚本末尾或需要显示时调用,通常在 Jupyter Notebook 中会自动显示

# --- 面向对象 (OO) API 示例 ---
# 1. 创建 Figure 和 Axes 对象
fig_oo, ax_oo = plt.subplots(figsize=(5, 3)) # plt.subplots() 是创建 Figure 和一个或多个 Axes 的便捷函数
                                          # 返回一个 Figure 对象和一个 Axes 对象 (或 Axes 数组)

# 2. 在 Axes 对象上调用绘图方法
x_oo = np.linspace(0, 10, 100) # 生成 x 数据
y_oo = np.cos(x_oo) # 生成 y 数据
ax_oo.plot(x_oo, y_oo, label='cos(x) - OO API', color='red', linestyle='--') # 在指定的 ax_oo 上绘图

# 3. 调用 Axes 对象的方法设置属性
ax_oo.set_title('Object-Oriented API Example') # 设置 ax_oo 的标题
ax_oo.set_xlabel('X Value') # 设置 ax_oo 的 X 轴标签
ax_oo.set_ylabel('Y Value') # 设置 ax_oo 的 Y 轴标签
ax_oo.legend(loc='lower left') # 在 ax_oo 上显示图例,并指定位置
ax_oo.grid(True, linestyle=':', alpha=0.7) # 在 ax_oo 上显示网格,并设置样式

# 如果需要对 Figure 级别进行操作
fig_oo.suptitle('Main Figure Title (OO)', fontsize=14) # 设置 Figure 的主标题
fig_oo.patch.set_facecolor('lightgray') # 设置 Figure 背景色 (通过 Figure 的 patch Artist)

# plt.show() # 显示所有创建的图表

# 演示一个 Figure 包含多个 Axes (子图)
fig_multi, axes_multi = plt.subplots(nrows=1, ncols=2, figsize=(10, 4)) # 创建一个1行2列的子图布局

# 操作第一个子图 (axes_multi[0])
axes_multi[0].plot(x_py, y_py, color='green', label='sin(x) in subplot 1') # 在第一个子图上绘制 sin(x)
axes_multi[0].set_title('Subplot 1: Sine Wave') # 设置第一个子图的标题
axes_multi[0].set_xlabel('X1') # 设置第一个子图的X轴标签
axes_multi[0].set_ylabel('Y1') # 设置第一个子图的Y轴标签
axes_multi[0].legend() # 显示第一个子图的图例
axes_multi[0].axhline(0, color='black', linewidth=0.5) # 在第一个子图的 y=0 位置画一条水平线

# 操作第二个子图 (axes_multi[1])
axes_multi[1].plot(x_oo, y_oo, color='purple', linestyle='-.', label='cos(x) in subplot 2') # 在第二个子图上绘制 cos(x)
axes_multi[1].scatter(x_oo[::10], y_oo[::10], color='orange', label='Sampled cos(x) points') # 在第二个子图上绘制散点
axes_multi[1].set_title('Subplot 2: Cosine Wave & Scatter') # 设置第二个子图的标题
axes_multi[1].set_xlabel('X2') # 设置第二个子图的X轴标签
axes_multi[1].set_ylabel('Y2') # 设置第二个子图的Y轴标签
axes_multi[1].legend() # 显示第二个子图的图例
axes_multi[1].set_ylim([-1.5, 1.5]) # 设置第二个子图的Y轴范围

fig_multi.tight_layout() # 自动调整子图参数,使其填充整个图表区域,避免重叠
fig_multi.suptitle('Figure with Multiple Subplots (OO API)', y=1.03, fontsize=16) # 设置整个 Figure 的主标题

plt.show() # 一次性显示所有 plt.figure() 或 plt.subplots() 创建的图表
1.1.3 Matplotlib 后端 (Backends)

Matplotlib 的一个强大之处在于其后端系统,这使得它可以在多种操作系统和多种输出格式下工作。后端分为两类:

用户界面后端 (UI Backends / Interactive Backends):

用于在桌面应用程序(如 Qt, GTK, Tkinter, wxWidgets, macOS/Cocoa)中显示交互式图表。
当你调用 plt.show() 时,这些后端会启动一个事件循环,允许你平移、缩放图表,并响应鼠标键盘事件。
常见的有:Qt5Agg, QtAgg (新版默认), GTK3Agg, TkAgg, WXAgg, MacOSX
在 Jupyter Notebook 或 IPython 中,通常使用特殊的内联后端,如 %matplotlib inline (生成静态图嵌入) 或 %matplotlib notebook / %matplotlib widget / %matplotlib ipympl (生成交互式图嵌入)。

硬拷贝后端 (Hardcopy Backends / Non-Interactive Backends):

用于将图表渲染为文件格式,如 PNG, JPG, SVG, PDF, PS, EPS 等。
这些后端不涉及用户交互。
常见的有:Agg (Anti-Grain Geometry, 用于生成高质量的 PNG), PDF, SVG, PS
Agg 是许多其他后端(包括一些UI后端)的底层渲染引擎。

选择和配置后端:

Matplotlib 会根据你的环境自动选择一个合适的默认后端。
可以通过 matplotlib.get_backend() 查看当前后端。
可以通过 matplotlib.use('BackendName') 在导入 pyplot 之前显式设置后端。一旦 pyplot 被导入,通常就不能再更改后端了(除非在特定环境中,如 Jupyter)。
也可以在 matplotlibrc 配置文件中设置默认后端。

import matplotlib

# 查看当前后端
current_backend = matplotlib.get_backend()
print(f"Current Matplotlib backend: {
              current_backend}") # 打印当前使用的 Matplotlib 后端

# 示例:尝试设置后端 (通常应在脚本开始,导入pyplot之前)
# try:
#     matplotlib.use('TkAgg') # 尝试使用 TkAgg 后端
#     import matplotlib.pyplot as plt_tk
#     print("Successfully switched to TkAgg for plt_tk.")
#     # fig_tk, ax_tk = plt_tk.subplots()
#     # ax_tk.plot([1,2,3], [4,5,1])
#     # plt_tk.title("Plot with TkAgg Backend")
#     # plt_tk.show() # 这会打开一个Tk窗口
# except ImportError:
#     print("TkAgg backend not available or pyplot already imported with another backend.")
# except Exception as e:
#     print(f"Error setting or using TkAgg: {e}")
# finally:
#     # 恢复到之前的后端或默认后端,以避免影响后续代码(如果可能且必要)
#     # 在脚本中动态切换后端通常不推荐,最好在开始时确定
#     matplotlib.use(current_backend) # 尝试恢复,但这可能不总是在所有环境中有效
#     import matplotlib.pyplot as plt # 确保 plt 仍然可用


# 在 Jupyter Notebook / IPython 中常用的魔法命令
# %matplotlib inline  # 静态图嵌入,默认
# %matplotlib notebook # 交互式图嵌入 (需要 ipympl 或类似依赖,有时不稳定)
# %matplotlib widget  # 基于 ipympl 的现代交互式后端,功能更强

# 保存图表到文件 (使用硬拷贝后端)
fig_to_save, ax_to_save = plt.subplots(figsize=(4,3)) # 创建一个新的 Figure 和 Axes
ax_to_save.plot([1, 2, 3, 4], [1, 4, 2, 3], marker='o') # 绘制示例数据
ax_to_save.set_title("Chart to be Saved") # 设置标题
ax_to_save.set_xlabel("X") # 设置X轴标签
ax_to_save.set_ylabel("Y") # 设置Y轴标签

# 保存为 PNG (Agg 后端通常用于此)
try:
    fig_to_save.savefig('my_chart.png', dpi=300, bbox_inches='tight') # 保存为 PNG 文件,设置DPI和边界框
    print("Chart saved as my_chart.png") # 打印保存成功的消息
except Exception as e:
    print(f"Error saving PNG: {
              e}") # 打印保存PNG时的错误

# 保存为 PDF (PDF 后端)
try:
    fig_to_save.savefig('my_chart.pdf', bbox_inches='tight') # 保存为 PDF 文件
    print("Chart saved as my_chart.pdf") # 打印保存成功的消息
except Exception as e:
    print(f"Error saving PDF: {
              e}") # 打印保存PDF时的错误

# 保存为 SVG (SVG 后端 - 矢量图,可缩放)
try:
    fig_to_save.savefig('my_chart.svg', bbox_inches='tight') # 保存为 SVG 文件
    print("Chart saved as my_chart.svg") # 打印保存成功的消息
except Exception as e:
    print(f"Error saving SVG: {
              e}") # 打印保存SVG时的错误

plt.close(fig_to_save) # 关闭这个 Figure,释放内存,避免在 plt.show() 时再次显示
# plt.show() # 如果前面有其他未关闭的图,这里会显示它们

理解后端机制有助于解决特定环境下的绘图问题,以及选择合适的输出格式和交互方式。

1.1.4 Matplotlib 配置文件 (matplotlibrc)

Matplotlib 的外观和行为可以通过配置文件 (matplotlibrc)进行全局定制。这包括默认的字体、字号、颜色、线条样式、后端、图表尺寸等。

查找 matplotlibrc 文件:

调用 matplotlib.matplotlib_fname() 可以找到当前 Matplotlib 使用的 matplotlibrc 文件的路径。
Matplotlib 会按特定顺序查找 matplotlibrc

当前工作目录下的 matplotlibrc
用户目录下的 .config/matplotlib/matplotlibrc (Linux) 或 .matplotlib/matplotlibrc (其他系统)。
Matplotlib 安装目录下的 mpl-data/matplotlibrc (这是默认的模板文件)。

定制内容: matplotlibrc 文件是一个纯文本文件,包含一系列 key : value 对。

例如:font.size : 12, lines.linewidth : 1.5, axes.grid : True, backend : TkAgg
可以复制默认的 matplotlibrc 文件到用户配置目录并进行修改,以实现持久化的自定义设置。

动态修改配置:

除了修改文件,还可以在 Python 脚本中通过 matplotlib.rcParams (一个类似字典的对象) 动态修改配置参数。这些修改只对当前 Python 会话有效。
plt.style.use('stylename') 可以应用预定义的样式表,这也是一种配置方式。

import matplotlib
import matplotlib.pyplot as plt
import numpy as np

# 查看 matplotlibrc 文件路径
print(f"Matplotlib rc file path: {
              matplotlib.matplotlib_fname()}") # 打印 matplotlibrc 文件路径

# 查看当前的 rcParams (部分示例)
print(f"
Current default font size: {
              matplotlib.rcParams['font.size']}") # 打印当前默认字体大小
print(f"Current default lines.linewidth: {
              matplotlib.rcParams['lines.linewidth']}") # 打印当前默认线条宽度
print(f"Current default figure.figsize: {
              matplotlib.rcParams['figure.figsize']}") # 打印当前默认图表尺寸

# 动态修改 rcParams (仅对当前会话有效)
original_font_size = matplotlib.rcParams['font.size'] # 保存原始字体大小
matplotlib.rcParams['font.size'] = 14 # 修改默认字体大小为14
matplotlib.rcParams['lines.color'] = 'cornflowerblue' # 修改默认线条颜色
matplotlib.rcParams['axes.titlesize'] = 16 # 修改坐标轴标题大小

fig_rc, ax_rc = plt.subplots(figsize=(5,3)) # 创建图表,此时会使用新的默认设置
ax_rc.plot(np.arange(5), np.random.rand(5) * 5, label='Random Data') # 绘制数据
ax_rc.set_title('Plot with Modified rcParams') # 设置标题 (应使用新的 titlesize)
ax_rc.set_xlabel('Index') # 设置X轴标签
ax_rc.set_ylabel('Value') # 设置Y轴标签
ax_rc.legend() # 显示图例
plt.show() # 显示图表

# 恢复原始的 rcParams 设置 (如果需要)
matplotlib.rcParams['font.size'] = original_font_size # 恢复原始字体大小
# 或者更彻底地恢复所有到初始状态 (如果知道如何做或使用特定函数)
# matplotlib.rc_file_defaults() # 可以加载 Matplotlib 的硬编码默认值
# plt.style.use('default') # 也可以恢复到 Matplotlib 的默认样式

# 使用样式表 (Style Sheets)
# Matplotlib 提供了许多预定义的样式表,可以快速改变图表的整体外观
print(f"
Available styles: {
              plt.style.available}") # 打印所有可用的样式表

plt.style.use('seaborn-v0_8-whitegrid') # 应用一个预定义的样式 (例如 seaborn 的白底带网格样式)
# 注意: 'seaborn-v0_8-whitegrid' 是较新版本Seaborn样式名称,旧版可能是 'seaborn-whitegrid'

fig_style, ax_style = plt.subplots(figsize=(5,3)) # 创建图表,应用了新样式
ax_style.plot(np.arange(5), np.random.rand(5) * 5 + 5, label='Styled Data', marker='^') # 绘制数据
ax_style.set_title('Plot with "seaborn-whitegrid" Style') # 设置标题
ax_style.set_xlabel('X-axis (Styled)') # 设置X轴标签
ax_style.set_ylabel('Y-axis (Styled)') # 设置Y轴标签
ax_style.legend() # 显示图例
plt.show() # 显示图表

# 恢复到默认样式
plt.style.use('default') # 恢复到 Matplotlib 的默认样式
fig_default_again, ax_default_again = plt.subplots(figsize=(5,3)) # 创建图表,此时为默认样式
ax_default_again.plot(np.arange(5), np.random.rand(5), label='Default Style Again') # 绘制数据
ax_default_again.set_title('Plot with Default Style Restored') # 设置标题
plt.show() # 显示图表

通过 matplotlibrc 文件或动态修改 rcParams,可以高效地统一项目或个人偏好的图表风格,避免在每个绘图脚本中重复设置大量参数。

1.2 Matplotlib 基础图表绘制与深度定制

在掌握了 Matplotlib 的核心对象 (Figure, Axes, Artist) 和绘图接口后,我们现在开始探索如何使用这些工具绘制常见的图表类型。对于每种图表,我们不仅会展示基本用法,更会深入其高级定制选项,包括但不限于颜色、标记、线条样式、文本注解、图例、坐标轴控制、以及如何结合数据特性进行有效的信息表达。

1.2.1 折线图 (plot):趋势与关系的展现

折线图是最常用的图表类型之一,非常适合展示数据随某个连续变量(通常是时间或序列)变化的趋势,或者两个变量之间的关系。

A. Axes.plot() 核心参数与底层机制

Axes.plot(*args, scalex=True, scaley=True, data=None, **kwargs) 是绘制折线图的核心方法。

*args: 指定要绘制的数据。常见的格式有:

plot(y): y 值,x 会自动生成为 [0, 1, ..., N-1]
plot(x, y): x 和 y 值。
plot(x, y, fmt_string): x, y 值以及一个格式字符串 (如 'ro-' 表示红色圆点实线)。
可以多次传入 x, y, [fmt_string] 组合来在同一个 Axes 上绘制多条线。

**kwargs: 大量的关键字参数用于控制线条的外观和行为,例如:

color (或 c): 线条颜色 (e.g., 'red', '#FF0000', (1,0,0) )。
linestyle (或 ls): 线条样式 (e.g., '-', '--', ':', '-.' )。
linewidth (或 lw): 线条宽度。
marker: 数据点标记样式 (e.g., '.', ',', 'o', 'v', '^', 's', '*', '+', 'x')。
markersize (或 ms): 标记大小。
markerfacecolor (或 mfc): 标记填充颜色。
markeredgecolor (或 mec): 标记边缘颜色。
markeredgewidth (或 mew): 标记边缘宽度。
label: 线条的标签,用于生成图例 (ax.legend())。
alpha: 透明度 (0 到 1)。
zorder: 绘图顺序,值越大越靠上层。
solid_capstyle, solid_joinstyle: 控制实线的端点和连接点样式。
dashed_capstyle, dashed_joinstyle: 控制虚线的端点和连接点样式。
drawstyle: 控制线条在数据点之间的绘制方式 (e.g., 'default', 'steps', 'steps-pre', 'steps-mid', 'steps-post')。这对于绘制阶梯图非常有用。

ax.plot() 方法返回一个包含所有被创建的 matplotlib.lines.Line2D 对象的列表。你可以保存这些对象,以便后续单独修改它们的属性。

B. 基础折线图与常用定制

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd # 引入 pandas 用于更方便地处理数据

# --- 准备数据 (模拟企业级场景:某产品过去一年的月度销售额和用户增长率) ---
months = pd.date_range(start='2023-01-01', end='2024-01-01', freq='MS') # 生成月份序列 (Month Start)
sales_data = np.array([
    100, 120, 110, 130, 150, 180, 
    160, 170, 190, 210, 200, 230 
]) # 模拟月度销售额 (单位:万元)
growth_rate_data = np.array([
    5.0, 4.5, -1.0, 3.0, 3.5, 4.0,
    -2.0, 2.5, 3.0, 2.0, -0.5, 2.8
]) # 模拟月度用户增长率 (单位:%)

# --- 创建 Figure 和 Axes ---
fig, ax1 = plt.subplots(figsize=(12, 7)) # 创建一个 Figure 和一个 Axes 对象

# --- 绘制第一条线:月度销售额 ---
line_sales = ax1.plot(
    months, sales_data, 
    color='dodgerblue', # 设置线条颜色为道奇蓝
    linestyle='-',    # 设置线条样式为实线
    linewidth=2.5,    # 设置线条宽度
    marker='o',       # 设置数据点标记为圆形
    markersize=8,     # 设置标记大小
    markerfacecolor='white', # 设置标记填充色为白色
    markeredgecolor='dodgerblue', # 设置标记边缘颜色
    markeredgewidth=1.5,   # 设置标记边缘宽度
    label='月度销售额 (万元)' # 设置线条标签,用于图例
)
ax1.set_xlabel('月份', fontsize=12) # 设置 X 轴标签
ax1.set_ylabel('月度销售额 (万元)', color='dodgerblue', fontsize=14) # 设置 Y1 轴标签和颜色
ax1.tick_params(axis='y', labelcolor='dodgerblue', labelsize=10) # 设置 Y1 轴刻度标签颜色和大小
ax1.grid(True, linestyle='--', alpha=0.6, axis='y') # 开启 Y1 轴的网格线

# --- 创建第二个 Y 轴共享 X 轴,用于绘制用户增长率 ---
ax2 = ax1.twinx() # 创建一个共享 X 轴的新 Axes 对象 (ax2)

# --- 绘制第二条线:用户增长率 ---
line_growth = ax2.plot(
    months, growth_rate_data,
    color='orangered', # 设置线条颜色为橘红色
    linestyle='--',   # 设置线条样式为虚线
    linewidth=2.0,    # 设置线条宽度
    marker='s',       # 设置数据点标记为方形 ('s' for square)
    markersize=7,     # 设置标记大小
    markerfacecolor='lightsalmon', # 设置标记填充色
    markeredgecolor='orangered', # 设置标记边缘颜色
    label='用户增长率 (%)' # 设置线条标签
)
ax2.set_ylabel('用户增长率 (%)', color='orangered', fontsize=14) # 设置 Y2 轴标签和颜色
ax2.tick_params(axis='y', labelcolor='orangered', labelsize=10) # 设置 Y2 轴刻度标签颜色和大小
ax2.axhline(0, color='gray', linestyle=':', linewidth=1) # 在增长率为0的位置画一条参考线

# --- 定制图表标题和图例 ---
fig.suptitle('产品月度销售额与用户增长率分析 (2023)', fontsize=18, fontweight='bold') # 设置 Figure 的主标题
# 合并两个 Axes 的图例项
lines = line_sales + line_growth # 将两条线的 Line2D 对象合并到一个列表中
labels = [l.get_label() for l in lines] # 获取每条线的标签
ax1.legend(lines, labels, loc='upper left', fontsize=10, frameon=True, shadow=True) # 创建并显示图例

# --- X 轴日期格式化 ---
from matplotlib.dates import DateFormatter
ax1.xaxis.set_major_formatter(DateFormatter('%Y-%m')) # 设置 X 轴日期的显示格式为 "YYYY-MM"
fig.autofmt_xdate(rotation=30, ha='right') # 自动旋转 X 轴标签以避免重叠,并右对齐

# --- 添加一些细节和美化 ---
ax1.set_ylim(bottom=0) #确保销售额 Y 轴从 0 开始
ax2.set_ylim(min(growth_rate_data.min() -1, -5) , growth_rate_data.max() + 1) # 给增长率Y轴一些边距
ax1.spines['top'].set_visible(False) # 隐藏顶部边框线
ax2.spines['top'].set_visible(False) # 隐藏顶部边框线 (对于 twinx 创建的 Axes 也需要)
# ax1.spines['right'].set_color('orangered') # 可以将右边框颜色设为与 ax2 匹配,但通常 twinx 的 Y 轴标签已足够区分
# ax1.spines['left'].set_color('dodgerblue') # 左边框颜色

plt.tight_layout(rect=[0, 0, 1, 0.96]) # 调整布局以适应主标题 (rect 参数留出顶部空间)
plt.show() # 显示图表

这个例子展示了:

使用面向对象的 API (fig, ax1 = plt.subplots())。
绘制两条具有不同 Y 轴标度的折线 (ax1.twinx())。
详细定制线条的颜色、样式、宽度、标记(形状、大小、颜色)。
设置轴标签、标题、图例,并进行美化(字体大小、颜色)。
格式化日期类型的 X 轴。
添加网格线和参考线 (axhline)。
调整边框线 (spines) 和整体布局 (tight_layout)。

C. 深入 drawstyle:绘制阶梯图

drawstyle 参数控制线条如何在数据点之间连接,这对于显示离散变化或事件序列非常有用。

'default''lines': 标准的直线连接。
'steps': 在每个数据点之后水平延伸,然后在下一个 x 值处垂直变化。
'steps-pre': 在每个数据点之前垂直变化,然后水平延伸到该数据点。x 值定义了水平线的开始。
'steps-mid': 水平线从前一个 x 值和当前 x 值的中间开始,到当前 x 值和下一个 x 值的中间结束。
'steps-post': 在每个数据点处水平延伸,然后在该数据点的 x 值处垂直变化到下一个 y 值。x 值定义了水平线的结束。

import matplotlib.pyplot as plt
import numpy as np

# 模拟数据:库存水平随时间的变化 (离散事件)
time_points = np.array([0, 1, 1, 2, 2, 3, 3, 4, 4, 5]) # 时间点 (有些时间点有两次记录代表瞬时变化)
inventory_levels = np.array([10, 10, 8, 8, 5, 5, 7, 7, 3, 3]) # 对应库存水平

# 不同 drawstyle 的对比
fig_steps, axs_steps = plt.subplots(2, 2, figsize=(12, 8), sharex=True, sharey=True) # 创建2x2子图
fig_steps.suptitle('Comparison of `drawstyle` Options for Step Plots', fontsize=16) # 设置主标题

# 1. drawstyle='default' (标准折线图)
axs_steps[0, 0].plot(time_points, inventory_levels, drawstyle='default', marker='o', label='default')
axs_steps[0, 0].set_title('drawstyle="default"') # 设置子图标题
axs_steps[0, 0].legend() # 显示图例

# 2. drawstyle='steps-pre'
# 线条在数据点 (x,y) 的 x 位置之前垂直变化到 y,然后水平延伸到 x
axs_steps[0, 1].plot(time_points, inventory_levels, drawstyle='steps-pre', marker='o', label='steps-pre', color='green')
axs_steps[0, 1].set_title('drawstyle="steps-pre"') # 设置子图标题
axs_steps[0, 1].legend() # 显示图例

# 3. drawstyle='steps-mid'
# 线条在数据点 (x,y) 的 x 位置的中间点进行垂直变化
axs_steps[1, 0].plot(time_points, inventory_levels, drawstyle='steps-mid', marker='o', label='steps-mid', color='red')
axs_steps[1, 0].set_title('drawstyle="steps-mid"') # 设置子图标题
axs_steps[1, 0].legend() # 显示图例

# 4. drawstyle='steps-post'
# 线条在数据点 (x,y) 的 x 位置之后垂直变化,水平线在该 x 位置结束
axs_steps[1, 1].plot(time_points, inventory_levels, drawstyle='steps-post', marker='o', label='steps-post', color='purple')
axs_steps[1, 1].set_title('drawstyle="steps-post"') # 设置子图标题
axs_steps[1, 1].legend() # 显示图例

# 添加统一的轴标签和网格
for ax_row in axs_steps:
    for ax in ax_row:
        ax.set_xlabel('Time Event') # 设置X轴标签
        ax.set_ylabel('Inventory Level') # 设置Y轴标签
        ax.grid(True, linestyle=':', alpha=0.7) # 设置网格
        ax.set_xticks(np.arange(0, 6, 1)) # 设置X轴刻度
        ax.set_yticks(np.arange(0, 12, 2)) # 设置Y轴刻度

plt.tight_layout(rect=[0, 0, 1, 0.95]) # 调整布局以适应主标题
plt.show() # 显示图表

# 企业级场景:绘制服务器 CPU 利用率的阶梯图,更准确地反映利用率在采样点之间的保持状态
timestamps_cpu = pd.to_datetime([
    "2023-01-01 10:00:00", "2023-01-01 10:00:10", "2023-01-01 10:00:20",
    "2023-01-01 10:00:30", "2023-01-01 10:00:40", "2023-01-01 10:00:50"
]) # CPU采样时间戳
cpu_utilization = [20, 25, 25, 60, 55, 55] # 对应的CPU利用率 (%)

fig_cpu, ax_cpu = plt.subplots(figsize=(10, 5)) # 创建Figure和Axes
# 使用 'steps-post' 通常能很好地表示这种采样数据,表示值在下一个采样点之前保持不变
ax_cpu.plot(timestamps_cpu, cpu_utilization, drawstyle='steps-post', marker='.', linestyle='-', color='teal', label='CPU Utilization (%)')
ax_cpu.set_title('Server CPU Utilization Over Time (Step Plot)', fontsize=15) # 设置标题
ax_cpu.set_xlabel('Timestamp', fontsize=12) # 设置X轴标签
ax_cpu.set_ylabel('CPU Utilization (%)', fontsize=12) # 设置Y轴标签
ax_cpu.fill_between(timestamps_cpu, cpu_utilization, step='post', alpha=0.2, color='teal') # 在阶梯线下填充颜色,step='post'需与drawstyle匹配
ax_cpu.legend() # 显示图例
ax_cpu.grid(True, linestyle=':', alpha=0.5) # 设置网格
ax_cpu.set_ylim(0, 100) # 设置Y轴范围为0-100

# 格式化X轴日期显示
ax_cpu.xaxis.set_major_formatter(DateFormatter('%H:%M:%S')) # 设置时间格式
fig_cpu.autofmt_xdate() # 自动调整日期标签格式

plt.tight_layout() # 调整布局
plt.show() # 显示图表

drawstyle 的选择取决于你想如何表达数据点之间的关系。对于表示状态在某个值上保持一段时间直到下一个事件发生的情况(如库存、利用率、数字信号),阶梯图 (steps-presteps-post) 通常比默认的线性插值更准确。

D. 处理缺失数据 (NaN)

Matplotlib 的 plot 函数默认会NaN 值处断开线条。这意味着如果你的 y 数据(或 x 数据)中包含 NaN,线条会在该点中断,然后在下一个非 NaN 点重新开始。

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

# 模拟包含 NaN 的时间序列数据
time_nan = np.arange(10) # 时间点
data_with_nan = np.array([1, 2, 3, np.nan, np.nan, 6, 7, np.nan, 9, 10], dtype=float) # 包含NaN的数据

fig_nan, ax_nan = plt.subplots(figsize=(8, 4)) # 创建Figure和Axes
ax_nan.plot(time_nan, data_with_nan, marker='o', linestyle='-', label='Data with NaNs (Default)', color='purple') # 绘制带NaN的数据
ax_nan.set_title('Plotting Data with NaN Values') # 设置标题
ax_nan.set_xlabel('Time') # 设置X轴标签
ax_nan.set_ylabel('Value') # 设置Y轴标签

# 如果想连接 NaN 点 (例如,通过线性插值)
# 方法1: 先对数据进行插值处理 (使用 Pandas)
s_nan = pd.Series(data_with_nan) # 将数据转换为 Pandas Series
data_interpolated = s_nan.interpolate(method='linear').to_numpy() # 使用线性插值填充 NaN
ax_nan.plot(time_nan, data_interpolated, marker='x', linestyle=':', label='Data with NaNs Interpolated (Linear)', color='green', alpha=0.7) # 绘制插值后的数据

# 方法2: (不推荐,但可了解) Matplotlib 没有直接连接 NaN 的 plot 选项。
# 但可以通过将 NaN 替换为一个非常特殊的值,然后可能在绘制后隐藏这些点,
# 或者在某些特殊情况下,如果数据是严格单调的,一些技巧或许可行,但通常不如预处理数据清晰。

ax_nan.legend() # 显示图例
ax_nan.grid(True, linestyle=':', alpha=0.5) # 设置网格
plt.show() # 显示图表

# 企业级场景:展示传感器数据,其中部分读数可能由于传输错误而丢失
sensor_times = pd.to_datetime(pd.date_range('2023-01-01', periods=10, freq='H')) # 传感器采样时间
sensor_readings = [10.1, 10.5, np.nan, 10.2, 10.8, np.nan, np.nan, 11.0, 10.7, 10.9] # 包含缺失值的传感器读数

fig_sensor, ax_sensor = plt.subplots(figsize=(10, 5)) # 创建Figure和Axes
ax_sensor.plot(sensor_times, sensor_readings, marker='D', linestyle='-.', color='crimson', label='Raw Sensor Readings (with gaps)') # 绘制原始传感器读数
ax_sensor.set_title('Sensor Readings Over Time (Handling Missing Data)') # 设置标题
ax_sensor.set_xlabel('Timestamp') # 设置X轴标签
ax_sensor.set_ylabel('Temperature (°C)') # 设置Y轴标签

# 可选:覆盖一层插值后的数据作为参考
series_readings = pd.Series(sensor_readings, index=sensor_times) # 创建 Pandas Series 以便插值
interpolated_readings = series_readings.interpolate(method='time') # 基于时间进行插值
ax_sensor.plot(interpolated_readings.index, interpolated_readings.values, 
               linestyle=':', color='gray', alpha=0.6, label='Time-Interpolated Readings (for reference)') # 绘制时间插值后的读数

ax_sensor.legend() # 显示图例
ax_sensor.grid(True) # 设置网格
fig_sensor.autofmt_xdate() # 自动格式化日期标签
plt.tight_layout() # 调整布局
plt.show() # 显示图表

理解 Matplotlib 如何处理 NaN 非常重要。通常,在绘图前使用 Pandas 等工具对缺失数据进行适当的填充(插值、前向/后向填充等)或明确移除,是更可控的做法。

E. 高级线条定制与 Artist 对象交互

ax.plot() 返回一个 Line2D 对象列表。你可以获取这些对象并直接修改它们的属性。

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 100) # 生成 x 数据
y1 = np.sin(x) # 生成 y1 数据
y2 = np.cos(x) # 生成 y2 数据

fig, ax = plt.subplots(figsize=(8, 5)) # 创建Figure和Axes

# 绘制线条并获取 Line2D 对象
line1_artists = ax.plot(x, y1, label='Sine') # line1_artists 是一个列表,通常只包含一个 Line2D 对象
line2_artists = ax.plot(x, y2, label='Cosine') # line2_artists 也是一个列表

# 假设我们只关心每组 plot 调用产生的第一个 (也是通常唯一一个) Line2D 对象
if line1_artists:
    line1 = line1_artists[0] # 获取第一个 Line2D 对象
    # 在绘制后修改 line1 的属性
    line1.set_color('magenta') # 修改颜色
    line1.set_linewidth(3) # 修改线宽
    line1.set_linestyle((0, (5, 2, 1, 2)))  # 自定义虚线样式: (offset, (on_off_sequence))
                                          # (5 points on, 2 off, 1 on, 2 off)
    line1.set_marker('^') # 修改标记
    line1.set_markersize(7) # 修改标记大小
    line1.set_alpha(0.8) # 修改透明度

if line2_artists:
    line2 = line2_artists[0] # 获取第二个 Line2D 对象
    # 使用 setp (set property) 函数批量设置属性
    plt.setp(line2, color='darkorange', linewidth=2, linestyle=':', 
             marker='s', markerfacecolor='yellow', markevery=10) # markevery=10 表示每隔10个点显示一个标记

# 动态改变某个点的标记 (更高级,通常用于交互或动画)
# 假设我们要突出显示 Sine 曲线在 x 接近 pi 的点
highlight_idx = np.argmin(np.abs(x - np.pi)) # 找到最接近 pi 的点的索引
# 获取 Line1D 的 x, y 数据
x_data_l1 = line1.get_xdata() # 获取 x 数据
y_data_l1 = line1.get_ydata() # 获取 y 数据
# 在该点添加一个特殊的散点标记 (这会创建一个新的 Scatter Artist)
highlight_point = ax.scatter(
    x_data_l1[highlight_idx], y_data_l1[highlight_idx], 
    s=150, # 散点大小
    facecolor='none', # 无填充色
    edgecolor='red', # 边缘颜色
    linewidth=2, # 边缘宽度
    zorder=5, # 确保在最上层
    label='Peak near $pi$' # 标签
)

ax.set_title('Advanced Line Customization via Line2D Objects') # 设置标题
ax.set_xlabel('Radians') # 设置X轴标签
ax.set_ylabel('Function Value') # 设置Y轴标签
handles, labels = ax.get_legend_handles_labels() # 获取所有可用于图例的句柄和标签
# 如果手动添加了 scatter,也需要将其加入图例
# handles.append(highlight_point) # 如果 highlight_point 有 label 并且想加入图例
# labels.append(highlight_point.get_label())
ax.legend(handles=handles, labels=labels, loc='best') # 创建图例
ax.grid(True) # 设置网格
ax.axhline(0, color='black', lw=0.5) # 添加水平参考线
ax.axvline(np.pi, color='blue', linestyle='--', lw=0.7, label='$pi$ line') # 添加垂直参考线,并给它一个标签

# 重新获取图例句柄和标签,确保包含 axvline 的标签 (如果它被正确创建为 Artist 并有 label)
# (更简单的方法是直接在 ax.legend() 中传递所有想要显示的 artists)
# ax.legend(loc='best') # 这样会自动收集

plt.show() # 显示图表

通过直接操作 Line2D Artist 对象,可以实现对线条样式的极致控制,甚至在图表生成后动态修改它们。plt.setp() 是一个方便的工具,用于一次性设置多个属性。

## 1.2.2 散点图 (`scatter`):多维关系、模式识别与交互探索

散点图用于展示两(或多)个变量之间的关系,是探索关联、聚类、异常点的首选图表类型。本节将深入探讨 Matplotlib `Axes.scatter()` 及其衍生技术,涵盖基础绘制、高维编码、性能优化、交互、企业级真实案例等多维度主题。

### 1.2.2.1 `Axes.scatter()` 基础语义与核心参数

| 形参 / 关键字 | 含义与要点 |
| ------------- | --------------------------------------- |
| `x`, `y`      | 必选,长度相同的序列,定义散点位置 |
| `s`           | 标量 / 数组,控制标记面积(像素<sup>2</sup>)|
| `c`           | 颜色;可为单色、数组 (按点映射 colormap)、RGBA 或字符串 |
| `marker`      | 标记形状,如 `'o'`, `'^'`, `'s'`, `'D'`, `'x'` 等 |
| `alpha`       | 透明度 (0.0–1.0) |
| `cmap`        | 当 `c` 为数值数组时指定 colormap |
| `norm`        | `colors.Normalize` 对象,用于自定义数值→颜色映射 |
| `edgecolors`  | 标记边缘颜色 |
| `linewidths`  | 标记边缘宽度 |
| `label`       | 图例标签 |
| `zorder`      | 绘制顺序 |

`scatter()` 返回 `matplotlib.collections.PathCollection`,这是管理所有散点的合集艺术家,可用于后续批量修改其属性或获取数据。

### 1.2.2.2 基础散点图:销售额 vs 市场投入

```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# ---------- 模拟企业级营销数据 ----------
np.random.seed(42)  # 固定随机种子确保可复现
n_samples = 60  # 样本数量
marketing_spend = np.random.uniform(5, 50, n_samples)        # 市场投入(万元)
sales_revenue   = marketing_spend * np.random.uniform(2, 4)  # 销售额(万元) = 投入 * 随机ROI
region_ids      = np.random.choice([0, 1, 2], n_samples)     # 区域ID(分类变量 0/1/2)

# ---------- 创建 Figure & Axes ----------
fig, ax = plt.subplots(figsize=(10, 6))

# ---------- 绘制散点 ----------
scatter = ax.scatter(
    marketing_spend,        # X坐标:市场投入
    sales_revenue,          # Y坐标:销售额
    c=region_ids,           # c传入分类ID,用于着色
    cmap='Set2',            # 色图选择Set2,三种柔和颜色
    s=120,                  # 散点面积(像素^2)
    alpha=0.8,              # 透明度
    edgecolors='black',     # 标记边缘颜色
    linewidths=0.6,         # 标记边缘宽度
    label='Region-wise ROI' # 图例标签
)

# ---------- 自定义轴与标题 ----------
ax.set_title('市场投入 vs 销售额 (按区域着色)', fontsize=16)
ax.set_xlabel('市场投入 (万元)', fontsize=12)
ax.set_ylabel('销售额 (万元)', fontsize=12)
ax.grid(True, linestyle=':', alpha=0.6)

# ---------- 构造分类图例 ----------
from matplotlib.lines import Line2D
region_labels = ['华东', '华南', '华北']
legend_elements = [
    Line2D([0], [0], marker='o', color='w',
           markerfacecolor=plt.cm.Set2(i/2),  # 取色图中的颜色
           markeredgecolor='black',
           markersize=10, label=region_labels[i])
    for i in range(3)
]
ax.legend(handles=legend_elements, title='区域', fontsize=10)

plt.tight_layout()
plt.show()

代码要点:

c=region_ids + cmap='Set2' 将类别映射到颜色。
edgecolors='black' + linewidths 强化散点边界,在打印稿或投影中更易区分。
构造自定义 Line2D 列表手动生成分类图例,避免色条式 legend 不适用离散类别的困扰。

1.2.2.3 高维编码:气泡图 (Bubble Chart) 与多变量映射

在二维平面同时编码第四、第五维常见做法是利用面积(s)与颜色(c)。

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

# ---------- 示例数据:全球电商订单 ----------
np.random.seed(7)
N = 200
df_orders = pd.DataFrame({
            
    'unit_price'   : np.random.uniform(10, 600, N),           # 单价($)
    'quantity'     : np.random.randint(1, 20, N),             # 购买数量
    'discount'     : np.random.uniform(0, 0.5, N),            # 折扣率
    'country'      : np.random.choice(['US', 'CN', 'DE', 'FR', 'JP'], N)
})
df_orders['order_value'] = df_orders['unit_price'] * df_orders['quantity'] * (1 - df_orders['discount'])

# ---------- 可视化 ----------
fig, ax = plt.subplots(figsize=(12, 7))
bubble = ax.scatter(
    df_orders['unit_price'],                   # X: 单价
    df_orders['order_value'],                  # Y: 订单价值
    s=df_orders['quantity']*10,                # s: 数量 -> 气泡面积
    c=df_orders['discount'],                   # c: 折扣率 -> 颜色深浅
    cmap='coolwarm',                           # 颜色映射:低折扣蓝色,高折扣红色
    norm=plt.Normalize(0, 0.5),                # 明确折扣范围映射
    alpha=0.75,
    edgecolors='gray', linewidths=0.5
)

ax.set_title('多变量气泡图:单价 vs 订单价值 (气泡大小=数量, 颜色=折扣率)', fontsize=16)
ax.set_xlabel('单价 ($)', fontsize=12)
ax.set_ylabel('订单价值 ($)', fontsize=12)
ax.grid(True, linestyle='--', alpha=0.4)

# ---------- 添加色条 ----------
cbar = plt.colorbar(bubble, ax=ax, pad=0.02)
cbar.set_label('折扣率', rotation=270, labelpad=15)

# ---------- 标注平均值参考线 ----------
avg_price = df_orders['unit_price'].mean()
avg_value = df_orders['order_value'].mean()
ax.axvline(avg_price, color='green', linestyle=':', linewidth=1)
ax.axhline(avg_value, color='green', linestyle=':', linewidth=1)
ax.text(avg_price, ax.get_ylim()[1]*0.95, '平均单价', color='green', ha='center')
ax.text(ax.get_xlim()[1]*0.02, avg_value, '平均订单价值', color='green', va='center')

plt.tight_layout()
plt.show()

企业启示: 团队可通过气泡图快速发现「高单价但低折扣仍成交大量数量」的黄金 SKU;或者定位「低价高折扣仍价值低」的低效促销。

1.2.2.4 大规模散点:性能优化与 Datashader

当点数 >10⁵ 时 Matplotlib 将难以流畅渲染。Datashader 通过 GPU / 多线程栅格化可轻松可视化千万级散点。

import numpy as np, pandas as pd, datashader as ds, datashader.transfer_functions as tf
from datashader.mpl_ext import dsshow  # Datashader 与 Matplotlib 集成
import matplotlib.pyplot as plt

# ---------- 生成百万级数据 ----------
N = 1_000_000
np.random.seed(0)
df_big = pd.DataFrame({
            
    'x': np.random.randn(N).cumsum(),  # 随机游走
    'y': np.random.randn(N).cumsum()
})

# ---------- Datashader Canvas ----------
canvas = ds.Canvas(plot_width=800, plot_height=600)
agg    = canvas.points(df_big, 'x', 'y', agg=ds.count())  # 对像素网格计数
img    = tf.shade(agg, cmap='viridis')                   # 栅格→RGBA 图像

# ---------- 展示 ----------
fig, ax = plt.subplots(figsize=(8,6))
dsshow(agg, cmap='viridis', aspect='auto', ax=ax)  # 使用 dsshow 高效绘制
ax.set_title('百万级散点可视化 (Datashader)', fontsize=15)
ax.set_xlabel('Random Walk X')
ax.set_ylabel('Random Walk Y')
plt.tight_layout(); plt.show()

关键说明:

Canvas.pointsagg=ds.count() 在 GPU/多线程上计算每像素命中数。
tf.shade 根据计数映射颜色,自动对数缩放避免过曝。
dsshow 将输出嵌入 Matplotlib;亦可输出到 Bokeh、HoloViews 以启用交互放大。

1.2.2.5 交互式散点:mplcursors 选点工具

import matplotlib.pyplot as plt
import numpy as np
import mplcursors  # pip install mplcursors

# ---------- 数据 ----------
np.random.seed(10)
x = np.random.normal(size=80)
y = np.random.normal(size=80)
labels = [f'点 {
              i}' for i in range(80)]

# ---------- 绘制 ----------
fig, ax = plt.subplots(figsize=(8,5))
sc = ax.scatter(x, y, c=y, cmap='summer', s=80, edgecolors='black', alpha=0.7)
ax.set_title('可交互散点 (悬停显示标签)', fontsize=15)

# ---------- mplcursors ----------
cursor = mplcursors.cursor(sc, hover=True)  # 启用 hover 模式
@cursor.connect("add")
def on_add(sel):
    i = sel.index  # 获取点索引
    sel.annotation.set(text=labels[i])  # 设置注释文本
    sel.annotation.get_bbox_patch().set(alpha=0.8)  # 注释背景透明度
plt.tight_layout(); plt.show()

交互价值: 对运营人员查看「单个样本」背后信息时尤为便捷,无需手动查表。

1.2.2.6 回归 & 置信区间:Seaborn regplot

import seaborn as sns, matplotlib.pyplot as plt, numpy as np

# ---------- 模拟价格-需求关系 ----------
np.random.seed(3)
price  = np.linspace(5, 25, 100)
demand = 1000/price + np.random.normal(scale=2, size=100)  # 简化需求曲线 + 噪声

# ---------- 绘制回归散点 ----------
fig, ax = plt.subplots(figsize=(7,5))
sns.regplot(x=price, y=demand, ci=95, scatter_kws={
            's':60, 'alpha':0.6}, ax=ax)
ax.set_title('价格与需求回归分析 (95%置信区间)', fontsize=14)
ax.set_xlabel('价格 (¥)')
ax.set_ylabel('需求量 (千件)')
ax.grid(True, linestyle=':')
plt.tight_layout(); plt.show()

说明: sns.regplot 默认使用 OLS 拟合,并绘制 95% 置信带,帮助分析线性关系可靠性;ci=None 可关闭置信带;order=2logx=True 支持非线性和对数回归。

1.2.2.7 分类决策边界:Logistic 回归 + 散点可视

import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
from matplotlib.colors import ListedColormap

# ---------- 生成二维可分数据 ----------
X, y = make_classification(
    n_samples=300, n_features=2, n_redundant=0, n_clusters_per_class=1,
    class_sep=1.3, random_state=11
)

# ---------- 拟合逻辑回归 ----------
clf = LogisticRegression().fit(X, y)

# ---------- 网格预测绘制决策面 ----------
h = .02
x_min, x_max = X[:,0].min()-1, X[:,0].max()+1
y_min, y_max = X[:,1].min()-1, X[:,1].max()+1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                     np.arange(y_min, y_max, h))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

fig, ax = plt.subplots(figsize=(8,6))
cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA'])
cmap_bold  = ListedColormap(['#FF0000', '#00AA00'])

# --- 决策面 ---
ax.contourf(xx, yy, Z, cmap=cmap_light, alpha=0.4)

# --- 散点 ---
ax.scatter(X[:,0], X[:,1], c=y, cmap=cmap_bold, edgecolor='k', s=60)
ax.set_title('逻辑回归决策边界可视化', fontsize=15)
ax.set_xlabel('特征 1')
ax.set_ylabel('特征 2')
plt.tight_layout(); plt.show()

企业落地: 客户流失预测、信用评分等二分类问题中,通过决策边界图能够向业务解释模型如何区分正负样本。

1.2.2.8 3D 散点:mpl_toolkits.mplot3d

from mpl_toolkits.mplot3d import Axes3D  # 导入3D工具
import matplotlib.pyplot as plt, numpy as np

# ---------- 数据生成 ----------
np.random.seed(1)
x = np.random.normal(0, 1, 200)
y = np.random.normal(0, 1, 200)
z = x * 0.5 + y * 0.3 + np.random.normal(0, 0.2, 200)  # 线性关系 + 噪声

# ---------- 绘制 ----------
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111, projection='3d')
sc = ax.scatter(x, y, z, c=z, cmap='plasma', s=50, alpha=0.8)
ax.set_title('三维散点:多变量空间分布', fontsize=14)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

# ---------- 添加色条 ----------
cb = fig.colorbar(sc, shrink=0.6, pad=0.1)
cb.set_label('Z 值')

plt.tight_layout(); plt.show()

说明: projection='3d' 启用三维坐标系;在大量点时可启用ax.view_init(elev, azim)动态调整角度,或结合 %matplotlib widget 实时旋转。

1.2.2.9 散点矩阵 (PairPlot) :高维探索

import seaborn as sns, pandas as pd

# ---------- 加载鸢尾花数据 ----------
iris = sns.load_dataset('iris')  # 含 sepal_length 等4数值列

# ---------- PairPlot ----------
g = sns.pairplot(
    iris,
    hue='species',          # 分类着色
    vars=['sepal_length','sepal_width','petal_length','petal_width'],  # 指定变量顺序
    diag_kind='kde',        # 对角线绘制核密度曲线
    plot_kws={
            'alpha':0.7, 's':60, 'edgecolor':'k'},
    height=2.3
)
g.fig.suptitle('鸢尾花特征散点矩阵', y=1.02, fontsize=16)
plt.tight_layout(); plt.show()

应用场景: 在早期 EDA 阶段快速检查「任意两特征」之间是否存在线性/非线性关系、分布模式及类别分离度。

1.2.2.10 地理散点:GeoPandas + Cartopy

import geopandas as gpd, matplotlib.pyplot as plt, pandas as pd

# ---------- 读取省级边界 ----------
china = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
china = china[china['name']=='China']

# ---------- 模拟门店坐标 ----------
df_stores = pd.DataFrame({
            
    'lon':[116.4,121.5,113.2,106.5,87.6],
    'lat':[39.9,31.2,23.1,29.5,43.8],
    'sales':[500,650,420,380,300]
})
gdf_stores = gpd.GeoDataFrame(
    df_stores, 
    geometry=gpd.points_from_xy(df_stores.lon, df_stores.lat),
    crs='EPSG:4326'
)

# ---------- 绘图 ----------
fig, ax = plt.subplots(figsize=(8,7))
china.to_crs(epsg=3857).plot(ax=ax, color='#EFEFEF', edgecolor='gray')  # 绘制底图(WEB墨卡托)
gdf_stores.to_crs(epsg=3857).plot(
    ax=ax,
    markersize=gdf_stores['sales'],   # 销售额映射大小
    color='tomato',
    edgecolor='black',
    alpha=0.8
)
ax.set_title('中国门店销售额地理分布 (气泡面积=销售额)', fontsize=15)
ax.axis('off')
plt.tight_layout(); plt.show()

交付价值: 销售/物流团队可明确识别「空白市场」和「重点投入区域」,指导选址与资源配置。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容