我有一个数字元素向量,以及一个带有两列的数据框,用于定义区间的起点和终点。数据帧中的每一行都是一个间隔。我想找出向量中每个元素属于哪个区间。
以下是一些示例数据:
# Find which interval that each element of the vector belongs in
library(tidyverse)
elements <- c(0.1, 0.2, 0.5, 0.9, 1.1, 1.9, 2.1)
intervals <- frame_data(~phase, ~start, ~end,
"a", 0, 0.5,
"b", 1, 1.9,
"c", 2, 2.5)
反对tidyverse的人的相同示例数据:
elements <- c(0.1, 0.2, 0.5, 0.9, 1.1, 1.9, 2.1)
intervals <- structure(list(phase = c("a", "b", "c"),
start = c(0, 1, 2),
end = c(0.5, 1.9, 2.5)),
.Names = c("phase", "start", "end"),
row.names = c(NA, -3L),
class = "data.frame")
这是一种方法:
library(intrval)
phases_for_elements <-
map(elements, ~.x %[]% data.frame(intervals[, c('start', 'end')])) %>%
map(., ~unlist(intervals[.x, 'phase']))
这是输出:
[[1]]
phase
"a"
[[2]]
phase
"a"
[[3]]
phase
"a"
[[4]]
character(0)
[[5]]
phase
"b"
[[6]]
phase
"b"
[[7]]
phase
"c"
但我正在寻找一种更简单的方法,减少打字。我在相关问题中看过findInterval
,但我不确定在这种情况下如何使用它。
答案 0 :(得分:15)
以下是使用新的&#34; 非equi &#34加入data.table
(v&gt; = 1.9.8)。虽然我怀疑你喜欢语法,但它应该是非常有效的解决方案。
此外,关于findInterval
,此函数假定您的时间间隔是连续的,而这不是这里的情况,所以我怀疑有一个直接的解决方案使用它。
library(data.table) #v1.10.0
setDT(intervals)[data.table(elements), on = .(start <= elements, end >= elements)]
# phase start end
# 1: a 0.1 0.1
# 2: a 0.2 0.2
# 3: a 0.5 0.5
# 4: NA 0.9 0.9
# 5: b 1.1 1.1
# 6: b 1.9 1.9
# 7: c 2.1 2.1
关于上面的代码,我发现它非常明显:按intervals
运算符中指定的条件加入elements
和on
。几乎就是这样。
虽然有一点需要注意,start
,end
和elements
应该都是相同的类型,所以如果其中一个是integer
,它应该是首先转换为numeric
。
答案 1 :(得分:6)
cut
在这里可能很有用。
out <- cut(elements, t(intervals[c("start","end")]))
levels(out)[c(FALSE,TRUE)] <- NA
intervals$phase[out]
#[1] "a" "a" "a" NA "b" "b" "c"
答案 2 :(得分:4)
受@ thelatemail的cut
解决方案的启发,这里有一个使用findInterval
仍然需要大量输入:
out <- findInterval(elements, t(intervals[c("start","end")]), left.open = TRUE)
out[!(out %% 2)] <- NA
intervals$phase[out %/% 2L + 1L]
#[1] "a" "a" "a" NA "b" "b" "c"
警告 cut
和findInterval
有左开的时间间隔。因此,使用cut
和findInterval
的解决方案 等同于使用intrval
的Ben,使用data.table
的David的非equi连接,以及我的其他解决方案使用foverlaps
。
答案 3 :(得分:4)
map
解决方案简单得多(虽然在我看来更具可读性),并且不会对thelatemail的cut
答案有所帮助。简洁。
对于我上面的例子,fuzzyjoin解决方案将是
library(fuzzyjoin)
library(tidyverse)
fuzzy_left_join(data.frame(elements), intervals,
by = c("elements" = "start", "elements" = "end"),
match_fun = list(`>=`, `<=`)) %>%
distinct()
给出了:
elements phase start end
1 0.1 a 0 0.5
2 0.2 a 0 0.5
3 0.5 a 0 0.5
4 0.9 <NA> NA NA
5 1.1 b 1 1.9
6 1.9 b 1 1.9
7 2.1 c 2 2.5
答案 4 :(得分:3)
只需lapply
即可:
l <- lapply(elements, function(x){
intervals$phase[x >= intervals$start & x <= intervals$end]
})
str(l)
## List of 7
## $ : chr "a"
## $ : chr "a"
## $ : chr "a"
## $ : chr(0)
## $ : chr "b"
## $ : chr "b"
## $ : chr "c"
或purrr
,如果你是purrrfurrr,
elements %>%
map(~intervals$phase[.x >= intervals$start & .x <= intervals$end]) %>%
# Clean up a bit. Shorter, but less readable: map_chr(~.x[1] %||% NA)
map_chr(~ifelse(length(.x) == 0, NA, .x))
## [1] "a" "a" "a" NA "b" "b" "c"
答案 5 :(得分:2)
这是一种&#34;单线&#34;哪个(mis-)使用foverlaps
包中的data.table
,但David的非equi连接仍然更简洁:
library(data.table) #v1.10.0
foverlaps(data.table(start = elements, end = elements),
setDT(intervals, key = c("start", "end")))
# phase start end i.start i.end
#1: a 0 0.5 0.1 0.1
#2: a 0 0.5 0.2 0.2
#3: a 0 0.5 0.5 0.5
#4: NA NA NA 0.9 0.9
#5: b 1 1.9 1.1 1.1
#6: b 1 1.9 1.9 1.9
#7: c 2 2.5 2.1 2.1
答案 6 :(得分:2)
为了完成起见,这是另一种方法,使用intervals
包:
library(tidyverse)
elements <- c(0.1, 0.2, 0.5, 0.9, 1.1, 1.9, 2.1)
intervalsDF <-
frame_data( ~phase, ~start, ~end,
"a", 0, 0.5,
"b", 1, 1.9,
"c", 2, 2.5
)
library(intervals)
library(rlist)
interval_overlap(
Intervals(intervalsDF %>% select(-phase) %>% as.matrix, closed = c(TRUE, TRUE)),
Intervals(data_frame(start = elements, end = elements), closed = c(TRUE, TRUE))
) %>%
list.map(data_frame(interval_index = .i, element_index = .)) %>%
do.call(what = bind_rows)
# A tibble: 6 × 2
# interval_index element_index
# <int> <int>
#1 1 1
#2 1 2
#3 1 3
#4 2 5
#5 2 6
#6 3 7