Getting column types of data in the environment via `type_convert` or `spec` functions

Viewed 35

I have an .RDS data file where all columns are strings. I want to convert each column of this data to an appropriate type. But I also want to save the column specifications to change some of them. I can get the specification of each column using spec() if I read a CSV file.

library(tidyverse)
library(lubridate)
df <- midwest %>% 
  slice(1:10) %>% 
  mutate(date1 = seq(ymd('2021-06-07'), ymd('2021-06-16'), by = '1 day'), 
         date2 = seq(as.Date('2021/6/7'), by = 'month', length.out = 10), 
         empty1 = NA, empty2 = NA, empty3 = NA, empty4 = NA,
         empty11 = NA, empty21 = NA, empty31 = NA, empty41 = NA,
         empty12 = NA, empty22 = NA, empty32 = NA, empty42 = NA,
         empty13 = NA, empty23 = NA, empty33 = NA, empty43 = NA,
         dt = date1 + seconds(row_number()-1), 
         time = format(dt, format = "%H:%M:%S")) %>% 
  mutate(across(everything(), as.character)) 

# A temporary file to hold the data 
f <- file.path(tempdir(), "midwest10.csv")
write.csv(df, f, row.names = FALSE)

df3 <- read_csv(f, show_col_types = FALSE)

spec(df3)  # <-- This works perfectly
#> cols(
#>   PID = col_double(),
#>   county = col_character(),
#>   state = col_character(),
#>   area = col_double(),
#>   poptotal = col_double(),
#>   popdensity = col_double(),
#>   popwhite = col_double(),
#>   popblack = col_double(),
#>   popamerindian = col_double(),
#>   popasian = col_double(),
#>   popother = col_double(),
#>   percwhite = col_double(),
#>   percblack = col_double(),
#>   percamerindan = col_double(),
#>   percasian = col_double(),
#>   percother = col_double(),
#>   popadults = col_double(),
#>   perchsd = col_double(),
#>   percollege = col_double(),
#>   percprof = col_double(),
#>   poppovertyknown = col_double(),
#>   percpovertyknown = col_double(),
#>   percbelowpoverty = col_double(),
#>   percchildbelowpovert = col_double(),
#>   percadultpoverty = col_double(),
#>   percelderlypoverty = col_double(),
#>   inmetro = col_double(),
#>   category = col_character(),
#>   date1 = col_date(format = ""),
#>   date2 = col_date(format = ""),
#>   empty1 = col_logical(),
#>   empty2 = col_logical(),
#>   empty3 = col_logical(),
#>   empty4 = col_logical(),
#>   empty11 = col_logical(),
#>   empty21 = col_logical(),
#>   empty31 = col_logical(),
#>   empty41 = col_logical(),
#>   empty12 = col_logical(),
#>   empty22 = col_logical(),
#>   empty32 = col_logical(),
#>   empty42 = col_logical(),
#>   empty13 = col_logical(),
#>   empty23 = col_logical(),
#>   empty33 = col_logical(),
#>   empty43 = col_logical(),
#>   dt = col_datetime(format = ""),
#>   time = col_time(format = "")
#> )

But I cannot get anything from the spec() function if the data is saved in an RDS file (or, more generally, a data set in the current environment). How can I make spec() work for data already in the working directory? Ideally, I don't want to write the RDS file to a CSV file and read it back via read_csv().

f <- file.path(tempdir(), "midwest10.RDS")
saveRDS(df, f)

df2 <- readRDS(f)
df2 <- type_convert(df2)
#> 
#> -- Column specification --------------------------------------------------------
#> cols(
#>   .default = col_double(),
#>   county = col_character(),
#>   state = col_character(),
#>   category = col_character(),
#>   # ... with 20 more columns
#> )
#> i Use `spec()` for the full column specifications.

spec(df2)  # <--- Ideally, this returns column specifications instead of NULL
#> NULL
1 Answers

Here is a makeshift solution I found by looking inside the type_convert function. This assumes that all columns of the data set are character types, so not very generalizable, but it does the trick for the question that I have.

library(readr)
# "df" is the dataset with all strings defined in the question above
is_character <- vapply(df, is.character, logical(1))
char_cols <- df[is_character]
guesses <- lapply(char_cols, guess_parser)
specs <- readr:::col_spec_standardise(col_types = NULL, 
                                      col_names = names(char_cols), 
                                      guessed_types = guesses)
specs
#> cols(
#>   PID = col_double(),
#>   county = col_character(),
#>   state = col_character(),
#>   area = col_double(),
#>   poptotal = col_double(),
#>   popdensity = col_double(),
#>   popwhite = col_double(),
#>   popblack = col_double(),
#>   popamerindian = col_double(),
#>   popasian = col_double(),
#>   popother = col_double(),
#>   percwhite = col_double(),
#>   percblack = col_double(),
#>   percamerindan = col_double(),
#>   percasian = col_double(),
#>   percother = col_double(),
#>   popadults = col_double(),
#>   perchsd = col_double(),
#>   percollege = col_double(),
#>   percprof = col_double(),
#>   poppovertyknown = col_double(),
#>   percpovertyknown = col_double(),
#>   percbelowpoverty = col_double(),
#>   percchildbelowpovert = col_double(),
#>   percadultpoverty = col_double(),
#>   percelderlypoverty = col_double(),
#>   inmetro = col_double(),
#>   category = col_character(),
#>   date1 = col_date(format = ""),
#>   date2 = col_date(format = ""),
#>   empty1 = col_logical(),
#>   empty2 = col_logical(),
#>   empty3 = col_logical(),
#>   empty4 = col_logical(),
#>   empty11 = col_logical(),
#>   empty21 = col_logical(),
#>   empty31 = col_logical(),
#>   empty41 = col_logical(),
#>   empty12 = col_logical(),
#>   empty22 = col_logical(),
#>   empty32 = col_logical(),
#>   empty42 = col_logical(),
#>   empty13 = col_logical(),
#>   empty23 = col_logical(),
#>   empty33 = col_logical(),
#>   empty43 = col_logical(),
#>   dt = col_datetime(format = ""),
#>   time = col_time(format = "")
#> )

Later, I can make some changes to the specs and convert the data:

# 
# <Make some changes that I want to specs>
#
# Read the data using specs: 
new_data <- type_convert(df, col_types = specs)
Related