提问人:Bjorn Blomberg 提问时间:9/13/2023 更新时间:9/13/2023 访问量:91
导入包含多个日期列 (yyyy-mm-dd) 的 CSV 文件时出现问题 - 所有日期都格式化为字符串
Problem importing CSV file with multiple date columns (yyyy-mm-dd) - all dates get formatted as strings
问:
我有一个大型数据库作为 CSV 文件,其中包含多个日期字段 (>30) 所有日期在 CSV 文件中显示为 yyyy-mm-dd(即 2023-09-13)。 使用 read.csv2 导入时,所有日期的格式都设置为字符串 (chr),我可以手动转换每一列,但由于日期太多,我希望找到一种更流畅的方法来修复它。
有没有聪明的方法可以让 R 首先将这些日期正确地导入为日期格式?
db <- read.csv2("~/Dropbox/data_in.csv", sep=";")
答:
您可以使用从包中选择帮助程序来更改多个变量:across
dplyr
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'。
评论
在讨论将 -class 列导入 R 时,有几件事:导入速度,以及它是在转换之前首先作为字符串读入还是即时转换。Date
后者可能是一个因素(数据量很大),因为 R 的全局字符串池具有历史意义,可能会阻碍更大的数据。有关 R 的更多讨论,请参阅 R 中字符的对象大小 - R 全局字符串池如何工作?、https://adv-r.hadley.nz/names-values.html#character-vectors 和 https://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
(请注意,最新版本在引擎盖下使用。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
(对于箭头,对于较大的数据,可以选择然后执行 dplyr ////...在它之前.支持的操作列表令人印象深刻,但它不需要任意/复杂的 R 表达式......但是,如果在将数据导入内存之前,您可以生活在支持的范围内,则可以节省大量内存。as_data_frame=FALSE
mutate
filter
select
summarize
collect
collect()
上面的所有调用都返回了文件第二列的类,它们都不需要用户代码来进行该更改。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.csv2
fread
colClasses=
Date
rss
其他人不仅建议提高基本的阅读速度,还建议显着提高内存。
我将把它作为一个练习(和苦差事),让读者在每个功能/包中找到使用/缺失。as.Date
取决于 (1) 我们是否真正知道哪些列是日期,(2) 我们知道前两列是日期,(3) 日期列是名称包含子字符串“dat”的列,或者 (4) 日期列是那些从 through 包括它们之间的任何列的列,我们可以使用以下列之一:dat1
second_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
评论
readr::read_delim2
data.table::fread
lubridate
datetime
ymd()
date type
strings
readr::parse_dates()