高效转换data.table

时间:2017-09-06 14:41:25

标签: r date 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"

在这两种情况下,class()都是“character”。由于数据集有很多行(450万),我正在寻找一种有效的data.table转换方法。现在,我使用这个自建功能:

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)]
然而,这仍然非常缓慢(每列接近45s)。

有没有办法通过data.table方法优化这个?到目前为止,我还没有找到更好的方法,所以我会感谢任何提示。

P.S:为了更好的可读性,我将该函数“外包”到第二个文件并在我的主程序中获取。这是否会对R?

中的计算速度产生(负面)重大影响

4 个答案:

答案 0 :(得分:10)

根据this benchmark,将标准明确格式(YYYY-MM-DD)中的字符日期转换为类Date的最快方法是使用as.Date(fasttime::fastPOSIXct())

不幸的是,这需要事先测试格式,因为DD/MM/YYYY误解了其他格式fasttime::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

benchmark timings表明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 ,则此列可能会转义为未转换。要处理这些情况,需要修改上述代码。

答案 1 :(得分:2)

您的数据

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" ...

答案 2 :(得分:1)

既然您事先知道只有两种日期格式,这很容易。 format的{​​{1}}参数是矢量化的:

as.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) } ,这更快

答案 3 :(得分:0)

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

数据

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)