如何在R中无重叠地打乱时间序列中的时间块?
问题描述
我们有包含3个个体的活动时间序列数据,需要对每个个体的时间块进行随机打乱,需满足以下要求:
- 个体内的时间块无重叠
- 所有打乱后的时间块必须完全处于原时间线范围内(示例中为1到30)
- 个体间的时间块可以重叠
示例数据框如下:
library(tidyverse) GrXX <- tibble(Individual = rep(c("A", "B", "C"), times = c(2, 3, 5)), Frame_beginning = c(1, 16, 7, 21, 29, 3, 9, 12, 19, 27), Frame_end = c(3, 22, 15, 24, 30, 7, 10, 12, 24, 30), Duration = Frame_end - Frame_beginning + 1, Frame_end_valid_group = 30)
现有代码的问题
原代码采用"先放大块、再依次放小块"的循环逻辑,但存在以下缺陷:
- 逻辑复杂,易出现索引错误(如
valid_frames_2未定义、排序后数组索引不匹配) - 无法彻底避免个体内重叠,极端情况下可能出现无有效位置可供放置的情况
- 随机化分布不均匀,小块的位置选择被大块严重限制
高效解决方案
采用间隙随机分配法,通过计算个体时间块的总占用时长,将剩余空闲时间随机分配为块之间的间隙,直接生成所有块的无重叠随机位置。此方法无需循环逐个放置,效率更高且能严格满足所有要求。
实现代码
# 定义单个个体的时间块打乱函数 shuffle_individual_blocks <- function(df, max_frame) { durations <- df$Duration n_blocks <- nrow(df) # 计算总占用时长与总空闲时长 total_used <- sum(durations) total_free <- max_frame - total_used # 生成n_blocks+1个随机间隙(首尾+块之间),总和为total_free if (total_free > 0) { # 通过随机分割空闲时长得到间隙 split_points <- sort(sample(1:total_free, n_blocks)) gaps <- diff(c(0, split_points, total_free + 1)) - 1 } else { # 无空闲时间时,所有间隙为0 gaps <- rep(0, n_blocks + 1) } # 计算每个块的打乱后起止位置 starts <- cumsum(c(gaps[1] + 1, durations[-n_blocks] + gaps[-c(1, n_blocks + 1)])) ends <- starts + durations - 1 # 返回添加打乱后时间的数据集 df %>% mutate( Frame_beginning_shuffled = starts, Frame_end_shuffled = ends, .after = Frame_end ) } # 对每个个体分组执行打乱操作 GrXX_shuffled <- GrXX %>% group_by(Individual) %>% group_modify(~shuffle_individual_blocks(.x, max_frame = .x$Frame_end_valid_group[1])) %>% ungroup()
方法说明
- 间隙计算:将总空闲时长随机分配为
n_blocks+1个间隙(包含首尾空闲区域),确保间隙总和等于总空闲时长 - 位置生成:通过累加间隙和前一个块的时长,直接计算每个块的起始位置,保证块之间无重叠且所有块都在有效时间线内
- 分组处理:利用
group_by+group_modify实现对每个个体的独立处理,符合tidyverse风格
内容的提问来源于stack exchange,提问作者KrisAnathema
相关产品推荐
相关产品推荐

