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

如何在ggplot2中正确填充geom_line与geom_step之间的区域?

动态血压监测数据可视化:阶梯线间区域正确填充问题

问题描述

尝试复现动态血压监测(ABPM)常用可视化图表,包含阶梯线及线间填充区域。已成功构建阶梯线,但使用stat_difference()填充阶梯线上方区域时无法保留阶梯结构,效果不符合预期。

最小可复现代码

data <- structure(list(Date = structure(c(1750165200, 1750166100, 1750167000, 
1750167900, 1750168800, 1750169700, 1750170600, 1750171500, 1750172400, 
1750173300, 1750174200, 1750175100, 1750176000, 1750176900, 1750177800, 
1750178700, 1750179600, 1750180500, 1750181400, 1750182300, 1750183200, 
1750184100, 1750185000, 1750185900, 1750186800, 1750187700, 1750188600, 
1750189500, 1750190400, 1750191300, 1750192200, 1750193100, 1750194000, 
1750194900, 1750195800, 1750196700, 1750197600, 1750198500, 1750199400, 
1750200300, 1750201200, 1750202100, 1750203000, 1750203900, 1750204800, 
1750205700, 1750206600, 1750207500, 1750208400, 1750209300, 1750210200, 
1750211100, 1750212000, 1750213800, 1750215600, 1750217400, 1750219200, 
1750221000, 1750222800, 1750224600, 1750226400, 1750228200, 1750230000, 
1750231800, 1750233600, 1750235400, 1750237200, 1750238100, 1750239000, 
1750239900, 1750240800, 1750241700, 1750242600, 1750243500, 1750244400, 
1750245300, 1750246200, 1750247100, 1750248000, 1750248900, 1750249800, 
1750250700), class = c("POSIXct", "POSIXt"), tzone = ""), SBP_Patient = c(154, 
150, 130, 145, 136, 140, 145, 148, 152, 140, 160, 150, 150, 145, 
139, 120, 125, 132, 145, 135, 150, 157, 154, 146, 145, 165, 170, 
120, 115, 168, 150, 148, 162, 146, 154, 167, 170, 145, 152, 148, 
152, 149, 145, 140, 152, 140, 164, 150, 145, 130, 120, 110, 112, 
110, 124, 128, 100, 90, 98, 100, 110, 105, 95, 105, 115, 119, 
130, 125, 130, 142, 140, 140, 142, 146, 145, 140, 145, 132, 145, 
150, 152, 160), DBP_Patient = c(95, 90, 98, 89, 95, 90, 95, 86, 
85, 90, 100, 85, 89, 82, 85, 80, 80, 87, 85, 83, 85, 90, 95, 
95, 85, 95, 105, 90, 80, 95, 90, 92, 98, 85, 85, 95, 98, 90, 
95, 95, 97, 89, 80, 82, 82, 80, 98, 95, 82, 80, 74, 70, 70, 60, 
68, 80, 70, 65, 70, 75, 80, 70, 62, 75, 75, 80, 80, 75, 80, 80, 
98, 89, 82, 84, 80, 85, 83, 80, 89, 90, 95, 95), SBP_Threshold = c(135, 
135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 
135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 
135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 
135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 
135, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 
135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 
135, 135, 135), DBP_Threshold = c(85, 85, 85, 85, 85, 85, 85, 
85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 
85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 
85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 70, 
70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 85, 85, 85, 85, 
85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85), Period = structure(c(1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L), levels = c("Wake", "Sleep"), class = "factor")), row.names = c(NA, 
-82L), class = c("tbl_df", "tbl", "data.frame"))

ggplot(data, aes(x = Date)) +
  ggh4x::stat_difference(aes(ymin = SBP_Threshold,
                             ymax = SBP_Patient), alpha = 0.5,
                         levels = c("SBP Above", "SBP Normal"),
                         show.legend = F) +
  ggh4x::stat_difference(aes(ymin = DBP_Threshold,
                             ymax = DBP_Patient), alpha = 0.5,
                         levels = c("DBP Above", "DBP Normal"),
                         show.legend = F) +
  geom_step(aes(y = SBP_Threshold, group = 1), color = "grey50") +
  geom_step(aes(y = DBP_Threshold, group = 1), color = "grey50") +
  geom_line(aes(y = SBP_Patient, group = 1, color = "Systolic")) +
  geom_line(aes(y = DBP_Patient, group = 1, color = "Diastolic")) +
  scale_color_manual(values = c("Systolic" = "red",
                                "Diastolic" = "blue3")) +
  scale_fill_manual(values = c("SBP Above" = "red",
                               "SBP Normal" = "transparent",
                               "DBP Above" = "blue3",
                               "DBP Normal" = "transparent")) +
  scale_x_datetime(date_breaks = "1 hour", date_labels = "%H") +
  labs(y = "Arterial blood pressure (mmHg)", x = "Hour", color = NULL) +
  theme_bw()

解决方案

stat_difference()默认使用线性插值生成填充区域,无法匹配geom_step()的阶梯结构。需要手动构造阶梯形状的阈值数据,再结合geom_ribbon()实现正确填充:

步骤1:扩展数据为阶梯格式

首先生成包含阶梯节点的数据集,每个阈值会保持到下一个时间点:

library(dplyr)
library(tidyr)

# 扩展阈值数据,生成阶梯节点
step_data <- data %>%
  select(Date, SBP_Threshold, DBP_Threshold) %>%
  mutate(
    next_Date = lead(Date),
    # 复制当前行的阈值到下一个时间点
    SBP_Threshold_step = SBP_Threshold,
    DBP_Threshold_step = DBP_Threshold
  ) %>%
  # 合并原行和扩展的阶梯行
  pivot_longer(cols = c(Date, next_Date), names_to = "type", values_to = "Date_step") %>%
  filter(!is.na(Date_step)) %>%
  select(Date_step, SBP_Threshold_step, DBP_Threshold_step) %>%
  arrange(Date_step)

# 合并患者数据与阶梯阈值数据,按时间匹配
filled_data <- data %>%
  select(Date, SBP_Patient, DBP_Patient) %>%
  full_join(step_data, by = c("Date" = "Date_step")) %>%
  arrange(Date) %>%
  # 填充缺失的患者血压值(用前值填充,匹配阶梯的时间点)
  fill(SBP_Patient, DBP_Patient, .direction = "down") %>%
  # 计算填充的上下限:仅保留血压超过阈值的部分
  mutate(
    SBP_fill_ymax = pmax(SBP_Patient, SBP_Threshold_step),
    SBP_fill_ymin = pmin(SBP_Patient, SBP_Threshold_step),
    DBP_fill_ymax = pmax(DBP_Patient, DBP_Threshold_step),
    DBP_fill_ymin = pmin(DBP_Patient, DBP_Threshold_step),
    # 标记是否需要填充(血压超过阈值时)
    SBP_above = SBP_Patient > SBP_Threshold_step,
    DBP_above = DBP_Patient > DBP_Threshold_step
  )

步骤2:绘制带正确填充的图表

使用geom_ribbon()绘制填充区域,再叠加阶梯线和患者血压线:

ggplot() +
  # 填充SBP超过阈值的区域
  geom_ribbon(data = filter(filled_data, SBP_above),
              aes(x = Date, ymin = SBP_Threshold_step, ymax = SBP_Patient),
              fill = "red", alpha = 0.5) +
  # 填充DBP超过阈值的区域
  geom_ribbon(data = filter(filled_data, DBP_above),
              aes(x = Date, ymin = DBP_Threshold_step, ymax = DBP_Patient),
              fill = "blue3", alpha = 0.5) +
  # 绘制阶梯状阈值线
  geom_step(data = data, aes(x = Date, y = SBP_Threshold), color = "grey50") +
  geom_step(data = data, aes(x = Date, y = DBP_Threshold
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 00:25:13