Оптимизация формата fwrite.data.table и `гггг-мм-дд чч: мм: сс` с фиксированным смещением UTC - PullRequest
0 голосов
/ 18 декабря 2018

Я хотел бы использовать R data.table fwrite для вывода значений DateTime в формате ГГГГ-ММ-ДД чч: nn: ss (в не-DST постоянном ETC / GMT + 8 часовом поясе), а скореечем формат по умолчанию (ISO 8601) YYYY-MM-DDThh: nn: ssZ, где у некоторых меток времени есть доли секунды, которые я хочу округлить до ближайшей секунды.

Используя lubridate я смогиспользуйте fread для чтения дат, затем используйте x:=with_tz(x, "Etc/GMT+8"), затем x:=force_tz(x,"GMT").

Однако, с моим набором тестовых данных (6,5 миллиона записей из 12 столбцов), у меня в основном медленные решенияИ ищу лучший способ идти о вещах.Я не хочу использовать fwrite(..., dateTimeAs="write.csv"), поскольку это будет игнорировать фиксированное смещение UTC в пользу местного времени.

(Различные решения перенесены в мой "ответ" ниже)

Любые другие оптимизациичто вы можете придумать?

1 Ответ

0 голосов
/ 20 декабря 2018

Лучшее на сегодняшний день решение: base-R + data.table + fasttime

#!/usr/bin/env Rscript
# above this point: set d_f and o_f to valid file paths

totTime<-proc.time()
install.load <- function(package.name)
{
  if (!require(package.name, character.only=T)) install.packages(package.name)
  library(package.name, character.only=T)
}
pp<-function(...) {
  print(paste0(...))
}
ISO2Human<-function(x) {
  ot<-substr(x,1,19) # ignore fractional seconds and "Z"
  substr(ot,11,12)<-" "
  if(anyNA(ot)) ot<-substr(x,1,10)
  return(ot)
}

install.load('data.table')
install.load('fasttime')
pp("parameters read and libraries loaded: ",timetaken(totTime))

main <- function() {
  dat<-fread(d_f,fill=TRUE)
  # notably dat has a "d_utc" column in YYYY-MM-DD hh:nn:ss format
  pp("data file Read: ",timetaken(totTime)) # 5.200sec

  # A fair amount of code is inserted here. Highlights include
  #   1. As computations appear to be faster in double/numeric form 
  #      than POSIXct (and starts as character), I adjust it as follows:
  #        dat[,d_utc:=setattr(fastPOSIXct(d_utc,tz="GMT"),"class","numeric")]
  #   2. dat gets merged with another DT using foverlaps, producing fo (see https://stackoverflow.com/q/53858287/4228193)
  # as we resume code, 8.690sec have elapsed

  # As my target timezone is UTC-08:00 (POSIXct ETC/GMT+8), I subtract 28800 seconds.
  # But to protect against a rounding error in the double type
  # (and because I have some fractional second data that I want to round)
  # I add 0.5 to this value.
  fo[,d_pst:=setattr(d_utc-28799.5,c("POSIXct","POSIXt"))][,d_utc:=NULL]
  pp("timestamps adjusted to PST (UTC-08:00): ",timetaken(totTime)) # 16.8sec

Это особая часть кода, которую я пытался оптимизировать в этом вопросе;но при этом я обнаружил, что некоторые из приведенных выше преобразований типов кажутся более оптимальными.

  tf<-tempfile()
  fwrite(fo,file=tf)
  fo<-fread(tf)
  # fread reads in as character, not timestamps
  # POSIXct's as.character and format calls are much slower than fwrite + fread (!!!)
  fo[,DetectDate:=ISO2Human(DetectDate)] 
  # this truncates seconds, effectively rounding due to the previous adjustment of 0.5s
  unlink(tf) # delete file
  pp("coerced to string: ",timetaken(totTime)) # 26.9sec

  fwrite(fo, file = o_f, quote = FALSE)
  pp("output file written: ",timetaken(totTime)) # 27.1sec
  # aren't SSDs awesome?
}
main()

Другие решения

Блок на основе Lubridate (без временного файла),Временами вверху являются мм: сс

# 01:17
j<-copy(fo)
tt<-proc.time()
j[,c("dd","dt"):=IDateTime(d_pst, ms="nearest")]
# if adding 0.5 seconds, trunc rather than nearest
j[,d_pst:=paste(dd,dt)][,c("dd","dt"):=NULL]
timetaken(tt) # 1:17
j
j[,lapply(.SD,class)]
rm(j)

преобразование POSIXct в base-R в строку с использованием as.character или в формате

# 01:02
j<-copy(fo)
tt<-proc.time()
j[,DD2:=format(DetectDate,"%Y-%m-%d %H:%M:%S")]
timetaken(tt) # 1:02
j
j[,lapply(.SD,class)]
rm(j)

неявное преобразование в base-R в символ + дата сплайсинга иtime

# 12:36
j<-copy(fo)
tt<-proc.time()
j[,DD2:=paste(lapply(DetectDate,substr,1,10),lapply(DetectDate,substr,12,19))] 
timetaken(tt) # 12:36
j
j[,lapply(.SD,class)]
rm(j)

base-R, избегая лапы (глупый я)

# 02:29
j<-copy(fo)
tt<-proc.time()
j[,DD2:=paste(substr(DetectDate,1,10),substr(DetectDate,12,19))]
timetaken(tt) # 2:29
j
j[,lapply(.SD,class)] # just to confirm our target column is character
rm(j)

data.table + base-R, но используя tstrsplit и вставку data.table вместо того, чтобы захватыватьдиапазон символов

# 00:24
j<-copy(fo)
tt<-proc.time()
tf<-tempfile()
fwrite(j,file=tf)
fo2<-fread(tf)
fo2[,c("compDate","compTime","compMS"):=tstrsplit(DetectDate,"[TZ.]")][
    ,DD2:=paste(compDate,compTime)]
unlink(tf)
timetaken(tt) # 0:24
fo2
fo2[,lapply(.SD,class)]
rm(j,tf,fo2)

в основном оптимальное решение, хотя повторное использование имен переменных и полей сокращает это до 10 с

# 00:14    
fap<-function(x) {
  ot<-substr(x,1,19)
  substr(ot,11,12)<-" "
  if(is.na(ot)) ot<-substr(x,1,10)
  return(ot)
}
j<-copy(fo)
tt<-proc.time()
tf<-tempfile()
fwrite(j,file=tf)
fo2<-fread(tf)
fo2[,DD2:=fap(DetectDate)]
unlink(tf)
timetaken(tt) # 0:14
fo2
fo2[,lapply(.SD,class)]
rm(j,tf,fo2,fap)

Я использую (n) SSD,что, вероятно, значительно ускоряет решение временного файла по сравнению со «стандартной» настройкой

...