Erstellen Sie einen leeren Datenrahmen

480

Ich versuche, einen data.frame ohne Zeilen zu initialisieren. Grundsätzlich möchte ich die Datentypen für jede Spalte angeben und benennen, aber keine Zeilen als Ergebnis erstellen lassen.

Das Beste, was ich bisher tun konnte, ist so etwas wie:

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

Dadurch wird ein data.frame mit einer einzelnen Zeile erstellt, die alle von mir gewünschten Datentypen und Spaltennamen enthält, aber auch eine nutzlose Zeile erstellt, die dann entfernt werden muss.

Gibt es einen besseren Weg, dies zu tun?

Jeff Allen
quelle

Antworten:

652

Initialisieren Sie es einfach mit leeren Vektoren:

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

Hier ist ein weiteres Beispiel mit verschiedenen Spaltentypen:

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:

Das Initialisieren von a data.framemit einer leeren Spalte des falschen Typs verhindert nicht das weitere Hinzufügen von Zeilen mit Spalten unterschiedlichen Typs.
Diese Methode ist nur ein bisschen sicherer in dem Sinne, dass Sie von Anfang an die richtigen Spaltentypen haben. Wenn Ihr Code also auf einer Überprüfung des Spaltentyps beruht, funktioniert er auch mit a data.framemit null Zeilen.

digEmAll
quelle
3
Wäre es dasselbe, wenn ich alle Felder mit NULL initialisieren würde?
Yosukesabai
8
@yosukesabai: Nein, wenn Sie eine Spalte mit NULL initialisieren, wird die Spalte nicht hinzugefügt :)
digEmAll
6
@yosukesabai: data.frame's haben Spalten eingegeben, also ja, wenn Sie eine initialisieren möchten, data.framemüssen Sie den Typ der Spalten entscheiden ...
digEmAll
1
@jxramos: Nun, tatsächlich data.frameist die "Primitivität" der Spaltentypen nicht wirklich einschränkend (Sie können beispielsweise eine Spalte mit Datumsangaben oder sogar eine Spalte mit einer Liste von Elementen hinzufügen). Diese Frage ist auch keine absolute Referenz, da Sie beispielsweise, wenn Sie nicht den richtigen Spaltentyp angeben, keine weitere Zeilenaddition mit Spalten unterschiedlichen Typs blockieren. Ich werde also eine Notiz hinzufügen, aber nicht Ein Beispiel mit allen primitiven Typen, da es nicht alle Möglichkeiten
abdeckt
3
@ user4050: Die Frage betraf das Erstellen eines leeren data.frame. Wenn also die Anzahl der Zeilen Null ist, möchten Sie möglicherweise einen data.frame erstellen, der voll auf NAs ist. In diesem Fall können Sie zBdata.frame(Doubles=rep(as.double(NA),numberOfRow), Ints=rep(as.integer(NA),numberOfRow))
digEmAll
140

Wenn Sie bereits einen vorhandenen Datenrahmen haben , der beispielsweise dfdie gewünschten Spalten enthält, können Sie einfach einen leeren Datenrahmen erstellen, indem Sie alle Zeilen entfernen:

empty_df = df[FALSE,]

Beachten Sie, dass dfdie Daten weiterhin enthalten sind, dies jedoch empty_dfnicht.

Ich habe diese Frage auf der Suche nach einer neuen Instanz mit leeren Zeilen gefunden, daher denke ich, dass sie für einige Leute hilfreich sein könnte.

toto_tico
quelle
2
Wundervolle Idee. Behalten Sie keine der Zeilen, sondern ALLE Spalten. Wer herabgestimmt hat, hat etwas verpasst.
Ram Narasimhan
1
Gute Lösung, aber ich fand, dass ich einen Datenrahmen mit 0 Zeilen bekomme. Um die Größe des Datenrahmens gleich zu halten, schlage ich new_df = df [NA,] vor. Dies ermöglicht auch das Speichern einer vorherigen Spalte im neuen Datenrahmen. Zum Beispiel, um die Spalte "Datum" vom ursprünglichen df zu erhalten (während die Rest-NA beibehalten wird): new_df $ Date <- df $ Date.
Katya
2
@Katya, wenn Sie dies tun, wirkt sich df[NA,]dies auch auf den Index aus (was wahrscheinlich nicht das ist, was Sie wollen), würde ich stattdessen verwenden df[TRUE,] = NA; Beachten Sie jedoch, dass dadurch das Original überschrieben wird. Sie müssen zuerst den Datenrahmen kopieren copy_df = data.frame(df)und danncopy_df[TRUE,] = NA
toto_tico
3
@Katya, oder Sie können auch einfach leere Zeilen zum empty_dfmit hinzufügen empty_df[0:nrow(df),] <- NA.
toto_tico
1
@Katya, Sie verwenden ein Backquote (`) um das, was Sie als Code markieren möchten, und es gibt andere Dinge, die mit * kursiv und mit ** fett gedruckt sind. Sie möchten wahrscheinlich die gesamte Markdown-Syntax von SO lesen . Das meiste davon macht jedoch nur für Antworten Sinn.
toto_tico
79

Sie können dies tun, ohne Spaltentypen anzugeben

df = data.frame(matrix(vector(), 0, 3,
                dimnames=list(c(), c("Date", "File", "User"))),
                stringsAsFactors=F)
zeleniy
quelle
4
In diesem Fall sind die Spaltentypen standardmäßig logisch pro vector (), werden dann jedoch mit den Typen der zu df hinzugefügten Elemente überschrieben. Versuchen Sie str (df), df [1,1] <- 'x'
Dave X
58

Sie können read.tableeine leere Zeichenfolge für die Eingabe textwie folgt verwenden:

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

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

Alternativ können Sie das col.namesals Zeichenfolge angeben:

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

Vielen Dank an Richard Scriven für die Verbesserung

Rentrop
quelle
4
Oder read.table(text = "", ...)Sie müssen keine Verbindung explizit öffnen.
Rich Scriven
schick. wahrscheinlich die erweiterbarste / automatisierbarste Methode, dies für viele potenzielle Spalten zu
tun
3
Der read.csvAnsatz funktioniert auch mit readr::read_csv, wie in read_csv("Date,File,User\n", col_types = "Dcc"). Auf diese Weise können Sie direkt ein leeres Tibble der erforderlichen Struktur erstellen.
Heather Turner
27

Der effizienteste Weg, dies zu tun, besteht structuredarin, eine Liste mit der Klasse zu erstellen "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)

Um dies im Vergleich zur derzeit akzeptierten Antwort ins rechte Licht zu rücken, hier ein einfacher Maßstab:

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
Thomas
quelle
data.tableis enthält normalerweise ein .internal.selfrefAttribut, das nicht gefälscht werden kann, ohne die data.tableFunktionen aufzurufen . Sind Sie sicher, dass Sie sich hier nicht auf ein undokumentiertes Verhalten verlassen?
Adam Ryczkowski
@AdamRyczkowski Ich denke, Sie verwechseln die Basisklasse "data.frame" und die Add-On-Klasse "data.table" aus dem Paket "data.table" .
Thomas
Ja. Bestimmt. Mein Fehler. Ignoriere meinen letzten Kommentar. Ich bin auf diesen Thread gestoßen, als ich nach dem gesucht habe, data.tableund habe angenommen, dass Google gefunden hat, was ich wollte, und alles hier hängt damit zusammen data.table.
Adam Ryczkowski
1
@PatrickT Es wird nicht überprüft, ob das, was Ihr Code tut, Sinn macht. data.frame()bietet Überprüfungen von Namen, Rownamen usw.
Thomas
26

Einfach deklarieren

table = data.frame()

Wenn Sie versuchen, in rbinddie erste Zeile zu gelangen, werden die Spalten erstellt

Daniel Fischer
quelle
2
Entspricht nicht wirklich den Anforderungen des OP "Ich möchte die Datentypen für jede Spalte angeben und benennen". Wenn der nächste Schritt ein ist, rbindwürde dies gut funktionieren, wenn nicht ...
Gregor Thomas
Trotzdem danke für diese einfache Lösung. Ich wollte auch einen data.frame mit bestimmten Spalten initialisieren, da ich dachte, dass rbind nur verwendet werden kann, wenn die Spalten zwischen den beiden data.frame entsprechen. Dies scheint nicht der Fall zu sein. Ich war überrascht, dass ich bei Verwendung von rbind so einfach einen data.frame initialisieren kann. Vielen Dank.
Giordano
4
Die beste vorgeschlagene Lösung hier. Für mich hat der vorgeschlagene Weg perfekt funktioniert rbind().
Kots
17

Wenn Sie nach Kürze suchen:

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

Sie müssen die Spaltennamen also nicht separat angeben. Sie erhalten den Standardspaltentyp logisch, bis Sie den Datenrahmen füllen.

marc
quelle
read.csv analysiert das Textargument, sodass Sie die Spaltennamen erhalten. Es ist kompakter als read.table (text = "", col.names = c ( "col1", "col2"))
marc
Ich bekomme:Error in data.frame(..., check.names = FALSE) : arguments imply differing number of rows: 0, 2
Climbs_lika_Spyder
Dies entspricht nicht den Anforderungen von OP "Ich möchte die Datentypen für jede Spalte angeben" , obwohl dies wahrscheinlich geändert werden könnte.
Gregor Thomas
14

Ich habe einen leeren Datenrahmen mit folgendem Code erstellt

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

und versuchte, einige Zeilen zu binden, um das gleiche wie folgt zu füllen.

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

Es wurden jedoch folgende falsche Spaltennamen angegeben

  X3 X4
1  3  4

Die Lösung hierfür besteht darin, newrow wie folgt in den Typ df umzuwandeln

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

Gibt jetzt den korrekten Datenrahmen an, wenn er wie folgt mit Spaltennamen angezeigt wird

  id nobs
1  3   4 
Shrikant Prabhu
quelle
7

Übergeben Sie zum Erstellen eines leeren Datenrahmens die Anzahl der benötigten Zeilen und Spalten an die folgende Funktion:

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

Um einen leeren Frame zu erstellen, während Sie die Klasse jeder Spalte angeben , übergeben Sie einfach einen Vektor der gewünschten Datentypen an die folgende Funktion:

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)
}

Verwenden Sie wie folgt:

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

Welches gibt:

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

Führen Sie die folgenden Schritte aus, um Ihre Auswahl zu bestätigen:

lapply(df, class)

#output
$X1
[1] "character"

$X2
[1] "logical"

$X3
[1] "numeric"
Kybernetisch
quelle
1
Dies entspricht nicht den Anforderungen von OP: "Ich möchte die Datentypen für jede Spalte angeben"
Gregor Thomas,
6

Wenn Sie einen leeren data.frame mit dynamischen Namen (Spaltennamen in einer Variablen) erstellen möchten, kann dies helfen:

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

Sie können die Typen auch ändern, wenn Sie dies benötigen. mögen:

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

Wenn es Ihnen nichts ausmacht, Datentypen nicht explizit anzugeben, können Sie dies folgendermaßen tun:

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)
Odysseus Ithaka
quelle
4

Mit Using können data.tablewir Datentypen für jede Spalte angeben.

library(data.table)    
data=data.table(a=numeric(), b=numeric(), c=numeric())
Rushabh Patel
quelle
3

Wenn Sie eine solche data.framemit vielen Spalten deklarieren möchten , ist es wahrscheinlich schwierig, alle Spaltenklassen manuell einzugeben. Insbesondere wenn Sie davon Gebrauch machen können, repist dieser Ansatz einfach und schnell (ungefähr 15% schneller als die andere Lösung, die so verallgemeinert werden kann):

Wenn sich Ihre gewünschten Spaltenklassen in einem Vektor befinden colClasses, können Sie Folgendes tun:

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

lapplyführt zu einer Liste der gewünschten Länge, von der jedes Element einfach ein leerer typisierter Vektor wie numeric()oder ist integer().

setDFkonvertiert dies listunter Bezugnahme auf a data.frame.

setnames fügt die gewünschten Namen als Referenz hinzu.

Geschwindigkeitsvergleich:

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 

Es ist auch schneller als structureauf ähnliche Weise:

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
MichaelChirico
quelle
1

Angenommen, Ihre Spaltennamen sind dynamisch. Sie können eine leere Matrix mit Zeilennamen erstellen und in einen Datenrahmen umwandeln.

nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))
jpmarindiaz
quelle
Dies entspricht nicht den Anforderungen von OP: "Ich möchte die Datentypen für jede Spalte angeben"
Gregor Thomas,
1

Diese Frage ging nicht speziell auf meine Bedenken ein ( hier beschrieben ), aber falls jemand dies mit einer parametrisierten Anzahl von Spalten und ohne Zwang tun möchte:

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

Wie Divibisan zu der damit verbundenen Frage feststellt,

... der Grund, warum [Zwang] auftritt [wenn Bindungsmatrizen und ihre Bestandteile gebunden werden], ist, dass eine Matrix nur einen einzigen Datentyp haben kann. Wenn Sie 2 Matrizen binden, ist das Ergebnis immer noch eine Matrix, und daher werden alle Variablen vor der Konvertierung in einen data.frame zu einem einzigen Typ gezwungen

d8aninja
quelle
1

Wenn Sie bereits einen Datenrahmen haben, können Sie die Metadaten (Spaltennamen und -typen) aus einem Datenrahmen extrahieren (z. B. wenn Sie einen BUG steuern, der nur mit bestimmten Eingaben ausgelöst wird und einen leeren Dummy-Datenrahmen benötigt):

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)))

Verwenden Sie dann die read.table, um den leeren Datenrahmen zu erstellen

read.table(text = "",
   colClasses = c('integer', 'factor'),
   col.names = c('col1', 'col2'))
toto_tico
quelle