Создание R-кадра данных построчно
Я хотел бы построить строку данных построчно в R. Я провел некоторый поиск, и все, что мне пришло в голову, это предложение создать пустой список, сохранить скалярный индекс списка, а затем каждый раз добавлять в список. однострочный фрейм данных и продвижение индекса списка на единицу. В заключение, do.call(rbind,)
в списке.
Хотя это работает, это кажется очень громоздким. Разве нет более простого способа достижения той же цели?
Очевидно, я имею в виду случаи, когда я не могу использовать некоторые apply
функции и явно нужно создавать датафрейм построчно. По крайней мере, есть ли способ push
в конец списка вместо того, чтобы явно отслеживать последний использованный индекс?
7 ответов
Вы можете увеличивать их построчно, добавляя или используя 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, вы всегда можете уменьшить пустые строки в конце.
Можно добавить строки в 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)
Это глупый пример того, как использовать 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"
Я использую эту конструкцию довольно часто.
Причина, по которой мне так нравится 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()
все ссылки являются префиксными ссылками на начало выделенного векторного хранилища.
Я нашел этот способ для создания dataframe по сырью без матрицы.
С автоматическим названием столбца
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")
)
Ответ Дирка Эддельбюттеля самый лучший; здесь я просто отмечаю, что вы можете избежать предварительного указания измерений или типов данных в фрейме, что иногда полезно, если у вас несколько типов данных и много столбцов:
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(d,row2) #now this works as you'd expect.
Если у вас есть векторы, предназначенные стать строками, объедините их, используя 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, я уверен, что оно создает новые проблемы в будущем. Кто-нибудь может прокомментировать это?
В зависимости от формата вашей новой строки вы можете использовать tibble::add_row
если ваша новая строка проста и может быть указана в "парах значений". Или вы могли бы использоватьdplyr::bind_rows
, "эффективная реализация общего шаблона do.call(rbind, dfs)".