以下是一些替代方法。
(1) 和 (1a) 仅使用 Base R。除了 (1) 使用邻接矩阵和 (1a) 使用邻域列表以避免形成潜在的大邻接矩阵之外,它们是相同的。
(2) 是使用 sqldf 包的 SQL 解决方案。
(3) 使用 igraph 包并可能给出与上述替代方案不同的答案,尽管在问题中的示例的情况下答案是相同的。 (3a) 与 (3) 类似,但与 (1a) 类似,避免了形成邻接矩阵。
最后我们提供了一些图形。
替代方案
1) Base R 首先我们将日期转换为Date 类,给出df2。然后我们定义一个函数betw,它检查它的第一个参数是否在第二个和第三个之间,并使用它来定义一个函数overlap,它给df2中的两个行索引确定它们是否重叠(TRUE)或不(FALSE )。
如果V 是从1 到df2 中行数的序列,那么我们可以形成一个邻接矩阵adj,这样adj[i,j] 在第i 行和第j 行重叠时为1。使用它可以直接计算overlap、newStartDate 和newEndDate 列。
这种方法不使用任何包。
df2 <- transform(df, startDate = as.Date(startDate), endDate = as.Date(endDate))
betw <- function(x, a, b) x >= a & x <= b
overlap <- function(i, j) {
betw(df2[i, "startDate"], df2[j, "startDate"], df2[j, "endDate"]) ||
betw(df2[j, "startDate"], df2[i, "startDate"], df2[i, "endDate"])
}
# form adjacency matrix of graph having vertices V
V <- 1:nrow(df2)
adj <- sapply(V, function(u) sapply(V, overlap, u)) + 0
orig <- "1970-01-01"
transform(df2, overlap = colSums(adj) > 1,
newStartDate = as.Date(apply(adj, 1, function(ix) min(startDate[ix == 1])), orig),
newEndDate = as.Date(apply(adj, 1, function(ix) max(endDate[ix == 1])), orig))
给予:
id startDate endDate overlap newStartDate newEndDate
1 1 1990-01-01 1990-01-24 TRUE 1990-01-01 1990-01-25
2 1 1990-01-23 1990-01-25 TRUE 1990-01-01 1990-01-25
3 1 1990-01-30 1990-01-31 FALSE 1990-01-30 1990-01-31
1a) 避免形成adj 邻接矩阵的(1) 的一种变体是创建一个邻居列表,使得nbrs[[i]] 是第i 行重叠的行号的向量。
nbrs <- lapply(1:nr, function(j) Filter(function(i) overlap(i, j), 1:nr))
names(nbrs) <- 1:nr
orig <- "1970-01-01"
transform(df2, overlap = lengths(nbrs) > 1,
newStartDate = as.Date(sapply(nbrs, function(ix) min(startDate[ix])), orig),
newEndDate = as.Date(sapply(nbrs, function(ix) max(endDate[ix])), orig))
2) sqldf 使用df2,我们可以使用 SQL 在单个 SQL 语句中计算所需的输出:
library(sqldf)
sqldf("select
a.id,
a.startDate as startDate__Date,
a.endDate as endDate__Date,
count(b.rowid) > 1 as overlap__logical,
min(b.startDate) as newStartDate__Date,
max(b.endDate) as newEndDate__Date
from df2 as a
left join df2 as b on (a.startDate between b.startDate and b.endDate) or
(b.startDate between a.startDate and a.endDate)
group by a.rowid
order by a.rowid", method = "name__class")
给予:
id startDate endDate overlap newStartDate newEndDate
1 1 1990-01-01 1990-01-24 TRUE 1990-01-01 1990-01-25
2 1 1990-01-23 1990-01-25 TRUE 1990-01-01 1990-01-25
3 1 1990-01-30 1990-01-31 FALSE 1990-01-30 1990-01-31
3) igraph 另一种不等同于 (1) 或 (2) 但可能更受欢迎的方法是使用重叠关系的传递完成将行划分为连接的组件。这里类似于这个问题:R: Find groups of vectors that have a > 80% overlap in their elements
使用 (1) 中的 adj 使用 igraph 包形成图形 g。然后,在其连接组件中没有其他行的行不会重叠。如果我们将连通分量编号为 1、2、...,那么 memb 是这样的,memb[i] 是包含第 i 行的连通分量的编号,因此对于每一行,我们可以找到连通分量上的最小和最大日期它属于。尽管对于问题中的输入,这给出了与 (1) 相同的答案,但总的来说,这与 (1) 不同,因为例如,如果行 i 和 j 不重叠但每个重叠行 k 则 i, j和 k 都在同一个连通分量中,用于计算输出的列。
library(igraph)
g <- graph_from_adjacency_matrix(adj, mode = "undirected", diag = FALSE)
memb <- components(g)$membership
# assemble desired output data frame
transform(df2,
overlap = ave(memb, memb, FUN = length) > 1,
newStartDate = ave(startDate, memb, FUN = min),
newEndDate = ave(endDate, memb, FUN = max))
给予:
id startDate endDate overlap newStartDate newEndDate
1 1 1990-01-01 1990-01-24 TRUE 1990-01-01 1990-01-25
2 1 1990-01-23 1990-01-25 TRUE 1990-01-01 1990-01-25
3 1 1990-01-30 1990-01-31 FALSE 1990-01-30 1990-01-31
3a) 或者,我们可以从 nbrs 形成 g 以避免像这样形成 adj:
g0 <- graph_from_edgelist(as.matrix(stack(nbrs)), directed = FALSE)
g <- simplify(g0) # remove self loops
图形
顺便说一句,使用g,我们可以显示一个图形表示,其中节点 i 表示第 i 行,边表示重叠。
plot(g)