R-有效地转换Data.Table中的日期列



我有一个大数据集,其中包含两种不同格式的许多列:

"1996-01-04" "1996-01-05" "1996-01-08" "1996-01-09" "1996-01-10" "1996-01-11"

"02/01/1996" "03/01/1996" "04/01/1996" "05/01/1996" "08/01/1996" "09/01/1996"

在这两种情况下,类()都是"字符"。由于数据集有许多行(450万),因此我正在寻找有效的数据。表转换方法。现在,我使用此自行构建功能:

convert_to_date <- function(in_array){
  tmp <- try(as.Date(in_array, format = "%d/%m/%Y"),TRUE)
  if (all(!is.na(tmp)) & class(tmp) != "try-error"){
    return(tmp)
  } else{
    tmp2 <- try(as.Date(in_array),TRUE)
    if (all(!is.na(tmp2)) & class(tmp2) != "try-error"){
      return(tmp2)
    } else{
      return(in_array)
    }
  }
}

然后,我通过

将其转换为 的data.table df的列
DF[,date:=convert_to_date(date)]

但是,这仍然非常慢(每列近45秒)。

有什么方法可以通过Data.table方法优化此方法?到目前为止,我还没有找到更好的方法,所以我会感谢任何提示。

P.S:为了更好地可读性,我将功能"外包"到第二个文件中,并在我的主要例程中采购。这是否对R?

中的计算速度有重大影响

根据此基准标准,将字符日期(YYYY-MM-DD)转换为Date 类的最快方法是使用as.Date(fasttime::fastPOSIXct())

不幸的是,这需要事先测试格式,因为您的其他格式DD/MM/YYYYfasttime::fastPOSIXct()误解了。

因此,如果您不想打扰每个日期列的格式,则可以使用anytime::anydate()函数:

# sample data
df <- data.frame(
    X1 = c("1996-01-04", "1996-01-05", "1996-01-08", "1996-01-09", "1996-01-10", "1996-01-11"), 
    X2 = c("02/01/1996", "03/01/1996", "04/01/1996", "05/01/1996", "08/01/1996", "09/01/1996"), 
    stringsAsFactors = FALSE)
library(data.table)
# convert date columns
date_cols <- c("X1", "X2")
setDT(df)[, (date_cols) := lapply(.SD, anytime::anydate), .SDcols = date_cols]
df
           X1         X2
1: 1996-01-04 1996-02-01
2: 1996-01-05 1996-03-01
3: 1996-01-08 1996-04-01
4: 1996-01-09 1996-05-01
5: 1996-01-10 1996-08-01
6: 1996-01-11 1996-09-01

基准时间安排表明,anytime软件包提供的便利与性能之间的方便之间存在权衡。因此,如果速度至关重要,则没有其他方法可以测试每列的格式并使用用于格式的最快转换方法。

OP已将try()功能用于此目的。下面的解决方案使用正则表达式查找与给定格式相匹配的所有列(仅第1行用于节省时间)。这具有额外的好处,即相关列的名称是自动确定的,不需要输入。

# enhanced sample data with additional columns
df <- data.frame(
    X1 = c("1996-01-04", "1996-01-05", "1996-01-08", "1996-01-09", "1996-01-10", "1996-01-11"), 
    X2 = c("02/01/1996", "03/01/1996", "04/01/1996", "05/01/1996", "08/01/1996", "09/01/1996"), 
    X3 = "other data",
    X4 = 1:6,
    stringsAsFactors = FALSE)
library(data.table)
options(datatable.print.class = TRUE)
# coerce to data.table
setDT(df)[]
# convert date columns in standard unambiguous format YYYY-MM-DD
date_cols1 <- na.omit(names(df)[
  df[1, sapply(.SD, stringr::str_detect, pattern = "\d{4}-\d{2}-\d{2}"),]])
# use fasttime package
df[, (date_cols1) := lapply(.SD, function(x) as.Date(fasttime::fastPOSIXct(x))), 
   .SDcols = date_cols1]
# convert date columns in DD/MM/YYYY format
date_cols2 <- na.omit(names(df)[
  df[1, sapply(.SD, stringr::str_detect, pattern = "\d{2}/\d{2}/\d{4}"),]])
# use lubridate package
df[, (date_cols2) := lapply(.SD, lubridate::dmy), .SDcols = date_cols2]
df
           X1         X2         X3    X4
       <Date>     <Date>     <char> <int>
1: 1996-01-04 1996-01-02 other data     1
2: 1996-01-05 1996-01-03 other data     2
3: 1996-01-08 1996-01-04 other data     3
4: 1996-01-09 1996-01-05 other data     4
5: 1996-01-10 1996-01-08 other data     5
6: 1996-01-11 1996-01-09 other data     6

警告

如果日期列之一确实包含NA 在第一行中中,则该列可能会逃脱不转化。要处理这些情况,需要修改上述代码。

您的数据

df <- data.frame(X1 = c("1996-01-04", "1996-01-05", "1996-01-08", "1996-01-09", "1996-01-10", "1996-01-11"), X2 = c("02/01/1996", "03/01/1996", "04/01/1996", "05/01/1996", "08/01/1996", "09/01/1996"), stringsAsFactors=F)
'data.frame':   6 obs. of  2 variables:
 $ X1: chr  "1996-01-04" "1996-01-05" "1996-01-08" "1996-01-09" ...
 $ X2: chr  "02/01/1996" "03/01/1996" "04/01/1996" "05/01/1996" ...

解决方案

library(dplyr)
library(lubridate)
ans <- df %>%
         mutate(X1 = ymd(X1), X2 = mdy(X2))
          X1         X2
1 1996-01-04 1996-02-01
2 1996-01-05 1996-03-01
3 1996-01-08 1996-04-01
4 1996-01-09 1996-05-01
5 1996-01-10 1996-08-01
6 1996-01-11 1996-09-01
str(ans)
'data.frame':   6 obs. of  2 variables:
 $ X1: Date, format: "1996-01-04" "1996-01-05" ...
 $ X2: Date, format: "1996-02-01" "1996-03-01" ...

,由于您事先知道只有两种日期格式,因此很容易。formatas.Date的参数已矢量化:

as_date_either <- function(x) {
    format_vec <- rep_len("%Y-%m-%d", length(x))
    format_vec[grep("/", x, fixed = TRUE)] <- "%m/%d/%Y"
    as.Date(x, format = format_vec)
}

编辑:用子集分配替换ifelse,这更快

如果数据集中有任何重复的日期字段,那么您可以做的一种方法是设置de deplicated参考表,然后在较小的数据集上进行映射。这将比转换所有记录上的日期字段更快。

数据

df <- data.frame(
  X1 = c("1996-01-04", "1996-01-05", "1996-01-08", "1996-01-09", "1996-01-10", rep("1996-01-11", 100)), 
  X2 = c("02/01/1996", "03/01/1996", "04/01/1996", "05/01/1996", "08/01/1996", rep("09/01/1996", 100)), 
  stringsAsFactors = FALSE)

创建映射的唯一日期行

date_mapping <- function(date_col){
  ref_df <- data.frame(date1 = unique(date_col), stringsAsFactors = FALSE)
  if(all(grepl("/", ref_df$date1))) {
    ref_df$date2 <- as.Date(ref_df$date1, format = "%d/%m/%Y")
  } else {
    ref_df$date2 <- as.Date(ref_df$date1)  
  }
  date_col_mapped <- ref_df[match(date_col, ref_df$date1), "date2"]
  return(date_col_mapped)
}

date_mapping(df$X1)
date_mapping(df$X2)

最新更新