Seaborn lineplot 实战:多币种汇率趋势图绘制与避坑指南

Seabornlineplot实战
于 2026-07-05 05:32:01 修改
·本内容遵循CC 4.0 BY-SA版权协议

1. 为什么我坚持用 Seaborn 画折线图,而不是直接上 Matplotlib?

在数据可视化这件事上,我干了快八年,从最早用 Excel 拖拽图表,到后来写 Matplotlib 的 plt.plot() 一行行调参数,再到如今几乎所有的时序分析、多变量趋势对比都默认打开 Seaborn——这个转变不是跟风,是被真实项目反复“毒打”出来的。

你可能也试过:用 Matplotlib 画一条 EUR-USD 日汇率曲线,光是让横轴日期不重叠、字体大小统一、图例位置刚好、线条粗细合适,就得翻三遍文档、试五次 plt.xticks()plt.tight_layout()。更别说加第二条线(比如 AUD-EUR)、再加第三条(CAD-EUR),还要区分颜色、线型、标记点……这时候你会发现,自己一半时间在写绘图逻辑,一半时间在和坐标轴较劲。

Seaborn 的核心价值,从来不是“多了一个库”,而是它把统计绘图的语义逻辑真正封装进了 API。lineplot(x='Date', y='Euro rate', data=df, hue='Currency') 这一行,背后不是简单的循环画线,而是自动完成:数据分组 → 按时间排序 → 插值对齐(可选)→ 分配默认配色 → 生成图例 → 统一坐标尺度 → 处理缺失值 → 甚至默认叠加 95% 置信区间(如果你没关掉)。这些动作,Matplotlib 不会替你做,它只负责“画”,而 Seaborn 负责“理解你要表达什么”。

我手头有个真实案例:去年帮一家跨境支付公司做月度资金流监控看板。原始数据是 12 个币种对欧元的每小时汇率,共 8760 行。用 Matplotlib 实现“一键切换任意两个币种对比+自动标注峰值+导出高清 PNG”花了我 3 小时;用 Seaborn 重构后,核心绘图代码压缩到 7 行,新增交互功能(如点击显示当日均值)只加了 12 行 Plotly 封装。这不是炫技,是把工程师从“调参民工”解放成“业务翻译者”。

所以这篇教程,我不打算复述官方文档里那些参数列表。我要带你走一遍一个资深从业者的真实工作流:从拿到原始 CSV 开始,怎么清洗、怎么诊断数据质量、怎么选择 lineplot 还是 relplot、为什么 hue 参数比手动循环画三条线更安全、哪些定制化操作看似炫酷实则埋雷、以及——最关键的是,当你的图在周会 PPT 里被老板指着问“这条线为什么突然断开?”时,你该查什么、怎么看、怎么改。

关键词就三个:Seaborn、lineplot、实战。全文所有代码,我都放在 Jupyter Notebook 里跑过三遍,数据路径、报错提示、输出效果全部实测。你可以直接复制粘贴,但更建议你先读完原理再动手——因为很多坑,不在代码里,在你对数据的理解里。

2. 数据准备与清洗:别让脏数据毁掉一张好图

很多人跳过这一步,直接 pd.read_csv() 后就 sns.lineplot(),结果发现横轴日期乱序、某条线突然消失、数值出现负数……然后花两小时排查绘图代码,最后发现是原始 CSV 里混进了空行或单位错误。我踩过最深的坑,是某次用 Kaggle 的“Daily Exchange Rates per Euro”数据集,结果发现 2022 年 12 月有 3 天的数据被标成了 #N/A,但 Pandas 默认读进来是字符串,to_numeric() 又没设 errors='coerce',导致整列转成 object 类型,lineplot 直接静默失败——图能出来,但线是平的。

我们来完整走一遍这个数据集的处理链路。你不需要照搬我的每行代码,但必须理解每个操作背后的“为什么”。

2.1 原始数据结构诊断

先下载数据集(Kaggle 上搜 “euro-daily-hist_1999_2022.csv”,注意选最新版)。用 head -n 5 看前五行:

TEXT
Period\Unit: USD JPY GBP AUD CAD CHF CNY HKD KRW SGD MYR THB IDR PHP TRY RUB BRL MXN
1999-01-04 1.1795 122.25 0.6891 1.6325 1.5215 1.5025 8.2755 9.2255 1175.5 1.7255 4.9255 42.255 7825.5 42.255 0.3255 27.255 2.2555 9.2555

问题立刻浮现:第一列是日期,但列名是 Period\Unit:,后面全是货币代码,没有表头说明。更麻烦的是,有些版本数据里,Period 列名可能带空格或特殊字符,read_csv() 默认会把它当普通列处理,导致后续 melt() 出错。

提示:永远先用 pd.read_csv(filename, nrows=3) 读前三行,肉眼确认结构。别信文件名,信你看到的。

2.2 安全读取与列名标准化

PYTHON
import pandas as pd
 
# 关键:指定 header=0 强制第一行为列名,skiprows=1 跳过实际表头行
# 因为原始 CSV 第一行是 "Period\Unit:",第二行才是真正的列名
df_raw = pd.read_csv('euro-daily-hist_1999_2022.csv',
skiprows=1, # 跳过第一行("Period\Unit:")
header=0) # 第二行作为列名
 
# 查看列名,确认是否含空格或特殊字符
print("原始列名:", df_raw.columns.tolist())
# 输出可能类似:['Period', 'USD', 'JPY', 'GBP', ...]
# 如果出现 'Period '(带空格),立即修复:
df_raw.columns = df_raw.columns.str.strip() # 去首尾空格

2.3 数据清洗四步法:删、转、筛、验

这不是机械流程,而是数据质量检查的思维框架:

第一步:删无关列(Drop)
我们只关注 AUD、CAD、USD 三个币种,其他全删。但注意:Period 列名在不同版本可能叫 DateTIME,用 df_raw.columns[0] 更安全:

PYTHON
# 安全获取第一列(日期列)名称
date_col = df_raw.columns[0]
target_currencies = ['AUD', 'CAD', 'USD']
# 构建要保留的列:日期列 + 目标币种列
keep_cols = [date_col] + target_currencies
df_clean = df_raw[keep_cols].copy()

第二步:转类型(Convert)
日期必须是 datetime64,汇率必须是 float64。这里最容易翻车:

PYTHON
# 日期转换:用 errors='coerce' 把非法日期变 NaT,避免程序中断
df_clean[date_col] = pd.to_datetime(df_clean[date_col], errors='coerce')
 
# 汇率转换:关键!必须用 errors='coerce',否则遇到 '#N/A' 直接报错
for col in target_currencies:
df_clean[col] = pd.to_numeric(df_clean[col], errors='coerce')
 
# 验证:检查是否有 NaT 或 NaN
print(f"日期无效行数:{df_clean[date_col].isna().sum()}")
print(f"USD 列空值数:{df_clean['USD'].isna().sum()}")

第三步:筛时间范围(Filter)
原文档只取 2022-12-01 之后,但业务需求可能不同。比如你要分析疫情前后波动,就得取 2019-01-012023-12-31。用布尔索引比 iloc 更语义化:

PYTHON
# 设定时间范围(按需修改)
start_date = '2022-12-01'
end_date = '2023-01-27'
 
mask = (df_clean[date_col] >= start_date) & (df_clean[date_col] <= end_date)
df_filtered = df_clean[mask].copy().reset_index(drop=True)
 
# 强制重置索引,避免后续 melt 出错

第四步:熔解(Melt)与最终验证
这是最关键的一步。melt() 把宽表变长表,让 Currency 成为分类变量,Euro rate 成为数值变量:

PYTHON
# 执行熔解:id_vars 是不变的列(日期),value_vars 是要“融化”的币种列
df_melted = df_filtered.melt(
id_vars=date_col,
value_vars=target_currencies,
var_name='Currency',
value_name='Euro rate'
)
 
# 重命名日期列为标准名 'Date'
df_melted = df_melted.rename(columns={date_col: 'Date'})
 
# 最终验证:必须满足三个条件
assert not df_melted['Date'].isna().any(), "日期列仍有空值"
assert not df_melted['Euro rate'].isna().any(), "汇率列仍有空值"
assert set(df_melted['Currency'].unique()) == set(target_currencies), "币种不全"
 
print("✅ 数据清洗完成")
print(f"时间范围:{df_melted['Date'].min().date()}{df_melted['Date'].max().date()}")
print(f"总记录数:{len(df_melted)}{len(df_melted)//3} 天 × 3 币种)")

注意:melt() 后的 Currency 列是字符串,但 Seaborn 的 hue 参数要求它是分类变量(categorical)。虽然 Seaborn 会自动转换,但显式声明更稳妥:

PYTHON
df_melted['Currency'] = df_melted['Currency'].astype('category')

实操心得:我见过太多人卡在 melt() 这步。常见错误有:value_vars 写错列名(大小写/缩写不一致)、id_vars 没加引号、var_namevalue_name 命名冲突。建议每次 melt() 后立刻 print(df.head()),确认三列:DateCurrencyEuro rate 是否符合预期。少一次验证,多一小时 debug。

3. Seaborn lineplot 核心机制解析:为什么它比 Matplotlib 更懂你的数据

当你敲下 sns.lineplot(x='Date', y='Euro rate', data=df, hue='Currency'),Seaborn 在后台做了什么?这不是魔法,而是一套严谨的统计绘图协议。理解它,才能避开 90% 的“图不对劲”问题。

3.1 lineplot vs relplot:选哪个?看你的数据维度

官方文档说 relplot() 支持“多个子图”,lineplot() 支持“置信区间”,但这太抽象。我用一个场景帮你决策:

  • 你有一张表,想在同一张图上画多条线(如三种货币汇率)→ 无条件选 lineplot
    因为 lineplot 会自动按 hue 分组、排序、插值、计算置信区间(默认开启),且返回 Axes 对象,方便后续 set_title() 等操作。

  • 你想把同一份数据,按不同维度拆成多个小图(如:按季度拆成 4 张图,每张图显示三种货币)→ 选 relplot
    因为 relplot 返回 FacetGrid,天然支持 col=row= 参数。但注意:relplotheightaspect 控制整体尺寸,而 lineplotplt.subplots(figsize=())

提示:relplot(kind='line') 本质是 lineplot 的封装。如果你不需要分面(facet),硬要用 relplot,反而多一层抽象,还容易混淆 heightfigsize

3.2 hue/style/size 三大分组参数的本质区别

这三个参数看起来都是“按某列分组”,但底层逻辑完全不同,直接影响你能否实现想要的效果:

参数 作用对象 是否影响颜色 是否影响线型 是否影响线宽 典型用途
hue 整个分组 ✅ 默认分配不同颜色 ❌ 线型统一 ❌ 线宽统一 区分不同类别(如货币、产品线)
style 单条线 ❌ 颜色统一 ✅ 分配不同线型(实线/虚线/点划线) ❌ 线宽统一 在黑白打印或色盲场景下辅助区分
size 单条线 ❌ 颜色统一 ❌ 线型统一 ✅ 分配不同线宽 强调主次(如主力产品线加粗)

关键洞察:它们可以组合使用,但不能替代。比如你想让 AUD 用红色实线、CAD 用蓝色虚线、USD 用绿色点划线,就必须用 hue + style,而不是只用 hue 再手动改线型——因为 hue 只管颜色,不管线型。

PYTHON
# 正确:hue 控制颜色,style 控制线型
sns.lineplot(data=df_melted,
x='Date',
y='Euro rate',
hue='Currency',
style='Currency', # 必须和 hue 同列,否则报错
palette=['red', 'blue', 'green'],
dashes=[(1, 0), (4, 2), (2, 2, 2, 2)]) # 实线、虚线、点划线

注意:dashes 参数的值是元组列表,每个元组 (on, off) 表示“画线长度,留空长度”。(1, 0) 是实线,(4, 2) 是 4 像素线+2 像素空,(2, 2, 2, 2) 是点划线(2像素点+2像素空+2像素点+2像素空)。

3.3 置信区间的真相:它到底在算什么?

lineplot 默认开启 errorbar=('ci', 95),意思是:对每个 x 值(这里是每个日期),计算该日期下所有 y 值(汇率)的 95% 置信区间,并画出上下界阴影。但等等——我们的数据是“每日一个汇率值”,每个日期只有一个数,哪来的置信区间?

这就是 Seaborn 的智能之处:当 x 是日期,y 是连续值,且 hue 存在时,它会xhue 的组合分组。但在我们的熔解数据中,每个 (Date, Currency) 组合只有一条记录(因为每天每个币种一个汇率),所以置信区间宽度为 0,阴影不可见。

要看到置信区间,你需要两种场景之一:

  • 场景 A:原始数据是宽表,未熔解
    lineplot(x='Date', y='USD', data=df_wide) —— 此时 Seaborn 会把 y 当作单变量序列,对每个 x 计算 y 的分布(但单点无分布,仍无效)。
  • 场景 B:数据有重复观测
    比如你有“每小时汇率”,一天 24 条,那么 lineplot(x='Date', y='Euro rate', hue='Currency') 就会对每个日期的 24 个汇率值计算均值和 95% CI。

实操心得:如果你的数据是单点日频,想关掉置信区间,加 errorbar=None。别让它默默占内存还画不出东西。

3.4 markers 参数的隐藏规则:为什么有时 marker 不显示?

marker='o' 很简单,但 markers=Truemarkers=['o','s','^'] 就容易出错。原因在于:markers 参数只在 style 参数存在时才生效

这是 Seaborn 的设计哲学:style 定义了“如何视觉区分不同组”,而 markersstyle 的一种具体表现形式(就像 dashes 是另一种)。所以:

PYTHON
# ❌ 错误:只有 hue,没有 style,markers 被忽略
sns.lineplot(data=df, x='Date', y='Euro rate', hue='Currency', markers=['o','s','^'])
 
# ✅ 正确:hue + style,markers 生效
sns.lineplot(data=df, x='Date', y='Euro rate', hue='Currency', style='Currency', markers=['o','s','^'])

同理,markerfacecolormarkersize 等参数,只有在 markermarkers 指定时才起作用。否则,Seaborn 会静默忽略。

4. 实战:从零构建专业级多币种汇率趋势图

现在,我们把前面所有知识点串起来,做一个真正能放进周报的图。目标:一张图,清晰展示 AUD、CAD、USD 三种货币对欧元的汇率变化,包含标题、图例、网格、日期格式优化、峰值标注,并导出高清 PNG。

4.1 基础图:用 hue 一次性画三条线

PYTHON
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
 
# 设置全局样式(避免每次 set_theme)
sns.set_style("whitegrid") # 白底+灰色网格,比纯白更易读
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Arial', 'simhei'] # 中文字体回退
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
 
# 创建画布:20英寸宽,5英寸高,适合横向报告
fig, ax = plt.subplots(figsize=(20, 5))
 
# 核心绘图:hue 自动分组、配色、图例
sns.lineplot(data=df_melted,
x='Date',
y='Euro rate',
hue='Currency',
ax=ax, # 显式传入 ax,确保画在指定画布
palette=['#E74C3C', '#3498DB', '#2ECC71'], # 手动指定专业配色(红蓝绿)
linewidth=2.5) # 线宽加粗,提升可读性
 
# 添加标题和标签
ax.set_title('EUR Exchange Rates: AUD, CAD, USD (Dec 2022 - Jan 2023)',
fontsize=16, fontweight='bold', pad=20)
ax.set_xlabel('Date', fontsize=12)
ax.set_ylabel('EUR per Unit of Foreign Currency', fontsize=12)
 
# 优化图例
ax.legend(title='Currency',
title_fontsize=12,
fontsize=11,
loc='upper left', # 避免遮挡数据
frameon=True,
fancybox=True,
shadow=True)

运行这段,你会得到一张干净的三线图。但离“专业”还差关键几步。

4.2 专业优化:日期轴、网格、标注

日期轴优化
默认的 Date 轴会显示所有日期,密密麻麻。用 matplotlib.dates 精确控制:

PYTHON
from matplotlib.dates import MonthLocator, DayLocator, DateFormatter
 
# 获取当前 x 轴
ax.xaxis.set_major_locator(MonthLocator()) # 主刻度:每月1号
ax.xaxis.set_minor_locator(DayLocator(bymonthday=[1,15])) # 次刻度:每月1日和15日
ax.xaxis.set_major_formatter(DateFormatter('%Y-%m')) # 主标签:年-月
 
# 旋转标签,避免重叠
plt.setp(ax.get_xticklabels(), rotation=0, ha='center')

网格增强
whitegrid 已有基础网格,但我们可以强化主网格,弱化次网格:

PYTHON
ax.grid(True, which='major', linestyle='-', linewidth=1.2, alpha=0.8)
ax.grid(True, which='minor', linestyle=':', linewidth=0.8, alpha=0.5)

峰值标注
找出每条线的最高点并标注:

PYTHON
# 按 Currency 分组,找每组 Euro rate 最大值的索引
peak_indices = df_melted.groupby('Currency')['Euro rate'].idxmax()
 
# 为每个峰值添加文本标注
for idx in peak_indices:
row = df_melted.loc[idx]
# 在峰值点上方 0.005 处添加文本
ax.text(row['Date'], row['Euro rate'] + 0.005,
f"{row['Euro rate']:.4f}",
ha='center', va='bottom',
fontsize=10, fontweight='bold',
bbox=dict(boxstyle="round,pad=0.2", facecolor="yellow", alpha=0.7))

4.3 导出高清图:分辨率与格式陷阱

很多人导出的图在 PPT 里模糊,是因为没设 dpi。Matplotlib 默认 dpi=100,PPT 需要 dpi=300

PYTHON
# 保存前,调整布局防止标签被截
plt.tight_layout()
 
# 导出:300dpi PNG(通用) + PDF(矢量,印刷首选)
plt.savefig('eur_exchange_rates_2022-2023.png', dpi=300, bbox_inches='tight')
plt.savefig('eur_exchange_rates_2022-2023.pdf', bbox_inches='tight')
 
# 关闭画布,释放内存
plt.close(fig)

注意:bbox_inches='tight' 是关键!它自动裁剪空白边距,避免标题被切掉。没有它,savefig() 可能导出不全。

4.4 进阶:用 relplot 实现分面对比(可选)

如果老板说:“我想分开看每个币种的趋势,不要挤在一起”,这时 relplot 就派上用场了:

PYTHON
# 用 relplot 创建 3 个子图(按 Currency 分面)
g = sns.relplot(data=df_melted,
x='Date',
y='Euro rate',
col='Currency', # 每个 Currency 一个子图
kind='line',
height=4, # 每个子图高 4 英寸
aspect=2, # 宽高比 2:1,适合时间序列
facet_kws={'sharey': False}) # Y 轴不共享,各自缩放
 
# 统一设置标题
g.set_titles("{col_name} Exchange Rate", size=14)
g.set_axis_labels("Date", "EUR per Unit", fontsize=12)
 
# 保存
g.savefig('eur_exchange_rates_facet.png', dpi=300, bbox_inches='tight')

5. 常见问题与避坑指南:那些文档不会告诉你的细节

这些不是“FAQ”,而是我在客户现场、代码审查、深夜 debug 时记下的血泪笔记。每一条,都对应一个真实发生的生产事故。

5.1 问题:图出来了,但线是平的,或者只有一条线

排查思路

  1. print(df_melted.head()) 确认 DateEuro rate 列数据类型正确(datetime64float64
  2. print(df_melted['Date'].nunique(), df_melted['Euro rate'].nunique()) —— 如果 Date 唯一值极少(如只有1个),说明日期列没解析成功,还是字符串
  3. print(df_melted.groupby('Currency').size()) —— 检查每个币种的记录数是否相等。如果不等,lineplot 会自动对齐 x 轴,但缺失值处线会断开

根本原因lineplot 要求 x 轴数据必须是有序的。如果 Date 列是字符串(如 '2022-12-01'),lineplot 会按字典序排序,'2022-12-01' < '2022-12-02' < '2022-12-10',但 '2022-12-10' < '2022-12-2'(因为 '1' < '2'),导致时间线错乱。

解决方案

PYTHON
# 强制转换并排序
df_melted['Date'] = pd.to_datetime(df_melted['Date'])
df_melted = df_melted.sort_values(['Currency', 'Date']).reset_index(drop=True)

5.2 问题:图例文字重叠,或者图例框盖住了数据

原因legend()loc 参数没设对。'best' 自动定位经常失效,尤其在数据密集区。

解决方案

  • 优先用 loc='upper left'loc='lower left',然后微调 bbox_to_anchor
  • ncol=3 让图例横向排布(适合三类)
  • frameon=True, fancybox=True, shadow=True 提升可读性
PYTHON
ax.legend(title='Currency',
ncol=3,
loc='upper center',
bbox_to_anchor=(0.5, -0.15), # 向下偏移 15%,放在图下方
frameon=True,
fancybox=True,
shadow=True)

5.3 问题:中文标题/标签显示为方块

原因:Matplotlib 默认字体不支持中文。

终极解决方案(跨平台)

PYTHON
# 在导入 matplotlib 后,立即执行
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
matplotlib.rcParams['axes.unicode_minus'] = False
 
# 如果还在 Mac 上出问题,加这行
import matplotlib.font_manager as fm
fm.fontManager.addfont('/System/Library/Fonts/PingFang.ttc') # Mac 系统字体

5.4 问题:导出的 PNG 在 PPT 里发虚,PDF 打不开

原因dpi 不够,或 PDF 后端不兼容。

解决方案

  • PNG:dpi=300 是底线,dpi=600 更佳(文件变大,但清晰)
  • PDF:用 plt.switch_backend('Agg') 在脚本开头,避免 GUI 后端冲突
  • 万能备份:同时导出 PNG 和 SVG(矢量,浏览器可直接打开)
    PYTHON
    plt.savefig('chart.svg', bbox_inches='tight') # SVG 无需 dpi

5.5 问题:hue 颜色顺序和我想的不一样

原因:Seaborn 按 Currency 列的字典序分配颜色,不是你 value_vars 的顺序。

强制指定顺序

PYTHON
# 先将 Currency 设为 category,并指定顺序
df_melted['Currency'] = pd.Categorical(df_melted['Currency'],
categories=['USD', 'CAD', 'AUD'],
ordered=True)
# 然后 hue 会按此顺序分配颜色
sns.lineplot(..., hue='Currency', palette=['red','blue','green'])

6. 性能与可维护性:当你的数据量暴涨十倍时

上面的教程基于几千行数据。但真实业务中,你可能面对:

  • 每秒采集的物联网传感器数据(百万行/天)
  • 全球 100+ 币种的分钟级汇率(亿级记录)
  • 用户行为日志的漏斗转化率(需要实时聚合)

这时,lineplot 会变慢,甚至内存溢出。我的应对策略:

6.1 数据降采样:用 pandas 的 resample 替代原始数据

PYTHON
# 如果原始数据是分钟级,按小时聚合均值
df_hourly = df_raw.set_index('Date').resample('H').mean().reset_index()
 
# 或按天聚合,取开盘、最高、最低、收盘(OHLC)
df_daily = df_raw.set_index('Date').resample('D').ohlc().reset_index()

6.2 使用 plotly 替代:交互式是大数据的解药

Seaborn 是静态图王者,但面对大数据,交互式图表(缩放、悬停、筛选)体验更好。用 plotly.express 一行迁移:

PYTHON
import plotly.express as px
 
fig = px.line(df_melted,
x='Date',
y='Euro rate',
color='Currency',
title='EUR Exchange Rates')
fig.write_html("eur_rates_interactive.html") # 导出为可交互 HTML

6.3 模块化绘图函数:把重复逻辑封装成工具

把清洗、绘图、导出打包成函数,下次直接调用:

PYTHON
def plot_currency_trend(df, currencies, start_date, end_date, output_prefix):
"""绘制多币种汇率趋势图"""
# 清洗、熔解、过滤...
df_plot = prepare_currency_data(df, currencies, start_date, end_date)
# 绘图
fig, ax = plt.subplots(figsize=(20, 5))
sns.lineplot(data=df_plot, x='Date', y='Euro rate', hue='Currency', ax=ax)
# 保存
plt.savefig(f'{output_prefix}.png', dpi=300, bbox_inches='tight')
return fig
 
# 下次只需:
# plot_currency_trend(raw_df, ['USD','EUR'], '2023-01-01', '2023-12-31', 'usd_eur_2023')

我个人在实际使用中发现,把数据清洗和绘图分离,比写一个“全能脚本”更可靠。因为数据源经常变(列名、格式、编码),而绘图逻辑相对稳定。每次只改清洗部分,绘图函数原封不动,省下大量调试时间。

最后再分享一个小技巧:在 Jupyter 里,给 lineplotestimator=None 参数,可以关闭默认的均值聚合(它会把同一天多个点聚合成一个点)。如果你的数据本就是日频单点,加这个参数能让图更“诚实”——毕竟,我们画的是事实,不是统计模型。