Cree un marco de datos vacío.

480

Estoy tratando de inicializar un data.frame sin filas. Básicamente, quiero especificar los tipos de datos para cada columna y nombrarlos, pero no tener ninguna fila creada como resultado.

Lo mejor que he podido hacer hasta ahora es algo como:

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

Lo que crea un data.frame con una sola fila que contiene todos los tipos de datos y nombres de columnas que quería, pero también crea una fila inútil que luego debe eliminarse.

¿Hay una mejor manera de hacer esto?

Jeff Allen
fuente

Respuestas:

652

Solo inicialízalo con vectores vacíos:

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

Aquí hay otro ejemplo con diferentes tipos de columna:

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:

La inicialización de a data.framecon una columna vacía del tipo incorrecto no impide nuevas adiciones de filas que tienen columnas de diferentes tipos.
Este método es un poco más seguro en el sentido de que tendrá los tipos de columna correctos desde el principio, por lo tanto, si su código se basa en alguna verificación de tipo de columna, funcionará incluso con una data.framefila cero.

digEmAll
fuente
3
¿Sería lo mismo si inicializo todos los campos con NULL?
yosukesabai
8
@yosukesabai: no, si inicializa una columna con NULL, la columna no se agregará :)
digEmAll
66
@yosukesabai: data.frameha escrito columnas, así que sí, si desea inicializar data.framedebe decidir el tipo de columnas ...
digEmAll
1
@jxramos: bueno, en realidad data.frameno es realmente restrictivo en la "primitividad" de los tipos de columnas (por ejemplo, puede agregar una columna de fechas o incluso una columna que contenga una lista de elementos). Además, esta pregunta no es una referencia absoluta, ya que, por ejemplo, si no especifica el tipo correcto de la columna, no bloqueará la adición de filas adicionales que tengan columnas de diferentes tipos ... por lo tanto, agregaré una nota, pero no un ejemplo con todos los tipos primitivos, ya que no cubre todas las posibilidades ...
digEmAll
3
@ user4050: la pregunta era sobre la creación de un data.frame vacío, por lo que cuando el número de filas es cero ... tal vez desee crear un data.frame lleno en NA ... en ese caso puede usar, por ejemplodata.frame(Doubles=rep(as.double(NA),numberOfRow), Ints=rep(as.integer(NA),numberOfRow))
digEmAll
140

Si ya tiene un marco de datos existente , digamos dfque tiene las columnas que desea, puede crear un marco de datos vacío eliminando todas las filas:

empty_df = df[FALSE,]

Tenga en cuenta que dftodavía contiene los datos, pero empty_dfno los contiene .

Encontré esta pregunta buscando cómo crear una nueva instancia con filas vacías, por lo que creo que podría ser útil para algunas personas.

toto_tico
fuente
2
Maravillosa idea. No conserve ninguna de las filas, sino TODAS las columnas. Quien rechazó el voto perdió algo.
Ram Narasimhan
1
Buena solución, sin embargo, descubrí que obtengo un marco de datos con 0 filas. Para mantener el tamaño del marco de datos igual, sugiero new_df = df [NA,]. Esto también permite almacenar cualquier columna anterior en el nuevo marco de datos. Por ejemplo, para obtener la columna "Fecha" del df original (manteniendo el resto NA): new_df $ Date <- df $ Date.
Katya
2
@Katya, si haces df[NA,]esto también afectará el índice (que es poco probable que sea lo que quieres), en su lugar usaría df[TRUE,] = NA; sin embargo, tenga en cuenta que esto sobrescribirá el original. Necesitará copiar el marco de datos primero copy_df = data.frame(df)y luegocopy_df[TRUE,] = NA
toto_tico
3
@Katya, o también puede agregar fácilmente filas vacías al empty_dfcon empty_df[0:nrow(df),] <- NA.
toto_tico
1
@Katya, utiliza una comilla (`) alrededor de lo que desea marcar como código, y hay otras cosas como cursiva con * y negrita con **. Probablemente desee leer toda la sintaxis de Markdown de SO . Sin embargo, la mayor parte solo tiene sentido para las respuestas.
toto_tico
79

Puede hacerlo sin especificar tipos de columna.

df = data.frame(matrix(vector(), 0, 3,
                dimnames=list(c(), c("Date", "File", "User"))),
                stringsAsFactors=F)
zeleniy
fuente
44
En ese caso, los tipos de columna son predeterminados como lógicos por vector (), pero luego se anulan con los tipos de elementos agregados a df. Pruebe str (df), df [1,1] <- 'x'
Dave X
58

Puede usar read.tableuna cadena vacía para la entrada de la textsiguiente manera:

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

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

Alternativamente, especificando col.namescomo una cadena:

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

Gracias a Richard Scriven por la mejora.

Rentrop
fuente
44
O incluso read.table(text = "", ...)así, no necesita abrir explícitamente una conexión.
Rich Scriven
elegante. probablemente la forma más extensible / automatizable de hacer esto para muchas columnas potenciales
MichaelChirico
3
El read.csvenfoque también funciona con readr::read_csv, como en read_csv("Date,File,User\n", col_types = "Dcc"). De esta manera, puede crear directamente un tibble vacío de la estructura requerida.
Heather Turner
27

La forma más eficiente de hacer esto es usar structurepara crear una lista que tenga la clase "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 poner esto en perspectiva en comparación con la respuesta actualmente aceptada, aquí hay un punto de referencia simple:

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
fuente
data.tableusualmente contiene un .internal.selfrefatributo, que no se puede falsificar sin llamar a las data.tablefunciones. ¿Estás seguro de que no estás confiando en un comportamiento indocumentado aquí?
Adam Ryczkowski
@AdamRyczkowski Creo que estás confundiendo la clase base "data.frame" y la clase complementaria "data.table" del paquete data.table .
Thomas
Si. Seguro. Culpa mía. Ignora mi último comentario. Encontré este hilo cuando busqué data.tabley asumí que Google encontró lo que quería y que todo aquí está data.tablerelacionado.
Adam Ryczkowski
1
@PatrickT No hay comprobación de que lo que hace su código tiene sentido. data.frame()proporciona comprobaciones de nombres, filas, etc.
Thomas
26

Solo declara

table = data.frame()

cuando intentes con rbindla primera línea creará las columnas

Daniel Fischer
fuente
2
Realmente no cumple con los requisitos del OP de "Quiero especificar los tipos de datos para cada columna y nombrarlos". Si el siguiente paso es un, rbindesto funcionaría bien, si no ...
Gregor Thomas
De todos modos, gracias por esta solución simple. También quería inicializar un data.frame con columnas específicas, ya que pensé que rbind solo se puede usar si las columnas corresponden entre los dos data.frame. Este no parece ser el caso. Me sorprendió poder simplemente inicializar un data.frame cuando uso rbind. Gracias.
giordano
44
La mejor solución propuesta aquí. Para mí, utilizando la forma propuesta, funcionó perfectamente con rbind().
Kots
17

Si buscas escasez:

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

por lo que no necesita especificar los nombres de columna por separado. Obtiene el tipo de columna predeterminado lógico hasta que complete el marco de datos.

bagazo
fuente
read.csv analiza el argumento de texto para que obtenga los nombres de las columnas. Es más compacto que read.table (text = "", col.names = c ("col1", "col2"))
marc
Obtengo:Error in data.frame(..., check.names = FALSE) : arguments imply differing number of rows: 0, 2
Climbs_lika_Spyder
Esto no cumple con los requisitos de OP, "Quiero especificar los tipos de datos para cada columna" , aunque probablemente podría modificarse para hacerlo.
Gregor Thomas
14

Creé un marco de datos vacío usando el siguiente código

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

e intenté vincular algunas filas para llenar el mismo de la siguiente manera.

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

pero comenzó a dar nombres de columna incorrectos de la siguiente manera

  X3 X4
1  3  4

La solución a esto es convertir newrow para escribir df de la siguiente manera

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

ahora proporciona el marco de datos correcto cuando se muestra con los nombres de columna de la siguiente manera

  id nobs
1  3   4 
Shrikant Prabhu
fuente
7

Para crear un marco de datos vacío , pase el número de filas y columnas necesarias en la siguiente función:

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

Para crear un marco vacío mientras especifica la clase de cada columna , simplemente pase un vector de los tipos de datos deseados a la siguiente función:

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 de la siguiente manera:

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

Lo que da:

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

Para confirmar sus elecciones, ejecute lo siguiente:

lapply(df, class)

#output
$X1
[1] "character"

$X2
[1] "logical"

$X3
[1] "numeric"
Cibernético
fuente
1
Esto no cumple con los requisitos de OP, "Quiero especificar los tipos de datos para cada columna"
Gregor Thomas
6

Si desea crear un data.frame vacío con nombres dinámicos (colnames en una variable), esto puede ayudar:

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

También puede cambiar los tipos si lo necesita. me gusta:

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

Si no le importa no especificar los tipos de datos explícitamente, puede hacerlo de esta manera:

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)
Odiseo Ítaca
fuente
4

Al usar data.tablepodemos especificar los tipos de datos para cada columna.

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

Si desea declarar tal data.framecon muchas columnas, probablemente será difícil escribir a mano todas las clases de columnas. Especialmente si puede utilizarlo rep, este enfoque es fácil y rápido (aproximadamente un 15% más rápido que la otra solución que se puede generalizar así):

Si sus clases de columna deseadas están en un vector colClasses, puede hacer lo siguiente:

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

lapplydará como resultado una lista de la longitud deseada, cada elemento del cual es simplemente un vector tipeado vacío como numeric()o integer().

setDFconvierte esto listpor referencia a a data.frame.

setnames agrega los nombres deseados por referencia.

Comparación de velocidad:

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 

También es más rápido que usarlo structurede manera similar:

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

Digamos que los nombres de sus columnas son dinámicos, puede crear una matriz vacía con nombre de fila y transformarla en un marco de datos.

nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))
jpmarindiaz
fuente
Esto no cumple con los requisitos de OP, "Quiero especificar los tipos de datos para cada columna"
Gregor Thomas
1

Esta pregunta no abordó específicamente mis inquietudes (descritas aquí ), pero en caso de que alguien quiera hacer esto con un número parametrizado de columnas y sin coerción:

> 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 dice divibisan sobre la pregunta vinculada,

... la razón [coerción] ocurre [cuando se unen matrices y sus tipos constituyentes] es que una matriz solo puede tener un único tipo de datos. Cuando se unen 2 matrices, el resultado sigue siendo una matriz y, por lo tanto, todas las variables se convierten en un solo tipo antes de convertirlas en un marco de datos.

d8aninja
fuente
1

Si ya tiene un marco de datos, puede extraer los metadatos (nombres y tipos de columna) de un marco de datos (por ejemplo, si está controlando un ERROR que solo se activa con ciertas entradas y necesita un marco de datos ficticio vacío):

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

Y luego use el read.tablepara crear el marco de datos vacío

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