自定义非线性Date X轴绘制能耗时序数据的实现求助
自定义时序X轴(按日/周/月粒度分层)解决方案
需求说明
我需要绘制从过去到当前的水、电、气能耗时序数据,且需突出近期数据的重要性,X轴需按如下规则展示:
- 当日至回溯1周:按日粒度
- 回溯1周至3周:按周粒度
- 更早数据:按月粒度
X轴刻度需大致为2022-05 2022-06 2022-07 2022-07-25 2022-08-01 2022-08-08 2022-08-09 2022-08-10 2022-08-11且等间距分布,以便清晰查看昨日、上周及过往整体情况。但在实现forward和reverse函数及返回值时遇到瓶颈,附上已有代码求示例参考:
已有代码
import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates def forward(x): return x def reverse(x): return x rng = pd.date_range(end='2022-08-14', periods=180, freq='D') df = pd.DataFrame({'Date': rng, 'Val': np.random.randn(len(rng)) }) df.set_index("Date",inplace=True) ax=df.plot() plt.gca().set_xscale('function', functions=(forward, reverse)) ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) plt.xticks(rotation = 30) plt.show()
解决方案代码
import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates from datetime import timedelta # 定义关键时间节点 current_date = pd.to_datetime('2022-08-14') one_week_ago = current_date - timedelta(weeks=1) three_weeks_ago = current_date - timedelta(weeks=3) def forward(x): """将日期转换为非线性映射后的数值,放大近期数据的X轴占比""" # 将matplotlib日期数值转为pandas datetime对象 x_dates = pd.to_datetime(mdates.num2date(x)) # 划分三个时间区间的掩码 mask_recent = x_dates >= one_week_ago mask_mid = (x_dates < one_week_ago) & (x_dates >= three_weeks_ago) mask_old = x_dates < three_weeks_ago # 初始化映射结果数组 mapped = np.zeros_like(x, dtype=np.float64) # 近期(1周内):分配X轴后40%空间,按日线性映射 recent_days = (x_dates[mask_recent] - one_week_ago).days total_recent_days = (current_date - one_week_ago).days mapped[mask_recent] = 0.6 + (recent_days / total_recent_days) * 0.4 # 中期(1-3周):分配X轴20%空间,按周线性映射 mid_weeks = (one_week_ago - x_dates[mask_mid]).days // 7 total_mid_weeks = 2 # 共2周区间 mapped[mask_mid] = 0.4 + (mid_weeks / total_mid_weeks) * 0.2 # 早期(3周前):分配X轴前40%空间,按月线性映射 old_months = (three_weeks_ago - x_dates[mask_old]).days // 30 total_old_months = (three_weeks_ago - (current_date - timedelta(days=180))).days // 30 mapped[mask_old] = (old_months / total_old_months) * 0.4 return mapped def reverse(x): """将映射后的数值逆向转换为原始日期""" # 划分映射值对应的三个区间掩码 mask_recent = x >= 0.6 mask_mid = (x >= 0.4) & (x < 0.6) mask_old = x < 0.4 # 初始化日期数值数组 reversed_dates = np.zeros_like(x, dtype=np.float64) # 近期逆向转换 recent_ratio = (x[mask_recent] - 0.6) / 0.4 recent_days = recent_ratio * (current_date - one_week_ago).days reversed_dates[mask_recent] = mdates.date2num(one_week_ago + timedelta(days=recent_days)) # 中期逆向转换 mid_ratio = (x[mask_mid] - 0.4) / 0.2 mid_weeks = mid_ratio * 2 reversed_dates[mask_mid] = mdates.date2num(one_week_ago - timedelta(weeks=mid_weeks)) # 早期逆向转换 old_ratio = x[mask_old] / 0.4 total_old_months = (three_weeks_ago - (current_date - timedelta(days=180))).days // 30 old_days = old_ratio * total_old_months * 30 reversed_dates[mask_old] = mdates.date2num(three_weeks_ago - timedelta(days=old_days)) return reversed_dates # 生成测试时序数据 rng = pd.date_range(end='2022-08-14', periods=180, freq='D') df = pd.DataFrame({'Date': rng, 'Val': np.random.randn(len(rng))}) df.set_index("Date", inplace=True) # 绘图配置 fig, ax = plt.subplots(figsize=(12, 6)) df.plot(ax=ax) # 设置自定义非线性X轴缩放 ax.set_xscale('function', functions=(forward, reverse)) # 手动设置目标刻度点,确保符合需求的粒度和间距 target_ticks = [ '2022-05', '2022-06', '2022-07', '2022-07-25', '2022-08-01', '2022-08-08', '2022-08-09', '2022-08-10', '2022-08-11' ] target_dates = pd.to_datetime(target_ticks) ax.set_xticks([mdates.date2num(d) for d in target_dates]) # 配置日期格式化,自动适配不同粒度显示 ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) plt.xticks(rotation=45) plt.tight_layout() plt.show()
关键说明
- 非线性映射:
forward函数将日期分为三个区间,给近期数据分配最多X轴空间,实现近期数据的视觉突出;reverse函数完成逆向转换,保证坐标轴交互正常。 - 刻度控制:手动指定目标刻度点,确保X轴严格按需求的粒度和等间距展示,同时通过日期格式化器自动适配显示格式。
内容的提问来源于stack exchange,提问作者Falk
相关产品推荐
相关产品推荐

