导入包含多个日期列 (yyyy-mm-dd) 的 CSV 文件时出现问题 - 所有日期都格式化为字符串

Problem importing CSV file with multiple date columns (yyyy-mm-dd) - all dates get formatted as strings

提问人:Bjorn Blomberg 提问时间:9/13/2023 更新时间:9/13/2023 访问量:91

问:

我有一个大型数据库作为 CSV 文件,其中包含多个日期字段 (>30) 所有日期在 CSV 文件中显示为 yyyy-mm-dd(即 2023-09-13)。 使用 read.csv2 导入时,所有日期的格式都设置为字符串 (chr),我可以手动转换每一列,但由于日期太多,我希望找到一种更流畅的方法来修复它。

有没有聪明的方法可以让 R 首先将这些日期正确地导入为日期格式?

db <- read.csv2("~/Dropbox/data_in.csv", sep=";")

R CSV 日期 格式

评论

0赞 Gregor Thomas 9/13/2023
我不相信有基本的 R 方法,但如果您使用 OR,它们往往会更聪明地处理列类(并且在大文件上会更快)。readr::read_delim2data.table::fread
1赞 Golem 9/13/2023
您可以使用 package 将所有列转换为 type using 函数。另一个提示是尝试在 excel 中打开 csv 文件并检查日期列是否真的在 或者它们是否在 .您的数据示例对于某人帮助您很有用。lubridatedatetimeymd()date typestrings
0赞 Michael M 9/13/2023
另一种可能性是在所有日期列上单独使用,例如 'X[, date_cols] <- lapply(X[date_cols], parse_dates)readr::parse_dates()

答:

0赞 Claudio 9/13/2023 #1

您可以使用从包中选择帮助程序来更改多个变量:acrossdplyr

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
dat <- data.frame(
   id = 1:100,
   date1 = rep("2023-09-13", 100),
   date2 = rep("2023-09-13", 100),
   date3 = rep("2023-09-13", 100),
   date4 = rep("2023-09-13", 100),
   date5 = rep("2023-09-13", 100)
)

str(dat)
#> 'data.frame':    100 obs. of  6 variables:
#>  $ id   : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ date1: chr  "2023-09-13" "2023-09-13" "2023-09-13" "2023-09-13" ...
#>  $ date2: chr  "2023-09-13" "2023-09-13" "2023-09-13" "2023-09-13" ...
#>  $ date3: chr  "2023-09-13" "2023-09-13" "2023-09-13" "2023-09-13" ...
#>  $ date4: chr  "2023-09-13" "2023-09-13" "2023-09-13" "2023-09-13" ...
#>  $ date5: chr  "2023-09-13" "2023-09-13" "2023-09-13" "2023-09-13" ...

dat |> 
   mutate(across(starts_with("date"), as.Date)) |> 
   str()
#> 'data.frame':    100 obs. of  6 variables:
#>  $ id   : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ date1: Date, format: "2023-09-13" "2023-09-13" ...
#>  $ date2: Date, format: "2023-09-13" "2023-09-13" ...
#>  $ date3: Date, format: "2023-09-13" "2023-09-13" ...
#>  $ date4: Date, format: "2023-09-13" "2023-09-13" ...
#>  $ date5: Date, format: "2023-09-13" "2023-09-13" ...

dat |> 
   mutate(across(2:6, as.Date)) |> 
   str()
#> 'data.frame':    100 obs. of  6 variables:
#>  $ id   : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ date1: Date, format: "2023-09-13" "2023-09-13" ...
#>  $ date2: Date, format: "2023-09-13" "2023-09-13" ...
#>  $ date3: Date, format: "2023-09-13" "2023-09-13" ...
#>  $ date4: Date, format: "2023-09-13" "2023-09-13" ...
#>  $ date5: Date, format: "2023-09-13" "2023-09-13" ...

创建于 2023-09-13,使用 reprex v2.0.2

还有其他几个帮助程序函数,请参阅 ''dplyr::select'。

评论

0赞 Bjorn Blomberg 9/13/2023
非常感谢。非常感谢。这正是我所需要的:dat |> mutate(across(starts_with(“date”), as.日期))
1赞 r2evans 9/13/2023 #2

在讨论将 -class 列导入 R 时,有几件事:导入速度,以及它是在转换之前首先作为字符串读入还是即时转换。Date

后者可能是一个因素(数据量很大),因为 R 的全局字符串池具有历史意义,可能会阻碍更大的数据。有关 R 的更多讨论,请参阅 R 中字符的对象大小 - R 全局字符串池如何工作?https://adv-r.hadley.nz/names-values.html#character-vectorshttps://github.com/krlmlr/stringpool,但最重要的是,如果我们能避免将许多我们知道不会保留为字符串的字符串读入 R,那通常会更好。如果数据为 1000 行左右,是否应该担心全局字符串池?可能不是。当然,不要为了减少对它的接触而牺牲舒适的代码和/或其他功能。

我将尝试一个粗略的衡量标准:对于“导入速度”,以及导入前后的常驻集大小 (RSS)。system.time(.)ps::ps_memory_info()

数据设置:

n <- 1e7
write.csv2(data.frame(int=1:n, dat=rep(Sys.Date(),n)), "quux.csv", row.names=FALSE)
file.info("quux.csv")
#               size isdir mode               mtime               ctime               atime  uid  gid uname grname
# quux.csv 188888909 FALSE  664 2023-09-13 08:01:47 2023-09-13 08:01:47 2023-09-13 08:02:11 1000 1000    r2     r2

对于每种导入机制,我都运行类似的代码,并替换为特定包的变体。read.csv2

# library(...)                # for all others, not for read.csv2
ps::ps_memory_info()
#        rss        vms     shared       text        lib       data      dirty 
#   60686336 2385027072   14942208       4096          0 2280714240          0 
system.time(dat <- read.csv2("quux.csv", colClasses = c("integer", "Date")))
#    user  system elapsed 
#  48.198  13.453  61.665 
ps::ps_memory_info()
#        rss        vms     shared       text        lib       data      dirty 
# 1304268800 3628929024   14942208       4096          0 3524616192          0 
str(dat)
# 'data.frame': 10000000 obs. of  2 variables:
#  $ int: int  1 2 3 4 5 6 7 8 9 10 ...
#  $ dat: Date, format: "2023-09-13" "2023-09-13" "2023-09-13" "2023-09-13" ...
(1304268800 - 60686336) / 2^20
# [1] 1185.973                # MB

前期:这些是低强度基准。在单次运行时测量时间和内存消耗容易出现其他几个外部/不相关的问题,因此请对任何“小”差异持保留态度。更大(数量级)的差异应该更值得注意。

library(readr)
dat <- read_delim("quux.csv", col_types = "iD", delim = ";")
#    user  system elapsed 
#  16.885   0.624   1.583 
(227213312 - 78270464) / 2^20
# [1] 142.043

(请注意,最新版本在引擎盖下使用。readrvroom

library(vroom)
dat <- vroom("quux.csv", col_types = "iD", delim = ";")
#    user  system elapsed 
#   9.374   0.452   0.800 
(511553536 - 80756736) / 2^20
# [1] 410.8398
library(data.table)
dat <- fread("quux.csv", colClasses = c("integer", "Date"), sep = ";")
#    user  system elapsed 
#  50.908  13.652  57.492 
(1405636608 - 72237056) / 2^20
# [1] 1271.629

dat <- fread("quux.csv", sep = ";")
#    user  system elapsed 
#   1.822   0.132   0.285 
(155389952 - 60440576) / 2^20
# [1] 90.55078
class(dat$dat)
# [1] "IDate" "Date" 
library(arrow)
dat <- read_delim_arrow("quux.csv", delim = ";")
#    user  system elapsed 
#   1.667   0.516   0.300 
(601952256 - 101597148) / 2^20
# [1] 477.1758

(对于箭头,对于较大的数据,可以选择然后执行 dplyr ////...在它之前.支持的操作列表令人印象深刻,但它不需要任意/复杂的 R 表达式......但是,如果在将数据导入内存之前,您可以生活在支持的范围内,则可以节省大量内存。as_data_frame=FALSEmutatefilterselectsummarizecollectcollect()

上面的所有调用都返回了文件第二列的类,它们都不需要用户代码来进行该更改。Date

然而。。。

基准测试描绘了一些不同的效率:

函数 过去了 RSS订阅
读.csv2 61.665 1185.97300
readr::read_delim 1.583 142.04300
vroom::vroom 0.800 410.83980
data.table::fread (col类) 57.492 1271.62900
data.table::fread (日期) 0.285 90.55078
箭头::read_delim_arrow 0.300 477.17580

和 (with ) 显然是最慢的,这表明它们在内部使用本机 R 代码将字符串转换为 -class 列。它们各自的数字支持它们首先作为字符串拉入 R 的概念。read.csv2freadcolClasses=Daterss

其他人不仅建议提高基本的阅读速度,还建议显着提高内存。

我将把它作为一个练习(和苦差事),让读者在每个功能/包中找到使用/缺失。as.Date

1赞 G. Grothendieck 9/13/2023 #3

取决于 (1) 我们是否真正知道哪些列是日期,(2) 我们知道前两列是日期,(3) 日期列是名称包含子字符串“dat”的列,或者 (4) 日期列是那些从 through 包括它们之间的任何列的列,我们可以使用以下列之一:dat1second_dat

library(dplyr)
DF <- tibble(dat1 = "2000-01-01", second_dat = "2000-01-02", c = 3:5, d = "d4")

can.be.date <- function(x) {
  all(is.character(x) & grepl("^\\d{4}-\\d{2}-\\d{2}$", x))
}
DF %>% mutate(across(where(can.be.date), as.Date))

DF %>% mutate(across(1:2, as.Date))

DF %>% mutate(across(contains("dat"), as.Date))

DF %>% mutate(across(dat1:second_dat, as.Date))

这些中的任何一个都会返回

# A tibble: 3 × 4
  dat1       second_dat     c d    
  <date>     <date>     <int> <chr>
1 2000-01-01 2000-01-02     3 d4   
2 2000-01-01 2000-01-02     4 d4   
3 2000-01-01 2000-01-02     5 d4