在数据框中查找向量的每个元素所属的区间行

时间:2016-12-13 23:03:37

标签: r dataframe intervals

我有一个数字元素向量,以及一个带有两列的数据框,用于定义区间的起点和终点。数据帧中的每一行都是一个间隔。我想找出向量中每个元素属于哪个区间。

以下是一些示例数据:

# 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,但我不确定在这种情况下如何使用它。

7 个答案:

答案 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运算符中指定的条件加入elementson。几乎就是这样。

虽然有一点需要注意,startendelements应该都是相同的类型,所以如果其中一个是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"

警告 cutfindInterval有左开的时间间隔。因此,使用cutfindInterval的解决方案 等同于使用intrval的Ben,使用data.table的David的非equi连接,以及我的其他解决方案使用foverlaps

答案 3 :(得分:4)

David Arenburg提到非equi连接对于理解这是什么样的问题非常有帮助(谢谢!)。我现在可以看到它是not implemented for dplyr。感谢this answer,我发现有一个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

答案 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