如何在R中拆分时间间隔数据?

3

我有一些数据,格式为起始时间和结束时间(以分钟:秒的格式)。一个简单的例子可能是灯打开的时间戳和随后灯关闭的时间戳。

例如:

Start    Stop   
00:03.1  00:40.9
00:55.0  01:38.2
01:40.0  02:01.1

我希望能够重新排列数据,以便最终可以在R中按整分钟间隔查看数据。

选项1:将数据转换为每十分之一秒的二进制列表,然后按时间戳稍后聚合数据。

Time.in.sec   Yes.or.No
0.0           N
0.1           N
...           ...
3.0           N
3.1           Y
3.2           Y
...           ...
40.8          Y
40.9          N
...           ...
选项2: 将时间间隔分割为分钟标记,并使用某种逻辑规则按分钟聚合总时间(从时间 = 0:00.0 开始)。
Start        Stop
00:03.10     00:40.90
00:55.00     00:59.99
01:00.00     01:38.20
01:40.00     01:59.99
02:00.00     02:01.10

我尝试过研究lubridate函数 (即将每个范围转换为间隔类),以及cut(),但似乎无法弄清楚如何使这些想法之一起作用。 我也不清楚像zoo这样的包是否适合此操作;老实说,我对日期/时间格式和时间序列的经验非常少。
Stackoverflow上的其他问题似乎是针对从原始时间戳制作存储箱 (例如 What is an efficient method for partitioning and aggregating intervals from timestamped rows in a data frame?Aggregate data by equally spaced time intervals in R), 但我基本上想做相反的事情。

编辑1:这是一个CSV格式的示例数据,截至第6分钟。

Start, Stop 
00:03.1, 00:40.9
00:55.0, 01:38.2
01:40.0, 02:01.1
03:03.1, 04:30.3
04:50.0, 05:01.5
05:08.7, 05:22.0
05:40.1, 05:47.9

EDIT 2:我的最终目标是将数据格式化,以便我可以将观测值分成规范的时间段(第1分钟,第2分钟等)来获取按分钟计算的“是”的数据百分比。基本上,我想得到每分钟状态分布的摘要,由于数据是二进制的,所以我可以通过查看“是”状态来完成此操作。
对于前3分钟(从00:00.0到03:00.0),输出将类似于以下内容:
Minute  time.yes.sec  perc.time.yes
1       42.8          71.33
2       58.2          96.98
3       1.1           1.83

# *NOTE: Here, Minute 1 = [0, 60), Minute 2 = [60, 120), etc.; I'm not opposed 
# to the reverse definitions though (Minute 1 = (0, 60], etc.).  

我可以选择将数据查看为累积分布图,每个连续时间点更新“总时间是”的值。然而,如果我能以选项1的格式获取数据,我就可以灵活地查看数据。


我刚刚添加的CSV格式数据摘录是否足够? - GH28
这次没问题,因为我已经使用了你提供的原始数据来编写答案。如果有任何疑问,请随时告诉我。干杯! - Hack-R
你为什么想要这样做?你试图解决的更大问题是什么? - Hugh
整分钟区间中有什么是值得关注的?过渡次数?正向过渡次数(从关闭到打开)?最短的开启间隔?总的开启间隔?如何对数据进行重新排列取决于你希望用它做什么。 - Jason
1
不是很确定你想要什么,但很有趣:library(tidyverse) ; df %>% mutate_all(funs(as.numeric(lubridate::ms(.)))) %>% rowwise() %>% mutate(instant = list(seq(Start, Stop, by = 0.1))) %>% unnest() %>% mutate(minute = cut(instant, (0:6) * 60)) %>% group_by(minute) %>% summarise(elapsed = (n()-1)/10) - alistaire
@alistaire,您介意将您上面编写的代码放入实际答案中吗?我认为在查看Hack-R的答案并进行调整后,我可能已经使类似的东西起作用了,但我也想给您以荣誉(并理解每个步骤实际上正在做什么)。我对通过第二个mutate()调用的rowwise()特别不清楚。 - GH28
2个回答

4

一个选项,经过轻微编辑,来自我在评论中的版本:

library(tidyverse)
library(lubridate)

df %>% mutate_all(funs(period_to_seconds(ms(.)))) %>%    # convert each time to seconds
    rowwise() %>%    # evaluate the following row-by-row
    # make a sequence from Start to Stop by 0.1, wrapped in a list
    mutate(instant = list(seq(Start, Stop, by = 0.1))) %>% 
    unnest() %>%    # expand list column
    # make a factor, cutting instants into 60 second bins
    mutate(minute = cut(instant, breaks = (0:6) * 60, labels = 1:6)) %>% 
    group_by(minute) %>%    # evaluate the following grouped by new factor column
    # for each group, count the rows, subtracting 1 for starting instants, and
    # dividing by 10 to convert from tenths of seconds to secontds
    summarise(elapsed = (n() - n_distinct(Start)) / 10,
              pct_elapsed = elapsed / 60 * 100)    # convert to percent

## # A tibble: 6 × 3
##   minute elapsed pct_elapsed
##   <fctr>   <dbl>       <dbl>
## 1      1    42.8   71.333333
## 2      2    58.1   96.833333
## 3      3     1.0    1.666667
## 4      4    56.9   94.833333
## 5      5    40.2   67.000000
## 6      6    22.5   37.500000

请注意,计算开始瞬间的修正并不完美,因为它将减去每个开始瞬间,即使它是前一分钟序列的延续。如果精度很重要,可以更加深入地计算。
一个更精确但有些困难的方法是在每分钟转变时添加停止和开始:
df %>% mutate_all(funs(period_to_seconds(ms(.)))) %>%    # convert to seconds
    gather(var, val) %>%    # gather to long form
    # construct and rbind data.frame of breaks at minute changes
    bind_rows(expand.grid(var = c('Start', 'Stop'), 
                          val = seq(60, by = 60, length.out = floor(max(.$val)/60)))) %>% 
    arrange(val, desc(var)) %>%    # sort
    mutate(index = rep(seq(n()/2), each = 2)) %>%    # make indices for spreading
    spread(var, val) %>%    # spread back to wide form
    mutate(elapsed = Stop - Start) %>%    # calculate elapsed time for each row
    # add and group by factor of which minute each falls in
    group_by(minute = cut(Stop, seq(0, by = 60, length.out = ceiling(max(Stop) / 60 + 1)), 
                        labels = 1:6)) %>% 
    summarise(elapsed = sum(elapsed),    # calculate summaries
              pct_elapsed = elapsed / 60 * 100)

## # A tibble: 6 × 3
##   minute elapsed pct_elapsed
##   <fctr>   <dbl>       <dbl>
## 1      1    42.8   71.333333
## 2      2    58.2   97.000000
## 3      3     1.1    1.833333
## 4      4    56.9   94.833333
## 5      5    40.3   67.166667
## 6      6    22.6   37.666667

3

在编辑之前,我使用您的原始数据执行了以下操作:

Start    Stop   
00:03.1  00:40.9
00:55.0  01:38.2
01:40.0  02:01.1

agg <- read.table(con<-file("clipboard"), header=T)

下面的ms函数接受从剪贴板中读取的原始字符输入,并将其转换为以分钟和秒为单位的适当类别,以便进行比较。对于seconds函数也是如此,唯一的区别是我处理的数据仅以秒为单位,而不是以分钟和秒为单位。
agg$Start <- lubridate::ms(agg$Start)
agg$Stop  <- lubridate::ms(agg$Stop)

option1 <- data.frame(time = lubridate::seconds(seq(.1, 122, .1)),
                      flag = as.character("N"), stringsAsFactors = F)

for(i in 1:nrow(agg)){
  option1$flag[option1$time > agg$Start[i] & option1$time < agg$Stop[i]] <- "Y"
}

为了验证它是否奏效,让我们看看 table():
table(option1$flag)
   N    Y 
 201 1019
option1$minute <- ifelse(option1$time < lubridate::seconds(60), 0, 1)
option1$minute[option1$time > lubridate::seconds(120)] <- 2

table(option1$flag, option1$minute)
    0   1   2
N 172  19  10
Y 427 582  10
prop.table(table(option1$flag, option1$minute),2)
             0          1          2
  N 0.28714524 0.03161398 0.50000000
  Y 0.71285476 0.96838602 0.50000000

我认为这是我想要的正确方向。然而,观察期实际上从0:00.0开始,持续整数分钟。这并不一定反映在记录的第一个时间戳或最后一个时间戳上;如果状态在观察开始或结束时为“否”,则不会有涵盖这些时间的时间戳。 - GH28
另外,如果您能更详细地解释一下您是如何实现lubridate魔法的,那就太好了。 - GH28
例如,lubridate :: ms(agg $ Start)将开始时间戳值的向量更改为称为“时段”的对象向量,其中包括以分钟和秒为单位的时间长度。 这将使我们能够做什么.....等等。 - GH28
@GH28 我已经为lubridate添加了额外的说明。 - Hack-R
@GH28 好的,我终于追溯到足够的程度,意识到我的答案已经回答了你的第一个评论。请记住,我们填充了给定范围内每个可能的1/10秒,而不管原始时间戳如何。然后我创建了“minute”变量来表示第一分钟(0,即0秒-60秒),第二分钟(1)和第三分钟(2)。我可以用许多不同的方式进行聚合,但是没有指示,我只是举了一些例子。因此,你在第一个评论中所要求的内容已经存在。一定要仔细阅读代码。 - Hack-R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接