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

Viewed 241

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
3 Answers

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
Related