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