J'essaye d'initialiser un data.frame sans aucune ligne. En gros, je veux spécifier les types de données pour chaque colonne et les nommer, mais aucune ligne ne doit être créée 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 les 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?
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
N.B.:
L'initialisation d'un data.frame
avec une colonne vide de type incorrect n'empêche pas l'ajout supplémentaire de lignes comportant 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 depuis le début, donc si votre code repose sur une vérification du type de colonne, cela fonctionnera même avec un data.frame
avec zéro ligne.
Si vous avez déjà un cadre de données existant, supposons que df
comporte les colonnes souhaitées, vous pouvez simplement créer un cadre de données vide en supprimant toutes les lignes:
empty_df = df[FALSE,]
Notez que df
contient toujours les données, mais pas empty_df
.
J'ai trouvé cette question en cherchant comment créer une nouvelle instance avec des lignes vides, alors je pense que cela pourrait être utile pour certaines personnes.
Vous pouvez le faire sans spécifier de type de colonne
df = data.frame(matrix(vector(), 0, 3,
dimnames=list(c(), c("Date", "File", "User"))),
stringsAsFactors=F)
Vous pouvez utiliser read.table
avec une chaîne vide pour l'entrée text
comme 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.names
en tant que chaîne:
df <- read.csv(text="Date,File,User", colClasses = colClasses)
Merci à Richard Scriven pour l'amélioration
La méthode la plus efficace consiste à utiliser structure
pour créer une liste contenant 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 un repère 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
Il suffit de déclarer
table = data.frame()
lorsque vous essayez de rbind
la première ligne, il créera les colonnes
Si vous cherchez de la 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.
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 le même comme suit.
newrow = c(3, 4)
df <- rbind(df, newrow)
mais il a commencé à donner des noms de colonne incorrects comme suit
X3 X4
1 3 4
La solution à cela est de convertir newrow en type df comme suit
newrow = data.frame(id=3, jobs=4)
df <- rbind(df, newrow)
donne maintenant le cadre de données correct lorsqu'il est affiché avec les noms de colonne comme suit
id nobs
1 3 4
Pour créer un bloc de données vide, indiquez 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(df[,i])}
if(type_vec[i] == 'character') {frame[,i] <- as.character(df[,i])}
if(type_vec[i] == 'logical') {frame[,i] <- as.logical(df[,i])}
if(type_vec[i] == 'factor') {frame[,i] <- as.factor(df[,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"
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()
Si cela ne vous dérange pas de ne pas spécifier explicitement les types de données, vous pouvez le faire comme suit:
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)
Si vous voulez déclarer un tel data.frame
avec plusieurs colonnes, il vous sera probablement pénible de taper manuellement toutes les classes de colonnes. Surtout si vous pouvez utiliser rep
, cette approche est simple et rapide (environ 15% plus rapide que l'autre solution pouvant être généralisée de la manière suivante):
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)
lapply
donnera une liste de longueur désirée, dont chaque élément est simplement un vecteur typé vide comme numeric()
ou integer()
.
setDF
convertit cette list
par référence à un 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 structure
de la même manière:
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
En utilisant data.table
, nous pouvons spécifier des types de données pour chaque colonne.
library(data.table)
data=data.table(a=numeric(), b=numeric(), c=numeric())
Vous pouvez également extraire les métadonnées (noms et types de colonne)) à partir d'une image (par exemple, if vous contrôlez un bogue qui n'est déclenché qu'avec certaines entrées et nécessite une image vide de données factice ):
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.table
pour créer le dataframe vide
read.table(text = "",
colClasses = c('integer', 'factor'),
col.names = c('col1', 'col2'))
Supposons que vos noms de colonnes soient dynamiques, vous pouvez créer une matrice vide nommée et la transformer en un cadre de données.
nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))
Cette question ne répondait pas spécifiquement à mes préoccupations (soulignées 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>
Comme le dit le divibisan sur la question liée,
... la raison [la coercition] se produit [lorsque les matrices de liaison et leurs types constituants] est qu'une matrice ne peut avoir qu'un seul type de données. Lorsque vous cbind 2 matrices, le résultat est toujours une matrice et les variables sont donc toutes forcées dans un seul type avant la conversion en data.frame.