Crie um data.frame vazio

480

Estou tentando inicializar um data.frame sem nenhuma linha. Basicamente, desejo especificar os tipos de dados para cada coluna e nomeá-los, mas não ter nenhuma linha criada como resultado.

O melhor que pude fazer até agora é algo como:

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

O que cria um data.frame com uma única linha que contém todos os tipos de dados e nomes de colunas que eu queria, mas também cria uma linha inútil que precisa ser removida.

Existe uma maneira melhor de fazer isso?

Jeff Allen
fonte

Respostas:

652

Apenas inicialize-o com vetores vazios:

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

Aqui está outro exemplo com diferentes tipos de colunas:

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:

Iniciar um data.framecom uma coluna vazia do tipo errado não impede adições adicionais de linhas com colunas de tipos diferentes.
Esse método é um pouco mais seguro, no sentido de que você terá os tipos de coluna corretos desde o início; portanto, se seu código depender de alguma verificação de tipo de coluna, ele funcionará mesmo com a data.framelinha zero.

digEmAll
fonte
3
Seria o mesmo se eu inicializar todos os campos com NULL?
precisa saber é o seguinte
8
@yosukesabai: não, se você inicializar uma coluna com NULL coluna não será adicionado :)
digEmAll
6
@yosukesabai: data.frame's digitou colunas, então sim, se você quiser inicializar um data.framevocê deve decidir o tipo das colunas ...
digEmAll
1
@jxramos: bem, na verdade data.framenão é realmente restritivo à "primitividade" dos tipos de colunas (por exemplo, você pode adicionar uma coluna de datas ou mesmo uma coluna contendo a lista de elementos). Além disso, essa pergunta não é uma referência absoluta, pois, por exemplo, se você não especificar o tipo correto da coluna, não bloqueará a adição de linhas adicionais com colunas de tipos diferentes ... portanto, adicionarei uma nota, mas não um exemplo com todos os tipos primitivos porque não cobrir todas as possibilidades ...
digEmAll
3
@ user4050: a pergunta era sobre a criação de um data.frame vazio, então quando o número de linhas é zero ... talvez você queira criar um data.frame completo em NAs ... nesse caso, você pode usar, por exemplodata.frame(Doubles=rep(as.double(NA),numberOfRow), Ints=rep(as.integer(NA),numberOfRow))
digEmAll
140

Se você já possui um quadro de dados existente , digamos dfque possui as colunas desejadas, basta criar um quadro de dados vazio removendo todas as linhas:

empty_df = df[FALSE,]

Observe que dfainda contém os dados, mas empty_dfnão.

Eu encontrei esta pergunta procurando como criar uma nova instância com linhas vazias, então acho que pode ser útil para algumas pessoas.

toto_tico
fonte
2
Ideia maravilhosa. Mantenha nenhuma das linhas, mas TODAS as colunas. Quem votou mal perdeu algo.
Ram Narasimhan
1
Solução agradável, no entanto, descobri que recebo um quadro de dados com 0 linhas. Para manter o tamanho do quadro de dados igual, sugiro new_df = df [NA,]. Isso também permite armazenar qualquer coluna anterior no novo quadro de dados. Por exemplo, para obter a coluna "Data" do df original (mantendo o NA restante): new_df $ Date <- df $ Date.
Katya
2
@ Katya, se você fizer df[NA,]isso, também afetará o índice (o que é improvável que seja o que você deseja), eu usaria df[TRUE,] = NA; no entanto, observe que isso substituirá o original. Você precisará copiar o trama de dados de primeiro copy_df = data.frame(df)e depoiscopy_df[TRUE,] = NA
toto_tico
3
@Katya, ou você também pode adicionar facilmente linhas vazias ao empty_dfwith empty_df[0:nrow(df),] <- NA.
toto_tico
1
@ Katya, você usa uma citação (`) ao redor do que você gostaria de marcar como código, e existem outras coisas como itálico usando * e negrito usando **. Você provavelmente deseja ler toda a sintaxe Markdown do SO . Porém, a maioria só faz sentido para respostas.
toto_tico 10/09
79

Você pode fazer isso sem especificar os tipos de coluna

df = data.frame(matrix(vector(), 0, 3,
                dimnames=list(c(), c("Date", "File", "User"))),
                stringsAsFactors=F)
zeleniy
fonte
4
Nesse caso, os tipos de coluna são padrão como lógicos por vetor (), mas são substituídos pelos tipos dos elementos adicionados ao df. Tente str (df), df [1,1] <- 'x'
Dave X
58

Você pode usar read.tablecom uma sequência vazia para a entrada da textseguinte maneira:

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

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

Como alternativa, especificando o col.namescomo uma sequência:

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

Agradecimentos a Richard Scriven pela melhoria

Rentrop
fonte
4
Ou mesmo read.table(text = "", ...)assim, você não precisa abrir explicitamente uma conexão.
Rich Scriven
snazzy. provavelmente a maneira mais extensível / automable de fazer isso por muitas colunas potenciais
MichaelChirico
3
A read.csvabordagem também trabalha com readr::read_csv, como em read_csv("Date,File,User\n", col_types = "Dcc"). Dessa forma, você pode criar diretamente um prato vazio da estrutura necessária.
Heather Turner
27

A maneira mais eficiente de fazer isso é usar structurepara criar uma lista que tenha a 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)

Para colocar isso em perspectiva em comparação com a resposta atualmente aceita, aqui está uma referência simples:

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
fonte
data.tablegeralmente contém um .internal.selfrefatributo que não pode ser falsificado sem chamar as data.tablefunções. Tem certeza de que não está confiando em um comportamento não documentado aqui?
Adam Ryczkowski
@AdamRyczkowski Acho que você está confundindo a classe base "data.frame" e a classe add-on "data.table" do pacote data.table .
Thomas
Sim. Definitivamente. Foi mal. Ignore meu último comentário. Me deparei com esse tópico ao pesquisar data.tablee presumi que o Google encontrou o que eu queria e tudo aqui está data.tablerelacionado.
Adam Ryczkowski
1
@PatrickT Não há como verificar se o que seu código está fazendo faz algum sentido. data.frame()fornece verificações de nomes, nomes de nomes de domínio, etc.
Thomas
26

Apenas declare

table = data.frame()

quando você tenta rbinda primeira linha, ela cria as colunas

Daniel Fischer
fonte
2
Realmente não atende aos requisitos do OP de "Desejo especificar os tipos de dados para cada coluna e nomeá-los". Se o próximo passo for um, rbindisso funcionaria bem, se não ...
Gregor Thomas
De qualquer forma, obrigado por esta solução simples. Eu também queria inicializar um data.frame com colunas específicas, pois pensei que o rbind só pode ser usado se as colunas corresponderem entre os dois data.frame. Parece não ser esse o caso. Fiquei surpreso por poder simplesmente inicializar um data.frame ao usar o rbind. Obrigado.
Giordano
4
A melhor solução proposta aqui. Para mim, usando a maneira proposta, funcionou perfeitamente rbind().
Kots
17

Se você está procurando falta:

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

portanto, você não precisa especificar os nomes das colunas separadamente. Você obtém o tipo de coluna padrão lógico até preencher o quadro de dados.

marc
fonte
O read.csv analisa o argumento do texto para obter os nomes das colunas. É mais compacto do que read.table (text = "", col.names = c ( "col1", "col2"))
marc
Eu recebo:Error in data.frame(..., check.names = FALSE) : arguments imply differing number of rows: 0, 2
Climbs_lika_Spyder
Isso não atende aos requisitos do OP, "Desejo especificar os tipos de dados para cada coluna" , embora provavelmente possa ser modificado para isso.
Gregor Thomas
14

Criei um quadro de dados vazio usando o código a seguir

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

e tentou vincular algumas linhas para preencher o mesmo da seguinte maneira.

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

mas começou a fornecer nomes de colunas incorretos da seguinte maneira

  X3 X4
1  3  4

A solução para isso é converter newrow no tipo df da seguinte maneira

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

agora fornece o quadro de dados correto quando exibido com nomes de colunas da seguinte maneira

  id nobs
1  3   4 
Shrikant Prabhu
fonte
7

Para criar um quadro de dados vazio , transmita o número de linhas e colunas necessárias para a seguinte função:

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

Para criar um quadro vazio ao especificar a classe de cada coluna , basta passar um vetor dos tipos de dados desejados para a seguinte função:

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

Use da seguinte maneira:

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

Que dá:

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

Para confirmar suas escolhas, execute o seguinte:

lapply(df, class)

#output
$X1
[1] "character"

$X2
[1] "logical"

$X3
[1] "numeric"
Cibernético
fonte
1
Isso não atender aos requisitos do OP, "Eu quero especificar os tipos de dados para cada coluna"
Gregor Thomas
6

Se você deseja criar um data.frame vazio com nomes dinâmicos (nomes de colunas em uma variável), isso pode ajudar:

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

Você também pode alterar os tipos, se precisar. gostar:

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

Se você não se importa em não especificar tipos de dados explicitamente, pode fazê-lo desta maneira:

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 Ithaca
fonte
4

Ao usar data.table, podemos especificar tipos de dados para cada coluna.

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

Se você deseja declarar esse tipo data.framecom muitas colunas, provavelmente será difícil digitar todas as classes de colunas manualmente. Especialmente se você puder usar rep, essa abordagem é fácil e rápida (cerca de 15% mais rápida que a outra solução que pode ser generalizada assim):

Se as classes de coluna desejadas estiverem em um vetor colClasses, você poderá fazer o seguinte:

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

lapplyresultará em uma lista do comprimento desejado, cada elemento do qual é simplesmente um vetor digitado vazio como numeric()ou integer().

setDFconverte isso listpor referência a a data.frame.

setnames adiciona os nomes desejados por referência.

Comparação de velocidade:

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 

Também é mais rápido do que usar de structuremaneira semelhante:

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

Digamos que os nomes das colunas sejam dinâmicos, você pode criar uma matriz com nome de linha vazia e transformá-la em um quadro de dados.

nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))
jpmarindiaz
fonte
Isso não atender aos requisitos do OP, "Eu quero especificar os tipos de dados para cada coluna"
Gregor Thomas
1

Esta pergunta não abordou especificamente minhas preocupações (descritas aqui ), mas caso alguém queira fazer isso com um número parametrizado de colunas e sem coerção:

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

Como o divibisan afirma sobre a questão vinculada,

... a razão [coerção] ocorre [quando cbinding matrizes e seus tipos constituintes] é que uma matriz pode ter apenas um único tipo de dados. Quando você liga 2 matrizes, o resultado ainda é uma matriz e, portanto, todas as variáveis ​​são coagidas em um único tipo antes de converter em um data.frame

d8aninja
fonte
1

Se você já possui um dataframe, pode extrair os metadados (nomes e tipos de colunas) de um dataframe (por exemplo, se estiver controlando um BUG que é acionado apenas com determinadas entradas e precisa de um Dataframe fictício vazio):

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

E, em seguida, use o read.tablepara criar o quadro de dados vazio

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