9

I have a data.frame or tibble that, in one script, is written to a CSV file. In another script, that same CSV file is read into a data.frame or tibble. Using read_csv(), with the col_types= parameter, I can specify the column types to read in. Here is an example:

# Create an example dataframe
df <- tibble::tibble(a=1L
                     , b=1.0
                     , c="a"
                     , d=TRUE
                     , e=lubridate::ymd_hms("2019-03-19T13:15:18Z")
                     , f=lubridate::ymd("2019-03-19")
                     , g=factor("a"))

# Write csv to file
readr::write_csv(df, "temp.csv")

# read it back in, supplying a col_types string spec
readr::read_csv("temp.csv", col_types="idclTDf")
#> # A tibble: 1 x 7
#>       a     b c     d     e                   f          g    
#>   <int> <dbl> <chr> <lgl> <dttm>              <date>     <fct>
#> 1     1     1 a     TRUE  2019-03-19 13:15:18 2019-03-19 a

Created on 2019-03-19 by the reprex package (v0.2.1)

The issue is that I need to know the col_types= parameter on the read_csv() function (or let it guess, which I don't want to do). What I'd like is some way to take the original df and, before I write it out, generate the col_types string from the df object that can be used to read the dumped CSV back in. That is, I want something that will create the "idclTDf" string given the data.frame as an argument.

I see there is a feature request to do this (and I have added my two cents) here: https://github.com/tidyverse/readr/issues/895 .

mpettis
  • 3,222
  • 4
  • 28
  • 35
  • 1
    This would be very useful and hopefully will be implemented in the `readr` package. I think the [`csvy`](https://cran.r-project.org/web/packages/csvy/index.html) package and the more general [`csvy`](https://csvy.org/) framework is intended to eliminate the need for this functionality, but development on the `csvy` R package is indefinitely stalled and I'm not aware of any alternatives that might be gaining traction. – bschneidr Mar 28 '19 at 17:01

1 Answers1

8

I do have a solution, and it works, but I consider it very incomplete and not hardened. Here is my attempt at the solution.

# https://github.com/tidyverse/readr/issues/895
# Create function to take a tibble and return a character string that can be used in `readr::read_csv()`
# as the `col_types` argument to re-read this back into a dataframe after it had been written out
# by `write_csv()`.

get_col_types_short <- function(.df) {
    # Get column classes from input dataframe
    lst_col_classes__ <- purrr::map(.df, ~ class(.x))

    # Map classes to known single-character col_types indicator
    vl_col_class_char__ <- purrr::map_chr(lst_col_classes__, function(.e) {
        dplyr::case_when(
              "logical" %in% .e   ~ "l"
            , "integer" %in% .e   ~ "i"
            , "numeric" %in% .e   ~ "d"
            , "double" %in% .e    ~ "d"
            , "character" %in% .e ~ "c"
            , "factor" %in% .e    ~ "f"
            , "Date" %in% .e      ~ "D"
            , "POSIXct" %in% .e   ~ "T"
            , TRUE                ~ "c"
        )
    })

    # Return vector of single-character col_type indicator.
    # Element name is the source column it came from.
    vl_col_class_char__
}

# Test it:
df <- tibble::tibble(a=1L
                     , b=1.0
                     , c="a"
                     , d=TRUE
                     , e=lubridate::ymd_hms("2019-03-19T13:15:18Z")
                     , f=lubridate::ymd("2019-03-19")
                     , g=factor("a"))

v__ <- get_col_types_short(df)

# Show what is actually returned
v__
#>   a   b   c   d   e   f   g 
#> "i" "d" "c" "l" "T" "D" "f"

# Collapse it to show how to use it
paste(v__, collapse="")
#> [1] "idclTDf"


# Write csv to file
readr::write_csv(df, "temp.csv")

# read it back in, using the above col_types string spec
readr::read_csv("temp.csv", col_types=paste(v__, collapse=""))
#> # A tibble: 1 x 7
#>       a     b c     d     e                   f          g    
#>   <int> <dbl> <chr> <lgl> <dttm>              <date>     <fct>
#> 1     1     1 a     TRUE  2019-03-19 13:15:18 2019-03-19 a

Created on 2019-03-19 by the reprex package (v0.2.1)

mpettis
  • 3,222
  • 4
  • 28
  • 35
  • 1
    What do you feel needs to be improved? I think it's a great idea – camille Mar 19 '19 at 22:46
  • 2
    We'll, I'm not certain I covered all possible variant types on date and datetime, I have no error handling, it might be better to have a non-short function that returns quoted calls to like 'col_character()', etc. I think it's a usable start, and haven't tested edge cases. And I doubt I'm the first one to have this problem, and a better, more robust solution may be out there. – mpettis Mar 20 '19 at 00:53
  • 2
    It seems like this has recently been solved in [readr](https://github.com/tidyverse/readr/issues/895) with the new function `as.col_type()`. – hplieninger May 10 '19 at 15:01
  • 2
    @hplieninger, I think that's supposed to be `as.col_spec()`, right? There is no `as.col_type()` in the current (1.3.1) version of `readr`. – D. Woods Sep 26 '19 at 04:08
  • 1
    @D.Woods, right, it seems like the function was renamed to `as.col_spec()`. – hplieninger Sep 26 '19 at 09:33
  • Maybe I'm misunderstanding, but I don't think `as.col_spec()` takes a data frame. I takes a string of short column types – Unrelated Dec 01 '21 at 20:20