Compute means across a list on R - r

I have a list with the following structure:
> str(test)
List of 5
$ :List of 2
..$ : num [1:4] 0.0544 0.0839 0.0486 0.043
..$ : num [1:4] 0.0799 0.2434 0.0373 0.2166
$ :List of 2
..$ : num [1:6] 0.938 1.047 1.022 0.689 0.39 ...
..$ : num [1:6] 0.871 0.25 0.824 0.664 0.481 ...
$ :List of 2
..$ : num [1:4] 0.000598 0.000923 0.000535 0.000473
..$ : num [1:4] 0.001039 0.003164 0.000485 0.002816
$ :List of 2
..$ : num [1:6] 0.01032 0.01152 0.01124 0.00758 0.00429 ...
..$ : num [1:6] 0.01133 0.00325 0.01071 0.00864 0.00625 ...
$ :List of 2
..$ : num -0.659
..$ : num -0.962
I want to compute the mean of each entry, i.e. for the first entry I want:
x <- mean(c(0.0544, 0.0839, 0.0486, 0.043))
y <- mean(c(0.0799, 0.2434, 0.0373, 0.2166))
And finally the mean of those two results = mean(c(x,y).
What is the most efficient way to do this?

Sample data - one (parent) list of (child) lists.
set.seed(1)
foo <- list(
list(runif(4),runif(4)),
list(runif(6),runif(6))
)
#> [[1]]
#> [[1]][[1]]
#> [1] 0.2655087 0.3721239 0.5728534 0.9082078
#>
#> [[1]][[2]]
#> [1] 0.2016819 0.8983897 0.9446753 0.6607978
#>
#>
#> [[2]]
#> [[2]][[1]]
#> [1] 0.62911404 0.06178627 0.20597457 0.17655675 0.68702285 0.38410372
#>
#> [[2]][[2]]
#> [1] 0.7698414 0.4976992 0.7176185 0.9919061 0.3800352 0.7774452
Get the means of each child list:
foo_means <- lapply(foo, lapply, mean)
#> [[1]]
#> [[1]][[1]]
#> [1] 0.5296734
#>
#> [[1]][[2]]
#> [1] 0.6763862
#>
#>
#> [[2]]
#> [[2]][[1]]
#> [1] 0.3574264
#>
#> [[2]][[2]]
#> [1] 0.6890909
To get the average of (child) averages for each (parent) list element:
lapply(foo_means, function(x) mean(unlist(x)))
#> [[1]]
#> [1] 0.6030298
#>
#> [[2]]
#> [1] 0.5232587

The first question appears to be a perfect use for rapply. Using the same sample data as provided above in #ngm's answer, you can do:
foo2 <- rapply(foo, mean, how = "replace")
foo2
#[[1]]
#[[1]][[1]]
#[1] 0.5296734
#
#[[1]][[2]]
#[1] 0.6763862
#
#
#[[2]]
#[[2]][[1]]
#[1] 0.3574264
#
#[[2]][[2]]
#[1] 0.6890909

Since the elements of your list are also lists, then use some nested lapply's to get the means of each list.
lapply(test, function(x) lapply(x, mean))
If you like a matrix as an output, you can do
sapply(test, function(x) sapply(x, mean))
With the sapply, you can get the means of each pair with
colMeans(sapply(test, function(x) sapply(x, mean, na.rm = TRUE)))

This has been answered many times.
It is as simple as
mean(unlist(lapply(mylist,sapply,mean)))
Follow this link.
Grouping functions (tapply, by, aggregate) and the *apply family

You can also use modify_depth function from the purrr package. Take example posted by #ngm
library(purrr)
set.seed(1)
foo <- list(list(runif(4), runif(4)),
list(runif(6), runif(6)))
modify_depth(foo, 2, mean)
#> [[1]]
#> [[1]][[1]]
#> [1] 0.5296734
#>
#> [[1]][[2]]
#> [1] 0.6763862
#>
#>
#> [[2]]
#> [[2]][[1]]
#> [1] 0.3574264
#>
#> [[2]][[2]]
#> [1] 0.6890909
Edit: to calculate the mean of all elements in the 1st level of that list
modify_depth(foo, 2, mean) %>%
map(flatten_dbl) %>%
map_dbl(mean)
modify_depth(foo, 2, mean) %>%
simplify_all() %>%
map_dbl(mean)
[1] 0.60303 0.52326
Created on 2018-04-20 by the reprex package (v0.2.0).

Related

Get an element from a vector with its attribute in R

gregexpr returns a list containing a vector with some additional data:
[[1]]
[1] 21 136 409 512 587 693
attr(,"match.length")
[1] 3 4 5 5 4 9
How do I extract just one element with a corresponding attribute at once?
[[1]]
[1] 409
attr(,"match.length")
[1] 5
UPD: The final object must be compatible with regmatches function.
In general, there's no way for R to know that elements of the vector correspond 1-1 with elements of one of its attributes.
If you know this is true (as it is with gregexpr results), then the way to tell R about it is to set a class on the object, and write your own subsetting code. For example,
`[.gregexpr_result` <- function(x, i) {
attrs <- lapply(x, function(element) {
allattrs <- attributes(element)
allattrs[["match.length"]] <- allattrs[["match.length"]][i]
allattrs
})
x <- lapply(x, `[`, i)
for (j in seq_along(x))
attributes(x[[j]]) <- attrs[[j]]
x
}
x <- paste(letters[1:2], letters[1:2])
result <- gregexpr("b", x)
class(result) <- "gregexpr_result"
result
#> [[1]]
#> [1] -1
#> attr(,"match.length")
#> [1] -1
#> attr(,"index.type")
#> [1] "chars"
#> attr(,"useBytes")
#> [1] TRUE
#>
#> [[2]]
#> [1] 1 3
#> attr(,"match.length")
#> [1] 1 1
#> attr(,"index.type")
#> [1] "chars"
#> attr(,"useBytes")
#> [1] TRUE
#>
#> attr(,"class")
#> [1] "gregexpr_result"
result[2]
#> [[1]]
#> [1] NA
#> attr(,"match.length")
#> [1] NA
#> attr(,"index.type")
#> [1] "chars"
#> attr(,"useBytes")
#> [1] TRUE
#>
#> [[2]]
#> [1] 3
#> attr(,"match.length")
#> [1] 1
#> attr(,"index.type")
#> [1] "chars"
#> attr(,"useBytes")
#> [1] TRUE
Created on 2022-11-20 with reprex v2.0.2
We may do
out <- lapply(lst1, `[`, 3)
attr(out, "match.length") <- attr(lst1, "match.length")[3]
-output
> out
[[1]]
[1] 409
attr(,"match.length")
[1] 5
data
lst1 <- structure(list(c(21, 136, 409, 512, 587, 693)),
match.length = c(3,
4, 5, 5, 4, 9))

Convert a string with curly brackets to a nested list object

We have a string with curly brackets to represent nested lists:
x <- "{{1,2,3,4},{1,2,3,4,{Axe,Bat,Cat,Donkey}},{1,2,3,4}}"
How can we convert it to a nested list R object?
So far I have below solution, it is not ideal. The idea is to convert it to a valid JSON format, then use jsonlite::fromJSON. I would like to skip/improve quoting step, so that numbers read are numeric. Yes, I could loop through the nested list and convert "numbers" to numeric, but would like to avoid.
Any other non-JSON solutions are welcome, too.
library(jsonlite)
# translate brackets to JSON square brackets
x <- chartr("{}", "[]", x)
# wrap in quotes
x <- gsub("(\\w+)", '"\\1"', x)
# finally read as JSON
fromJSON(x)
# [[1]]
# [1] "1" "2" "3" "4"
#
# [[2]]
# [[2]][[1]]
# [1] "1"
#
# [[2]][[2]]
# [1] "2"
#
# [[2]][[3]]
# [1] "3"
#
# [[2]][[4]]
# [1] "4"
#
# [[2]][[5]]
# [1] "Axe" "Bat" "Cat" "Donkey"
#
#
# [[3]]
# [1] "1" "2" "3" "4"
This solution does not use any packages. It replaces innermost lists with c(...) and then quotes the words that start with a letter and replaces { and } with list( and ) respectively. Then it parses and evaluates that to get the result.
x1 <- gsub("\\{([^{]+)\\}", "c(\\1)", x) # x is from question
x2 <- gsub("([a-zA-Z]\\w+)", "'\\1'", x1)
x3 <- gsub("\\{", "list(", x2)
x4 <- gsub("\\}", ")", x3)
result <- eval(parse(text = x4))
str(result)
giving:
List of 3
$ : num [1:4] 1 2 3 4
$ :List of 5
..$ : num 1
..$ : num 2
..$ : num 3
..$ : num 4
..$ : chr [1:4] "Axe" "Bat" "Cat" "Donkey"
$ : num [1:4] 1 2 3 4
This could also be written as a pipeline using magrittr:
library(magrittr)
x %>%
gsub("\\{([^{]+)\\}", "c(\\1)", .) %>%
gsub("([a-zA-Z]\\w+)", "'\\1'", .) %>%
gsub("\\{", "list(", .) %>%
gsub("\\}", ")", .) %>%
parse(text = .) %>%
eval
You could do:
jsonlite::fromJSON(txt=gsub('([A-Za-z]+)','"\\1"',chartr('{}','[]',x)))
[[1]]
[1] 1 2 3 4
[[2]]
[[2]][[1]]
[1] 1
[[2]][[2]]
[1] 2
[[2]][[3]]
[1] 3
[[2]][[4]]
[1] 4
[[2]][[5]]
[1] "Axe" "Bat" "Cat" "Donkey"
[[3]]
[1] 1 2 3 4
Or you could use alpha ie:
jsonlite::fromJSON(txt=gsub('([[:alpha:]]+)','"\\1"',chartr('{}','[]',x)))
The issue here is to obtain a good regex such that it does not include the numeric elements.

Change every element of every list (nested lists) using purrr

I have hundreds of observations of census data - each feature is stored within a list with the name census. I am trying to perform an action
a) on all elements of all lists: I want to make all non character elements numeric.
b) a named element present within each list: I want to remove a prefix from a named column in every list
A toy example below.
Census is a nested list within a list
library(tidyverse)
library(purrr)
POA_CODE = c("POA101","POA102")
dogs = c(4,4)
cats = c(3,2)
children = c(0, 1)
salary = c(100, 120)
employed.prop = c(1,0.5)
pets <- list(POA_CODE, as.integer(dogs), as.integer(cats))
children <-list(POA_CODE, as.integer(children))
employment <-list(POA_CODE, salary, employed.prop)
census <- list(pets, children, employment)
Attempt to change all non-numeric elements in every list to numeric
#change all non-numeric elements to numeric
census_num <- census %>%
map(function(x){
ifelse(is.character == TRUE, x,
as.numeric(x))}
)
I get the following error message:
Error in is.character == TRUE :
comparison (1) is possible only for atomic and list types
Attempt to remove prefix from every postcode in census[[]]$'POA_CODE'
#Remove "POA" prefix from every postcode
census_code <- pmap(census, ~.x[["POA_CODE"]],function(x){
str_replace(POA_CODE,"POA","")
})
I get the error
Error: Element 2 of `.l` must have length 1 or 3, not 2
You have a nested list, so you need nested maps :
library(purrr)
map(census, function(x) map_if(x, is.character, ~as.numeric(sub('POA', '', .x))))
#[[1]]
#[[1]][[1]]
#[1] 101 102
#[[1]][[2]]
#[1] 4 4
#[[1]][[3]]
#[1] 3 2
#[[2]]
#[[2]][[1]]
#[1] 101 102
#[[2]][[2]]
#[1] 0 1
#[[3]]
#[[3]][[1]]
#[1] 101 102
#[[3]][[2]]
#[1] 100 120
#[[3]][[3]]
#[1] 1.0 0.5
In base R, we can solve it with nested lapply :
lapply(census, function(x) lapply(x, function(y)
if(is.character(y)) as.numeric(sub('POA', '', y)) else y))
You could use rapply() in base R:
rapply(
census,
function(x) if(is.character(x)) as.numeric(sub("^\\D+","", x)) else x,
how = "replace")
#> [[1]]
#> [[1]][[1]]
#> [1] 101 102
#>
#> [[1]][[2]]
#> [1] 4 4
#>
#> [[1]][[3]]
#> [1] 3 2
#>
#>
#> [[2]]
#> [[2]][[1]]
#> [1] 101 102
#>
#> [[2]][[2]]
#> [1] 0 1
#>
#>
#> [[3]]
#> [[3]][[1]]
#> [1] 101 102
#>
#> [[3]][[2]]
#> [1] 100 120
#>
#> [[3]][[3]]
#> [1] 1.0 0.5
or purrr::map_depth()
library(purrr)
map_depth(census, 2, ~if(is.character(.)) as.numeric(sub("^\\D+","", .)) else .)
#> [[1]]
#> [[1]][[1]]
#> [1] 101 102
#>
#> [[1]][[2]]
#> [1] 4 4
#>
#> [[1]][[3]]
#> [1] 3 2
#>
#>
#> [[2]]
#> [[2]][[1]]
#> [1] 101 102
#>
#> [[2]][[2]]
#> [1] 0 1
#>
#>
#> [[3]]
#> [[3]][[1]]
#> [1] 101 102
#>
#> [[3]][[2]]
#> [1] 100 120
#>
#> [[3]][[3]]
#> [1] 1.0 0.5
We can use rrapply with parse_number
library(rrapply)
library(readr)
rrapply(census, f = function(x) if(is.character(x)) readr::parse_number(x) else x)
#[[1]]
#[[1]][[1]]
#[1] 101 102
#[[1]][[2]]
#[1] 4 4
#[[1]][[3]]
#[1] 3 2
#[[2]]
#[[2]][[1]]
#[1] 101 102
#[[2]][[2]]
#[1] 0 1
#[[3]]
#[[3]][[1]]
#[1] 101 102
#[[3]][[2]]
#[1] 100 120
#[[3]][[3]]
#[1] 1.0 0.5

Equalizing the lengths of all the lists within a list?

I have a list of lists and I want the sub-lists to all have the same length
i.e. to pad them with NAs if needed so they all reach the length of the longest list.
Mock example
list1 <- list(1, 2, 3)
list2 <- list(1, 2, 3, 4, 5)
list3 <- list(1, 2, 3, 4, 5, 6)
list_lists <- list(list1, list2, list3)
My best attempt yet
max_length <- max(unlist(lapply (list_lists, FUN = length)))
# returns the length of the longest list
list_lists <- lapply (list_lists, function (x) length (x) <- max_length)
Problem, it is replacing all my sub-lists into an integer = max_length...
list_lists [[1]]
> [1] 6
Can someone help?
Try this (where ls is your list):
lapply(lapply(sapply(ls, unlist), "length<-", max(lengths(ls))), as.list)
In lists, NULL would seem more appropriate than NA, and could be added with vector:
list_lists <- list(list(1, 2, 3),
list(1, 2, 3, 4, 5),
list(1, 2, 3, 4, 5, 6))
list_lists2 <- Map(function(x, y){c(x, vector('list', length = y))},
list_lists,
max(lengths(list_lists)) - lengths(list_lists))
str(list_lists2)
#> List of 3
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : NULL
#> ..$ : NULL
#> ..$ : NULL
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : num 4
#> ..$ : num 5
#> ..$ : NULL
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : num 4
#> ..$ : num 5
#> ..$ : num 6
If you really want NAs, just change vector to rep:
list_lists3 <- Map(function(x, y){c(x, rep(NA, y))},
list_lists,
max(lengths(list_lists)) - lengths(list_lists))
str(list_lists3)
#> List of 3
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : logi NA
#> ..$ : logi NA
#> ..$ : logi NA
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : num 4
#> ..$ : num 5
#> ..$ : logi NA
#> $ :List of 6
#> ..$ : num 1
#> ..$ : num 2
#> ..$ : num 3
#> ..$ : num 4
#> ..$ : num 5
#> ..$ : num 6
Note the types in the latter won't match up unless you specify NA_real_ or coerce NA to match the type of x.
Here is your code fixed.
The function should return x, not length(x).
Also, I used vectors, not lists for clarity.
list1 <- c(1, 2, 3)
list2 <- c(1, 2, 3, 4, 5)
list3 <- c(1, 2, 3, 4, 5, 6)
list_lists <- list(list1, list2, list3)
max_length <- max(unlist(lapply (list_lists, FUN = length)))
list_lists <- lapply (list_lists, function (x) {length (x) <- max_length;x})
# [[1]]
# [1] 1 2 3 NA NA NA
#
# [[2]]
# [1] 1 2 3 4 5 NA
#
# [[3]]
# [1] 1 2 3 4 5 6
For original lists the result is:
# [[1]]
# [[1]][[1]]
# [1] 1
#
# [[1]][[2]]
# [1] 2
#
# [[1]][[3]]
# [1] 3
#
# [[1]][[4]]
# NULL
#
# [[1]][[5]]
# NULL
#
# [[1]][[6]]
# NULL
#
#
# [[2]]
# [[2]][[1]]
# [1] 1
#
# [[2]][[2]]
# [1] 2
#
# [[2]][[3]]
# [1] 3
#
# [[2]][[4]]
# [1] 4
#
# [[2]][[5]]
# [1] 5
#
# [[2]][[6]]
# NULL
#
#
# [[3]]
# [[3]][[1]]
# [1] 1
#
# [[3]][[2]]
# [1] 2
#
# [[3]][[3]]
# [1] 3
#
# [[3]][[4]]
# [1] 4
#
# [[3]][[5]]
# [1] 5
#
# [[3]][[6]]
# [1] 6
Try this:
funJoeOld <- function(ls) {
list_length <- sapply(ls, length)
max_length <- max(list_length)
lapply(seq_along(ls), function(x) {
if (list_length[x] < max_length) {
c(ls[[x]], lapply(1:(max_length - list_length[x]), function(y) NA))
} else {
ls[[x]]
}
})
}
funJoeOld(list_lists)[[1]]
[[1]]
[1] 1
[[2]]
[1] 2
[[3]]
[1] 3
[[4]]
[1] NA
[[5]]
[1] NA
[[6]]
[1] NA
Edit
Just wanted to illuminate how using the right tools in R makes a huge difference. Although my solution gives correct results, it is very inefficient. By replacing sapply(ls, length) with lengths as well as lapply(1:z, function(y) NA) with as.list(rep(NA, z)), we obtain almost a 15x speed up. Observe:
funJoeNew <- function(ls) {
list_length <- lengths(ls)
max_length <- max(list_length)
lapply(seq_along(ls), function(x) {
if (list_length[x] < max_length) {
c(ls[[x]], as.list(rep(NA, max_length - list_length[x])))
} else {
ls[[x]]
}
})
}
funAlistaire <- function(ls) {
Map(function(x, y){c(x, rep(NA, y))},
ls,
max(lengths(ls)) - lengths(ls))
}
fun989 <- function(ls) {
lapply(lapply(sapply(ls, unlist), "length<-", max(lengths(ls))), as.list)
}
Compare equality
set.seed(123)
samp_list <- lapply(sample(1000, replace = TRUE), function(x) {lapply(1:x, identity)})
## have to unlist as the NAs in 989 are of the integer
## variety and the NAs in Joe/Alistaire are logical
identical(sapply(fun989(samp_list), unlist), sapply(funJoeNew(samp_list), unlist))
[1] TRUE
identical(funJoeNew(samp_list), funAlistaire(samp_list))
[1] TRUE
Benchmarks
microbenchmark(funJoeOld(samp_list), funJoeNew(samp_list), fun989(samp_list),
funAlistaire(samp_list), times = 30, unit = "relative")
Unit: relative
expr min lq mean median uq max neval cld
funJoeOld(samp_list) 21.825878 23.269846 17.434447 20.803035 18.851403 4.8056784 30 c
funJoeNew(samp_list) 1.827741 1.841071 2.253294 1.667047 1.780324 2.4659653 30 ab
fun989(samp_list) 3.108230 3.563780 3.170320 3.790048 3.888632 0.9890681 30 b
funAli(samp_list) 1.000000 1.000000 1.000000 1.000000 1.000000 1.0000000 30 a
There are two take aways here:
Having a good understanding of the apply family of functions makes for
concise and efficient code (as can be seen in #alistaire's and #989's solution).
Understanding the nuances of base R in general can have considerable consequences
Not sure if you are you looking for this and you may use lengths function for lists:
list_lists <- list(unlist(list1), unlist(list2), unlist(list3))
list_lists1 <- lapply(list_lists, `length<-`, max(lengths(list_lists)))
list_lists1
> list_lists1
[[1]]
[1] 1 2 3 NA NA NA
[[2]]
[1] 1 2 3 4 5 NA
[[3]]
[1] 1 2 3 4 5 6
OR for lists of the lists, you can go one step further:
list_lists2 <- lapply(list_lists1,as.list)
> list_lists2
[[1]]
[[1]][[1]]
[1] 1
[[1]][[2]]
[1] 2
[[1]][[3]]
[1] 3
[[1]][[4]]
[1] NA
[[1]][[5]]
[1] NA
[[1]][[6]]
[1] NA
[[2]]
[[2]][[1]]
[1] 1
[[2]][[2]]
[1] 2
[[2]][[3]]
[1] 3
[[2]][[4]]
[1] 4
[[2]][[5]]
[1] 5
[[2]][[6]]
[1] NA
[[3]]
[[3]][[1]]
[1] 1
[[3]][[2]]
[1] 2
[[3]][[3]]
[1] 3
[[3]][[4]]
[1] 4
[[3]][[5]]
[1] 5
[[3]][[6]]
[1] 6
>

How to manipulate NULL elements in a nested list?

I have a nested list containing NULL elements, and I'd like to replace those with something else. For example:
l <- list(
NULL,
1,
list(
2,
NULL,
list(
3,
NULL
)
)
)
I want to replace the NULL elements with NA. The natural way to do this is to recursively loop over the list using rapply. I tried:
rapply(l, function(x) NA, classes = "NULL", how = "replace")
rapply(l, function(x) if(is.null(x)) NA else x, how = "replace")
Unfortunately, neither of these methods work, since rapply apparently ignores NULL elements.
How can I manipulate the NULL elements in a nested list?
I'm going to go with "use a version of rapply doesn't doesn't have weird behaviour with NULL". This is the simplest implementation I can think of:
simple_rapply <- function(x, fn)
{
if(is.list(x))
{
lapply(x, simple_rapply, fn)
} else
{
fn(x)
}
}
(rawr::rapply2, as mentioned in the comments by #rawr is a more sophisticated attempt.)
Now I can do the replacement using
simple_rapply(l, function(x) if(is.null(x)) NA else x)
This is what William Dunlap suggested in 2010 when this question was asked on Rhelp:
replaceInList <- function (x, FUN, ...)
{
if (is.list(x)) {
for (i in seq_along(x)) {
x[i] <- list(replaceInList(x[[i]], FUN, ...))
}
x
}
else FUN(x, ...)
}
replaceInList(l, function(x)if(is.null(x))NA else x)
This is a hack, but as far as hacks go, I think I'm somewhat happy with it.
lna <- eval(parse(text = gsub("NULL", "NA", deparse(l))))
str(lna)
#> List of 3
#> $ : logi NA
#> $ : num 1
#> $ :List of 3
#> ..$ : num 2
#> ..$ : logi NA
#> ..$ :List of 2
#> .. ..$ : num 3
#> .. ..$ : logi NA
Update:
If for some reason you needed "NULL" as a character entry in the list (corner case, much?) you can still use the above hack since it replaces the contents of the string, not the quotes, thus it just requires another step
l2 <- list(
NULL,
1,
list(
2,
"NULL",
list(
3,
NULL
)
)
)
lna2 <- eval(parse(text = gsub("NULL", "NA", deparse(l2))))
lna2_2 <- eval(parse(text = gsub('\\"NA\\"', '\"NULL\"', deparse(lna2))))
str(lna2_2)
#> List of 3
#> $ : logi NA
#> $ : num 1
#> $ :List of 3
#> ..$ : num 2
#> ..$ : chr "NULL"
#> ..$ :List of 2
#> .. ..$ : num 3
#> .. ..$ : logi NA
I wrapped the replacement inside the sapply, which makes it more readable/understandable to me, albeit less general.
replace_null <- function(x) {
lapply(x, function(x) {
if (is.list(x)){
replace_null(x)
} else{
if(is.null(x)) NA else(x)
}
})
}
replace_null(l)
This can also be done with rrapply() in the rrapply-package. Below are a few different ways we could replace the NULL elements in a nested list by NA values:
library(rrapply)
l <- list(
NULL,
1,
list(
2,
NULL,
list(
3,
NULL
)
)
)
## replace NULL by NA using only f
rrapply(l, f = function(x) if(is.null(x)) NA else x, how = "replace")
#> [[1]]
#> [1] NA
#>
#> [[2]]
#> [1] 1
#>
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#>
#> [[3]][[2]]
#> [1] NA
#>
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#>
#> [[3]][[3]][[2]]
#> [1] NA
## replace NULL by NA using condition argument
rrapply(l, condition = is.null, f = function(x) NA, how = "replace")
#> [[1]]
#> [1] NA
#>
#> [[2]]
#> [1] 1
#>
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#>
#> [[3]][[2]]
#> [1] NA
#>
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#>
#> [[3]][[3]][[2]]
#> [1] NA
## replace NULL by NA using condition and deflt arguments
rrapply(l, condition = Negate(is.null), deflt = NA, how = "list")
#> [[1]]
#> [1] NA
#>
#> [[2]]
#> [1] 1
#>
#> [[3]]
#> [[3]][[1]]
#> [1] 2
#>
#> [[3]][[2]]
#> [1] NA
#>
#> [[3]][[3]]
#> [[3]][[3]][[1]]
#> [1] 3
#>
#> [[3]][[3]][[2]]
#> [1] NA
We can also prune the NULL elements from the list altogether by setting how = "prune":
## keep only non-NULL elements
rrapply(l, condition = Negate(is.null), how = "prune")
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [[2]][[1]]
#> [1] 2
#>
#> [[2]][[2]]
#> [[2]][[2]][[1]]
#> [1] 3

Resources