Say, this is my somewhat convoluted and ragged list:
res <- list(
a = TRUE,
b = "error msg 1",
c = list(
TRUE,
"error msg 2"
),
d = list(
e = "error msg 3",
"error msg 4", # no name for this list item just to make things interesting
f = list(
g = list(
h = "error msg 5",
i = TRUE
)
)
)
)
I would now like to, say, apply some function at 2
depth (from the top).
My list can be arbitrarily deep and ragged.
I want to be all cool and tidyvers-y, so I though this would work:
purrr::modify_depth(.x = res, .depth = 2, .f = str, .ragged = TRUE)
But that, unexpectedly, fails with
Error in .x[] <- .f(.x, ...) : replacement has length zero
Can't make heads nor tails of this, because when I str()
my way through all the list elements manually, it works just fine; str()
does always give some result.
I am guessing that I'm using .ragged =
wrong.
I'm also noticing that the same setup works, when using is.null()
as a function, instead of str()
, but is then applied to leaves which don't actually exist (expanding the list).
purrr::modify_depth(.x = res, .depth = 4, .f = is.null, .ragged = TRUE)
This creates a list that is uniformly 4 deep, though the original is actually quite ragged and only 4 deep down 1 branch.
What I'd like to do, is to modify only those list elements for which a n
depth actually exists, and to leave all others unmodified.
How can I get purrr::modify_depth()
to do that?
This is an old question, but since a satisfying answer is missing here it goes.
First, str
displays output to the terminal, but does not actually return anything:
is.null(str("anything"))
#> chr "anything"
#> [1] TRUE
The purrr
-call fails as .f = str
tries to assign NULL
values to the list elements, which is not allowed. The error message in the current purrr version (3.4.0) also states this more clearly:
purrr::modify_depth(.x = res, .depth = 2, .f = str, .ragged = TRUE)
#> logi TRUE
#> Error: Result 1 must be a single logical, not NULL of length 0
Replacing str
by a different function, purrr
no longer complains:
purrr::modify_depth(.x = res, .depth = 2, .f = is.character, .ragged = TRUE)
#> $a
#> [1] FALSE
#>
#> $b
#> [1] "TRUE"
#>
#> $c
#> $c[[1]]
#> [1] FALSE
#>
#> $c[[2]]
#> [1] TRUE
#>
#>
#> $d
#> $d$e
#> [1] TRUE
#>
#> $d[[2]]
#> [1] TRUE
#>
#> $d$f
#> [1] FALSE
However, the issue remains that modify_depth
applies .f
exactly at .depth = 2
so any sublists at deeper levels will be collapsed into a logical value by .f = is.character
Another (non-purrr
) option could be rrapply
in the rrapply
-package (extended version of base-rrapply
) to recurse through a nested list. Setting how = "replace"
we can modify only elements at a specific depth, while keeping all other list elements unmodified:
library(rrapply)
rrapply(res, condition = function(x, .xpos) length(.xpos) == 2, f = function(x) paste(x, "<- modified"), how = "replace")
#> $a
#> [1] TRUE
#>
#> $b
#> [1] "error msg 1"
#>
#> $c
#> $c[[1]]
#> [1] "TRUE <- modified"
#>
#> $c[[2]]
#> [1] "error msg 2 <- modified"
#>
#>
#> $d
#> $d$e
#> [1] "error msg 3 <- modified"
#>
#> $d[[2]]
#> [1] "error msg 4 <- modified"
#>
#> $d$f
#> $d$f$g
#> $d$f$g$h
#> [1] "error msg 5"
#>
#> $d$f$g$i
#> [1] TRUE
Here, condition
decides to which list elements the f
function is applied and the .xpos
argument evaluates to the position of the element in the nested list as an integer vector. length(.xpos)
can then be used to evaluate the depth of any element in the nested list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With