¿Cómo crear todas las combinaciones de una lista anidada mientras se preserva la estructura usando R?
recursion (4)
Al reunir las excelentes respuestas de Ben Nutzer y Joris Chau , tenemos una manera de crear todas las combinaciones posibles de una lista anidada, independientemente de si algunos componentes de la sublista tienen una longitud desigual.
Poner juntos como una función:
list.combine <- function(input) {
# Create list skeleton.
skeleton <- rapply(input, head, n = 1, how = "list")
# Create storage for the flattened list.
flattened = list()
# Flatten the list.
invisible(rapply(input, function(x) {
flattened <<- c(flattened, list(x))
}))
# Create all possible combinations from list elements.
combinations <- expand.grid(flattened, stringsAsFactors = FALSE)
# Create list for storing the output.
output <- apply(combinations, 1, relist, skeleton = skeleton)
return(output)
}
Nota: Si existe un tipo de carácter en los componentes de la sublista, todo se convertirá en un carácter. Por ejemplo:
# Input list.
l <- list(
a = "string",
b = list(
c = 1:2,
d = 3
)
)
# Applying the function.
o <- list.combine(l)
# View the list:
str(o)
# List of 2
# $ :List of 2
# ..$ a: chr "string"
# ..$ b:List of 2
# .. ..$ c: chr "1"
# .. ..$ d: chr "3"
# $ :List of 2
# ..$ a: chr "string"
# ..$ b:List of 2
# .. ..$ c: chr "2"
# .. ..$ d: chr "3"
Una forma
lenta de
evitar esto es
relist
a
relist
dentro de un ciclo que mantendrá los datos en un marco de datos
1x1
.
Acceder al marco de datos como
df[, 1]
dará un vector de longitud 1 del tipo original como elemento en la lista de entrada.
Por ejemplo:
list.combine()
actualizada.combine
list.combine()
:
list.combine <- function(input) {
# Create list skeleton.
skeleton <- rapply(input, head, n = 1, how = "list")
# Create storage for the flattened list.
flattened = list()
# Flatten the list.
invisible(rapply(input, function(x) {
flattened <<- c(flattened, list(x))
}))
# Create all possible combinations from list elements.
combinations <- expand.grid(flattened, stringsAsFactors = FALSE)
# Create list for storing the output.
output <- list()
# Relist and preserve original data type.
for (i in 1:nrow(combinations)) {
output[[i]] <- retain.element.type(relist(flesh = combinations[i, ], skeleton = skeleton))
}
return(output)
}
Entonces el
retain.element.type()
:
retain.element.type <- function(input.list) {
for (name in names(input.list)) {
# If the element is a list, recall the function.
if(inherits(input.list[[name]], "list")) {
input.list[[name]] <- Recall(input.list[[name]])
# Else, get the first element and preserve the type.
} else {
input.list[[name]] <- input.list[[name]][, 1]
}
}
return(input.list)
}
Ejemplo:
# Input list.
l <- list(
a = "string",
b = list(
c = 1:2,
d = 3
)
)
# Applying the updated function to preserve the data type.
o <- list.combine(l)
# View the list:
str(o)
# List of 2
# $ :List of 2
# ..$ a: chr "string"
# ..$ b:List of 2
# .. ..$ c: int 1
# .. ..$ d: num 3
# $ :List of 2
# ..$ a: chr "string"
# ..$ b:List of 2
# .. ..$ c: int 2
# .. ..$ d: num 3
Dada una lista anidada, ¿cómo crear todas las listas posibles a partir de sus elementos, mientras se preserva la estructura de la lista anidada?
Lista anidada:
l = list(
a = list(
b = 1:2
),
c = list(
d = list(
e = 3:4,
f = 5:6
)
),
g = 7
)
Salida deseada: todas las combinaciones posibles de los elementos de
l
, conservando la estructura, por ejemplo:
# One possible output:
list(
a = list(
b = 1
),
c = list(
d = list(
e = 3,
f = 5
)
),
g = 7
)
# Another possible output:
list(
a = list(
b = 1
),
c = list(
d = list(
e = 4,
f = 5
)
),
g = 7
)
Mi enfoque hasta ahora es:
- aplanar la lista (por ejemplo, como se discutió en esta respuesta )
-
expand.grid()
y obtiene una matriz donde cada fila representa una combinación única -
analizar cada fila de la matriz resultante y reconstruir la estructura a partir de los
names()
usando expresiones regulares
Estoy buscando un enfoque menos engorroso porque no tengo garantía de que los nombres de los elementos de la lista no cambien.
Combinando la brillante respuesta de Ben Nutzer y el brillante comentario de Joris Chau , la respuesta se convertirá en una frase:
apply(expand.grid(data.frame(l)), 1L, relist, skeleton = rapply(l, head, n = 1L, how = "list"))
Crea una lista de listas con tantos elementos como filas devueltas por
expand.grid()
.
El resultado se visualiza mejor con la salida de
str()
:
str(apply(expand.grid(data.frame(l)), 1L, relist, skeleton = rapply(l, head, n = 1L, how = "list")))
List of 16 $ :List of 3 ..$ a:List of 1 .. ..$ b: num 1 ..$ c:List of 1 .. ..$ d:List of 2 .. .. ..$ e: num 3 .. .. ..$ f: num 5 ..$ g: num 7 $ :List of 3 ..$ a:List of 1 .. ..$ b: num 2 ..$ c:List of 1 .. ..$ d:List of 2 .. .. ..$ e: num 3 .. .. ..$ f: num 5 ..$ g: num 7 ... ... ... $ :List of 3 ..$ a:List of 1 .. ..$ b: num 2 ..$ c:List of 1 .. ..$ d:List of 2 .. .. ..$ e: num 4 .. .. ..$ f: num 6 ..$ g: num 7
La función
relist
de
utils
parece estar diseñada para esta tarea:
rl <- as.relistable(l)
r <- expand.grid(data.frame(rl), KEEP.OUT.ATTRS = F)
> head(r, 5)
b c.d.e c.d.f g
1 1 3 5 7
2 2 3 5 7
3 1 4 5 7
4 2 4 5 7
5 1 3 6 7
Guarda la estructura de la lista (
skeleton
).
Esto significa que ahora se pueden manipular los datos dentro de la lista anidada y reasignarlos a la estructura (
flesh
).
Aquí con la primera fila de la matriz expandida.
r <- rep(unname(unlist(r[1,])),each = 2)
l2 <- relist(r, skeleton = rl)
> l2
$a
$a$b
[1] 1 1
$c
$c$d
$c$d$e
[1] 3 3
$c$d$f
[1] 5 5
$g
[1] 7
attr(,"class")
[1] "relistable" "list"
Tenga en cuenta que, dado que la estructura se mantiene igual, necesito proporcionar la misma cantidad de elementos que en la lista original.
Esta es la razón por la que usamos
rep
para repetir el elemento dos veces
Supongo que también se podría llenar con
NA
.
Para cada combinación posible, repita a través de
r
(expandido):
lapply(1:nrow(r), function(x)
relist(rep(unname(unlist(r[x,])),each = 2), skeleton = rl))
Longitudes desiguales de sublista
Aquí hay un enfoque, que se extiende a las respuestas de Uwe y Ben, que también funciona para longitudes arbitrarias de sublistas.
En lugar de llamar a
expand.grid
en
data.frame(l)
, primero aplana
l
a una lista de un solo nivel y luego llama a
expand.grid
en ella:
## skeleton
skel <- rapply(l, head, n = 1L, how = "list")
## flatten to single level list
l.flat <- vector("list", length = length(unlist(skel)))
i <- 0L
invisible(
rapply(l, function(x) {
i <<- i + 1L
l.flat[[i]] <<- x
})
)
## expand all list combinations
l.expand <- apply(expand.grid(l.flat), 1L, relist, skeleton = skel)
str(l.expand)
#> List of 12
#> $ :List of 3
#> ..$ a:List of 1
#> .. ..$ b: num 1
#> ..$ c:List of 1
#> .. ..$ d:List of 2
#> .. .. ..$ e: num 3
#> .. .. ..$ f: num 5
#> ..$ g: num 7
#> ...
#> ...
#> $ :List of 3
#> ..$ a:List of 1
#> .. ..$ b: num 2
#> ..$ c:List of 1
#> .. ..$ d:List of 2
#> .. .. ..$ e: num 4
#> .. .. ..$ f: num 7
#> ..$ g: num 7
Datos
Modifiqué ligeramente la estructura de datos, de modo que los componentes de la sublista
e
y
f
tienen una longitud desigual.
l <- list(
a = list(
b = 1:2
),
c = list(
d = list(
e = 3:4,
f = 5:7
)
),
g = 7
)
## calling data.frame on l does not work
data.frame(l)
#> Error in (function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE, : arguments imply differing number of rows: 2, 3