如何解决 R 和性能 vs SQL 中的差距和孤岛问题?
How to solve gaps and island problems in R and performance vs SQL?
我想知道孤岛和间隙问题是否可以在 R 中有效地解决,类似于 SQL。如果我们检查一个 ID,我有以下可用数据:
ID StartDate StartTime EndDate EndTime
1 19-05-2014 19:00 19-05-2014 20:00
1 19-05-2014 19:30 19-05-2014 23:30
1 19-05-2014 16:00 19-05-2014 18:00
1 20-05-2014 20:00 20-05-2014 20:30
注意前两行重叠,我想做的是合并重叠的行,结果:
ID StartDate StartTime EndDate EndTime
1 19-05-2014 19:00 19-05-2014 23:30
1 19-05-2014 16:00 19-05-2014 18:00
1 20-05-2014 20:00 20-05-2014 20:30
有没有办法在 R 中做到这一点?
我很清楚这是在 SQL 中完成的,但由于我的数据已经在 R 中,所以我更喜欢在 R 中执行此操作。其次,我对查找间隙和孤岛的性能有一些疑问,我知道 SQL 这样做非常快,但我想知道 R 是否更快,因为所有数据都在内存中。
我想用data.table
来做这个,但我不知道怎么做。
更新 - 对阿伦的回应
我创建了以下测试用例,其中包含所有可能的区间方向。
dat <- structure(
list(ID = c(1L, 1L, 1L, 1L, 1L, 1L),
stime = structure(c(as.POSIXct("2014-01-15 08:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 08:30:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 11:30:00"),
as.POSIXct("2014-01-15 12:00:00")),
class = c("POSIXct", "POSIXt"), tzone = ""),
etime = structure(c(as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 11:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 12:30:00"),
as.POSIXct("2014-01-15 13:00:00")),
class = c("POSIXct", "POSIXt"), tzone = "")
),
.Names = c("ID", "stime", "etime"),
sorted = c("ID", "stime", "etime"),
class = c("data.table", "data.frame"),
row.names = c(NA,-6L)
)
我希望从 8:30 - 10:00 的间隔是 "glued" 到 10:00 - 11:00,但事实并非如此。结果是:
idx ID stime etime
1: 4 1 2014-01-15 08:00:00 2014-01-15 10:00:00
2: 3 1 2014-01-15 10:00:00 2014-01-15 11:00:00
3: 6 1 2014-01-15 11:30:00 2014-01-15 13:00:00
以下数据集提供了更彻底的测试:
# The numbers represent seconds from 1970-01-01 01:00:01
dat <- structure(
list(ID = c(1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L),
stime = structure(c(as.POSIXct("2014-01-15 08:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 08:30:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 11:30:00"),
as.POSIXct("2014-01-15 12:00:00"),
as.POSIXct("2014-01-15 07:30:00"),
as.POSIXct("2014-01-15 08:00:00"),
as.POSIXct("2014-01-15 08:30:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 10:00:00")
),
class = c("POSIXct", "POSIXt"), tzone = ""),
etime = structure(c(as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 11:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 12:30:00"),
as.POSIXct("2014-01-15 13:00:00"),
as.POSIXct("2014-01-15 08:30:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 10:30:00"),
as.POSIXct("2014-01-15 11:00:00")
),
class = c("POSIXct", "POSIXt"), tzone = "")
),
.Names = c("ID", "stime", "etime"),
sorted = c("ID", "stime", "etime"),
class = c("data.table", "data.frame"),
row.names = c(NA,-6L)
)
所以我们的结果是:
idx ID stime etime
1: 4 1 2014-01-15 08:00:00 2014-01-15 10:00:00
2: 3 1 2014-01-15 10:00:00 2014-01-15 11:00:00
3: 6 1 2014-01-15 11:30:00 2014-01-15 13:00:00
4: 12 2 2014-01-15 07:30:00 2014-01-15 09:30:00
5: 13 2 2014-01-15 09:00:00 2014-01-15 11:00:00
现在对于 ID=2 的受访者,我们看到间隔重叠,但未报告为一个间隔。正确的解决方案是:
idx ID stime etime
1: ? 1 2014-01-15 08:00:00 2014-01-15 11:00:00
3: ? 1 2014-01-15 11:30:00 2014-01-15 13:00:00
4: ?? 2 2014-01-15 07:30:00 2014-01-15 11:00:00
更新 - 基准和测试以及大型数据集
我有以下包含大约 1000 个用户的数据集,每个用户有 500 个持续时间,提供 50 万行。您可以在我的 Google Drive 下载数据集,包括 Google Drive.
中的解决方案
SQL 服务器 2014 在 8GB RAM、64 位、i5-4210U 的笔记本电脑上 CPU @ 1.70Ghz - 2.39Ghz 使用 Itzik 提供的解决方案大约需要 5 秒本甘在 SQL。这 5 秒不包括创建函数的过程。此外,没有为任何 table 创建任何索引。
PS:我用library(lubridate);
这是一个非常简单的想法。按开始时间排序,然后找到结束时间的累积最大值。一旦你这样做了,重叠组就是那些下一个开始时间仍然小于或等于当前累计最大结束时间的组(全部由 ID 完成):
setorder(dat, ID, stime) # ordering by ID is unnecessary, it's just prettier
dat[, etime.max := as.POSIXct(cummax(as.numeric(etime)), origin = '1970-01-01'), by = ID]
# find the grouping of intervals (1:.N hack is to avoid warnings when .N=1)
dat[, grp := cumsum(c(FALSE, stime[2:.N] > etime.max[1:(.N-1)]))[1:.N], by = ID]
dat[, .(stime = min(stime), etime = max(etime)), by = .(ID, grp)][, grp := NULL][]
# ID stime etime
#1: 1 2014-01-15 08:00:00 2014-01-15 11:00:00
#2: 1 2014-01-15 11:30:00 2014-01-15 13:00:00
#3: 2 2014-01-15 07:30:00 2014-01-15 11:00:00
由于这不需要找到所有可能的重叠,所以速度非常快。在与 OP 的描述大致相符的模拟数据集上,它对我来说是瞬时的 (< 0.2s)。
我想知道孤岛和间隙问题是否可以在 R 中有效地解决,类似于 SQL。如果我们检查一个 ID,我有以下可用数据:
ID StartDate StartTime EndDate EndTime
1 19-05-2014 19:00 19-05-2014 20:00
1 19-05-2014 19:30 19-05-2014 23:30
1 19-05-2014 16:00 19-05-2014 18:00
1 20-05-2014 20:00 20-05-2014 20:30
注意前两行重叠,我想做的是合并重叠的行,结果:
ID StartDate StartTime EndDate EndTime
1 19-05-2014 19:00 19-05-2014 23:30
1 19-05-2014 16:00 19-05-2014 18:00
1 20-05-2014 20:00 20-05-2014 20:30
有没有办法在 R 中做到这一点?
我很清楚这是在 SQL 中完成的,但由于我的数据已经在 R 中,所以我更喜欢在 R 中执行此操作。其次,我对查找间隙和孤岛的性能有一些疑问,我知道 SQL 这样做非常快,但我想知道 R 是否更快,因为所有数据都在内存中。
我想用data.table
来做这个,但我不知道怎么做。
更新 - 对阿伦的回应
我创建了以下测试用例,其中包含所有可能的区间方向。
dat <- structure(
list(ID = c(1L, 1L, 1L, 1L, 1L, 1L),
stime = structure(c(as.POSIXct("2014-01-15 08:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 08:30:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 11:30:00"),
as.POSIXct("2014-01-15 12:00:00")),
class = c("POSIXct", "POSIXt"), tzone = ""),
etime = structure(c(as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 11:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 12:30:00"),
as.POSIXct("2014-01-15 13:00:00")),
class = c("POSIXct", "POSIXt"), tzone = "")
),
.Names = c("ID", "stime", "etime"),
sorted = c("ID", "stime", "etime"),
class = c("data.table", "data.frame"),
row.names = c(NA,-6L)
)
我希望从 8:30 - 10:00 的间隔是 "glued" 到 10:00 - 11:00,但事实并非如此。结果是:
idx ID stime etime
1: 4 1 2014-01-15 08:00:00 2014-01-15 10:00:00
2: 3 1 2014-01-15 10:00:00 2014-01-15 11:00:00
3: 6 1 2014-01-15 11:30:00 2014-01-15 13:00:00
以下数据集提供了更彻底的测试:
# The numbers represent seconds from 1970-01-01 01:00:01
dat <- structure(
list(ID = c(1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L),
stime = structure(c(as.POSIXct("2014-01-15 08:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 08:30:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 11:30:00"),
as.POSIXct("2014-01-15 12:00:00"),
as.POSIXct("2014-01-15 07:30:00"),
as.POSIXct("2014-01-15 08:00:00"),
as.POSIXct("2014-01-15 08:30:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 10:00:00")
),
class = c("POSIXct", "POSIXt"), tzone = ""),
etime = structure(c(as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 11:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 12:30:00"),
as.POSIXct("2014-01-15 13:00:00"),
as.POSIXct("2014-01-15 08:30:00"),
as.POSIXct("2014-01-15 09:00:00"),
as.POSIXct("2014-01-15 09:30:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 10:00:00"),
as.POSIXct("2014-01-15 10:30:00"),
as.POSIXct("2014-01-15 11:00:00")
),
class = c("POSIXct", "POSIXt"), tzone = "")
),
.Names = c("ID", "stime", "etime"),
sorted = c("ID", "stime", "etime"),
class = c("data.table", "data.frame"),
row.names = c(NA,-6L)
)
所以我们的结果是:
idx ID stime etime
1: 4 1 2014-01-15 08:00:00 2014-01-15 10:00:00
2: 3 1 2014-01-15 10:00:00 2014-01-15 11:00:00
3: 6 1 2014-01-15 11:30:00 2014-01-15 13:00:00
4: 12 2 2014-01-15 07:30:00 2014-01-15 09:30:00
5: 13 2 2014-01-15 09:00:00 2014-01-15 11:00:00
现在对于 ID=2 的受访者,我们看到间隔重叠,但未报告为一个间隔。正确的解决方案是:
idx ID stime etime
1: ? 1 2014-01-15 08:00:00 2014-01-15 11:00:00
3: ? 1 2014-01-15 11:30:00 2014-01-15 13:00:00
4: ?? 2 2014-01-15 07:30:00 2014-01-15 11:00:00
更新 - 基准和测试以及大型数据集
我有以下包含大约 1000 个用户的数据集,每个用户有 500 个持续时间,提供 50 万行。您可以在我的 Google Drive 下载数据集,包括 Google Drive.
中的解决方案SQL 服务器 2014 在 8GB RAM、64 位、i5-4210U 的笔记本电脑上 CPU @ 1.70Ghz - 2.39Ghz 使用 Itzik 提供的解决方案大约需要 5 秒本甘在 SQL。这 5 秒不包括创建函数的过程。此外,没有为任何 table 创建任何索引。
PS:我用library(lubridate);
这是一个非常简单的想法。按开始时间排序,然后找到结束时间的累积最大值。一旦你这样做了,重叠组就是那些下一个开始时间仍然小于或等于当前累计最大结束时间的组(全部由 ID 完成):
setorder(dat, ID, stime) # ordering by ID is unnecessary, it's just prettier
dat[, etime.max := as.POSIXct(cummax(as.numeric(etime)), origin = '1970-01-01'), by = ID]
# find the grouping of intervals (1:.N hack is to avoid warnings when .N=1)
dat[, grp := cumsum(c(FALSE, stime[2:.N] > etime.max[1:(.N-1)]))[1:.N], by = ID]
dat[, .(stime = min(stime), etime = max(etime)), by = .(ID, grp)][, grp := NULL][]
# ID stime etime
#1: 1 2014-01-15 08:00:00 2014-01-15 11:00:00
#2: 1 2014-01-15 11:30:00 2014-01-15 13:00:00
#3: 2 2014-01-15 07:30:00 2014-01-15 11:00:00
由于这不需要找到所有可能的重叠,所以速度非常快。在与 OP 的描述大致相符的模拟数据集上,它对我来说是瞬时的 (< 0.2s)。