Créer un data.frame vide

480

J'essaie d'initialiser un data.frame sans aucune ligne. Fondamentalement, je veux spécifier les types de données pour chaque colonne et les nommer, mais ne pas avoir de lignes créées en conséquence.

Le mieux que j'ai pu faire jusqu'à présent est quelque chose comme:

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

Ce qui crée un data.frame avec une seule ligne contenant tous les types de données et noms de colonnes que je voulais, mais crée également une ligne inutile qui doit ensuite être supprimée.

Y a-t-il une meilleure manière de faire cela?

Jeff Allen
la source

Réponses:

652

Il suffit de l'initialiser avec des vecteurs vides:

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

Voici un autre exemple avec différents types de colonnes:

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'initialisation d'un data.frameavec une colonne vide de mauvais type n'empêche pas d'autres ajouts de lignes ayant des colonnes de types différents.
Cette méthode est juste un peu plus sûre dans le sens où vous aurez les bons types de colonnes dès le début, donc si votre code repose sur une vérification de type de colonne, il fonctionnera même avec un data.frameavec zéro lignes.

digEmAll
la source
3
Serait-ce la même chose si j'initialise tous les champs avec NULL?
yosukesabai
8
@yosukesabai: non, si vous initialisez une colonne avec NULL, la colonne ne sera pas ajoutée :)
digEmAll
6
@yosukesabai: data.frameont des colonnes tapées, donc oui, si vous voulez initialiser un, data.framevous devez décider du type des colonnes ...
digEmAll
1
@jxramos: eh bien, en fait, ce data.framen'est pas vraiment restrictif sur la "primitivité" des types de colonnes (par exemple, vous pouvez ajouter une colonne de dates ou même une colonne contenant une liste d'éléments). En outre, cette question n'est pas une référence absolue, car par exemple si vous ne spécifiez pas le type correct de la colonne, vous ne bloquerez pas l'ajout de lignes supplémentaires ayant une colonne de types différents ... donc, j'ajouterai une note, mais pas un exemple avec tous les types primitifs car il ne couvre pas toutes les possibilités ...
digEmAll
3
@ user4050: la question portait sur la création d'un data.frame vide, donc quand le nombre de lignes est nul ... peut-être que vous voulez créer un data.frame plein sur les NA ... dans ce cas vous pouvez utiliser par exempledata.frame(Doubles=rep(as.double(NA),numberOfRow), Ints=rep(as.integer(NA),numberOfRow))
digEmAll
140

Si vous avez déjà un bloc de données existant , disons dfqu'il a les colonnes que vous voulez, alors vous pouvez simplement créer un bloc de données vide en supprimant toutes les lignes:

empty_df = df[FALSE,]

Notez que dfcontient toujours les données, mais empty_dfpas.

J'ai trouvé cette question en cherchant comment créer une nouvelle instance avec des lignes vides, donc je pense que cela pourrait être utile pour certaines personnes.

toto_tico
la source
2
Merveilleuse idée. Ne conservez aucune des lignes, mais TOUTES les colonnes. Celui qui a voté contre a raté quelque chose.
Ram Narasimhan
1
Belle solution, mais j'ai trouvé que j'obtiens un bloc de données avec 0 lignes. Afin de garder la taille de la trame de données la même, je suggère new_df = df [NA,]. Cela permet également de stocker n'importe quelle colonne précédente dans le nouveau bloc de données. Par exemple pour obtenir la colonne "Date" du df d'origine (tout en gardant le reste NA): new_df $ Date <- df $ Date.
Katya
2
@Katya, si vous faites df[NA,]cela, cela affectera également l'index (ce qui n'est probablement pas ce que vous voulez), j'utiliserais plutôt df[TRUE,] = NA; notez cependant que cela écrasera l'original. Vous devrez d'abord copier la trame de données copy_df = data.frame(df), puiscopy_df[TRUE,] = NA
toto_tico
3
@Katya, ou vous pouvez également ajouter facilement des lignes vides au empty_dfavec empty_df[0:nrow(df),] <- NA.
toto_tico
1
@Katya, vous utilisez une citation arrière (`) autour de ce que vous souhaitez marquer comme code, et il y a d'autres choses en italique en utilisant * et en gras en utilisant **. Vous voulez probablement lire toute la syntaxe Markdown de SO . Cependant, la plupart d'entre elles n'ont de sens que pour les réponses.
toto_tico
79

Vous pouvez le faire sans spécifier de types de colonnes

df = data.frame(matrix(vector(), 0, 3,
                dimnames=list(c(), c("Date", "File", "User"))),
                stringsAsFactors=F)
zeleniy
la source
4
Dans ce cas, les types de colonnes par défaut sont logiques par vecteur (), mais sont ensuite remplacés par les types des éléments ajoutés à df. Essayez str (df), df [1,1] <- 'x'
Dave X
58

Vous pouvez utiliser read.tableavec une chaîne vide pour l'entrée textcomme suit:

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

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

Vous pouvez également spécifier le col.namescomme une chaîne:

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

Merci à Richard Scriven pour l'amélioration

Rentrop
la source
4
Ou même read.table(text = "", ...)si vous n'avez pas besoin d'ouvrir explicitement une connexion.
Rich Scriven
snazzy. probablement la façon la plus extensible / automatisable de le faire pour de nombreuses colonnes potentielles
MichaelChirico
3
L' read.csvapproche fonctionne également avec readr::read_csv, comme dans read_csv("Date,File,User\n", col_types = "Dcc"). De cette façon, vous pouvez créer directement un morceau vide de la structure requise.
Heather Turner
27

La façon la plus efficace de le faire est d'utiliser structurepour créer une liste qui a 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)

Pour mettre cela en perspective par rapport à la réponse actuellement acceptée, voici une référence 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
la source
data.tableIl contient généralement un .internal.selfrefattribut, qui ne peut pas être simulé sans appeler les data.tablefonctions. Êtes-vous sûr de ne pas vous fier à un comportement non documenté ici?
Adam Ryczkowski
@AdamRyczkowski Je pense que vous confondez la classe "data.frame" de base et la classe add-on "data.table" du package data.table .
Thomas
Oui. Absolument. Ma faute. Ignorez mon dernier commentaire. Je suis tombé sur ce fil lors de la recherche de data.tableet j'ai supposé que Google avait trouvé ce que je voulais et tout ici est data.tablelié.
Adam Ryczkowski
1
@PatrickT Il n'y a aucune vérification que ce que fait votre code est logique. data.frame()fournit des vérifications sur la dénomination, les noms de domaine, etc.
Thomas
26

Déclarez simplement

table = data.frame()

lorsque vous essayez à rbindla première ligne, cela créera les colonnes

Daniel Fischer
la source
2
Ne répond pas vraiment aux exigences du PO "Je veux spécifier les types de données pour chaque colonne et les nommer". Si la prochaine étape est un, rbindcela fonctionnerait bien, sinon ...
Gregor Thomas
Quoi qu'il en soit, merci pour cette solution simple. Je voulais aussi initialiser un data.frame avec des colonnes spécifiques car je pensais que rbind ne peut être utilisé que si les colonnes correspondent entre les deux data.frame. Cela ne semble pas être le cas. J'ai été surpris de pouvoir tout simplement initialiser un data.frame lors de l'utilisation de rbind. Merci.
giordano
4
La meilleure solution proposée ici. Pour moi, en utilisant la méthode proposée, a parfaitement fonctionné avec rbind().
Kots
17

Si vous recherchez une brièveté:

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

vous n'avez donc pas besoin de spécifier les noms de colonne séparément. Vous obtenez le type de colonne par défaut logique jusqu'à ce que vous remplissiez le bloc de données.

marc
la source
read.csv analyse l'argument de texte pour obtenir les noms de colonne. Il est plus compact que read.table (text = "", col.names = c ("col1", "col2"))
marc
Je reçois:Error in data.frame(..., check.names = FALSE) : arguments imply differing number of rows: 0, 2
Climbs_lika_Spyder
Cela ne répond pas aux exigences de l'OP, "Je veux spécifier les types de données pour chaque colonne" , bien qu'il puisse probablement être modifié pour le faire.
Gregor Thomas
14

J'ai créé un bloc de données vide en utilisant le code suivant

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

et essayé de lier certaines lignes pour remplir la même chose comme suit.

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

mais il a commencé à donner des noms de colonnes incorrects comme suit

  X3 X4
1  3  4

La solution à cela consiste à convertir newrow en type df comme suit

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

donne maintenant un cadre de données correct lorsqu'il est affiché avec les noms de colonne comme suit

  id nobs
1  3   4 
Shrikant Prabhu
la source
7

Pour créer un bloc de données vide , passez le nombre de lignes et de colonnes nécessaires dans la fonction suivante:

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

Pour créer un cadre vide tout en spécifiant la classe de chaque colonne , passez simplement un vecteur des types de données souhaités dans la fonction suivante:

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

Utilisez comme suit:

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

Qui donne:

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

Pour confirmer vos choix, exécutez ce qui suit:

lapply(df, class)

#output
$X1
[1] "character"

$X2
[1] "logical"

$X3
[1] "numeric"
Cybernétique
la source
1
Cela ne répond pas aux exigences de l'OP, "Je veux spécifier les types de données pour chaque colonne"
Gregor Thomas
6

Si vous souhaitez créer un data.frame vide avec des noms dynamiques (noms de colonnes dans une variable), cela peut aider:

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

Vous pouvez également modifier les types si vous en avez besoin. comme:

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

Si cela ne vous dérange pas de ne pas spécifier explicitement les types de données, vous pouvez le faire de cette façon:

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)
Ulysse Ithaque
la source
4

En utilisant, data.tablenous pouvons spécifier des types de données pour chaque colonne.

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

Si vous souhaitez déclarer un tel data.framenombre de colonnes, il sera probablement difficile de taper toutes les classes de colonnes à la main. Surtout si vous pouvez utiliserrep , cette approche est simple et rapide (environ 15% plus rapide que l'autre solution généralisable comme celle-ci):

Si vos classes de colonnes souhaitées sont dans un vecteur colClasses, vous pouvez effectuer les opérations suivantes:

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

lapplyse traduira par une liste de longueur souhaitée, dont chaque élément est simplement un vecteur typé vide comme numeric()ou integer().

setDFconvertit cela listpar référence à a data.frame.

setnames ajoute les noms souhaités par référence.

Comparaison de vitesse:

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 

C'est aussi plus rapide que d'utiliser structured'une manière similaire:

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
la source
1

Supposons que les noms de vos colonnes soient dynamiques, vous pouvez créer une matrice vide nommée par ligne et la transformer en un bloc de données.

nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))
jpmarindiaz
la source
Cela ne répond pas aux exigences de l'OP, "Je veux spécifier les types de données pour chaque colonne"
Gregor Thomas
1

Cette question n'a pas répondu spécifiquement à mes préoccupations (décrites ici ), mais au cas où quelqu'un voudrait le faire avec un nombre de colonnes paramétré et sans contrainte:

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

En tant qu'États divergents sur la question liée,

... la raison pour laquelle [la coercition] se produit [lors de la liaison des matrices et de leurs types constitutifs] est qu'une matrice ne peut avoir qu'un seul type de données. Lorsque vous cindrez 2 matrices, le résultat est toujours une matrice et donc les variables sont toutes contraintes en un seul type avant d'être converties en data.frame

d8aninja
la source
1

Si vous disposez déjà d'une trame de données, vous pouvez extraire les métadonnées (noms et types de colonnes) d'une trame de données (par exemple, si vous contrôlez un BUG qui n'est déclenché qu'avec certaines entrées et a besoin d'une trame de données vide):

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

Et puis utilisez le read.tablepour créer le dataframe vide

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