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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 16:04:06