Utwórz pustą ramkę danych

480

Próbuję zainicjować ramkę data.frame bez żadnych wierszy. Zasadniczo chcę określić typy danych dla każdej kolumny i nazwać je, ale nie chcę tworzyć żadnych wierszy.

Do tej pory najlepsze, co udało mi się zrobić, to:

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

Który tworzy ramkę danych z pojedynczym wierszem zawierającym wszystkie typy danych i nazwy kolumn, które chciałem, ale także tworzy bezużyteczny wiersz, który następnie należy usunąć.

Czy jest na to lepszy sposób?

Jeff Allen
źródło

Odpowiedzi:

652

Wystarczy zainicjować go pustymi wektorami:

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

Oto inny przykład z różnymi typami kolumn:

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:

Inicjowanie data.framez pustą kolumną niewłaściwego typu nie zapobiega dalszym dodawaniu wierszy zawierających kolumny różnych typów.
Ta metoda jest nieco bezpieczniejsza w tym sensie, że będziesz mieć prawidłowe typy kolumn od samego początku, dlatego jeśli twój kod polega na sprawdzeniu typu kolumny, zadziała nawet data.framez zerowymi wierszami.

digEmAll
źródło
3
Czy byłoby tak samo, gdybym zainicjował wszystkie pola wartością NULL?
yosukesabai,
8
@yosukesabai: nie, jeśli zainicjujesz kolumnę wartością NULL, kolumna nie zostanie dodana :)
digEmAll 20.08.2013
6
@yosukesabai: data.framemają wpisane kolumny, więc tak, jeśli chcesz zainicjować data.frame, musisz zdecydować o typie kolumn ...
digEmAll
1
@jxramos: cóż, tak data.framenaprawdę nie jest tak naprawdę ograniczający „prymitywność” typów kolumn (na przykład możesz dodać kolumnę dat lub nawet kolumnę zawierającą listę elementów). To pytanie nie jest również bezwzględnym odniesieniem, ponieważ na przykład, jeśli nie określisz poprawnego typu kolumny, nie zablokujesz dalszego dodawania wiersza z kolumną różnych typów ... więc dodam notatkę, ale nie przykład wszystkich prymitywnych typów, ponieważ nie obejmuje wszystkich możliwości ...
digEmAll
3
@ user4050: pytanie dotyczyło utworzenia pustej ramki danych. więc kiedy liczba wierszy wynosi zero ... może chcesz utworzyć ramkę danych pełną na NA ... w takim przypadku możesz użyć np.data.frame(Doubles=rep(as.double(NA),numberOfRow), Ints=rep(as.integer(NA),numberOfRow))
digEmAll
140

Jeśli masz już istniejącą ramkę danych , powiedzmy, dfże ma żądane kolumny, możesz po prostu utworzyć pustą ramkę danych, usuwając wszystkie wiersze:

empty_df = df[FALSE,]

Zauważ, że dfnadal zawiera dane, ale empty_dfich nie zawiera.

Znalazłem to pytanie, szukając sposobu utworzenia nowej instancji z pustymi wierszami, więc myślę, że może być pomocne dla niektórych osób.

toto_tico
źródło
2
Cudowny pomysł. Nie przechowuj żadnego wiersza, ale WSZYSTKIE kolumny. Kto głosował za czymś, coś przeoczył.
Ram Narasimhan
1
Ładne rozwiązanie, ale okazało się, że otrzymuję ramkę danych z 0 wierszami. Aby zachować ten sam rozmiar ramki danych, sugeruję new_df = df [NA,]. Pozwala to również zapisać dowolną poprzednią kolumnę w nowej ramce danych. Na przykład, aby uzyskać kolumnę „Data” z oryginalnego pliku df (zachowując resztę NA): new_df $ Data <- df $ Data.
Katya
2
@Katya, jeśli to zrobisz df[NA,], wpłynie również na indeks (co raczej nie będzie tym, czego chcesz), zamiast tego użyłbym df[TRUE,] = NA; zauważ jednak, że spowoduje to zastąpienie oryginału. Najpierw musisz skopiować ramkę danych, copy_df = data.frame(df)a następniecopy_df[TRUE,] = NA
toto_tico,
3
@Katya, lub możesz również łatwo dodać puste wiersze za empty_dfpomocą empty_df[0:nrow(df),] <- NA.
toto_tico
1
@Katya, używasz cudzysłowu (`) wokół tego, co chciałbyś oznaczyć jako kod, i istnieją inne rzeczy, takie jak kursywa przy użyciu * i pogrubienie przy użyciu **. Prawdopodobnie chcesz przeczytać całą składnię Markdown SO . Większość z nich ma jednak sens tylko w przypadku odpowiedzi.
toto_tico,
79

Możesz to zrobić bez określania typów kolumn

df = data.frame(matrix(vector(), 0, 3,
                dimnames=list(c(), c("Date", "File", "User"))),
                stringsAsFactors=F)
Zelenyy
źródło
4
W takim przypadku typy kolumn są domyślnie logiczne dla wektora (), ale są zastępowane typami elementów dodawanych do df. Spróbuj str (df), df [1,1] <- 'x'
Dave X
58

Możesz użyć read.tablepustego łańcucha wejściowego textw następujący sposób:

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

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

Alternatywnie, określając col.namesjako ciąg:

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

Dzięki Richardowi Scrivenowi za poprawę

Rentrop
źródło
4
A nawet read.table(text = "", ...)nie musisz jawnie otwierać połączenia.
Rich Scriven
odlotowy. prawdopodobnie najbardziej rozszerzalny / zautomatyzowany sposób na zrobienie tego dla wielu potencjalnych kolumn
MichaelChirico
3
read.csvPodejście działa również readr::read_csv, podobnie jak w read_csv("Date,File,User\n", col_types = "Dcc"). W ten sposób możesz bezpośrednio utworzyć pustą tabliczkę o wymaganej strukturze.
Heather Turner
27

Najskuteczniejszym sposobem na to jest structureutworzenie listy zawierającej klasę "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)

Aby umieścić to w perspektywie w porównaniu z obecnie akceptowaną odpowiedzią, oto prosty punkt odniesienia:

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
Tomasz
źródło
data.tablezawiera zwykle .internal.selfrefatrybut, którego nie można sfałszować bez wywołania data.tablefunkcji. Czy na pewno nie polegasz na nieudokumentowanym zachowaniu?
Adam Ryczkowski
@AdamRyczkowski Myślę, że mylisz podstawową klasę „data.frame” i dodatkową klasę „data.table” z pakietu data.table .
Thomas
Tak. Zdecydowanie. Mój błąd. Zignoruj ​​mój ostatni komentarz. Natknąłem się na ten wątek, szukając data.tablei zakładając, że Google znalazł to, czego chciałem i wszystko tutaj jest data.tablepowiązane.
Adam Ryczkowski
1
@PatrickT Nie ma możliwości sprawdzenia, czy to, co robi Twój kod, ma sens. data.frame()zapewnia kontrolę nad nazwami, nazwami itp.
Thomas
26

Po prostu zadeklaruj

table = data.frame()

próba przejścia do rbindpierwszego wiersza spowoduje utworzenie kolumn

Daniel Fischer
źródło
2
Naprawdę nie spełnia wymagań OP: „Chcę określić typy danych dla każdej kolumny i nazwać je”. Jeśli następnym krokiem będzie, rbindto zadziałałoby dobrze, jeśli nie ...
Gregor Thomas
W każdym razie dzięki za to proste rozwiązanie. Chciałem również zainicjować ramkę danych z określonymi kolumnami, ponieważ myślałem, że rbind może być użyty tylko wtedy, gdy kolumny odpowiadają między dwoma ramkami danych. Wydaje się, że tak nie jest. Byłem zaskoczony, że tak łatwo mogę zainicjować ramkę data.frame podczas korzystania z rbind. Dzięki.
giordano,
4
Najlepsze proponowane rozwiązanie tutaj. Dla mnie, używając proponowanego sposobu, działało idealnie rbind().
Kots
17

Jeśli szukasz krótkości:

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

więc nie trzeba osobno określać nazw kolumn. Otrzymasz logiczny domyślny typ kolumny, dopóki nie wypełnisz ramki danych.

Marc
źródło
read.csv analizuje argument tekstowy, aby uzyskać nazwy kolumn. To jest bardziej kompaktowy niż read.table (tekst = "", col.names = c ( "col1", "col2"))
wytłoków
Dostaję:Error in data.frame(..., check.names = FALSE) : arguments imply differing number of rows: 0, 2
Climbs_lika_Spyder
Nie spełnia to wymagań OP: „Chcę określić typy danych dla każdej kolumny” , choć prawdopodobnie można to zmodyfikować.
Gregor Thomas
14

Utworzyłem pustą ramkę danych za pomocą następującego kodu

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

i próbował powiązać niektóre wiersze, aby wypełnić to samo, co następuje.

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

ale zaczął nadawać niepoprawne nazwy kolumn w następujący sposób

  X3 X4
1  3  4

Rozwiązaniem tego jest konwersja newrow na typ df w następujący sposób

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

teraz wyświetla poprawną ramkę danych, gdy jest wyświetlana z następującymi nazwami kolumn

  id nobs
1  3   4 
Shrikant Prabhu
źródło
7

Aby utworzyć pustą ramkę danych , wprowadź wymaganą liczbę wierszy i kolumn do następującej funkcji:

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

Aby utworzyć pustą ramkę , określając klasę każdej kolumny , wystarczy przekazać wektor pożądanych typów danych do następującej funkcji:

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

Użyj w następujący sposób:

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

Co daje:

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

Aby potwierdzić wybór, uruchom następujące polecenie:

lapply(df, class)

#output
$X1
[1] "character"

$X2
[1] "logical"

$X3
[1] "numeric"
Cybernetyczny
źródło
1
To nie spełnia wymagań OP: „Chcę określić typy danych dla każdej kolumny”
Gregor Thomas
6

Jeśli chcesz utworzyć pustą ramkę danych z dynamicznymi nazwami (nazwy zmiennych w zmiennej), może to pomóc:

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

Możesz także zmienić typy, jeśli potrzebujesz. lubić:

names <- c("u", "v")
df <- data.frame()
df[[names[1]]] <- as.numeric()
df[[names[2]]] <- as.character()
Ali Khosro
źródło
4

Jeśli nie masz nic przeciwko jawnemu określaniu typów danych, możesz to zrobić w ten sposób:

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)
Odyseusz Ithaca
źródło
4

Za pomocą data.tablemożemy określić typy danych dla każdej kolumny.

library(data.table)    
data=data.table(a=numeric(), b=numeric(), c=numeric())
Rushabh Patel
źródło
3

Jeśli chcesz zadeklarować taką data.frameliczbę kolumn, prawdopodobnie trudno będzie ręcznie wpisać wszystkie klasy kolumn. Zwłaszcza jeśli możesz skorzystać z reptej metody, jest to łatwe i szybkie (około 15% szybsze niż inne rozwiązanie, które można uogólnić w ten sposób):

Jeśli żądane klasy kolumn są w wektorze colClasses, możesz wykonać następujące czynności:

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

lapplyspowoduje utworzenie listy pożądanej długości, której każdy element jest po prostu pustym wektorem wektorowym, takim jak numeric()lub integer().

setDFkonwertuje to listprzez odniesienie do data.frame.

setnames dodaje żądane nazwy przez odniesienie.

Porównanie prędkości:

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 

Jest także szybszy niż używanie structurew podobny sposób:

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
źródło
1

Załóżmy, że nazwy kolumn są dynamiczne, możesz utworzyć pustą macierz o nazwie wiersza i przekształcić ją w ramkę danych.

nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))
jpmarindiaz
źródło
To nie spełnia wymagań OP: „Chcę określić typy danych dla każdej kolumny”
Gregor Thomas
1

To pytanie nie dotyczyło w szczególności moich obaw ( tutaj nakreślonych ), ale na wypadek, gdyby ktoś chciał to zrobić ze sparametryzowaną liczbą kolumn i bez przymusu:

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

Jak stwierdza divibisan w powiązanym pytaniu,

... powodem [przymusu] [gdy cbinding macierzy i ich typów składowych] jest to, że macierz może mieć tylko jeden typ danych. Gdy łączysz 2 macierze, wynikiem jest nadal macierz, więc wszystkie zmienne są konwertowane do jednego typu przed konwersją do data.frame

d8aninja
źródło
1

Jeśli masz już ramkę danych, możesz wyodrębnić metadane (nazwy i typy kolumn) z ramki danych (np. Jeśli kontrolujesz BŁĄD, który jest wyzwalany tylko z pewnymi danymi wejściowymi i potrzebujesz pustej obojętnej ramki danych):

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

A następnie użyj read.tabledo utworzenia pustej ramki danych

read.table(text = "",
   colClasses = c('integer', 'factor'),
   col.names = c('col1', 'col2'))
toto_tico
źródło