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

of1yzvn4  于 2023-09-27  发布在  其他
关注(0)|答案(3)|浏览(100)

我有一个大型数据库作为CSV文件与多个日期字段(>30)所有日期显示为yyyy-mm-dd(即. 2023-09-13)。当使用read.csv2导入时,所有的日期都被格式化为字符串(chr),我可以手动转换每列,但由于有这么多的日期,我希望找到一个更平滑的方法来修复它。
有没有什么聪明的方法可以让R首先正确地导入这些日期作为日期格式?
db <- read.csv2("~/Dropbox/data_in.csv", sep=";")

ffvjumwh

ffvjumwh1#

在讨论将Date类列导入R时,有几件事:导入的速度,以及它是在转换之前首先作为字符串读取还是在运行中转换。
后者可能是一个因素(对于较大的数据),因为R的全局字符串池具有历史重要性,可能会阻碍较大的数据。请参阅Object size for characters in R - How does R global string pool work?,https://adv-r.hadley.nz/names-values.html#character-vectors和https://github.com/krlmlr/stringpool以获得更多关于它的讨论,但底线是,如果我们可以避免阅读许多我们知道不会作为字符串保存的字符串读入R,通常会更好。如果您的数据是1000行左右,您是否应该关心全局字符串池?大概不会当然,这并不是为了减少对它的暴露而牺牲舒适的代码和/或其他功能。
我将尝试一个粗略的测量:system.time(.)表示“导入速度”,ps::ps_memory_info()表示导入前后的驻留集大小(RSS)。
数据设置:

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

(Note最新版本的readr在幕后使用了vroom

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

(For箭头,对于较大的数据,可以选择as_data_frame=FALSE,然后执行dplyr mutate/filter/select/summarize/...在collect之前。支持的操作列表令人印象深刻,但它不接受任意/复杂的R表达式...但是如果您能够在将数据写入内存之前使用所支持的内容,则可以节省大量内存。)
上面的所有调用都返回了文件第二列的类Date,它们都不需要用户代码来进行更改。

然而...

基准测试描绘了一些不同的效率:
| func|经过|RSS|
| --|--|--|
| read.csv2 |61.665| 1185.97300|
| readr::read_delim|一千五百八十三|142.04300|
| vroom::vroom|零点八|410.83980|
| data.table::fread(colClasses)|五十七点四九二|1271.62900|
| data.table::fread(Idate)|0.285| 90.55078|
| arrow::read_delim_arrow|三百|477.17580|
read.csv2fread(带有colClasses=)显然是最慢的,这表明它们在内部使用原生R代码将字符串转换为Date类列。它们各自的rss数字支持它们首先作为字符串拉入R的概念。
其他人认为,不仅基本的阅读速度提高,而且显着的记忆改善。
我将把它作为一个练习(和苦差事),让读者在每个函数/包中找到as.Date的使用/缺失。

e4yzc0pl

e4yzc0pl2#

根据(1)我们是否真的不知道哪些列是日期,(2)我们知道前两列是日期,(3)日期列是名称包含子字符串“dat”的列,或者(4)日期列是从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
xtfmy6hx

xtfmy6hx3#

您可以使用across沿着从dplyr包中选择helper来更改多个变量:

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`。

相关问题