1. 为什么我坚持用 Seaborn 画折线图,而不是 Matplotlib 原生方案?

在数据可视化这条路上,我踩过太多坑。十年前刚转行做数据分析时,我习惯性地用 matplotlib.pyplot.plot() 画时间序列——写十几行代码调字体、改颜色、加图例,结果导出的图在汇报PPT里糊成一片;后来换用 plotly ,交互是炫了,但一发给业务同事,对方电脑没装环境,图直接打不开;再后来试过 bokeh ,部署到内网服务器又卡在 JavaScript 兼容性上……直到我真正吃透 Seaborn 的 lineplot() ,才明白什么叫“少写代码,多出价值”。

Seaborn 不是另一个绘图库,它是对 Matplotlib 的 语义级封装 。它把“画一条线”这件事,从“指定坐标、设置样式、管理图层”的底层操作,升维成“描述数据关系”的高层表达。比如你要对比三种货币兑欧元的走势,Matplotlib 需要你手动循环三次 plt.plot() ,再逐个 plt.legend() ;而 Seaborn 一行 sns.lineplot(x='Date', y='Euro rate', hue='Currency') 就搞定——它自动识别 Currency 是分类变量,自动分配颜色、生成图例、统一坐标轴尺度。这不是偷懒,是把工程师从“画图匠”解放成“叙事者”。

更关键的是,Seaborn 天然适配真实工作流。我们拿到的数据从来不是理想化的二维数组,而是带缺失值、含时间戳、混着多类别标签的 DataFrame。Seaborn 的 lineplot() 内置了稳健的聚合逻辑:当 x 轴有重复值(比如某天多个汇率快照),它默认按 y 值取均值并画置信区间;当数据有缺失,它自动跳过断点而非报错;当 hue 分组后某类数据量极少,它会静默降级为散点而不强行连线。这些细节,是我在银行风控部门陪业务方跑通 27 个监管报表后,才刻进肌肉记忆里的经验。

所以这篇教程不讲“怎么调参”,而是带你重建一套 生产级折线图思维框架 :从数据结构如何影响绘图逻辑,到为什么 hue 比手动循环更安全,再到如何让一张图同时满足技术评审的严谨性和老板扫一眼就懂的传达力。所有代码都基于真实外汇数据集,所有参数选择都有业务场景锚点——比如为什么 linewidth=2.5 3 更适合财报附图,为什么 dashes=[[4,2]] 在周报里比纯实线更易读。现在,我们从最基础的“画出第一根线”开始,但每一步都指向你明天就要交的那份报告。

2. 数据准备与结构化清洗:为什么 80% 的绘图失败源于数据没“长对样子”

很多人以为绘图失败是参数没调对,其实 80% 的问题出在数据没“长对样子”。Seaborn 的 lineplot() 对数据结构有明确偏好:它最擅长处理 “长格式”(long format)DataFrame ,即每个观测值占一行,变量名作为列,分类维度(如货币类型)单独成列。而原始数据往往是“宽格式”(wide format)——日期一列,澳元、加元、美元各占一列。强行用宽格式画图,要么代码冗长,要么图例错乱,要么时间轴错位。下面我带你用真实外汇数据走一遍清洗全流程,每一步都解释清楚“为什么必须这样”。

2.1 原始数据结构诊断

我们下载的 Kaggle 数据集 euro-daily-hist_1999_2022.csv 是典型宽格式:第一列是日期,后面几十列是各国货币兑欧元的汇率。直接读入后,用 df.head() 看前五行:

import pandas as pd
df_raw = pd.read_csv('euro-daily-hist_1999_2022.csv')
print(df_raw.shape)  # (6000+, 40+)
print(df_raw.columns.tolist()[:8])  
# ['[US dollar ]', '[Australian dollar ]', '[Canadian dollar ]', ...]

问题立刻浮现:列名带方括号和空格,且包含大量非目标货币(如日元、英镑)。如果此时直接 sns.lineplot(x='[US dollar ]', y='[Australian dollar ]') ,x 轴会变成数值而非时间,图完全不可读。这步诊断必须做,否则后续全是无用功。

2.2 列筛选与重命名:砍掉干扰项,建立语义清晰的列名

我们只关注澳元、加元、美元三类主流货币,且需剔除原始列名中的噪声。这里不用正则硬匹配,而是用 iloc 定位关键列(经检查,第0、1、4、-2列对应日期、澳元、加元、美元):

# 只取四列:日期 + 三种货币
df_subset = df_raw.iloc[:, [0, 1, 4, -2]]
# 重命名:去掉方括号和空格,用下划线分隔
df_subset.columns = ['Date', 'Australian_dollar', 'Canadian_dollar', 'US_dollar']

提示:为什么不用 df_raw.filter(regex='dollar') ?因为原始数据中存在 [US dollar ] [US dollar ] (1) 这类重复列名,正则可能误抓。 iloc 定位虽显笨拙,但在数据源不稳定时,是保证可复现性的黄金准则。

2.3 数据重塑(Melt):从宽到长的质变

这是最关键的一步。 pd.melt() 将宽格式“压扁”为长格式,让分类变量(货币类型)成为独立列:

df_long = pd.melt(
    df_subset,
    id_vars='Date',  # 保持不变的标识列(日期)
    value_vars=['Australian_dollar', 'Canadian_dollar', 'US_dollar'],  # 要融化的列
    var_name='Currency',  # 新列名:存储原列名(即货币类型)
    value_name='Euro_rate'  # 新列名:存储原列的值(即汇率)
)

执行后,数据从 4 列 × N 行,变成 3 列 × (N×3) 行。 df_long.head() 输出:

        Date         Currency  Euro_rate
0  1999-01-04  Australian_dollar     1.623
1  1999-01-05  Australian_dollar     1.621
2  1999-01-06  Australian_dollar     1.619
3  1999-01-07  Australian_dollar     1.617
4  1999-01-08  Australian_dollar     1.615

注意: var_name='Currency' 是刻意为之。很多教程写 var_name='currency' (小写),但实际业务中,列名大小写混乱极易引发 KeyError 。统一用大驼峰 Currency ,既符合 Python 命名规范,又避免与 currency (货币单位)等业务字段混淆。

2.4 时间与数值清洗:让数据真正“可计算”

长格式只是第一步,数据还需通过“可计算性”检验:

# 1. 日期转 datetime:必须用 pd.to_datetime,而非 strptime
df_long['Date'] = pd.to_datetime(df_long['Date'])

# 2. 数值清洗:原始数据含字符串'-'代表缺失,需转为 NaN 再转 float
df_long['Euro_rate'] = pd.to_numeric(df_long['Euro_rate'], errors='coerce')

# 3. 时间范围裁剪:只取近一年高频数据(2022-12-01 至今)
df_long = df_long[df_long['Date'] >= '2022-12-01'].reset_index(drop=True)

# 4. 剔除无效行:确保每行都有有效汇率
df_clean = df_long.dropna(subset=['Euro_rate']).copy()

实操心得: pd.to_numeric(..., errors='coerce') 是救命稻草。原始数据中常有 'N/A' '-' 、甚至空格, errors='coerce' 会将它们全转为 NaN ,比手动 replace() 安全得多。而 dropna(subset=['Euro_rate']) 明确指定只删汇率列为空的行,避免误删日期有效的记录。

最终, df_clean 结构完美匹配 Seaborn 要求: Date (连续型 x 轴)、 Euro_rate (连续型 y 轴)、 Currency (分类型分组变量)。此时调用 sns.lineplot(x='Date', y='Euro_rate', hue='Currency', data=df_clean) 才是水到渠成。记住这个铁律: 画图前花 10 分钟理清数据结构,胜过画图后花 2 小时调参 debug

3. 单线图构建与核心参数解密:从“能画”到“画得准”的跨越

当你第一次用 sns.lineplot(x='Date', y='Euro_rate', data=df_usd) 画出 EUR-USD 走势图时,可能会觉得“不过如此”。但真正的专业分水岭,藏在那些看似微小的参数选择里。我见过太多人因忽略 estimator errorbar ,把波动剧烈的汇率数据画成一条虚假平滑的直线,导致风控模型误判。下面拆解单线图的四大核心参数,每个都关联真实业务风险。

3.1 x y :不只是坐标轴,更是数据语义的声明

x y 参数表面是选列名,实则是向 Seaborn 声明:“这是我的自变量(驱动因素),这是我的因变量(响应指标)”。在时间序列中, x='Date' 意味着 Seaborn 会自动启用时间轴优化:智能缩放刻度(月/季/年)、格式化日期标签( 2023-01 而非 2023-01-01 )、处理周末/节假日空白。若错误地设 x='Euro_rate' ,图会变成汇率分布直方图,完全偏离分析目标。

注意: x y 必须是 DataFrame 中存在的列名字符串,不能是 Series 或 numpy 数组。曾有同事传入 x=df_usd['Date'].values ,结果报 TypeError: unhashable type: 'numpy.ndarray' ——这是新手最常踩的坑,根源在于混淆了“数据容器”与“数据引用”。

3.2 data :为什么必须传入完整 DataFrame,而非切片后的 Series?

你可能想省事,直接传 data=df_usd['Euro_rate'] 。但这是危险操作。Seaborn 的 lineplot() 内部依赖 data 的索引对齐机制。当 x='Date' y='Euro_rate' 同时存在时,Seaborn 会用 data.index 作为隐式连接键。若传入 Series,索引可能错位(如 reset_index(drop=True) 后索引重排),导致日期和汇率值错配。正确做法永远是传入完整 DataFrame,并确保 x y 列同属该 DataFrame。

3.3 estimator errorbar :处理重复观测的“风控开关”

真实金融数据常有重复时间戳(如日内多次报价)。默认情况下, lineplot() 对每个 x 值(日期)聚合所有 y 值(汇率),用均值( np.mean )作为代表点,并绘制 95% 置信区间(CI)。这既是优势也是风险点。

# 默认行为:均值 + 95% CI
sns.lineplot(x='Date', y='Euro_rate', data=df_with_duplicates)

# 关闭置信区间,仅显示均值线
sns.lineplot(x='Date', y='Euro_rate', data=df_with_duplicates, errorbar=None)

# 改用中位数(对异常值更鲁棒)
sns.lineplot(x='Date', y='Euro_rate', data=df_with_duplicates, estimator='median')

# 自定义误差条:标准差 ±1σ
sns.lineplot(x='Date', y='Euro_rate', data=df_with_duplicates, errorbar=('sd', 1))

实操心得:在汇率分析中,我通常设 errorbar=None 。因为日内波动本就是分析对象,画 CI 反而模糊了真实波动特征。但在宏观经济指标(如季度GDP)分析中, estimator='mean' + errorbar='ci' 是标配,它直观呈现数据可靠性。参数选择没有绝对对错,只有是否匹配你的分析目标。

3.4 sort :时间序列的“隐形地雷”

sort=True (默认)会让 Seaborn 按 x 值排序后再连线。这看似合理,但埋下隐患:若数据已按时间倒序排列(如 2023-01-27 在前, 2023-01-26 在后), sort=True 会强制正序,导致线条从右向左“倒流”,违背时间逻辑。更糟的是,若 Date 列是字符串而非 datetime, sort=True 会按字典序排序( '2023-01-10' 排在 '2023-01-2' 前),造成严重错乱。

# 安全做法:确保 Date 是 datetime,且显式关闭 sort(因数据已有序)
df_usd['Date'] = pd.to_datetime(df_usd['Date'])
df_usd = df_usd.sort_values('Date').reset_index(drop=True)  # 先手动排序
sns.lineplot(x='Date', y='Euro_rate', data=df_usd, sort=False)  # 再关闭自动排序

提示: sort=False 并非偷懒,而是把排序权交给数据预处理环节。这符合“数据清洗与可视化分离”的工程原则,让绘图代码更纯粹、更易测试。

4. 多线图构建与分组逻辑: hue style size 的业务语义选择

当你要对比澳元、加元、美元兑欧元的走势时,“画三条线”只是表象,本质是 用视觉通道编码业务维度 。Seaborn 提供 hue style size 三个参数,它们不是功能重复的备选项,而是针对不同业务场景的语义化工具。选错一个,图就可能传递错误信号。

4.1 hue :当分类维度需要“高辨识度”时的首选

hue='Currency' 是最常用方案,它用颜色区分三类货币。但颜色选择绝非随意:

# 错误示范:用默认色板(蓝/橙/绿),在黑白打印时无法区分
sns.lineplot(x='Date', y='Euro_rate', data=df_clean, hue='Currency')

# 正确实践:用色盲友好色板 + 业务隐喻
palette = {
    'Australian_dollar': '#1f77b4',  # 深蓝:澳洲国旗主色
    'Canadian_dollar': '#ff7f0e',    # 橙色:加拿大枫叶色
    'US_dollar': '#2ca02c'          # 绿色:美元绿(美钞主色)
}
sns.lineplot(x='Date', y='Euro_rate', data=df_clean, hue='Currency', palette=palette)

为什么强调色盲友好?我曾因用红绿色区分货币,在给一位色觉障碍的风控总监汇报时,对方完全无法分辨哪条线是加元。 #1f77b4 (蓝)、 #ff7f0e (橙)、 #2ca02c (绿)是公认的色盲安全组合,且暗合各国视觉符号,增强记忆点。

4.2 style :当颜色已被占用,或需强化“模式差异”时

假设你的图已用颜色编码了“货币类型”,现在想再叠加“数据来源”(如彭博 vs 路透), hue 就不够用了。此时 style='Source' 用线型(实线/虚线/点划线)区分,实现视觉通道复用:

# 添加 Source 列(模拟不同数据源)
df_clean['Source'] = 'Bloomberg'
df_clean.loc[::2, 'Source'] = 'Reuters'  # 每隔一行切换

# 用 style 编码数据源,hue 仍编码货币
sns.lineplot(
    x='Date', y='Euro_rate', 
    data=df_clean, 
    hue='Currency', 
    style='Source',
    dashes={'Bloomberg': (1, 0), 'Reuters': (2, 2)}  # 实线 vs 虚线
)

关键细节: dashes 参数接受字典,键为 style 分类值,值为 (on_length, off_length) 元组。 (1,0) 是实线, (2,2) 是标准虚线。避免用 (1,1) (太密)或 (5,5) (太疏), 2,2 是人眼最易分辨的平衡点。

4.3 size :当分类维度具有“强度”或“权重”含义时

size='Currency' 用线宽区分货币,这在业务上有强暗示:线越粗,代表该货币在分析中“权重越高”或“影响力越大”。例如,在分析欧元区贸易伙伴时,美元线宽设为 5 ,澳元设为 2 ,暗示美元是核心参照系:

# 按业务重要性设定线宽
sizes = {'US_dollar': 5, 'Australian_dollar': 2, 'Canadian_dollar': 3}
sns.lineplot(
    x='Date', y='Euro_rate', 
    data=df_clean, 
    size='Currency', 
    sizes=sizes,
    linewidth=2  # 基础线宽,size 参数在此基础上缩放
)

注意: size 参数需配合 linewidth 使用。 linewidth=2 是基准, sizes 字典值是乘数( US_dollar 线宽 = 2 * 5 = 10 )。若只设 sizes 不设 linewidth ,Seaborn 会用默认 linewidth=1.5 ,导致比例失真。

4.4 组合拳: hue + style + size 的协同设计

真实场景往往需要多维编码。例如,分析“主要货币(hue)在不同市场(style)的流动性(size)”:

# 构建三维数据
df_3d = df_clean.copy()
df_3d['Market'] = 'Spot'  # 即期市场
df_3d.loc[df_3d['Date'] > '2023-01-01', 'Market'] = 'Forward'  # 远期市场
df_3d['Liquidity'] = df_3d['Euro_rate'].rolling(5).std()  # 流动性用5日波动率代理

# 三维编码:颜色=货币,线型=市场,宽度=流动性
sns.lineplot(
    x='Date', y='Euro_rate',
    data=df_3d,
    hue='Currency',
    style='Market',
    size='Liquidity',
    sizes=(1, 6),  # 流动性最小值→线宽1,最大值→线宽6
    palette=palette,
    dashes={'Spot': (1,0), 'Forward': (3,2)}
)

实操心得:三维编码需克制。人眼最多同时分辨 3-4 种视觉通道。若 hue 用 3 种色, style 用 2 种线型, size 用 3 种宽度,组合数达 18 种,远超认知负荷。我的经验是: hue 固定核心维度(货币), style 用于二元对比(如新旧系统), size 用于连续变量(如交易量)。永远问自己:这张图,用户需要几秒内抓住什么信息?

5. 生产级定制:从“能看”到“能用”的 7 个关键技巧

画出图只是起点,让它在真实工作流中“能用”,需要解决一系列工程细节。我整理了 7 个高频痛点,每个都来自血泪教训——比如因未设 dpi 导致 PPT 插图模糊,或因未处理 xticks 导致时间轴挤成黑线。

5.1 图形尺寸与 DPI:告别 PPT 里的马赛克

figsize=(20,5) 看似够大,但若未设 dpi ,保存为 PNG 时默认 dpi=100 ,在 4K 屏幕或高清打印中仍是模糊的。正确姿势:

import matplotlib.pyplot as plt

# 创建高分辨率画布
plt.figure(figsize=(20, 5), dpi=300)  # dpi=300 是印刷级标准
sns.lineplot(x='Date', y='Euro_rate', data=df_clean, hue='Currency')

# 保存时指定 bbox_inches='tight' 防止标签被截
plt.savefig('eur_rates.png', bbox_inches='tight', dpi=300)

注意: dpi 必须在 plt.figure() 时指定, plt.savefig(dpi=300) 无效。 bbox_inches='tight' 会自动压缩空白边距,避免图例被切掉。

5.2 时间轴刻度精控:让“2023-01”不再变成“Jan 01”

默认时间轴对金融数据极不友好:它可能显示 Jan 01 Jan 08 ,而业务方需要 2023-01 2023-02 。用 matplotlib.dates 精细控制:

import matplotlib.dates as mdates

fig, ax = plt.subplots(figsize=(20, 5), dpi=300)
sns.lineplot(x='Date', y='Euro_rate', data=df_clean, hue='Currency', ax=ax)

# 设置 x 轴主刻度为每月第一天,格式为 YYYY-MM
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))

# 设置次刻度为每周,避免刻度过疏
ax.xaxis.set_minor_locator(mdates.WeekdayLocator(byweekday=mdates.MO))

# 旋转日期标签,防止重叠
plt.xticks(rotation=30)

提示: MonthLocator() YearLocator() 更实用,因汇率分析多聚焦 1-2 年。 byweekday=mdates.MO 确保次刻度总在周一,符合交易日逻辑。

5.3 图例位置与样式:让关键信息“一眼锁定”

默认图例在右上角,常遮挡数据。移到底部或右侧,并精简文字:

# 移到图下方,水平排列,去边框
ax.legend(
    loc='upper center', 
    bbox_to_anchor=(0.5, -0.15),  # (x,y) 相对于图的位置
    ncol=3,  # 3 列显示
    frameon=False,  # 去边框
    title='Currency'  # 标题用业务术语,非 'Currency'
)

# 或移到右侧,节省横向空间
ax.legend(
    loc='center left', 
    bbox_to_anchor=(1, 0.5),
    frameon=False
)

5.4 置信区间透明度:让“不确定性”不抢戏

默认 CI 区域不透明,常盖住其他线条。调低 alpha 值:

sns.lineplot(
    x='Date', y='Euro_rate', 
    data=df_clean, 
    hue='Currency',
    errorbar=('ci', 95),
    err_kws={'alpha': 0.2}  # CI 区域透明度 20%
)

5.5 标题与标签字体:确保投影仪上清晰可读

PPT 投影时,小字号全糊成一片。用 plt.rcParams 全局设置:

# 全局字体设置(生效于后续所有图)
plt.rcParams.update({
    'font.size': 16,           # 基础字号
    'axes.titlesize': 20,      # 标题字号
    'axes.labelsize': 18,      # 坐标轴标签字号
    'legend.fontsize': 16,     # 图例字号
    'xtick.labelsize': 14,     # x轴刻度字号
    'ytick.labelsize': 14,     # y轴刻度字号
    'figure.titlesize': 22     # figure标题字号(若用 plt.suptitle)
})

5.6 线条抗锯齿与渲染:消除“毛刺感”

默认线条有锯齿,尤其放大时明显。开启抗锯齿:

sns.lineplot(
    x='Date', y='Euro_rate', 
    data=df_clean, 
    hue='Currency',
    linewidth=2.5,  # 非整数更柔顺
    antialiased=True  # 强制抗锯齿
)

5.7 保存为矢量图:让图表无限缩放不失真

PNG 是位图,放大失真。PDF 或 SVG 是矢量图,适合嵌入报告:

# 保存为 PDF(推荐,兼容性最好)
plt.savefig('eur_rates.pdf', bbox_inches='tight')

# 或 SVG(网页嵌入首选)
plt.savefig('eur_rates.svg', bbox_inches='tight')

最后提醒:矢量图文件体积小,但若图中含大量数据点(>10万),PDF 渲染可能卡顿。此时应先用 df.sample(frac=0.1) 降采样,再绘图。

6. 常见问题与排查技巧实录:那些文档里不会写的“坑”

以下问题,90% 的 Seaborn 教程不会提,但你在真实项目中必然撞上。我按发生频率排序,每个都附带“现场排查指令”和“根治方案”。

6.1 问题:图例显示 Currency 而非具体货币名(如 Australian_dollar

现象 :图例显示 Currency Currency Currency ,三条线无法区分。
排查 print(df_clean['Currency'].unique()) → 发现值为 ['Australian_dollar', 'Canadian_dollar', 'US_dollar'] ,正常。
根因 hue 参数传入了列名字符串 'Currency' ,但 Seaborn 在图例中默认显示列名,而非值。
根治 :用 legend='full' 强制显示值,或重命名列:

# 方案1:显式指定图例
sns.lineplot(x='Date', y='Euro_rate', data=df_clean, hue='Currency', legend='full')

# 方案2:重命名列(推荐,一劳永逸)
df_clean = df_clean.rename(columns={'Currency': 'Currency_Type'})
sns.lineplot(x='Date', y='Euro_rate', data=df_clean, hue='Currency_Type')

6.2 问题:时间轴显示为数字(如 1.9e7 )而非日期

现象 :x 轴刻度是 1.9e7 1.9e7 ,完全不可读。
排查 print(df_clean['Date'].dtype) datetime64[ns] ,类型正确。
根因 Date 列是 datetime,但 Seaborn 未触发时间轴优化,常因 data 传入方式错误(如传了 df_clean[['Date','Euro_rate']] 子集,丢失了原始索引)。
根治 :确保 data 是完整 DataFrame,且 x 列名准确:

# 错误:传入子集
sns.lineplot(x='Date', y='Euro_rate', data=df_clean[['Date','Euro_rate']])

# 正确:传入完整 df,或显式 reset_index
df_clean_full = df_clean.reset_index(drop=True)
sns.lineplot(x='Date', y='Euro_rate', data=df_clean_full)

6.3 问题:多线图中某条线“消失”或“断开”

现象 :澳元线完整,加元线只有一小段,美元线完全不见。
排查 print(df_clean.groupby('Currency').size()) Australian_dollar: 40, Canadian_dollar: 5, US_dollar: 0 。发现美元数据为空!
根因 :数据清洗时 dropna() 过度,或 Currency 列有隐藏空格(如 'US_dollar ' )。
根治 :清洗时严格处理空格,并验证分组:

# 清洗 Currency 列空格
df_clean['Currency'] = df_clean['Currency'].str.strip()

# 验证分组数据量
print(df_clean.groupby('Currency').size())
# 若某组为0,检查原始数据中该列是否存在
print(df_raw.columns.str.contains('US dollar').sum())  # 应 >0

6.4 问题: palette 参数不生效,颜色仍是默认蓝橙绿

现象 :传入 palette=['red','blue','green'] ,图中仍是默认色。
排查 print(sns.__version__) 0.12.2 ,版本正常。
根因 palette 仅在 hue 存在时生效。若误写 sns.lineplot(..., palette=...) 但未设 hue ,参数被忽略。
根治 :确认 hue 已设置,且 palette 长度匹配分类数:

# 检查 hue 分类数
n_categories = df_clean['Currency'].nunique()  # 应为3
palette_list = ['#e41a1c', '#377eb8', '#4daf4a']  # 长度=3

# 确保 hue 存在
sns.lineplot(x='Date', y='Euro_rate', data=df_clean, hue='Currency', palette=palette_list)

6.5 问题:保存的 PNG 图中中文标题显示为方块

现象 plt.title('欧元汇率') 保存后是 □□□
根因 :Matplotlib 默认字体不支持中文。
根治 :全局设置中文字体(以 macOS 为例):

import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
matplotlib.rcParams['axes.unicode_minus'] = False  # 解决负号显示为方块

提示:Windows 用 'SimHei' ,Linux 用 'WenQuanYi Zen Hei' axes.unicode_minus=False 是关键,否则负号 - 也变方块。

6.6 问题: relplot() 生成的图无法用 plt.savefig() 保存

现象 g = sns.relplot(...); plt.savefig('a.png') 保存为空白图。
根因 relplot() 返回 FacetGrid 对象,其画布与 plt 不共享。
根治 :用 g.savefig()

g = sns.relplot(x='Date', y='Euro_rate', data=df_clean, hue='Currency', kind='line')
g.savefig('eur_rates_relplot.png', dpi=300, bbox_inches='tight')

6.7 问题:图中出现意外的“阶梯状”线条

现象 :本该平滑的汇率线,变成一级级台阶。
根因 Date 列是字符串,非 datetime,Seaborn 按字典序排序连线( '2023-01-01' < '2023-01-02' < '2023-01-10' ),导致 01-02 后直接连到 01-10 ,中间跳过 01-03 01-09
根治 :强制转换并验证:

# 转换
df_clean['Date'] = pd.to_datetime(df_clean['Date'])

# 验证是否成功
print(df_clean['Date'].dtype)  # 必须是 datetime64[ns]
print(df_clean['Date'].is_monotonic_increasing)  # 必须为 True

最后分享一个终极技巧:当所有排查失效,运行 sns.reset_defaults() 重置 Seaborn 全局配置,再重绘。很多诡异问题源于之前代码污染了 rcParams。

7. 进阶实战:用 Seaborn 构建可交付的业务分析图

现在,我们整合所有技巧,构建一张真正能放进周报、发给老板的 EUR 汇率分析图。这张图需满足:1)清晰展示三币种趋势;2)标出关键事件点(如美联储加息日);3)添加业务解读注释;4)配色符合公司VI。以下是完整代码与设计逻辑:

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

# --- 数据准备(复用前文清洗逻辑)---
df_clean = pd.read_pickle('df_clean.pkl')  # 已清洗好的长格式数据

# --- 创建画布 ---
plt.figure(figsize=(24, 8), dpi=300)
sns.set_style("whitegrid", {'axes.grid': True, 'grid.linestyle': '--', 'grid.alpha': 0.4})

# --- 主图:三线对比 ---
ax = sns
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐