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

322

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

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

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

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

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

Теги:
dataframe

13 ответов

442
Лучший ответ

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

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 

N.B.:

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

  • 1
    Было бы то же самое, если бы я инициализировал все поля с NULL?
  • 7
    @yosukesabai: нет, если вы инициализируете столбец с NULL, столбец не будет добавлен :)
Показать ещё 13 комментариев
60

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

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

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

empty_df = df[FALSE,]

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

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

  • 7
    Любые комментарии о том, почему ответ был отклонен?
  • 2
    Не уверен - это хорошая техника, и я проголосовал за нее.
Показать ещё 7 комментариев
43

Вы можете использовать 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)

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

  • 4
    Или даже read.table(text = "", ...) так что вам не нужно явно открывать соединение.
  • 0
    притягательный. вероятно, самый расширяемый / автоматизируемый способ сделать это для многих потенциальных столбцов
Показать ещё 2 комментария
19

Самый эффективный способ сделать это - использовать 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
  • 0
    data.table обычно содержит атрибут .internal.selfref , который не может быть подделан без вызова функций data.table . Вы уверены, что не полагаетесь на недокументированное поведение здесь?
  • 0
    @AdamRyczkowski Я думаю, что вы путаете базовый класс «data.frame» и дополнительный класс «data.table» из пакета data.table .
Показать ещё 4 комментария
12

Если вы ищете одолжение:

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

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

  • 1
    Еще несколько объяснений было бы неплохо.
  • 0
    read.csv анализирует текстовый аргумент, чтобы получить имена столбцов. Он более компактен, чем read.table (text = "", col.names = c ("col1", "col2"))
Показать ещё 2 комментария
7

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

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 
3

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

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

просто объявить table = data.frame() когда вы пытаетесь перенести первую строку, он создаст столбцы

  • 2
    На самом деле не соответствует требованиям ОП: «Я хочу указать типы данных для каждого столбца и назвать их». Если следующим шагом будет rbind это будет хорошо, если нет ...
  • 0
    В любом случае, спасибо за это простое решение. Я также хотел инициализировать data.frame с конкретными столбцами, поскольку я думал, что rbind можно использовать только в том случае, если столбцы соответствуют между двумя data.frame. Кажется, это не так. Я был удивлен, что могу так просто инициализировать data.frame при использовании rbind. Благодарю.
Показать ещё 1 комментарий
2

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

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

Если вы хотите объявить такой 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
1

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

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

Используйте следующее:

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: «Я хочу указать типы данных для каждого столбца»
0

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню