Robbie
Robbie

Reputation: 275

Making a new list by modifying an existing list in R

I'd like to make a new list by modifying an existing list of strings. For an element in the list, eg ..0..fishgold..1..2, I'd like to extract the digits (0, 1, 2), and make a new list in which the string is assigned the values of the extracted digits ("..0..fishgold..1..2" = 0, "..0..fishgold..1..2" = 1, "..0..fishgold..1..2" = 2).

I've made a couple of attempts, see below code, but I can't get my desired result (goal_list).

# Load packages 
library(stringr)

# Load toy data 
df = data.frame("dog..0..brown..1.."=c(1,0,1,7,NA), "cat..0..grey..1..2"=c(5,0,2,1,NA), "..0..fishgold..1..2"=c(1,NA,2,9,NA))

# Existing list
my_list = as.list(colnames(df))


# Attempt 1 function for making new list
my_function = function(old_elem, new_elem){
  new_elem = paste(old_elem, "=", str_extract_all(old_elem, "[:digit:]+"))
  print(new_elem)
}


# Attempt 2 function for making new list
my_function = function(old_elem){
  assign(old_elem, str_extract_all(old_elem, "[:digit:]+"))
  print(old_elem)
}


# New list
new_list= as.list(lapply(my_list, my_function))

# Desired new list 
goal_list = list("dog..0..brown..1.." = 0,
              "dog..0..brown..1.." = 1,
              "cat..0..grey..1..2" = 0,
              "cat..0..grey..1..2" = 1,
              "cat..0..grey..1..2" = 2,
              "..0..fishgold..1..2" = 0,
              "..0..fishgold..1..2" = 1,
              "..0..fishgold..1..2" = 2)

Thanks for any help!

Upvotes: 2

Views: 373

Answers (1)

akrun
akrun

Reputation: 887193

Not clear whether the input should be the my_list. If the intention is to loop over the list (instead of unlisting), then use map, apply the str_extract_all (it can be directly applied on the vector), enframe into a two column tibble, unnest the 'value' column, convert it to integer (as str_extract_all returns a list of strings, deframe the data.frame to a named vector, and convert it to list

library(tidyverse)
out <- map_df(my_list, ~ set_names(str_extract_all(.x, "\\d+"), .x)  %>% 
            enframe %>%        
            unnest(value)  %>%
            mutate(value = as.integer(value))) %>%
       deframe %>% 
       as.list
all.equal(out, goal_list)
#[1] TRUE

Or in another way, use str_count to get the number of digits in the name, replicate the names of the data.frame based on that, extract the digits from the names with str_extract_all, set its names with the replicated names, and convert the vector to a list with as.list

as.list(setNames(as.integer(unlist(str_extract_all(names(df), "\\d+"))), 
              rep(names(df), str_count(names(df), "\\d+"))))

Or using only base R

l1 <- regmatches(names(df), gregexpr("\\d+", names(df)))
as.list(with(transform(stack(setNames(l1, names(df))), 
      values = as.integer(values)), setNames(values, ind)))

NOTE: assign is not needed here as it is to assign values to an identifier object

Upvotes: 1

Related Questions