如何使用 R 中另一个数据帧的起始值和结束值对数据帧中的 windows 进行子集化?

How to subset windows in a dataframe using start- and end-values from another dataframe in R?

我有一个时间序列数据的数据框,df1,我需要从 R 中提取一些 'windows'。windows 的起点和终点我需要在一个单独的数据框的两列中,df2。起点和终点的值对应于 windows 所需的行号。

在下面的示例中,我是解决方案的一部分,但目前只提取了第一个 window。我如何修改此示例以提取所有四个 windows?这可能是 purrr 的情况吗?

library(tidyverse)

# dataframe of data to subset
df1 <- tibble(my_values = rnorm(100))

# dataframe of windows (i.e. row number IDs) to extract from data
df2 <-tibble::tribble(
        ~window_start, ~window_end,
                   3L,         10L,
                  21L,         25L,
                  52L,         63L,
                  78L,         90L
        )

# extracted data
df3 <- df1 %>% 
  slice(df2$window_start : df2$window_end)

(注意。我知道这里有一个类似的问题 - - 但我的实际数据非常大,我很好奇非基于合并的解决方案是否会更快。)

也许用 purrr::map2

试试这个方法
# dataframe of data to subset
df1 <- tibble(my_values = rnorm(100, mean = 45, sd = 30) %>% abs())

# dataframe of windows (i.e. row number IDs) to extract from data
df2 <-tibble::tribble(
  ~window_start, ~window_end,
  3L,         10L,
  21L,         25L,
  52L,         63L,
  78L,         90L
)

subset_thats_in <- function(mini, maxi){
  df1 %>% 
    filter(between(my_values, mini, maxi))
}

purrr::map2(df2$window_start, 
            df2$window_end, 
            subset_thats_in)
[[1]]
# A tibble: 4 × 1
  my_values
      <dbl>
1      6.47
2      8.69
3      7.73
4      7.35

[[2]]
# A tibble: 12 × 1
   my_values
       <dbl>
 1      24.2
 2      22.9
 3      22.4
 4      24.4
 5      22.6
 6      21.7
 7      23.2
 8      21.3
 9      23.3
10      21.1
11      23.5
12      22.6

[[3]]
# A tibble: 10 × 1
   my_values
       <dbl>
 1      54.0
 2      61.4
 3      62.5
 4      60.8
 5      60.5
 6      55.5
 7      61.4
 8      59.0
 9      57.9
10      53.3

[[4]]
# A tibble: 6 × 1
  my_values
      <dbl>
1      87.8
2      79.1
3      80.5
4      82.7
5      85.2
6      80.6

purrr 对于这种数据转换来说是非常有效的内存。但是,如果要复制数据,长度为 10000 的列表可能仍然很笨重。

x = vector(mode = "list", 10000L)

x = purrr::transpose(df2) |> lapply(function(x) df1[x[1]:x[2],])

as.numeric on a transposed list gets the range, which can be used to subset df1.
对于较大的集合,尝试矢量化方法可能会有用。下面是基本 R 选项,调整 SIMPLIFY = TRUE 以将其简化为向量,以防您使用单列。

f = Vectorize(\(x, y) df1[seq.int(x, y),], SIMPLIFY = F)
f(df2[[1]], df2[[2]])

我们可以使用map2

library(tidyverse)

map2(df2[[1]], df2[[2]], ~ df1[.x:.y, ])
#> [[1]]
#> # A tibble: 8 × 1
#>   my_values
#>       <dbl>
#> 1   1.33   
#> 2   1.27   
#> 3   0.415  
#> 4  -1.54   
#> 5  -0.929  
#> 6  -0.295  
#> 7  -0.00577
#> 8   2.40   
#> 
#> [[2]]
#> # A tibble: 5 × 1
#>   my_values
#>       <dbl>
#> 1   -0.224 
#> 2    0.377 
#> 3    0.133 
#> 4    0.804 
#> 5   -0.0571
#> 
#> [[3]]
#> # A tibble: 12 × 1
#>    my_values
#>        <dbl>
#>  1   -0.377 
#>  2    2.44  
#>  3   -0.795 
#>  4   -0.0549
#>  5    0.250 
#>  6    0.618 
#>  7   -0.173 
#>  8   -2.22  
#>  9   -1.26  
#> 10    0.359 
#> 11   -0.0110
#> 12   -0.941 
#> 
#> [[4]]
#> # A tibble: 13 × 1
#>    my_values
#>        <dbl>
#>  1    -0.118
#>  2    -0.912
#>  3    -1.44 
#>  4    -0.797
#>  5     1.25 
#>  6     0.772
#>  7    -0.220
#>  8    -0.425
#>  9    -0.419
#> 10     0.997
#> 11    -0.276
#> 12     1.26 
#> 13     0.647

或者创造性地dplyr。

df2 %>%
  rowwise() %>%
  transmute(windows = list(c_across(starts_with("window")) %>% {df1[.[[1]]:.[[2]], ]}))
#> # A tibble: 4 × 1
#> # Rowwise: 
#>   windows          
#>   <list>           
#> 1 <tibble [8 × 1]> 
#> 2 <tibble [5 × 1]> 
#> 3 <tibble [12 × 1]>
#> 4 <tibble [13 × 1]>

reprex package (v2.0.1)

创建于 2022-01-09

数据:

set.seed(0)

# dataframe of data to subset
df1 <- tibble(my_values = rnorm(100))

# dataframe of windows (i.e. row number IDs) to extract from data
df2 <- tibble::tribble(
  ~window_start, ~window_end,
  3L, 10L,
  21L, 25L,
  52L, 63L,
  78L, 90L
)

你可以使用 mapply:

df1[unlist(mapply(function(x,y) x:y, df2$window_start, df2$window_end)),]

# A tibble: 38 x 1
   my_values
       <dbl>
 1     0.671
 2    -0.617
 3    -0.354
 4     2.76 
 5     0.382
 6    -0.488
 7     0.889
 8    -1.32 
 9     0.328
10     0.779
# ... with 28 more rows

一个简单的基础 R 解决方案是使用 sequence 函数生成您需要对数据框进行子集化的所有行索引。 sequence 的第一个参数指定要生成的序列的长度,每个序列从第二个参数中给定的数字开始。这应该是非常有效的,因为该函数唯一做的就是创建一个整数序列。

df1[sequence(df2$window_end - df2$window_start + 1L, df2$window_start), ]

输出

> set.seed(1234L)
> df1 <- tibble(my_values = rnorm(100))
> df1[sequence(df2$window_end - df2$window_start + 1L, df2$window_start), ]
# A tibble: 38 x 1
   my_values
       <dbl>
 1     1.08 
 2    -2.35 
 3     0.429
 4     0.506
 5    -0.575
 6    -0.547
 7    -0.564
 8    -0.890
 9     0.134
10    -0.491
# ... with 28 more rows

您还可以通过使用 data.table:::vecseq 获得较小的性能改进。代码与上面非常相似:

df1[data.table:::vecseq(df2$window_start, df2$window_end - df2$window_start + 1L, NULL), ]

延伸阅读: