查找向量的每个元素属于数据框中的哪个间隔行
Find which interval row in a data frame that each element of a vector belongs in
我有一个数字元素向量和一个包含两列的数据框,这些列定义了间隔的起点和终点。数据框中的每一行都是一个间隔。我想找出向量中的每个元素属于哪个区间。
下面是一些示例数据:
# 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
,但我不确定在这种情况下如何使用它。
这是一个可能的解决方案,使用 data.table
(v>=1.9.8) 中的新“non-equi”连接。虽然我怀疑您会喜欢这种语法,但它应该是非常有效的解决方案。
此外,关于 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
关于上面的代码,我觉得它很不言自明:通过 on
运算符中指定的条件连接 intervals
和 elements
。差不多就是这样。
这里有一点需要注意,start
、end
和 elements
应该都是同一类型,所以如果其中一个是 integer
,它应该首先转换为 numeric
。
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"
受@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
的解决方案 而不是 等同于 Ben 使用 intrval
、David 使用 data.table
的非相等连接,以及我使用 foverlaps
.
的其他解决方案
David Arenburg 提到的非等连接对于理解这是什么一般问题非常有帮助(谢谢!)。我现在可以看到它是 not implemented for dplyr. Thanks to , I see that there is a fuzzyjoin 包,可以用相同的成语来完成。但它几乎没有比我上面的 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
这是一种 "one-liner",它(误)使用了 data.table
包中的 foverlaps
,但 David 的非等值连接仍然更简洁:
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
为了完成,这是另一种方法,使用 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
仅 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
中,如果你咕噜咕噜,
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"
我有一个数字元素向量和一个包含两列的数据框,这些列定义了间隔的起点和终点。数据框中的每一行都是一个间隔。我想找出向量中的每个元素属于哪个区间。
下面是一些示例数据:
# 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
,但我不确定在这种情况下如何使用它。
这是一个可能的解决方案,使用 data.table
(v>=1.9.8) 中的新“non-equi”连接。虽然我怀疑您会喜欢这种语法,但它应该是非常有效的解决方案。
此外,关于 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
关于上面的代码,我觉得它很不言自明:通过 on
运算符中指定的条件连接 intervals
和 elements
。差不多就是这样。
这里有一点需要注意,start
、end
和 elements
应该都是同一类型,所以如果其中一个是 integer
,它应该首先转换为 numeric
。
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"
受@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
的解决方案 而不是 等同于 Ben 使用 intrval
、David 使用 data.table
的非相等连接,以及我使用 foverlaps
.
David Arenburg 提到的非等连接对于理解这是什么一般问题非常有帮助(谢谢!)。我现在可以看到它是 not implemented for dplyr. Thanks to 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
这是一种 "one-liner",它(误)使用了 data.table
包中的 foverlaps
,但 David 的非等值连接仍然更简洁:
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
为了完成,这是另一种方法,使用 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
仅 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
中,如果你咕噜咕噜,
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"