如何在R语言中计算循环时间的事件平均发生时刻(均值或中位数)
在R中计算循环时间(24小时制)的均值与中位数
这问题太常见了!循环时间(比如24小时制)的均值计算确实不能直接用普通的mean(),不然就会出现你说的23:00和01:00平均成12:00的尴尬情况。核心原因是时间是循环型数据,0点和24点是同一个位置,常规线性均值的逻辑不适用。下面给你分享两种可行的实现方法,分别对应均值和中位数:
一、计算循环时间的均值(方向均值)
思路是把时间映射到单位圆的角度上,通过计算向量的平均方向来得到正确的循环均值,步骤如下:
1. 准备数据并转换格式
首先修正你的示例数据(原写法是语法错误,需要用字符型时间),然后将时间转换为一天中的小数小时数:
library(lubridate) # 用lubridate处理时间更便捷 # 修正后的示例睡眠时间数据 sleeping_hours <- c("22:30", "21:45", "22:45", "23:40", "01:00", "23:30") # 转换为小数形式的小时数(比如22:30 → 22.5,01:00 → 1.0) time_hours <- hour(hms(sleeping_hours)) + minute(hms(sleeping_hours)) / 60
2. 转换为弧度并计算平均方向
把小时数映射到单位圆的弧度(24小时对应2π弧度),然后通过余弦和正弦的均值计算平均方向:
# 将小时数转换为弧度 radians <- time_hours * (2 * pi) / 24 # 计算所有弧度的余弦、正弦均值 mean_cos <- mean(cos(radians)) mean_sin <- mean(sin(radians)) # 计算平均弧度(注意atan2的参数顺序是sin在前,cos在后) mean_radian <- atan2(mean_sin, mean_cos)
3. 转换回24小时制时间
把平均弧度转回小时数,并调整到0-24的范围内,最后格式化为HH:MM:
# 弧度转小时数 mean_hours <- (mean_radian * 24) / (2 * pi) # 处理负数情况(比如平均弧度在第四象限时会得到负数小时) mean_hours <- ifelse(mean_hours < 0, mean_hours + 24, mean_hours) # 格式化为HH:MM时间格式 mean_time <- format(as.POSIXct(mean_hours * 3600, origin = "1970-01-01"), "%H:%M") # 查看结果 mean_time # 输出应该是接近"00:00"的时间,符合预期
封装成复用函数
如果需要多次使用,可以把上述步骤封装成函数:
circadian_mean <- function(time_strings) { library(lubridate) time_hours <- hour(hms(time_strings)) + minute(hms(time_strings)) / 60 radians <- time_hours * (2 * pi) / 24 mean_cos <- mean(cos(radians)) mean_sin <- mean(sin(radians)) mean_radian <- atan2(mean_sin, mean_cos) mean_hours <- (mean_radian * 24) / (2 * pi) mean_hours <- ifelse(mean_hours < 0, mean_hours + 24, mean_hours) format(as.POSIXct(mean_hours * 3600, origin = "1970-01-01"), "%H:%M") } # 调用函数测试 circadian_mean(sleeping_hours)
二、计算循环时间的中位数
循环数据的中位数需要考虑是否跨越0点的情况,比如当数据同时包含接近24点和0点的时间时,需要将部分数据偏移24小时后再计算中位数:
实现函数
circadian_median <- function(time_strings) { library(lubridate) # 转换为小数小时数 time_hours <- hour(hms(time_strings)) + minute(hms(time_strings)) / 60 # 排序时间 sorted_hours <- sort(time_hours) # 判断是否存在跨0点的情况(最大最小时间差超过12小时,说明数据分布在0点两侧) if (max(sorted_hours) - min(sorted_hours) > 12) { # 将较小的那部分时间加24,统一到同一个线性区间 sorted_hours[sorted_hours < median(sorted_hours)] <- sorted_hours[sorted_hours < median(sorted_hours)] + 24 } # 计算中位数并调整回0-24范围 median_hours <- median(sorted_hours) %% 24 # 格式化为HH:MM format(as.POSIXct(median_hours * 3600, origin = "1970-01-01"), "%H:%M") } # 测试示例数据 circadian_median(sleeping_hours)
原理说明
- 均值:通过三角函数将循环时间转换为单位圆上的向量,向量的平均方向就是循环数据的“中心”,完美解决了循环边界的问题。
- 中位数:当数据跨0点分布时,通过偏移24小时将数据拉到一个线性区间,计算常规中位数后再映射回原循环区间,保证结果符合直觉。
内容的提问来源于stack exchange,提问作者Bartu YURDACAN
相关产品推荐
相关产品推荐

