Tyler Rinker
Tyler Rinker

Reputation: 109844

Apply over nested list names: Sub out character in nested list names

I have lists of unknown structure (nesting) that always terminate with a named vector. I want to substitute all the periods in the list or atomic vector names for an underscore. There's rapply to apply functios to list elements but how do I apply over the list/atomic vector's names? I am after a base R solution but please share all solutions for others.

MWE

x <- list(
    urban = list(
        cars = c('volvo', 'ford'),
        food.dining = list(
            local.business = c('carls'),
            chain.business = c('dennys', 'panera')
        )
    ),
    rural = list(
        land.use = list(
            farming =list(
                dairy = c('cows'),
                vegie.plan = c('carrots')
            )
        ),
        social.rec = list(
            community.center = c('town.square')
        ),
        people.type = c('good', 'bad', 'in.between')
    ),
    other.locales = c('suburban'),
    missing = list(
        unknown = c(),
        known = c()
    ),
    end = c('wow')
)

Desired Outcome

## $urban
## $urban$cars
## [1] "volvo" "ford" 
## 
## $urban$food_dining
## $urban$food_dining$local_business
## [1] "carls"
## 
## $urban$food_dining$chain_business
## [1] "dennys" "panera"
## 
## 
## 
## $rural
## $rural$land_use
## $rural$land_use$farming
## $rural$land_use$farming$dairy
## [1] "cows"
## 
## $rural$land_use$farming$vegie_plan
## [1] "carrots"
## 
## 
## 
## $rural$social_rec
## $rural$social_rec$community_center
## [1] "town.square"
## 
## 
## $rural$people_type
## [1] "good"       "bad"        "in.between"
## 
## 
## $other_locales
## [1] "suburban"
## 
## $missing
## $missing$unknown
## NULL
## 
## $missing$known
## NULL
## 
## 
## $end
## [1] "wow"

Upvotes: 9

Views: 190

Answers (3)

Joris C.
Joris C.

Reputation: 6234

Not a base-R approach, but might still be relevant as this can be done out-of-the-box with rrapply in the rrapply-package (an extension of base-rapply):

x1 <- rrapply::rrapply(
  x,                                                  ## nested list
  f = function(x, .xname) gsub("\\.", "_", .xname),   ## new names
  how = "names"                                       ## replace names instead of content
)

str(x1)
#> List of 5
#>  $ urban        :List of 2
#>   ..$ cars       : chr [1:2] "volvo" "ford"
#>   ..$ food_dining:List of 2
#>   .. ..$ local_business: chr "carls"
#>   .. ..$ chain_business: chr [1:2] "dennys" "panera"
#>  $ rural        :List of 3
#>   ..$ land_use   :List of 1
#>   .. ..$ farming:List of 2
#>   .. .. ..$ dairy     : chr "cows"
#>   .. .. ..$ vegie_plan: chr "carrots"
#>   ..$ social_rec :List of 1
#>   .. ..$ community_center: chr "town.square"
#>   ..$ people_type: chr [1:3] "good" "bad" "in.between"
#>  $ other_locales: chr "suburban"
#>  $ missing      :List of 2
#>   ..$ unknown: NULL
#>   ..$ known  : NULL
#>  $ end          : chr "wow"

Upvotes: 1

Paul
Paul

Reputation: 9087

For list objects, it will rename the list and recursively call the same function for each of its elements. For character objects, it will just return the character.

library('purrr')

fix_names.list <- function(v) {
  names(v) <- gsub('\\.', '_', names(v))
  map(v, fix_names)
}

fix_names.default <- function(v) v

fix_names <- function(v) UseMethod('fix_names')

fix_names(x) %>% str
 # List of 5
 # $ urban        :List of 2
 #  ..$ cars       : chr [1:2] "volvo" "ford"
 #  ..$ food_dining:List of 2
 #  .. ..$ local_business: chr "carls"
 #  .. ..$ chain_business: chr [1:2] "dennys" "panera"
 # $ rural        :List of 3
 #  ..$ land_use   :List of 1
 #  .. ..$ farming:List of 2
 #  .. .. ..$ dairy     : chr "cows"
 #  .. .. ..$ vegie_plan: chr "carrots"
 #  ..$ social_rec :List of 1
 #  .. ..$ community_center: chr "town.square"
 #  ..$ people_type: chr [1:3] "good" "bad" "in.between"
 # $ other_locales: chr "suburban"
 # $ missing      :List of 2
 #  ..$ unknown: NULL
 #  ..$ known  : NULL
 # $ end          : chr "wow"

Upvotes: 4

Florian
Florian

Reputation: 25385

Here is an idea for a recursive function. It first substitutes the periods in the names with underscores. It then checks if the class of an element is list, and if yes, it applies the function on that element. Otherwise, if the class is character, it substitutes the periods in its elements with underscores. Note that this will not work if there are for example data.frames in the list, that would have to be an extension defined in the function as well. Hope this helps!

Function:

my_func <- function(x)
{
    names(x) <- gsub('\\.','_',names(x) )
    for(i in 1:length(x))
    {
      if(any(class(x[[i]])=='list'))
      {
        x[[i]] <- my_func(x[[i]])
      }
    }
    return(x)
}
y <- my_func(x)

Data:

x <- list(
  urban = list(
    cars = c('volvo', 'ford'),
    food.dining = list(
      local.business = c('carls'),
      chain.business = c('dennys', 'panera')
    )
  ),
  rural = list(
    land.use = list(
      farming =list(
        dairy = c('cows'),
        vegie.plan = c('carrots')
      )
    ),
    social.rec = list(
      community.center = c('town.square')
    ),
    people.type = c('good', 'bad', 'in.between')
  ),
  other.locales = c('suburban'),
  missing = list(
    unknown = c(),
    known = c()
  ),
  end = c('wow')
)

Output:

 str(y)

List of 5
 $ urban        :List of 2
  ..$ cars       : chr [1:2] "volvo" "ford"
  ..$ food_dining:List of 2
  .. ..$ local_business: chr "carls"
  .. ..$ chain_business: chr [1:2] "dennys" "panera"
 $ rural        :List of 3
  ..$ land_use   :List of 1
  .. ..$ farming:List of 2
  .. .. ..$ dairy     : chr "cows"
  .. .. ..$ vegie_plan: chr "carrots"
  ..$ social_rec :List of 1
  .. ..$ community_center: chr "town.square"
  ..$ people_type: chr [1:3] "good" "bad" "in.between"
 $ other_locales: chr "suburban"
 $ missing      :List of 2
  ..$ unknown: NULL
  ..$ known  : NULL
 $ end          : chr "wow"

Upvotes: 5

Related Questions