
引言
在数据分析和可视化中,我们经常需要突出图表中的特定时间段或数据区域,以标记事件、异常值或重要阶段。matplotlib提供了强大的绘图功能,其中axvspan函数是实现垂直区域着色的关键工具。本教程将指导您如何根据一个独立的事件序列(例如,一个表示事件发生与否的二进制序列)来动态地为图表的不同部分着色,特别地,我们将实现事件发生前、发生中和发生后三个阶段的不同颜色标记。
核心概念:axvspan函数
matplotlib.axes.Axes.axvspan(xmin, xmax, ymin=0, ymax=1, **kwargs) 函数用于在坐标轴上绘制一个垂直的矩形区域。
- xmin, xmax: 定义了矩形区域的水平范围。
- ymin, ymax: 定义了矩形区域的垂直范围,默认是0到1,表示覆盖整个y轴范围。
- facecolor: 矩形区域的填充颜色。
- alpha: 矩形区域的透明度,值介于0(完全透明)和1(完全不透明)之间。
通过多次调用axvspan并指定不同的xmin、xmax和facecolor,我们可以创建多个自定义着色区域。
准备数据
首先,我们需要模拟一些数据,包括主数据系列和用于触发着色事件的事件序列。事件序列通常是一个二进制值,例如0表示无事件,1表示事件发生。
import numpy as np import matplotlib.pyplot as plt import pandas as pd # 设置随机种子以便结果可复现 np.random.seed(42) # 生成事件数据,初始为0 data_length = 56 event = pd.DataFrame(np.zeros(data_length, dtype=int), columns=['event_status']) # 模拟事件发生 # 事件1:从索引10到13(即[10:14]) event.iloc[10:14, 0] = 1 # 事件2:从索引24到35(即[24:36]) event.iloc[24:36, 0] = 1 # 生成主图表数据 data_series_1 = pd.DataFrame(np.random.randint(200, 300, size=(data_length, 1)), columns=['Series1']) data_series_2 = pd.DataFrame(np.random.randint(0, 3, size=(data_length, 1)), columns=['Series2']) data_series_3 = pd.DataFrame(np.random.randint(300, 400, size=(data_length, 1)), columns=['Series3']) data_series_4 = pd.DataFrame(np.random.randint(0, 5, size=(data_length, 1)), columns=['Series4'])
识别事件周期
为了实现精确着色,我们需要从事件序列中识别出所有连续的事件发生周期(即event值为1的连续区间)。
def find_event_periods(event_series):
"""
识别事件序列中值为1的连续周期。
返回一个列表,每个元素是一个元组 (start_index, end_index),
其中end_index是该周期的结束索引(不包含)。
"""
event_periods = []
in_event = False
start_idx = -1
for i in range(len(event_series)):
if event_series.iloc[i] == 1 and not in_event:
start_idx = i
in_event = True
elif event_series.iloc[i] == 0 and in_event:
event_periods.append((start_idx, i))
in_event = False
# 处理事件持续到序列末尾的情况
if in_event:
event_periods.append((start_idx, len(event_series)))
return event_periods
event_periods = find_event_periods(event['event_status'])
print(f"识别到的事件周期: {event_periods}")输出示例:识别到的事件周期: [(10, 14), (24, 36)]
实现区域着色逻辑
现在,我们将根据识别到的事件周期,为每个周期定义三个着色区域:
- 事件前区域 (Pre-event): 事件开始前一个索引到事件开始的区域。
- 事件中区域 (During-event): 事件发生期间的区域。
- 事件后区域 (Post-event): 事件结束后两个索引的区域。
我们将定义这些区域的颜色和透明度。
# 定义着色方案 color_pre_event = 'skyblue' # 事件前区域颜色 color_during_event = 'lightcoral' # 事件中区域颜色 color_post_event = 'lightgreen' # 事件后区域颜色 alpha_level = 0.2 # 透明度
完整代码示例
以下是整合了数据生成、事件识别和区域着色逻辑的完整Matplotlib绘图代码。它将使用原始问题中定义的双轴子图结构。
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# --- 1. 数据准备 ---
np.random.seed(42) # 确保结果可复现
data_length = 56
event = pd.DataFrame(np.zeros(data_length, dtype=int), columns=['event_status'])
event.iloc[10:14, 0] = 1 # 事件1
event.iloc[24:36, 0] = 1 # 事件2
# 主图表数据
data_series_1 = pd.DataFrame(np.random.randint(200, 300, size=(data_length, 1)), columns=['Series1'])
data_series_2 = pd.DataFrame(np.random.randint(0, 3, size=(data_length, 1)), columns=['Series2'])
data_series_3 = pd.DataFrame(np.random.randint(300, 400, size=(data_length, 1)), columns=['Series3'])
data_series_4 = pd.DataFrame(np.random.randint(0, 5, size=(data_length, 1)), columns=['Series4'])
# --- 2. 事件周期识别函数 ---
def find_event_periods(event_series):
event_periods = []
in_event = False
start_idx = -1
for i in range(len(event_series)):
if event_series.iloc[i] == 1 and not in_event:
start_idx = i
in_event = True
elif event_series.iloc[i] == 0 and in_event:
event_periods.append((start_idx, i))
in_event = False
if in_event:
event_periods.append((start_idx, len(event_series)))
return event_periods
event_periods = find_event_periods(event['event_status'])
# --- 3. 定义着色方案 ---
color_pre_event = 'blue' # 事件前区域颜色
color_during_event = 'red' # 事件中区域颜色
color_post_event = 'green' # 事件后区域颜色
alpha_level = 0.2 # 透明度
# --- 4. 绘图部分 ---
plt.figure(figsize=(18, 8)) # 调整图表大小以适应内容和布局
# 第一个子图 (2行2列的第1个)
ax1 = plt.subplot(1, 2, 1) # 调整为1行2列,便于展示
ax2 = ax1.twinx() # 创建第二个y轴
# 绘制主数据系列
ax1.plot(data_series_1, label='Series 1', color='g')
ax1.plot(data_series_2, label='Series 2', color='r')
ax2.plot(event, label='Event Status', color='k', linestyle='--', linewidth=1) # 事件状态曲线
# 应用区域着色
for start, end in event_periods:
# 事件前区域: 从 max(0, start-1) 到 start
pre_event_xmin = max(0, start - 1)
pre_event_xmax = start
if pre_event_xmin < pre_event_xmax: # 确保区域有效
ax1.axvspan(pre_event_xmin, pre_event_xmax, facecolor=color_pre_event, alpha=alpha_level, label='Pre-Event' if start == event_periods[0][0] else "")
# 事件中区域: 从 start 到 end
ax1.axvspan(start, end, facecolor=color_during_event, alpha=alpha_level, label='During-Event' if start == event_periods[0][0] else "")
# 事件后区域: 从 end 到 min(data_length, end+2)
post_event_xmin = end
post_event_xmax = min(data_length, end + 2)
if post_event_xmin < post_event_xmax: # 确保区域有效
ax1.axvspan(post_event_xmin, post_event_xmax, facecolor=color_post_event, alpha=alpha_level, label='Post-Event' if start == event_periods[0][0] else "")
# 设置标签和标题
ax1.set_ylabel('Value (m)', fontsize=12)
ax2.set_ylabel('Event Status (t)', color='k', fontsize=12)
ax1.set_title('图表 0: 事件驱动背景着色示例', fontsize=14)
ax1.tick_params(axis='y', labelsize=10)
ax1.tick_params(axis='x', labelsize=10)
ax2.tick_params(axis='y', labelsize=10)
# 合并图例,避免重复标签
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# 过滤掉axvspan的重复标签,只保留第一次出现的
unique_labels = {}
for line, label in zip(lines + lines2, labels + labels2):
if label not in unique_labels:
unique_labels[label] = line
ax1.legend(unique_labels.values(), unique_labels.keys(), loc='upper left', prop={'size': 10})
# 第二个子图 (2行2列的第2个) - 结构与第一个类似,但使用不同的数据
ax3 = plt.subplot(1, 2, 2)
ax4 = ax3.twinx()
ax3.plot(data_series_3, label='Series 3', color='purple')
ax3.plot(data_series_4, label='Series 4', color='orange')
ax4.plot(event, label='Event Status', color='k', linestyle='--', linewidth=1)
# 应用区域着色 (与第一个子图逻辑相同)
for start, end in event_periods:
pre_event_xmin = max(0, start - 1)
pre_event_xmax = start
if pre_event_xmin < pre_event_xmax:
ax3.axvspan(pre_event_xmin, pre_event_xmax, facecolor=color_pre_event, alpha=alpha_level)
ax3.axvspan(start, end, facecolor=color_during_event, alpha=alpha_level)
post_event_xmin = end
post_event_xmax = min(data_length, end + 2)
if post_event_xmin < post_event_xmax:
ax3.axvspan(post_event_xmin, post_event_xmax, facecolor=color_post_event, alpha=alpha_level)
ax3.set_ylabel('Value (m)', fontsize=12)
ax4.set_ylabel('Event Status (t)', color='k', fontsize=12)
ax3.set_title('图表 1: 事件驱动背景着色示例', fontsize=14)
ax3.tick_params(axis='y', labelsize=10)
ax3.tick_params(axis='x', labelsize=10)
ax4.tick_params(axis='y', labelsize=10)
# 合并图例
lines, labels = ax3.get_legend_handles_labels()
lines2, labels2 = ax4.get_legend_handles_labels()
unique_labels = {}
for line, label in zip(lines + lines2, labels + labels2):
if label not in unique_labels:
unique_labels[label] = line
ax3.legend(unique_labels.values(), unique_labels.keys(), loc='upper left', prop={'size': 10})
plt.tight_layout() # 自动调整子图参数,使之填充整个图像区域
plt.show()代码解析与注意事项
-
数据生成与事件定义:
- event DataFrame用于存储事件状态,其中0表示无事件,1表示事件发生。
- iloc用于精确设置事件发生的区间。
-
find_event_periods函数:
- 此函数通过遍历event_series来识别连续的1值块。它返回一个元组列表,每个元组(start, end)表示一个事件周期的起始和结束索引(end是排他性的)。
- 这种方法比简单地查找1的索引更健壮,因为它能正确处理连续的事件块。
-
着色方案定义:
- color_pre_event, color_during_event, color_post_event定义了三个阶段的颜色。
- alpha_level控制着色区域的透明度,通常设置为0.1到0.3之间,以便背景色不遮盖主要数据线。
-
axvspan调用:
- 对于每个识别到的事件周期(start, end),我们计算并调用三次axvspan:
- 事件前: xmin为max(0, start - 1),xmax为start。max(0, ...)确保区域不会超出图表左边界。
- 事件中: xmin为start,xmax为end。
- 事件后: xmin为end,xmax为`min(data_length, end +
- 对于每个识别到的事件周期(start, end),我们计算并调用三次axvspan:











