在子集化后将包含索引号的列表列表映射到标准索引序列

Mapping a list of lists containing index numbers to a standard index sequence after subsetting

本文关键字:索引 列表 映射 标准 子集 包含      更新时间:2023-10-16

我正在使用一个由三个级别分层的数据结构。例如,假设我的结构 D 是

First  Second  Third  Value  Index (let's do 1-indexing)
1       1      1      a      1
1       1      2      a      2
1       1      3      a      3
1       2      1      a      4
1       2      2      a      5
2       1      1      a      6
2       1      2      a      7

我形成了一个嵌套列表结构来索引对应于每个分组结构的组件:

[[1]]
[[1]][[1]]
1 2 3
[[1]][[2]]
4 5 
[[2]]
[[2]][[1]]
6 7

实际上,我的结构 D 是巨大的,在我的上下文中,我将对 D 的随机子集执行操作。我进行随机子集化的方式是对每个不同水平进行分层抽样。例如,假设第一个级别包含 1:1000,第二个级别包含 1:100,第三个级别包含 1:50。我可能会对 50 个第一级标识符、10 个二级标识符和 3 个第三级标识符进行子采样,这可能会给我类似的东西(调用下面的嵌套结构 A(

[[1]]
[[1]][[1]]
2 27 49
[[1]][[2]]
61 80 95
....
[[1]][[10]]
2409  3509   5609
[[2]]
[[2]][[1]]
7092 8091 9039
...

在基于上述索引的子集化之后,比如现在使用结构 D',D' 的索引不再是上面的索引,而是(称为下面的嵌套结构 A'(

[[1]]
[[1]][[1]]
1 2 3
[[1]][[2]]
4 5 6
....
[[1]][[10]]
28 29 30
[[2]]
[[2]][[1]]
31 32 33
...

将 A 映射到 A' 的最有效方法是什么?这样做效率会很好,因为看起来我会多次执行此操作,并且可能是我整个程序中的速率限制步骤。我目前正在使用 Rcpp (C++(,并且可能某些子集小于所需的采样数(例如,一些二级索引可能小于 10,例如 7,在这种情况下,我们只采用所有 7 个标签(。

在直接进入Rcpp之前,我鼓励您考虑如何在基础R中做到这一点。列表、子集化和索引是基础R的基础,可以非常有效。

下面,我们有一个解决您问题的解决方案。您会注意到,它可能更有效,但就目前而言,这是非常简单的方法,并且易于理解。稍后我们将测试效率,然后解决任何问题。

A_prime <- function(A) {
## Generate index vector for all of A
ind <- seq_len(sum(unlist(lapply(A, lengths))))

## Generate the ending index of each vector
endInd <- cumsum(lengths(unlist(A, recursive = F)))

## Use the endInd to create the corresponding start index
startInd <- c(1L, endInd[-length(endInd)] + 1L)

## Create a simple list with the appropriate index vector
A_ind <- mapply(function(s, e) ind[s:e], startInd, endInd, SIMPLIFY = FALSE)

## Again, using the structure of A, we begin creating starting
## and ending indices to replicate the structure of A
A_end <- cumsum(lengths(A))
A_strt <- c(1L, A_end[-length(A_end)] + 1L)

## Create the desired result
lapply(seq_along(A), function(x) A_ind[A_strt[x]:A_end[x]])
}

让我们在 OP 提出的类似问题上对其进行测试:

set.seed(35)
a <- sort(sample(10000, 60))
L1 <- lapply(seq.int(1, 60, 3), function(x) {
a[x:(x + 2)]
})
A <- list(L1[1:10], L1[11:20])
str(A)
List of 2
$ :List of 10
..$ : int [1:3] 4 203 205
..$ : int [1:3] 710 1281 1515
..$ : int [1:3] 1605 1784 1846
..$ : int [1:3] 1904 1993 2425
..$ : int [1:3] 2468 2499 2630
..$ : int [1:3] 2910 2920 3210
..$ : int [1:3] 3360 3464 3469
..$ : int [1:3] 3689 3811 4002
..$ : int [1:3] 4053 4304 4358
..$ : int [1:3] 4433 5290 5862
$ :List of 10
..$ : int [1:3] 6017 6021 6155
..$ : int [1:3] 6250 6370 6414
..$ : int [1:3] 6447 6530 6656
..$ : int [1:3] 6706 6820 6977
..$ : int [1:3] 6986 7148 7338
..$ : int [1:3] 7515 7522 7666
..$ : int [1:3] 7755 7889 7891
..$ : int [1:3] 8071 8143 8487
..$ : int [1:3] 8625 8731 8945
..$ : int [1:3] 8957 9149 9770

这是输出:

str(A_prime(A))
List of 2
$ :List of 10
..$ : int [1:3] 1 2 3
..$ : int [1:3] 4 5 6
..$ : int [1:3] 7 8 9
..$ : int [1:3] 10 11 12
..$ : int [1:3] 13 14 15
..$ : int [1:3] 16 17 18
..$ : int [1:3] 19 20 21
..$ : int [1:3] 22 23 24
..$ : int [1:3] 25 26 27
..$ : int [1:3] 28 29 30
$ :List of 10
..$ : int [1:3] 31 32 33
..$ : int [1:3] 34 35 36
..$ : int [1:3] 37 38 39
..$ : int [1:3] 40 41 42
..$ : int [1:3] 43 44 45
..$ : int [1:3] 46 47 48
..$ : int [1:3] 49 50 51
..$ : int [1:3] 52 53 54
..$ : int [1:3] 55 56 57
..$ : int [1:3] 58 59 60

这看起来还不错!函数A_prime的一个好处是嵌套列表不必是统一的。让我们在一个包含超过 10Mb 数据的非常大的列表上测试一下:

set.seed(123)
big_ind <- sort(sample(1e8, 1e6))
## generate random chunks
endIndBig <- sort(sample(1e6, 1e5))
startIndBig <- c(1L, endIndBig)
endIndBig <- c(endIndBig, 1e6)
A_big_init <- mapply(function(s, e) big_ind[s:e], startIndBig, endIndBig, SIMPLIFY = FALSE)
## generate random chunks for nested lists
A_big_ends <- sort(sample(length(A_big_init), 1e3))
A_big_strts <- c(1L, A_big_ends)
A_big_ends <- c(A_big_ends, length(A_big_init))
A_big <- lapply(seq_along(A_big_ends), function(x) A_big_init[A_big_strts[x]:A_big_ends[x]])

以下是有关A_big的一些摘要信息。如您所见,每个子列表的长度并不统一:

print(object.size(A_big), units = "Mb")
# 10.7 Mb
length(A_big)
# [1] 1001
head(lengths(A_big))
# [1] 159 175  59  69 175  38
tail(lengths(A_big))
# [1]  88   4 225  91  74  59
A_big[[1]][[1]]
# [1]    3   72  722  836  929 1014 1091 1127
A_big[[1]][[159]]
# [1] 170285 170370 170482 170763 170793 170913 170965 171066 171240 171397 171464 171572 171590 171722
# [15] 171898 171903 172196 172284 172298 172590 172696 172698
A_big[[1000]][[2]]
# [1] 99856337 99856415
A_big[[1000]][[74]]
# [1] 99938669 99938699 99938743 99939158 99939664 99939803

现在,对于关键时刻...

system.time(A_prime(A_big))
user  system elapsed 
0.201   0.003   0.203
A_big_prime <- A_prime(A_big)
A_big_prime[[1]][[1]]
# [1] 1 2 3 4 5 6 7 8
A_big_prime[[1]][[159]]
# [1] 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898
# [21] 1899 1900
A_big_prime[[1000]][[2]]
# [1] 1109671 1109672
A_big_prime[[1000]][[74]]
# [1] 1110583 1110584 1110585 1110586 1110587 1110588

这还不错!!如我们所见,映射都是使用基本R正确快速地执行的。