Crea un data.frame vuoto


480

Sto provando a inizializzare un data.frame senza alcuna riga. Fondamentalmente, voglio specificare i tipi di dati per ogni colonna e nominarli, ma non ho creato alcuna riga come risultato.

Il meglio che sono stato in grado di fare finora è qualcosa di simile:

df <- data.frame(Date=as.Date("01/01/2000", format="%m/%d/%Y"), 
                 File="", User="", stringsAsFactors=FALSE)
df <- df[-1,]

Il che crea un data.frame con una singola riga contenente tutti i tipi di dati e i nomi di colonna desiderati, ma crea anche una riga inutile che deve quindi essere rimossa.

C'è un modo migliore per farlo?

Risposte:


652

Basta inizializzarlo con vettori vuoti:

df <- data.frame(Date=as.Date(character()),
                 File=character(), 
                 User=character(), 
                 stringsAsFactors=FALSE) 

Ecco un altro esempio con diversi tipi di colonna:

df <- data.frame(Doubles=double(),
                 Ints=integer(),
                 Factors=factor(),
                 Logicals=logical(),
                 Characters=character(),
                 stringsAsFactors=FALSE)

str(df)
> str(df)
'data.frame':   0 obs. of  5 variables:
 $ Doubles   : num 
 $ Ints      : int 
 $ Factors   : Factor w/ 0 levels: 
 $ Logicals  : logi 
 $ Characters: chr 

NB:

L'inizializzazione di a data.framecon una colonna vuota di tipo errato non impedisce ulteriori aggiunte di righe con colonne di tipo diverso.
Questo metodo è solo un po ' più sicuro, nel senso che avrai i tipi di colonna corretti dall'inizio, quindi se il tuo codice si basa su un controllo del tipo di colonna, funzionerà anche con un data.framecon zero righe.


3
Sarebbe lo stesso se inizializzassi tutti i campi con NULL?
yosukesabai,

8
@yosukesabai: no, se si inizializza una colonna con NULL la colonna non verrà aggiunta :)
digEmAll

6
@yosukesabai: data.framehanno delle colonne digitate, quindi sì, se vuoi inizializzare un data.framedevi decidere il tipo di colonne ...
digEmAll

1
@jxramos: beh, in realtà data.framenon è veramente restrittivo sulla "primitività" dei tipi di colonne (ad esempio, puoi aggiungere una colonna di date o persino una colonna contenente un elenco di elementi). Inoltre, questa domanda non è un riferimento assoluto, poiché ad esempio se non si specifica il tipo corretto della colonna non si bloccherà l'ulteriore aggiunta di righe con colonne di tipi diversi ... quindi, aggiungerò una nota, ma non un esempio con tutti i tipi primitivi perché non copre tutte le possibilità ...
digEmAll

3
@ user4050: la domanda era sulla creazione di un data.frame vuoto, quindi quando il numero di righe è zero ... forse vuoi creare un data.frame pieno su NA ... in quel caso puoi usare ad esempiodata.frame(Doubles=rep(as.double(NA),numberOfRow), Ints=rep(as.integer(NA),numberOfRow))
digEmAll

140

Se hai già un frame di dati esistente , supponiamo dfche abbia le colonne che desideri, quindi puoi semplicemente creare un frame di dati vuoto rimuovendo tutte le righe:

empty_df = df[FALSE,]

Si noti che dfcontiene ancora i dati, ma empty_dfnon lo è.

Ho trovato questa domanda cercando come creare una nuova istanza con righe vuote, quindi penso che potrebbe essere utile per alcune persone.


2
Idea meravigliosa. Non mantenere nessuna delle righe, ma TUTTE le colonne. Chi ha effettuato il downgrade ha perso qualcosa.
Ram Narasimhan,

1
Bella soluzione, tuttavia ho scoperto che ottengo un frame di dati con 0 righe. Per mantenere invariate le dimensioni del frame di dati, suggerisco new_df = df [NA,]. Ciò consente anche di memorizzare qualsiasi colonna precedente nel nuovo frame di dati. Ad esempio, per ottenere la colonna "Data" dall'originale df (mantenendo il resto NA): new_df $ Date <- df $ Date.
Katya,

2
@Katya, se lo fai df[NA,]influenzerà anche l'indice (che è improbabile che tu sia quello che vuoi), vorrei invece usare df[TRUE,] = NA; tuttavia notare che questo sovrascriverà l'originale. Dovrai prima copiare il dataframe copy_df = data.frame(df)e poicopy_df[TRUE,] = NA
toto_tico,

3
@ Katya, oppure puoi anche aggiungere facilmente righe vuote a empty_dfcon empty_df[0:nrow(df),] <- NA.
toto_tico,

1
@ Katya, usi un backquote (`) attorno a ciò che vorresti contrassegnare come codice, e ci sono altre cose come corsivo che usa *, e grassetto che usa **. Probabilmente vuoi leggere tutta la sintassi Markdown di SO . Tuttavia, la maggior parte ha senso solo per le risposte.
toto_tico,

79

Puoi farlo senza specificare i tipi di colonna

df = data.frame(matrix(vector(), 0, 3,
                dimnames=list(c(), c("Date", "File", "User"))),
                stringsAsFactors=F)

4
In tal caso, i tipi di colonna predefiniti come logici per vettore (), ma vengono quindi sovrascritti con i tipi degli elementi aggiunti a df. Prova str (df), df [1,1] <- 'x'
Dave X il

58

È possibile utilizzare read.tablecon una stringa vuota per l'input textcome segue:

colClasses = c("Date", "character", "character")
col.names = c("Date", "File", "User")

df <- read.table(text = "",
                 colClasses = colClasses,
                 col.names = col.names)

In alternativa, specificare col.namescome stringa:

df <- read.csv(text="Date,File,User", colClasses = colClasses)

Grazie a Richard Scriven per il miglioramento


4
O anche read.table(text = "", ...)così non è necessario aprire esplicitamente una connessione.
Rich Scriven,

snazzy. probabilmente il modo più estensibile / automabile di farlo per molte potenziali colonne
MichaelChirico

3
L' read.csvapproccio funziona anche con readr::read_csv, come in read_csv("Date,File,User\n", col_types = "Dcc"). In questo modo è possibile creare direttamente un mazzo vuoto della struttura richiesta.
Heather Turner,

27

Il modo più efficiente per farlo è utilizzare structureper creare un elenco che ha la classe "data.frame":

structure(list(Date = as.Date(character()), File = character(), User = character()), 
          class = "data.frame")
# [1] Date File User
# <0 rows> (or 0-length row.names)

Per mettere questo in prospettiva rispetto alla risposta attualmente accettata, ecco un semplice benchmark:

s <- function() structure(list(Date = as.Date(character()), 
                               File = character(), 
                               User = character()), 
                          class = "data.frame")
d <- function() data.frame(Date = as.Date(character()),
                           File = character(), 
                           User = character(), 
                           stringsAsFactors = FALSE) 
library("microbenchmark")
microbenchmark(s(), d())
# Unit: microseconds
#  expr     min       lq     mean   median      uq      max neval
#   s()  58.503  66.5860  90.7682  82.1735 101.803  469.560   100
#   d() 370.644 382.5755 523.3397 420.1025 604.654 1565.711   100

data.tabledi solito contiene un .internal.selfrefattributo, che non può essere simulato senza chiamare le data.tablefunzioni. Sei sicuro di non fare affidamento su un comportamento non documentato qui?
Adam Ryczkowski,

@AdamRyczkowski Penso che tu stia confondendo la classe "data.frame" di base e la classe aggiuntiva "data.table" dal pacchetto data.table .
Thomas,

Sì. Decisamente. Colpa mia. Ignora il mio ultimo commento. Mi sono imbattuto in questo thread durante la ricerca di data.tablee ho ipotizzato che Google abbia trovato quello che volevo e tutto qui è data.tablecorrelato.
Adam Ryczkowski,

1
@PatrickT Non c'è alcun controllo sul fatto che ciò che sta facendo il tuo codice abbia un senso. data.frame()fornisce controlli su nomi, nomi, ecc.
Thomas,

26

Dichiara e basta

table = data.frame()

quando provi alla rbindprima riga creerà le colonne


2
Non soddisfa veramente i requisiti del PO di "Voglio specificare i tipi di dati per ogni colonna e nominarli". Se il prossimo passo è un rbindquesto, funzionerebbe bene, altrimenti ...
Gregor Thomas,

Comunque, grazie per questa semplice soluzione. Volevo anche inizializzare un data.frame con colonne specifiche poiché pensavo che rbind potesse essere usato solo se le colonne corrispondono tra i due data.frame. Questo non sembra essere il caso. Sono stato sorpreso di poter semplicemente inizializzare un data.frame quando utilizzo rbind. Grazie.
giordano

4
La migliore soluzione proposta qui. Per me, usando il modo proposto, ha funzionato perfettamente con rbind().
Kots

17

Se stai cercando mancanza:

read.csv(text="col1,col2")

quindi non è necessario specificare i nomi delle colonne separatamente. Ottieni il tipo di colonna predefinito logico finché non riempi il frame di dati.


read.csv analizza l'argomento del testo in modo da ottenere i nomi delle colonne. È più compatto di read.table (text = "", col.names = c ("col1", "col2"))
marc

Ottengo:Error in data.frame(..., check.names = FALSE) : arguments imply differing number of rows: 0, 2
Climbs_lika_Spyder

Questo non soddisfa i requisiti di OP, "Voglio specificare i tipi di dati per ogni colonna" , anche se probabilmente potrebbe essere modificato per farlo.
Gregor Thomas,

14

Ho creato un frame di dati vuoto usando il seguente codice

df = data.frame(id = numeric(0), jobs = numeric(0));

e ho provato a legare alcune righe per popolare lo stesso come segue.

newrow = c(3, 4)
df <- rbind(df, newrow)

ma ha iniziato a dare nomi di colonna errati come segue

  X3 X4
1  3  4

La soluzione a questo è convertire Newrow nel tipo df come segue

newrow = data.frame(id=3, jobs=4)
df <- rbind(df, newrow)

ora fornisce il frame di dati corretto quando viene visualizzato con i nomi di colonna come segue

  id nobs
1  3   4 

7

Per creare un frame di dati vuoto , passare il numero di righe e colonne necessarie nella seguente funzione:

create_empty_table <- function(num_rows, num_cols) {
    frame <- data.frame(matrix(NA, nrow = num_rows, ncol = num_cols))
    return(frame)
}

Per creare una cornice vuota specificando la classe di ogni colonna , è sufficiente passare un vettore dei tipi di dati desiderati nella seguente funzione:

create_empty_table <- function(num_rows, num_cols, type_vec) {
  frame <- data.frame(matrix(NA, nrow = num_rows, ncol = num_cols))
  for(i in 1:ncol(frame)) {
    print(type_vec[i])
    if(type_vec[i] == 'numeric') {frame[,i] <- as.numeric(frame[,i])}
    if(type_vec[i] == 'character') {frame[,i] <- as.character(frame[,i])}
    if(type_vec[i] == 'logical') {frame[,i] <- as.logical(frame[,i])}
    if(type_vec[i] == 'factor') {frame[,i] <- as.factor(frame[,i])}
  }
  return(frame)
}

Utilizzare come segue:

df <- create_empty_table(3, 3, c('character','logical','numeric'))

Che dà:

   X1  X2 X3
1 <NA> NA NA
2 <NA> NA NA
3 <NA> NA NA

Per confermare le scelte, eseguire quanto segue:

lapply(df, class)

#output
$X1
[1] "character"

$X2
[1] "logical"

$X3
[1] "numeric"

1
Questo non soddisfa i requisiti di OP, "Voglio specificare i tipi di dati per ogni colonna"
Gregor Thomas,

6

Se vuoi creare un data.frame vuoto con nomi dinamici (nomi di colonne in una variabile), questo può aiutare:

names <- c("v","u","w")
df <- data.frame()
for (k in names) df[[k]]<-as.numeric()

Puoi cambiare anche i tipi se ne hai bisogno. piace:

names <- c("u", "v")
df <- data.frame()
df[[names[1]]] <- as.numeric()
df[[names[2]]] <- as.character()

4

Se non ti dispiace non specificare esplicitamente i tipi di dati, puoi farlo in questo modo:

headers<-c("Date","File","User")
df <- as.data.frame(matrix(,ncol=3,nrow=0))
names(df)<-headers

#then bind incoming data frame with col types to set data types
df<-rbind(df, new_df)

4

Usando data.tablepossiamo specificare i tipi di dati per ogni colonna.

library(data.table)    
data=data.table(a=numeric(), b=numeric(), c=numeric())

3

Se vuoi dichiarare un tale data.framecon molte colonne, probabilmente sarà una seccatura digitare tutte le classi di colonne a mano. Soprattutto se puoi usarlo rep, questo approccio è facile e veloce (circa il 15% più veloce dell'altra soluzione che può essere generalizzata in questo modo):

Se le classi di colonne desiderate si trovano in un vettore colClasses, è possibile effettuare le seguenti operazioni:

library(data.table)
setnames(setDF(lapply(colClasses, function(x) eval(call(x)))), col.names)

lapplycomporterà un elenco della lunghezza desiderata, ogni elemento del quale è semplicemente un vettore tipizzato vuoto come numeric()o integer().

setDFconverte questo listin riferimento a data.frame.

setnames aggiunge i nomi desiderati per riferimento.

Confronto della velocità:

classes <- c("character", "numeric", "factor",
             "integer", "logical","raw", "complex")

NN <- 300
colClasses <- sample(classes, NN, replace = TRUE)
col.names <- paste0("V", 1:NN)

setDF(lapply(colClasses, function(x) eval(call(x))))

library(microbenchmark)
microbenchmark(times = 1000,
               read = read.table(text = "", colClasses = colClasses,
                                 col.names = col.names),
               DT = setnames(setDF(lapply(colClasses, function(x)
                 eval(call(x)))), col.names))
# Unit: milliseconds
#  expr      min       lq     mean   median       uq      max neval cld
#  read 2.598226 2.707445 3.247340 2.747835 2.800134 22.46545  1000   b
#    DT 2.257448 2.357754 2.895453 2.401408 2.453778 17.20883  1000  a 

È anche più veloce dell'utilizzo structurein modo simile:

microbenchmark(times = 1000,
               DT = setnames(setDF(lapply(colClasses, function(x)
                 eval(call(x)))), col.names),
               struct = eval(parse(text=paste0(
                 "structure(list(", 
                 paste(paste0(col.names, "=", 
                              colClasses, "()"), collapse = ","),
                 "), class = \"data.frame\")"))))
#Unit: milliseconds
#   expr      min       lq     mean   median       uq       max neval cld
#     DT 2.068121 2.167180 2.821868 2.211214 2.268569 143.70901  1000  a 
# struct 2.613944 2.723053 3.177748 2.767746 2.831422  21.44862  1000   b

1

Supponi che i nomi delle colonne siano dinamici, puoi creare una matrice vuota con nome di riga e trasformarla in un frame di dati.

nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))

Questo non soddisfa i requisiti di OP, "Voglio specificare i tipi di dati per ogni colonna"
Gregor Thomas,

1

Questa domanda non ha affrontato in modo specifico le mie preoccupazioni (delineate qui ) ma nel caso qualcuno voglia farlo con un numero parametrizzato di colonne e nessuna coercizione:

> require(dplyr)
> dbNames <- c('a','b','c','d')
> emptyTableOut <- 
    data.frame(
        character(), 
        matrix(integer(), ncol = 3, nrow = 0), stringsAsFactors = FALSE
    ) %>% 
    setNames(nm = c(dbNames))
> glimpse(emptyTableOut)
Observations: 0
Variables: 4
$ a <chr> 
$ b <int> 
$ c <int> 
$ d <int>

Come afferma divibisan sulla questione collegata,

... il motivo per cui [la coercizione] si verifica [quando si collegano le matrici e i loro tipi costituenti] è che una matrice può avere un solo tipo di dati. Quando si collegano 2 matrici, il risultato è ancora una matrice e quindi le variabili vengono tutte forzate in un singolo tipo prima di convertirle in un data.frame


1

Se disponi già di un frame di dati, puoi estrarre i metadati (nomi e tipi di colonne) da un frame di dati (ad esempio se stai controllando un BUG che viene attivato solo con determinati input e hai bisogno di un frame di dati fittizio vuoto):

colums_and_types <- sapply(df, class)

# prints: "c('col1', 'col2')"
print(dput(as.character(names(colums_and_types))))

# prints: "c('integer', 'factor')"
dput(as.character(as.vector(colums_and_types)))

Quindi utilizzare il read.tableper creare il frame di dati vuoto

read.table(text = "",
   colClasses = c('integer', 'factor'),
   col.names = c('col1', 'col2'))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.