【Python实战】Matplotlib时间序列绘图:从数据清洗到刻度自定义全解析

【Python实战】Matplotlib时间序列绘图:从数据清洗到刻度自定义全解析
1. 时间序列数据可视化入门时间序列数据可视化是数据分析中最常见的需求之一。无论是监控系统日志、分析销售趋势还是研究气象变化我们都需要将时间相关的数据直观地呈现出来。Python中的Matplotlib库提供了强大的时间序列绘图功能但很多新手在使用时会遇到各种问题。我刚开始接触时间序列绘图时经常被横坐标显示问题困扰。要么所有日期挤在一起看不清要么显示格式不是我想要的。后来经过多次实践终于掌握了从数据清洗到图表定制的完整流程。2. 数据准备与时间格式转换2.1 原始时间数据的常见问题我们拿到的原始数据通常存在各种问题时间格式不统一2023-01-01、01/01/2023、Jan 1 2023混用时区不一致时间戳精度不同有的精确到秒有的到毫秒存在缺失值或异常值假设我们有一组服务器日志数据前几条记录是这样的raw_times [ 2023-05-01 08:15:23, 2023-05-01 08:30:45, 2023-05-01 08:47:12, 2023-05-01 09:05:33, 2023-05-01 09:22:01 ]2.2 将字符串转换为datetime对象Matplotlib无法直接使用字符串格式的时间数据我们需要先转换为datetime对象from datetime import datetime # 标准转换方法 datetime_objs [datetime.strptime(t, %Y-%m-%d %H:%M:%S) for t in raw_times] # 处理格式不一致的情况 def safe_parse(time_str): try: return datetime.strptime(time_str, %Y-%m-%d %H:%M:%S) except ValueError: try: return datetime.strptime(time_str, %m/%d/%Y %H:%M) except ValueError: return None # 或者用默认值填充 cleaned_times [t for t in (safe_parse(s) for s in raw_times) if t is not None]2.3 处理时区和缺失值对于跨时区数据建议统一转换为UTC时间from pytz import timezone local_tz timezone(Asia/Shanghai) utc_tz timezone(UTC) local_time datetime.strptime(2023-05-01 12:00:00, %Y-%m-%d %H:%M:%S) local_time local_tz.localize(local_time) utc_time local_time.astimezone(utc_tz)3. 基础时间序列图绘制3.1 最简单的折线图有了datetime对象后我们可以绘制基本的时间序列图import matplotlib.pyplot as plt # 示例数据 times [datetime(2023,5,1,8,15), datetime(2023,5,1,9,30), datetime(2023,5,1,10,45)] values [15, 23, 7] plt.figure(figsize(10,4)) plt.plot(times, values) plt.title(简单时间序列图) plt.xlabel(时间) plt.ylabel(数值) plt.grid(True) plt.show()3.2 处理大量数据点当数据点很多时直接绘制会导致x轴标签重叠import numpy as np # 生成大量数据点 dates [datetime(2023,5,1) timedelta(hoursi) for i in range(72)] values np.random.randn(72).cumsum() plt.figure(figsize(12,4)) plt.plot(dates, values) plt.title(密集时间序列数据) plt.show()这时我们需要对x轴刻度进行优化。4. 高级时间轴定制4.1 使用DateFormatter控制显示格式Matplotlib提供了DateFormatter来精确控制时间显示格式from matplotlib.dates import DateFormatter fig, ax plt.subplots(figsize(12,4)) ax.plot(dates, values) # 设置日期格式 date_format DateFormatter(%m-%d %H:%M) ax.xaxis.set_major_formatter(date_format) # 自动旋转标签 fig.autofmt_xdate() plt.title(自定义日期格式) plt.show()4.2 使用AutoDateLocator控制刻度间隔AutoDateLocator可以自动选择合适的时间间隔from matplotlib.dates import AutoDateLocator fig, ax plt.subplots(figsize(12,4)) ax.plot(dates, values) ax.xaxis.set_major_locator(AutoDateLocator()) ax.xaxis.set_major_formatter(DateFormatter(%Y-%m-%d)) fig.autofmt_xdate() plt.title(自动刻度间隔) plt.show()4.3 精确控制刻度间隔如果需要精确控制每6小时显示一个刻度from matplotlib.dates import HourLocator fig, ax plt.subplots(figsize(12,4)) ax.plot(dates, values) ax.xaxis.set_major_locator(HourLocator(interval6)) ax.xaxis.set_major_formatter(DateFormatter(%m-%d %H:%M)) fig.autofmt_xdate() plt.title(精确控制刻度间隔) plt.show()5. 实用技巧与常见问题解决5.1 处理非均匀时间序列实际数据经常存在时间间隔不均匀的情况irregular_times [ datetime(2023,5,1,8,0), datetime(2023,5,1,8,30), datetime(2023,5,1,9,15), datetime(2023,5,1,10,0), datetime(2023,5,1,11,30) ] irregular_values [10, 15, 8, 12, 9] plt.figure(figsize(10,4)) plt.plot(irregular_times, irregular_values, o-) plt.title(非均匀时间序列) plt.show()5.2 绘制带标记的事件线在时间序列上标记重要事件fig, ax plt.subplots(figsize(12,4)) ax.plot(dates, values) # 添加事件标记 events { datetime(2023,5,1,12,0): 系统更新, datetime(2023,5,1,18,0): 数据备份 } for event_time, label in events.items(): ax.axvline(event_time, colorr, linestyle--, alpha0.7) ax.text(event_time, max(values)*0.9, label, rotation90, vatop) plt.title(带事件标记的时间序列) plt.show()5.3 处理大数据集当数据量很大时可以采样或聚合显示# 生成一年的每日数据 big_dates [datetime(2023,1,1) timedelta(daysi) for i in range(365)] big_values np.random.randn(365).cumsum() fig, ax plt.subplots(figsize(12,4)) ax.plot(big_dates, big_values) # 按月显示刻度 from matplotlib.dates import MonthLocator ax.xaxis.set_major_locator(MonthLocator()) ax.xaxis.set_major_formatter(DateFormatter(%Y-%m)) fig.autofmt_xdate() plt.title(大数据集按月聚合显示) plt.show()6. 出版级图表美化6.1 添加参考线和区域fig, ax plt.subplots(figsize(12,5)) ax.plot(dates, values) # 添加参考线 ax.axhline(0, colorgray, linestyle:, alpha0.5) # 高亮特定时间段 start datetime(2023,5,1,12,0) end datetime(2023,5,1,18,0) ax.axvspan(start, end, coloryellow, alpha0.3) plt.title(美化后的时间序列图) plt.show()6.2 多时间序列对比values2 np.random.randn(72).cumsum() 5 fig, ax plt.subplots(figsize(12,5)) ax.plot(dates, values, label序列A) ax.plot(dates, values2, label序列B) ax.xaxis.set_major_locator(AutoDateLocator()) ax.xaxis.set_major_formatter(DateFormatter(%m-%d)) fig.autofmt_xdate() plt.legend() plt.title(多时间序列对比) plt.show()6.3 保存高质量图表fig, ax plt.subplots(figsize(12,5), dpi300) ax.plot(dates, values) # 设置样式 ax.grid(True, linestyle:, alpha0.7) ax.set_title(高质量输出示例, pad20) fig.savefig(high_quality_plot.png, bbox_inchestight, pad_inches0.3, transparentFalse)7. 实际案例分析7.1 服务器负载监控假设我们有一组服务器CPU负载数据server_times [datetime(2023,5,1,i//2,30*(i%2)) for i in range(48)] cpu_load [10 5*np.sin(i/3) np.random.rand()*3 for i in range(48)] fig, ax plt.subplots(figsize(12,4)) ax.plot(server_times, cpu_load, b-, linewidth1.5, markero, markersize4) # 设置警告线 ax.axhline(15, colorred, linestyle--, label警告阈值) ax.xaxis.set_major_locator(HourLocator(interval4)) ax.xaxis.set_major_formatter(DateFormatter(%H:%M)) fig.autofmt_xdate() plt.ylim(0,25) plt.title(24小时服务器CPU负载监控) plt.ylabel(CPU使用率(%)) plt.legend() plt.grid(True, alpha0.3) plt.show()7.2 销售数据可视化# 生成季度销售数据 quarters [datetime(2022,3,31), datetime(2022,6,30), datetime(2022,9,30), datetime(2022,12,31), datetime(2023,3,31)] sales [120, 145, 160, 210, 195] fig, ax plt.subplots(figsize(10,5)) bars ax.bar(quarters, sales, width80) # 添加数据标签 for bar in bars: height bar.get_height() ax.text(bar.get_x() bar.get_width()/2., height, f{height:.0f}, hacenter, vabottom) ax.xaxis.set_major_formatter(DateFormatter(%Y-Q%q)) plt.title(季度销售数据) plt.ylabel(销售额(万元)) plt.grid(True, axisy, alpha0.3) plt.show()8. 性能优化技巧8.1 大数据集绘制优化当处理数十万点的时间序列数据时直接绘制会很慢。可以尝试# 生成大数据集 big_dates [datetime(2023,1,1) timedelta(secondsi) for i in range(86400)] # 1天的秒级数据 big_values [np.sin(i/1000) np.random.rand()*0.1 for i in range(86400)] # 优化绘制方法 fig, ax plt.subplots(figsize(12,4)) # 方法1降低采样率 ax.plot(big_dates[::100], big_values[::100], alpha0.7) # 每100个点取一个 # 方法2使用线条简化算法 from matplotlib.path import Path from matplotlib.transforms import Bbox path Path(list(zip(big_dates, big_values))) simplified path.cleaned(simplifyTrue) # 自动简化路径 plt.title(大数据集优化绘制) plt.show()8.2 使用Pandas集成Pandas与Matplotlib集成良好可以简化时间序列处理import pandas as pd # 创建时间序列DataFrame df pd.DataFrame({ value: values, value2: values2 }, indexdates) # 直接绘制 fig, ax plt.subplots(figsize(12,4)) df.plot(axax, style[-o,--s]) ax.xaxis.set_major_formatter(DateFormatter(%m-%d)) fig.autofmt_xdate() plt.title(Pandas时间序列绘图) plt.show()9. 交互式时间序列图虽然Matplotlib主要是静态绘图库但也可以实现简单交互from matplotlib.widgets import Slider fig, ax plt.subplots(figsize(12,5)) plt.subplots_adjust(bottom0.25) # 为滑块留出空间 # 绘制初始数据 line, ax.plot(dates[:24], values[:24]) # 添加滑块 ax_slider plt.axes([0.2, 0.1, 0.6, 0.03]) slider Slider(ax_slider, 小时, 0, 48, valinit24, valstep1) def update(val): hours int(slider.val) line.set_xdata(dates[:hours]) line.set_ydata(values[:hours]) fig.canvas.draw_idle() slider.on_changed(update) plt.title(交互式时间范围选择) plt.show()10. 常见问题解决方案10.1 中文显示问题Matplotlib默认不支持中文需要特殊设置plt.rcParams[font.sans-serif] [SimHei] # 设置中文字体 plt.rcParams[axes.unicode_minus] False # 解决负号显示问题 plt.figure(figsize(10,4)) plt.plot(dates[:10], values[:10]) plt.title(中文标题示例) plt.xlabel(时间轴) plt.ylabel(数值) plt.show()10.2 时间轴范围控制精确控制显示的时间范围fig, ax plt.subplots(figsize(10,4)) ax.plot(dates, values) # 设置x轴范围 start datetime(2023,5,1,12,0) end datetime(2023,5,1,20,0) ax.set_xlim(start, end) # 设置y轴范围 ax.set_ylim(min(values)-5, max(values)5) plt.title(精确控制坐标轴范围) plt.show()10.3 处理时区转换如果需要显示特定时区的时间import pytz # 创建带时区的时间对象 utc_times [pytz.utc.localize(dt) for dt in dates] shanghai_tz pytz.timezone(Asia/Shanghai) local_times [utc_dt.astimezone(shanghai_tz) for utc_dt in utc_times] fig, ax plt.subplots(figsize(12,4)) ax.plot(local_times, values) ax.xaxis.set_major_formatter(DateFormatter(%H:%M, tzshanghai_tz)) plt.title(上海时区时间显示) plt.show()11. 高级应用金融时间序列金融数据可视化有特殊需求如蜡烛图import mplfinance as mpf # 需要安装mplfinance库 # 准备金融数据 dates pd.date_range(2023-05-01, periods30, freqD) opens np.random.normal(100, 2, 30) highs opens np.random.random(30)*3 lows opens - np.random.random(30)*3 closes opens np.random.randn(30) data pd.DataFrame({ Open: opens, High: highs, Low: lows, Close: closes }, indexdates) # 绘制蜡烛图 mpf.plot(data, typecandle, stylecharles, title股票价格走势, ylabel价格, volumeFalse, figratio(12,4))12. 时间序列动画创建时间序列动画可以更好展示数据变化from matplotlib.animation import FuncAnimation fig, ax plt.subplots(figsize(10,4)) line, ax.plot([], [], b-) ax.set_xlim(dates[0], dates[-1]) ax.set_ylim(min(values)-2, max(values)2) def animate(i): line.set_data(dates[:i], values[:i]) return line, ani FuncAnimation(fig, animate, frameslen(dates), interval100, blitTrue) plt.title(时间序列动画) plt.show()13. 多子图时间序列比较多个相关时间序列fig, (ax1, ax2) plt.subplots(2, 1, figsize(12,6), sharexTrue) ax1.plot(dates, values, b-) ax1.set_title(温度变化) ax1.set_ylabel(℃) ax2.plot(dates, np.array(values)*1.832, r-) # 转换为华氏度 ax2.set_title(温度变化(华氏度)) ax2.set_ylabel(℉) fig.autofmt_xdate() plt.tight_layout() plt.show()14. 季节性分析时间序列的季节性分析# 生成带季节性的数据 years 3 dates [datetime(2020,1,1) timedelta(daysi) for i in range(365*years)] values [10*np.sin(2*np.pi*i/365) 5*np.sin(2*np.pi*i/30) np.random.randn() for i in range(365*years)] fig, ax plt.subplots(figsize(12,5)) ax.plot(dates, values) # 添加季节性标记 for year in range(2020, 2023): ax.axvline(datetime(year,1,1), colorgray, linestyle--, alpha0.5) ax.text(datetime(year,1,15), max(values)*0.9, f{year}年, haleft) plt.title(多年季节性数据) plt.show()15. 时间序列预测可视化展示预测结果与实际对比# 生成训练和预测数据 train_dates dates[:300] train_values values[:300] test_dates dates[300:] test_values values[300:] predicted [v*0.95 np.random.randn()*0.5 for v in test_values] # 模拟预测结果 fig, ax plt.subplots(figsize(12,5)) ax.plot(train_dates, train_values, b-, label训练数据) ax.plot(test_dates, test_values, g-, label实际值) ax.plot(test_dates, predicted, r--, label预测值) ax.axvline(test_dates[0], colork, linestyle:, alpha0.5) ax.text(test_dates[0], min(values), 预测起点, haright) plt.legend() plt.title(时间序列预测结果) plt.show()