2013-08-22 15 views
11

Durante la lettura di un file, la funzione read.table utilizza type.convert per distinguere tra colonne logiche, integer, numeriche, complesse o fattoriali e memorizzarle di conseguenza.rileva automaticamente le colonne della data durante la lettura di un file in un data.frame

Vorrei aggiungere date al mix, in modo che le colonne contenenti date possano essere automaticamente riconosciute e analizzate negli oggetti Date. Dovrebbero essere riconosciuti solo alcuni formati di data, ad es.

date.formats <- c("%m/%d/%Y", "%Y/%m/%d") 

Ecco un esempio:

fh <- textConnection(

"num char date-format1 date-format2 not-all-dates not-same-formats 
    10  a  1/1/2013 2013/01/01  2013/01/01   1/1/2013 
    20  b  2/1/2013 2013/02/01    a  2013/02/01 
    30  c  3/1/2013   NA    b   3/1/2013" 
) 

E l'uscita di

dat <- my.read.table(fh, header = TRUE, stringsAsFactors = FALSE, 
        date.formats = date.formats) 
sapply(dat, class) 

darebbe:

num    => numeric 
char    => character 
date-format1  => Date 
date-format2  => Date 
not-all-dates => character 
not-same-formats => character # not a typo: date format must be consistent 

Prima di andare e implementare da zero, è qualcosa di simile già disponibile in un pacchetto? O forse qualcuno ha già dato un crack (o volontà) ed è disposto a condividere il suo codice qui? Grazie.

+0

Relativi a [Specifica formato di data per la discussione in colClasses read.table/read.csv] (http://stackoverflow.com/q/13022299/271616). –

+1

Correlati, sì, e possono essere utili alle persone che fanno ricerche su questo argomento. Nel mio caso, però, ho bisogno che le colonne della data vengano rilevate automaticamente. – flodel

+0

di "stesso formato" è l'ordine "formato esatto" o "ymd" che è importante (ad esempio, "2013/01/01" e "2013-01-01" nella stessa colonna vanno bene?) – mnel

risposta

0

Qui ho gettato uno insieme rapidamente. Non sta gestendo l'ultima colonna correttamente perché la funzione as.Date non è abbastanza severa (vedere che as.Date("1/1/2013", "%Y/%m/%d") analizza ok per esempio ...)

my.read.table <- function(..., date.formats = c("%m/%d/%Y", "%Y/%m/%d")) { 
    dat <- read.table(...) 
    for (col.idx in seq_len(ncol(dat))) { 
     x <- dat[, col.idx] 
     if(!is.character(x) | is.factor(x)) next 
     if (all(is.na(x))) next 
     for (f in date.formats) { 
     d <- as.Date(as.character(x), f) 
     if (any(is.na(d[!is.na(x)]))) next 
     dat[, col.idx] <- d   
     } 
    } 
    dat 
} 

dat <- my.read.table(fh, header = TRUE, stringsAsFactors = FALSE) 
as.data.frame(sapply(dat, class)) 

#     sapply(dat, class) 
# num       integer 
# char      character 
# date.format1     Date 
# date.format2     Date 
# not.all.dates    character 
# not.same.formats    Date 

Se conoscete un modo per analizzare le date che è più stretta intorno a formati diversi as.Date (vedi l'esempio sopra), per favore fatemelo sapere.

Modifica: per rendere la data parsing super-rigida, posso aggiungere

if (!identical(x, format(d, f))) next 

Per farlo funzionare, ho bisogno di tutte le date il mio contributo per avere zeri iniziali se necessario, vale a dire 01/01/2013 e non 1/1/2013 . Posso vivere con quello se questo è il modo standard.

1

Puoi provare con le espressioni regolari.

my.read.table <- function(..., date.formats = c("%m/%d/%Y", "%Y/%m/%d")) { 
    require(stringr) 
    formats <- c(
    "%m" = "[0-9]{1,2}", 
    "%d" = "[0-9]{1,2}", 
    "%Y" = "[0-9]{4}" 
    ) 
    dat <- read.table(...) 
    for (col.idx in seq_len(ncol(dat))) { 
     for (format in date.formats) { 
     x <- dat[, col.idx] 
     if(!is.character(x) | is.factor(x)) break 
     if (all(is.na(x))) break 
     x <- as.character(x) 
     # Convert the format into a regular expression 
     for(k in names(formats)) { 
      format <- str_replace_all(format, k, formats[k]) 
     } 
     # Check if it matches on the non-NA elements 
     if(all(str_detect(x, format) | is.na(x))) { 
      dat[, col.idx] <- as.Date(x, format) 
      break 
     } 
     } 
    } 
    dat 
} 

dat <- my.read.table(fh, header = TRUE, stringsAsFactors = FALSE) 
as.data.frame(sapply(dat, class)) 
#     sapply(dat, class) 
# num       integer 
# char      character 
# date.format1     Date 
# date.format2     Date 
# not.all.dates    character 
# not.same.formats   character 
3

Si potrebbe utilizzare lubridate::parse_date_time, che è un po 'più rigorosa (e crea POSIXlt) di dati.

Ho anche aggiunto un po 'più di controllo per i valori NA esistenti (potrebbe non essere necessario).

esempio

library(lubridate) 
my.read.table <- function(..., date.formats = c("%m/%d/%Y", "%Y/%m/%d")) { 
    dat <- read.table(...) 
    for (col.idx in seq_len(ncol(dat))) { 
    x <- dat[, col.idx] 
    if(!is.character(x) | is.factor(x)) next 
    if (all(is.na(x))) next 
    for (format in date.formats) { 
     complete.x <- !(is.na(x)) 
     d <- as.Date(parse_date_time(as.character(x), format, quiet = TRUE)) 
     d.na <- d[complete.x] 
     if (any(is.na(d.na))) next 
     dat[, col.idx] <- d   
    } 
    } 
    dat 

} 

dat <- my.read.table(fh, stringsAsFactors = FALSE,header=TRUE) 

str(dat) 
'data.frame': 3 obs. of 6 variables: 
$ num    : int 10 20 30 
$ char   : chr "a" "b" "c" 
$ date.format1 : Date, format: "2013-01-01" "2013-02-01" "2013-03-01" 
$ date.format2 : Date, format: "2013-01-01" "2013-02-01" NA 
$ not.all.dates : chr "2013/01/01" "a" "b" 
$ not.same.formats: chr "1/1/2013" "2013/02/01" "3/1/2013" 

Un'alternativa sarebbe quella di utilizzare options(warn = 2) all'interno della funzione e avvolgere il parse_date_time(...) in un'istruzione try

my.read.table <- function(..., date.formats = c("%m/%d/%Y", "%Y/%m/%d")) { 
    dat <- read.table(...) 
    owarn <-getOption('warn') 
    on.exit(options(warn = owarn)) 
    options(warn = 2) 
    for (col.idx in seq_len(ncol(dat))) { 
    x <- dat[, col.idx] 
    if(!is.character(x) | is.factor(x)) next 
    if (all(is.na(x))) next 
    for (format in date.formats) { 
     d <- try(as.Date(parse_date_time(as.character(x), format)), silent= TRUE) 

     if (inherits(d, 'try-error')) next 
     dat[, col.idx] <- d   
    } 
    } 
    dat 

} 
+0

sembra promettente, prova ora, grazie! – flodel

+0

@flodel - ho appena aggiunto un'opzione usando 'try' invece del bit' any (is.na (...)) '. – mnel

+0

e il vantaggio è che uscirà non appena non è in grado di analizzare una data? così più veloce nel complesso? – flodel