Створіть порожній фрейм 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 

Примітка:

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


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, повний на NAs ... у такому випадку ви можете використовувати, наприкладdata.frame(Doubles=rep(as.double(NA),numberOfRow), Ints=rep(as.integer(NA),numberOfRow))
digEmAll

140

Якщо у вас вже є кадр даних , скажімо df, у ньому є потрібні стовпці, ви можете просто створити порожній кадр даних, видаливши всі рядки:

empty_df = df[FALSE,]

Зверніть увагу, що дані dfвсе ще містять, але empty_dfне містять .

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


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
toto_tico

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'
Дейв 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)

Дякуємо Річарду Скривену за покращення


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()надає перевірки на іменування, імена рядків тощо
Thomas

26

Просто задекларуйте

table = data.frame()

при спробі rbindпершого рядка він створить стовпці


2
Не відповідає вимогам ОП "Я хочу вказати типи даних для кожного стовпця та назвати їх". Якщо наступним кроком є ​​це, rbindце спрацювало б добре, як ні ...
Грегор Томас

У будь-якому випадку, дякую за це просте рішення. Я також хотів ініціалізувати data.frame з певними стовпцями, оскільки я вважав, що rbind може використовуватися лише у тому випадку, якщо стовпці відповідають між двома data.frame. Здається, це не так. Мене здивувало, що я можу так просто ініціалізувати data.frame при використанні rbind. Дякую.
giordano

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

Це не відповідає вимогам ОП: "Я хочу вказати типи даних для кожного стовпця" , хоча, можливо, це можна змінити для цього.
Грегор Томас

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
Це не відповідає вимогам ОП: "Я хочу вказати типи даних для кожного стовпця"
Грегор Томас

6

Якщо ви хочете створити порожній кадр даних з динамічними іменами (імена зі змінною), це може допомогти:

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посиланням на a 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

Скажімо, назви стовпців динамічні, ви можете створити порожню матрицю з ім’ям рядків і перетворити її в кадр даних.

nms <- sample(LETTERS,sample(1:10))
as.data.frame(t(matrix(nrow=length(nms),ncol=0,dimnames=list(nms))))

Це не відповідає вимогам ОП: "Я хочу вказати типи даних для кожного стовпця"
Грегор Томас

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


1

Якщо у вас вже є кадр даних, ви можете витягнути метадані (назви стовпців та типи) з фрейму даних (наприклад, якщо ви керуєте BUG, який спрацьовує лише з певних входів та потребує порожнього манекена Dataframe):

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'))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.