You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Seaborn绘制同时清晰展示极值与常规数据的折线图(消除数据间隙)

如何用Seaborn绘制同时清晰展示极值与常规数据的折线图(消除数据间隙)

我太懂你这个痛点了!当数据集里既有小范围波动的常规数据(比如员工销售额),又有高很多数量级的极值(比如门店总销售额)时,普通折线图要么把常规数据挤成一条几乎看不清的线,要么没法完整展示极值,完全达不到分析的目的。

针对你这种中间存在无数据断层区间的情况,我给你两个实用的解决方案,都能完美兼顾极值展示和常规数据的细节:


方案一:断裂Y轴(推荐!直观展示数据断层)

这种方法把Y轴拆成上下两部分,分别对应常规数据和极值,中间用断裂标记明确提示读者这里有数据间隙,既保留了所有数据的趋势,又能让常规数据的波动清晰可见。

修改后的代码如下:

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

# Sample data
data = {
    'Date': ['2025-01-10', '2025-01-17', '2025-01-24', '2025-01-31'],
    'Bob': [156, 60, 58, 62],
    'Joe': [37, 40, 139, 42],
    'Sally': [62, 265, 63, 67],
    'Total': [3698, 3750, 3720, 3800]
}

# 创建DataFrame并处理格式
df = pd.DataFrame(data)
df_melted = df.melt(id_vars='Date', var_name='Employee', value_name='Sales')
df_melted['Date'] = pd.to_datetime(df_melted['Date'])

# 创建上下两个子图,共享X轴
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
fig.subplots_adjust(hspace=0.05)  # 缩小子图间隙,让整体更连贯

# 下轴:展示员工常规销售额(限定范围在0-350,覆盖你的常规数据区间)
sns.lineplot(x='Date', y='Sales', hue='Employee', 
             data=df_melted[df_melted['Employee'] != 'Total'], 
             marker='o', ax=ax2)
ax2.set_ylim(0, 350)
ax2.set_ylabel('员工销售额')
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{int(x):,}'))

# 上轴:展示总销售额(限定范围在3500-4000,覆盖极值区间)
total_line = sns.lineplot(x='Date', y='Sales', hue='Employee', 
                          data=df_melted[df_melted['Employee'] == 'Total'], 
                          marker='o', ax=ax1, color='red')
ax1.set_ylim(3500, 4000)
ax1.set_ylabel('总销售额')
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{int(x):,}'))
ax1.get_legend().remove()  # 上轴不需要重复图例

# 添加断裂标记(//),明确提示数据断层
d = 0.5  # 调整断裂线的倾斜角度
kwargs = dict(marker=[(-1, -d), (1, d)], markersize=12,
              linestyle="none", color='gray', mec='gray', mew=1, clip_on=False)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)

# 设置整体标题和X轴标签
ax2.set_xlabel('日期')
fig.suptitle('员工销售额与总销售额趋势对比', y=0.95)

# 调整图例位置,避免遮挡数据
ax2.legend(loc='upper right')

plt.show()

方案优势:

  • 完全消除了无数据区间的空白,让常规数据的波动一目了然;
  • 断裂标记清晰传达了数据量级的差异,不会误导读者;
  • 适合你这种有明确数据断层的场景,尤其是后续扩展到多门店数据时,极值和个体数据的对比会更清晰。

方案二:双Y轴(适合观察数据关联)

如果你想在同一个坐标系里同时看常规数据和极值的趋势关联(比如总销售额的变化是否和员工销售波动有关),可以用双Y轴方案,左边对应常规数据,右边对应极值。

代码示例:

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

# Sample data
data = {
    'Date': ['2025-01-10', '2025-01-17', '2025-01-24', '2025-01-31'],
    'Bob': [156, 60, 58, 62],
    'Joe': [37, 40, 139, 42],
    'Sally': [62, 265, 63, 67],
    'Total': [3698, 3750, 3720, 3800]
}

# 数据预处理
df = pd.DataFrame(data)
df_melted = df.melt(id_vars='Date', var_name='Employee', value_name='Sales')
df_melted['Date'] = pd.to_datetime(df_melted['Date'])

# 创建主图和双Y轴
fig, ax1 = plt.subplots(figsize=(12, 8))

# 左Y轴:绘制员工常规销售额
sns.lineplot(x='Date', y='Sales', hue='Employee', 
             data=df_melted[df_melted['Employee'] != 'Total'], 
             marker='o', ax=ax1)
ax1.set_ylabel('员工销售额')
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{int(x):,}'))
ax1.set_xlabel('日期')

# 右Y轴:绘制总销售额
ax2 = ax1.twinx()
sns.lineplot(x='Date', y='Sales', hue='Employee', 
             data=df_melted[df_melted['Employee'] == 'Total'], 
             marker='o', ax=ax2, color='red', legend=False)
ax2.set_ylabel('总销售额')
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{int(x):,}'))

# 设置标题和图例
plt.title('员工销售额与总销售额趋势对比')
ax1.legend(loc='upper left')

plt.show()

方案优势:

  • 可以在同一个视图里观察两个数据系列的趋势关联;
  • 不需要拆分图表,整体视觉更统一。

针对大规模数据集的小技巧

当你扩展到上百个员工、多门店的数据集时,可以试试这些优化:

  • 给极值数据(比如门店总销售额)设置独特的高亮颜色,方便快速识别;
  • 动态计算Y轴范围:比如用df_melted[df_melted['Employee'] != 'Total']['Sales'].max() * 1.1作为常规数据轴的上限,适配不同周的波动;
  • 如果图例过于拥挤,可以把图例移到图表外部,或者只显示关键系列。

备注:内容来源于stack exchange,提问作者Ryan Barnes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 13:12:57