Построчное создание кадра данных R


108

Я хотел бы построить фрейм данных строка за строкой в ​​R. Я провел несколько поисков, и все, что я придумал, это предложение создать пустой список, сохранить скаляр индекса списка, а затем каждый раз добавлять в список однострочный фрейм данных и продвинуть индекс списка на единицу. Наконец, do.call(rbind,)в списке.

Хотя это работает, это кажется очень громоздким. Разве нет более простого способа достичь той же цели?

Очевидно, я имею в виду случаи, когда я не могу использовать какую-либо applyфункцию, и мне явно нужно создавать фрейм данных построчно. По крайней мере, есть ли способ pushпопасть в конец списка вместо того, чтобы явно отслеживать последний использованный индекс?


1
Вы можете использовать append()[который, вероятно, следует назвать insert] или c()добавлять элементы в конец списка, но здесь это вам не поможет.
hatmatrix

Там не много функций в R , что кадры возвращения данных , если не вернуть их [ряды] из lapply(), Map()и так далее, но вы также можете взглянуть на aggregate(), dapply() {heR.Misc}и cast() {reshape}чтобы увидеть , если ваши задачи не могут быть обработаны этими функции (все они возвращают фреймы данных).
hatmatrix 06

Ответы:


96

Вы можете увеличивать их строку за строкой, добавляя или используя rbind().

Это не значит, что вам следует. Динамически растущие структуры - один из наименее эффективных способов программирования на R.

Если можете, выделите весь свой data.frame заранее:

N <- 1e4  # total number of rows to preallocate--possibly an overestimate

DF <- data.frame(num=rep(NA, N), txt=rep("", N),  # as many cols as you need
                 stringsAsFactors=FALSE)          # you don't know levels yet

а затем во время ваших операций вставляйте строки за раз

DF[i, ] <- list(1.4, "foo")

Это должно работать для произвольного data.frame и быть намного более эффективным. Если вы переборщите с N, вы всегда можете сжать пустые строки в конце.


6
Разве вы не хотели поставить N вместо 10 и list (1.4, "foo") вместо c (1.4, "foo"), чтобы не переводить 1.4 в символьный режим?
hatmatrix

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

1
Лучше отредактировать ответ, чем оставлять в комментариях. Я был сбит с толку, пытаясь понять этот ответ.
User

4
data.tableкажется, даже быстрее, чем предварительное выделение с использованием data.frames. Тестирование здесь: stackoverflow.com/a/11486400/636656
Ари Б. Фридман,

это все еще верно в R 3.1, где это должно быть быстрее?
userJT

49

Можно добавлять строки в NULL:

df<-NULL;
while(...){
  #Some code that generates new row
  rbind(df,row)->df
}

например

df<-NULL
for(e in 1:10) rbind(df,data.frame(x=e,square=e^2,even=factor(e%%2==0)))->df
print(df)

3
выводит матрицу, а не фрейм данных
Ольга

1
@Olga Только если вы привязываете строки элементов одинакового типа - BTW в этом случае лучше sapply(или векторизовать) и транспонировать.
mbq

1
@mbq Именно то, что я делаю. Я также обнаружил, что если вы инициализируете его с помощью df <-data.frame (), он выводит фрейм данных.
Ольга

9

Это глупый пример того, как использовать do.call(rbind,)на выходе Map()[который похож на lapply()]

> DF <- do.call(rbind,Map(function(x) data.frame(a=x,b=x+1),x=1:3))
> DF
  x y
1 1 2
2 2 3
3 3 4
> class(DF)
[1] "data.frame"

Я использую эту конструкцию довольно часто.


8

Причина, по которой мне так нравится Rcpp, заключается в том, что я не всегда понимаю, как думает R Core, а с Rcpp, чаще всего, мне это не нужно.

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

Проблемы возникают, когда функциональное программирование сигнализирует малому кораблю отойти с дороги, а малый корабль отвечает: «Я - маяк». Внесение длинной серии небольших изменений в большой объект, который вы хотите обработать, тем временем помещает вас прямо на территорию маяка.

В C ++ STL push_back()это образ жизни. Он не пытается быть функциональным, но он действительно пытается приспособить общие идиомы программирования эффективно .

Проявив некоторую смекалку за кулисами, вы можете иногда договориться о том, чтобы ступить в каждый мир одной ногой. Файловые системы на основе моментальных снимков являются хорошим примером (которые произошли от таких концепций, как объединенное монтирование, которое также соединяет обе стороны).

Если бы R Core захотел это сделать, базовое векторное хранилище могло бы функционировать как объединенное монтирование. Одна ссылка на хранилище векторов может быть действительной для индексов 1:N, а другая ссылка на то же хранилище действительна для индексов 1:(N+1). Там может быть зарезервированное хранилище, на которое еще не ссылается что-либо, кроме удобного для быстрогоpush_back() . Вы не нарушаете функциональную концепцию при добавлении за пределы диапазона, который любая существующая ссылка считает допустимой.

Постепенно добавляя строки, вы исчерпываете зарезервированное хранилище. Вам нужно будет создать новые копии всего, с умножением объема хранилища на некоторое приращение. Реализации STL, которые я использовал, имеют тенденцию умножать объем памяти на 2 при расширении распределения. Я думал, что прочитал в R Internals, что есть структура памяти, в которой объем хранилища увеличивается на 20%. В любом случае операции роста происходят с логарифмической частотой относительно общего числа добавленных элементов. На амортизированной основе это обычно приемлемо.

Что касается закулисных трюков, я видел и похуже. Каждый раз, когда вы push_back()добавляете новую строку в фрейм данных, необходимо копировать структуру индекса верхнего уровня. Новая строка может добавляться к общему представлению, не влияя на старые функциональные значения. Я даже не думаю, что это сильно усложнит сборщик мусора; поскольку я не предлагаю, что push_front()все ссылки являются префиксными ссылками на переднюю часть выделенного векторного хранилища.


2

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

row1<-list("a",1,FALSE) #use 'list', not 'c' or 'cbind'!
row2<-list("b",2,TRUE)  

df<-data.frame(row1,stringsAsFactors = F) #first row
df<-rbind(df,row2) #now this works as you'd expect.

Вы имели в виду df<-rbind(df, row2)?
Тимоти К. Куинн,

1

Я нашел способ создать фрейм данных по необработанному, без матрицы.

С автоматическим названием столбца

df<-data.frame(
        t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
        ,row.names = NULL,stringsAsFactors = FALSE
    )

С именем столбца

df<-setNames(
        data.frame(
            t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
            ,row.names = NULL,stringsAsFactors = FALSE
        ), 
        c("col1","col2","col3")
    )

0

Если у вас есть векторы, которым суждено стать строками, объедините их с помощью c(), передайте их в матрицу построчно и преобразуйте эту матрицу в фрейм данных.

Например, строки

dummydata1=c(2002,10,1,12.00,101,426340.0,4411238.0,3598.0,0.92,57.77,4.80,238.29,-9.9)
dummydata2=c(2002,10,2,12.00,101,426340.0,4411238.0,3598.0,-3.02,78.77,-9999.00,-99.0,-9.9)
dummydata3=c(2002,10,8,12.00,101,426340.0,4411238.0,3598.0,-5.02,88.77,-9999.00,-99.0,-9.9)

можно преобразовать в кадр данных таким образом:

dummyset=c(dummydata1,dummydata2,dummydata3)
col.len=length(dummydata1)
dummytable=data.frame(matrix(data=dummyset,ncol=col.len,byrow=TRUE))

По общему признанию, я вижу 2 основных ограничения: (1) это работает только с одномодовыми данными и (2) вы должны знать свои последние # столбца, чтобы это работало (т.е. я предполагаю, что вы не работаете с рваный массив, наибольшая длина строки которого априори неизвестна ).

Это решение кажется простым, но, судя по моему опыту преобразования типов в R, я уверен, что в дальнейшем оно создает новые проблемы. Кто-нибудь может это прокомментировать?


0

В зависимости от формата вашей новой строки вы можете использовать, tibble::add_rowесли ваша новая строка простая и может быть указана в «парах значений». Или вы можете использовать dplyr::bind_rows«эффективную реализацию общего шаблона do.call (rbind, dfs)».

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.