EJJ
EJJ

Reputation: 1513

correctly subset a list to replace element values

Context:
I have a list of data frames, each containing with different dimensions and column data types. My final objective is to create a named vector containing the column name and data type, that I can use to explicitly assign field types for writing a table into a database - the field.types argument in DBI::dbWriteTable()

Approach:
My current method is to extract the data type of the columns in the list using class, modifying it so that so I can use it as the named vector to in the field.types argument. I need to assess whether the data are truly integer values and I wrote a function to do that based on this SO post.

Objective:
I want to use the list output from my own function to identify and modify the data type in the named vector that are truly integer values.

Problem:
I want to replace the elements in the list list_class based on another list list_int with logical vectors. I can do this simple replacement/assignment for single data frame but I run into subset issues when using a list of data frames. I've included a reproducible example and some attempts at this below.

library(purrr)

list_df <- list(
  df1 = data.frame(v1 = seq(1,10,2),
                   v2 = seq(1,5,1),
                   v3 = seq(1,10,length.out = 5)),
  df2 = data.frame(v2 = c(seq(1,5), NA),
                   v3 = seq(2,7,1),
                   v4 = rep(pi,6)),
  df3 = data.frame(v3 = seq(1,2,length.out = 5),
                   v4 = sample(letters,5),
                   v5 = seq(1,10,2),
                   v6 = seq(1,5,1))
  )

list_class <- map(list_df, ~map_chr(., class)) #named vector

check_int <- function(v) { #check if truly integer value
  if (!is.numeric(v)) FALSE
  else all((v%%1 == 0)[!is.na(v%%1 == 0)])
}

list_int <- map(list_df, ~map_lgl(., ~check_int(.)))

For a single data frame below works

list_class[[1]][list_int[[1]]] <- "newdatatype"

And I am able to extract the subset that I want from the list using base Map.

Map('[', list_class, list_int)

Looking for insight to how put all these pieces together or if my approach is completely off?

Upvotes: 2

Views: 428

Answers (2)

Frank
Frank

Reputation: 66819

You can do...

Map(replace, list_class, list_int, "newdatatatype")
# or
Map(function(x, p) replace(x, p, "newdatatatype"), list_class, list_int)

This creates a new object rather than modifying list_class, but you're using the tidyverse and so should not be interested in modifying input anyways, I guess. If you really want to, there's...

library(magrittr)
list_class %<>% Map(
  f = function(x, p) replace(x, p, "newdatatatype"), 
  p = list_int
)

Side note: If you look at the code in replace, you'll see it's just a wrapper / convenience function for what Ryan's code does more directly.

Upvotes: 3

IceCreamToucan
IceCreamToucan

Reputation: 28695

A simple for loop should work, but you could also use map2 if you wanted a purrr solution.

for Loop:

for(i in seq_along(list_class))
  list_class[[i]][list_int[[i]]] <- "newdatatype"

purrr:

map2(list_class, list_int, ~{.x[.y] <- 'newdatatype'; .x})

output:

# $df1
#            v1            v2            v3 
# "newdatatype" "newdatatype"     "numeric" 
# 
# $df2
#            v2            v3            v4 
# "newdatatype" "newdatatype"     "numeric" 
# 
# $df3
#            v3            v4            v5            v6 
#     "numeric"      "factor" "newdatatype" "newdatatype" 

Upvotes: 3

Related Questions