ggplot2:按年份分面并按月份对齐X轴日期的实现方案
日历热力图:实现分面周对齐与月份标签显示
需求说明
需要绘制每日数据的日历热力图,满足以下要求:
- Y轴为周一至周日(按周一开始排序)
- X轴为年周数,但显示月份标签
- 按年份分面(每行一个年份)
- 各分面的年周完全对齐
- 每个热力图方块(tile)为完美方形
测试数据集
先修正原数据中的变量名错误(VectorofDates未定义,改为Date):
library(tidyverse) library(lubridate) my_data <- tibble( Date = seq( as.Date("1/11/2013", "%d/%m/%Y"), as.Date("31/12/2014", "%d/%m/%Y"), "days" ), Value = runif(length(Date)) )
现有尝试的问题
尝试1:用week()标记周数(周对齐但无月份标签)
该方法能保证分面周对齐,但X轴是纯周数,无法直接替换为月份标签:
my_data %>% mutate(Week = week(Date)) %>% mutate(Weekday = wday(Date, label = TRUE, week_start = 1)) %>% mutate(Year = year(Date)) %>% ggplot(aes(fill = Value, x = Week, y = Weekday)) + geom_tile() + theme_bw() + facet_grid(Year ~ .) + coord_fixed()
尝试2:用日期型周起始(标签正确但周不对齐)
该方法X轴能显示月份标签,但跨年份的周无法对齐,且方块难以做到完美方形:
my_data %>% mutate(Week = floor_date(Date, week_start = 1), Week = round_date(Week, "week", week_start = 1)) %>% mutate(Weekday = wday(Date, label = TRUE, week_start = 1)) %>% mutate(Year = year(Date)) %>% ggplot(aes(fill = Value, x = Week, y = Weekday)) + geom_tile() + theme_bw() + facet_grid(Year ~ .) + scale_x_datetime(name = NULL, labels = label_date("%b")) + coord_fixed(7e5)
解决方案:手动映射月份到周数位置
核心想法:用周序号作为x轴数值保证对齐,同时计算每个月份对应的周数位置,手动替换X轴标签,结合coord_fixed()实现方形方块。
步骤1:数据预处理
添加周数、星期几、年份,同时计算每个周对应的月份,以及每个年份的周范围和月份标签位置:
data_processed <- my_data %>% mutate( Year = year(Date), Week = week(Date), Weekday = wday(Date, label = TRUE, week_start = 1), Month = month(Date, label = TRUE) ) %>% # 按年份+月份分组,计算对应周数的中间值(用于放置月份标签) group_by(Year, Month) %>% mutate(month_week_mid = median(Week)) %>% ungroup() %>% # 按年份获取周数的最小/最大值,保证分面x轴范围匹配 group_by(Year) %>% mutate( min_week = min(Week), max_week = max(Week) ) %>% ungroup()
步骤2:绘制热力图
使用scale_x_continuous手动设置X轴刻度和标签,结合coord_fixed()确保方块方形:
ggplot(data_processed, aes(x = Week, y = Weekday, fill = Value)) + geom_tile(color = "white", size = 0.5) + # 白色边框区分方块 # 分面:自由调整X轴长度但保持周数对齐 facet_grid(Year ~ ., scales = "free_x", space = "free_x") + # 手动替换X轴:用月份对应周数的中间值做刻度位置,显示月份名称 scale_x_continuous( name = NULL, breaks = unique(data_processed$month_week_mid), labels = unique(data_processed$Month), expand = c(0, 0) # 去掉X轴两端留白 ) + scale_fill_viridis_c(option = "plasma") + # 可选:替换配色方案 theme_bw() + theme( axis.text.x = element_text(angle = 45, hjust = 1), # 月份标签倾斜显示 panel.grid = element_blank(), # 去掉网格线 strip.background = element_rect(fill = "#f0f0f0") # 优化分面标题背景 ) + coord_fixed(ratio = 1) # 强制方块为完美正方形
关键说明
- 周对齐:用
week(Date)生成的周序号作为x轴数值,不同年份的周数直接对应,分面时自然对齐。 - 月份标签:通过计算每个月份对应的周数中位数,将其作为X轴刻度位置,替换为月份名称,实现“X轴是周数但显示月份标签”的效果。
- 方形方块:
coord_fixed(ratio = 1)保证x轴和y轴单位长度一致,配合expand = c(0,0)去掉留白,让每个方块成为正方形。 - 分面适配:
scales="free_x"和space="free_x"让不同年份的X轴长度根据实际周数调整,同时保持周数的对齐关系。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

