Создать пустой data.frame

480

Я пытаюсь инициализировать data.frame без каких-либо строк. По сути, я хочу указать типы данных для каждого столбца и дать им имена, но в результате не нужно создавать никаких строк.

Лучшее, что я смог сделать, это что-то вроде:

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

Который создает data.frame с единственной строкой, содержащей все нужные мне типы данных и имена столбцов, но также создает бесполезную строку, которую затем необходимо удалить.

Есть лучший способ сделать это?

Джефф Аллен
источник

Ответы:

652

Просто инициализируйте его пустыми векторами:

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

Вот другой пример с различными типами столбцов:

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:

Инициализация data.frameс пустым столбцом неправильного типа не препятствует дальнейшему добавлению строк, имеющих столбцы разных типов.
Этот метод немного более безопасен в том смысле, что у вас будут правильные типы столбцов с самого начала, следовательно, если ваш код использует некоторую проверку типов столбцов, он будет работать даже data.frameс нулевыми строками.

digEmAll
источник
3
Было бы то же самое, если бы я инициализировал все поля с NULL?
yosukesabai
8
@yosukesabai: нет, если вы инициализируете столбец с NULL, столбец не будет добавлен :)
digEmAll
6
@yosukesabai: у data.frameвас есть типизированные столбцы, так что да, если вы хотите инициализировать a, data.frameвы должны решить тип столбцов ...
digEmAll
1
@jxramos: ну, на самом деле data.frameэто не ограничивает «примитивность» типов столбцов (например, вы можете добавить столбец дат или даже столбец, содержащий список элементов). Кроме того, этот вопрос не является абсолютной ссылкой, поскольку, например, если вы не укажете правильный тип столбца, вы не будете блокировать дальнейшее добавление строки, имеющей столбец разных типов ... поэтому я добавлю примечание, но не пример со всеми примитивными типами, потому что он не охватывает все возможности ...
digEmAll
3
@ user4050: вопрос был о создании пустого data.frame, поэтому, когда число строк равно нулю ... может быть, вы хотите создать data.frame, полный для NA ... в этом случае вы можете использовать, например,data.frame(Doubles=rep(as.double(NA),numberOfRow), Ints=rep(as.integer(NA),numberOfRow))
digEmAll
140

Если у вас уже есть существующий фрейм данных , скажем df, с нужными столбцами, вы можете просто создать пустой фрейм данных, удалив все строки:

empty_df = df[FALSE,]

Обратите внимание, что dfвсе еще содержит данные, но empty_dfне содержит.

Я нашел этот вопрос в поисках того, как создать новый экземпляр с пустыми строками, поэтому я думаю, что это может быть полезно для некоторых людей.

toto_tico
источник
2
Прекрасная идея. Не храните ни одной строки, но ВСЕ столбцы. Кто бы ни понизил голосование, что-то пропустил.
Рам Нарасимхан
1
Хорошее решение, однако я обнаружил, что получаю фрейм данных с 0 строками. Чтобы сохранить размер фрейма данных одинаковым, я предлагаю new_df = df [NA,]. Это также позволяет сохранить любой предыдущий столбец в новом фрейме данных. Например, чтобы получить столбец «Дата» из исходного df (при сохранении остальных NA): new_df $ Date <- df $ Date.
Катя
2
@Katya, если вы сделаете df[NA,]это, это также повлияет на индекс (что вряд ли будет тем, что вы хотите), я бы вместо этого использовал df[TRUE,] = NA; однако обратите внимание, что это перезапишет оригинал. Сначала вам нужно будет скопировать copy_df = data.frame(df)copy_df[TRUE,] = NA
фрейм данных,
3
@Katya, или вы можете легко добавить пустые строки в empty_dfс empty_df[0:nrow(df),] <- NA.
toto_tico
1
@Katya, вы используете обратную кавычку (`) вокруг того, что вы хотели бы пометить как код, и есть другие вещи, такие как курсив, использующий *, и жирный, использующий **. Вы, вероятно, хотите прочитать весь синтаксис Markdown SO . Большинство из них имеет смысл только для ответов, хотя.
toto_tico
79

Вы можете сделать это без указания типов столбцов

df = data.frame(matrix(vector(), 0, 3,
                dimnames=list(c(), c("Date", "File", "User"))),
                stringsAsFactors=F)
Зеленый
источник
4
В этом случае типы столбцов по умолчанию считаются логическими для вектора (), но затем переопределяются с типами элементов, добавляемых в df. Попробуйте str (df), df [1,1] <- 'x'
Дейв Х
58

Вы можете использовать read.tableпустую строку для ввода textследующим образом:

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

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

Альтернативно, указав в col.namesвиде строки:

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

Спасибо Ричарду Скривену за улучшение

Rentrop
источник
4
Или даже read.table(text = "", ...)если вам не нужно явно открывать соединение.
Рич Скривен
притягательный. вероятно, самый расширяемый / автоматизируемый способ сделать это для многих потенциальных столбцов
MichaelChirico
3
read.csvПодход также работает с readr::read_csv, как и в read_csv("Date,File,User\n", col_types = "Dcc"). Таким образом, вы можете напрямую создать пустой столбик необходимой структуры.
Хизер Тернер,
27

Наиболее эффективный способ сделать это - использовать structureдля создания списка, который имеет класс "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)

Чтобы сравнить это с принятым в настоящее время ответом, вот простой тест:

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
Томас
источник
data.tableобычно содержит .internal.selfrefатрибут, который нельзя подделать без вызова data.tableфункций. Вы уверены, что не полагаетесь на недокументированное поведение здесь?
Адам
@AdamRyczkowski Я думаю, вы путаете базовый класс «data.frame» и дополнительный класс «data.table» из пакета data.table .
Томас,
Да. Определенно. Виноват. Не обращайте внимания на мой последний комментарий. Я наткнулся на эту тему при поиске data.tableи предположил, что Google нашел то, что хотел, и все здесь связано data.table.
Адам
1
@PatrickT Нет никакой проверки, что то, что делает ваш код, имеет какой-то смысл. data.frame()обеспечивает проверку имен, строк и т. д.
Томас
26

Просто объявить

table = data.frame()

при попытке до rbindпервой строки будут созданы столбцы

Даниэль Фишер
источник
2
На самом деле не соответствует требованиям ОП: «Я хочу указать типы данных для каждого столбца и назвать их». Если следующий шаг - rbindэто будет хорошо, если нет ...
Грегор Томас
В любом случае, спасибо за это простое решение. Я также хотел инициализировать data.frame с конкретными столбцами, поскольку я думал, что rbind можно использовать только в том случае, если столбцы соответствуют между двумя data.frame. Кажется, это не так. Я был удивлен, что могу так просто инициализировать data.frame при использовании rbind. Спасибо.
Джордано
4
Лучшее предложенное решение здесь. Для меня, используя предложенный способ, отлично сработало rbind().
Коц
17

Если вы ищете краткость:

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

поэтому вам не нужно указывать имена столбцов отдельно. Вы получаете логический тип столбца по умолчанию, пока не заполните заполненный фрейм данных.

Марк
источник
read.csv анализирует текстовый аргумент, чтобы вы получили имена столбцов. Он более компактен, чем read.table (text = "", col.names = c ("col1", "col2"))
marc
Я получаю:Error in data.frame(..., check.names = FALSE) : arguments imply differing number of rows: 0, 2
Climbs_lika_Spyder
Это не соответствует требованиям OP «Я хочу указать типы данных для каждого столбца» , хотя, возможно, это можно изменить для этого.
Грегор Томас
14

Я создал пустой фрейм данных, используя следующий код

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

и попытался связать несколько строк, чтобы заполнить так же, как показано ниже.

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

но он начал давать неправильные имена столбцов следующим образом

  X3 X4
1  3  4

Решением этой проблемы является преобразование newrow в тип df следующим образом

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

теперь дает правильный кадр данных при отображении с именами столбцов, как показано ниже

  id nobs
1  3   4 
Шрикант Прабху
источник
7

Чтобы создать пустой фрейм данных , передайте количество необходимых строк и столбцов в следующую функцию:

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

Чтобы создать пустой фрейм при указании класса каждого столбца , просто передайте вектор нужных типов данных в следующую функцию:

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

Используйте следующим образом:

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

Который дает:

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

Чтобы подтвердить свой выбор, выполните следующее:

lapply(df, class)

#output
$X1
[1] "character"

$X2
[1] "logical"

$X3
[1] "numeric"
кибернетический
источник
1
Это не соответствует требованиям OP: «Я хочу указать типы данных для каждого столбца»
Грегор Томас
6

Если вы хотите создать пустой data.frame с динамическими именами (имена столбцов в переменной), это может помочь:

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

Вы также можете изменить типы, если вам это нужно. подобно:

names <- c("u", "v")
df <- data.frame()
df[[names[1]]] <- as.numeric()
df[[names[2]]] <- as.character()
Али Хосро
источник
4

Если вы не против явно указать типы данных, вы можете сделать это следующим образом:

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)
Одиссей Итака
источник
4

Используя, data.tableмы можем указать типы данных для каждого столбца.

library(data.table)    
data=data.table(a=numeric(), b=numeric(), c=numeric())
Рашабх Патель
источник
3

Если вы хотите объявить такое data.frameсо многими столбцами, возможно, будет сложно набрать все классы столбцов вручную. Особенно, если вы можете использовать repэтот подход, он прост и быстр (примерно на 15% быстрее, чем другое решение, которое можно обобщить следующим образом):

Если ваши нужные классы столбцов находятся в векторе colClasses, вы можете сделать следующее:

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

lapplyприведет к списку желаемой длины, каждый элемент которого представляет собой просто пустой типизированный вектор, например numeric()или integer().

setDFпреобразует это listпутем ссылки на data.frame.

setnames добавляет нужные имена по ссылке.

Сравнение скорости:

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 

Это также быстрее, чем при использовании structureаналогичным способом:

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
источник
1

Допустим, ваши имена столбцов являются динамическими, вы можете создать пустую матрицу с именем строки и преобразовать ее во фрейм данных.

nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))
jpmarindiaz
источник
Это не соответствует требованиям OP: «Я хочу указать типы данных для каждого столбца»
Грегор Томас
1

Этот вопрос не решал конкретно мои проблемы (обрисованные в общих чертах здесь ), но в случае, если кто-то хочет сделать это с параметризованным количеством столбцов и без принуждения:

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

Как утверждает дивибисан по связанному вопросу,

... причина [принуждения] возникает [когда матрицы связывания и их составляющие типы] заключаются в том, что матрица может иметь только один тип данных. Когда вы связываете 2 матрицы, результатом остается матрица, поэтому все переменные приводятся в один тип перед преобразованием в data.frame.

d8aninja
источник
1

Если у вас уже есть фрейм данных, вы можете извлечь метаданные (имена и типы столбцов) из фрейма данных (например, если вы управляете БАГОМ, который запускается только с определенными входными данными и вам нужен пустой фиктивный фрейм данных):

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

А затем использовать read.tableдля создания пустого кадра данных

read.table(text = "",
   colClasses = c('integer', 'factor'),
   col.names = c('col1', 'col2'))
toto_tico
источник