Я огляделся на StackOverflow, но не могу найти решение, специфичное для моей проблемы, которое включает добавление строк в рамку данных R.
Я инициализирую пустой 2-колоночный фрейм данных следующим образом.
df = data.frame(x = numeric(), y = character())
Затем моя цель состоит в том, чтобы перебрать список значений и в каждой итерации добавить значение в конец списка. Я начал со следующего кода.
for (i in 1:10) {
df$x = rbind(df$x, i)
df$y = rbind(df$y, toString(i))
}
Я также безуспешно пытался выполнять функции c
, append
и merge
. Пожалуйста, дайте мне знать, если у вас есть какие-либо предложения.
Не зная, что вы пытаетесь сделать, я поделюсь еще одним предложением: перераспределите векторы того типа, который вы хотите для каждого столбца, вставьте значения в эти векторы, а затем, в конце, создайте свой data.frame
.
Продолжая использовать f3
Джулиана (предустановленный data.frame
) как самый быстрый вариант, определяемый как:
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(n), y = character(n), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
Вот аналогичный подход, но тот, где data.frame
создается как последний шаг.
# Use preallocated vectors
f4 <- function(n) {
x <- numeric(n)
y <- character(n)
for (i in 1:n) {
x[i] <- i
y[i] <- i
}
data.frame(x, y, stringsAsFactors=FALSE)
}
microbenchmark
из пакета "microbenchmark" даст нам более полное представление, чем system.time
:
library(microbenchmark)
microbenchmark(f1(1000), f3(1000), f4(1000), times = 5)
# Unit: milliseconds
# expr min lq median uq max neval
# f1(1000) 1024.539618 1029.693877 1045.972666 1055.25931 1112.769176 5
# f3(1000) 149.417636 150.529011 150.827393 151.02230 160.637845 5
# f4(1000) 7.872647 7.892395 7.901151 7.95077 8.049581 5
f1 ()
(подход ниже) невероятно неэффективен из-за того, как часто он называет data.frame
и потому, что растущие объекты таким образом обычно медленны в R. f3 ()
значительно улучшается из-за предварительного распределения, но сама структура data.frame может быть частью узкого места здесь. f4 ()
пытается обойти это узкое место, не ставя под угрозу подход, который вы хотите использовать.
Это действительно не очень хорошая идея, но если вы хотите сделать это таким образом, я думаю, вы можете попробовать:
for (i in 1:10) {
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
Обратите внимание, что в вашем коде есть еще одна проблема:
stringsAsFactors
, если вы хотите, чтобы символы не были преобразованы в факторы. Использование: df = data.frame (x = numeric (), y = char (), stringsAsFactors = FALSE)
Давайте сравним три предложенных решения:
# use rbind
f1 <- function(n){
df <- data.frame(x = numeric(), y = character())
for(i in 1:n){
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
df
}
# use list
f2 <- function(n){
df <- data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
for(i in 1:n){
df[i,] <- list(i, toString(i))
}
df
}
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(1000), y = character(1000), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
system.time(f1(1000))
# user system elapsed
# 1.33 0.00 1.32
system.time(f2(1000))
# user system elapsed
# 0.19 0.00 0.19
system.time(f3(1000))
# user system elapsed
# 0.14 0.00 0.14
Лучшее решение - предварительно выделить пространство (как задумано в R). Следующим лучшим решением является использование list
, а худшим решением (по крайней мере, на основе этих временных результатов), по-видимому, является rbind
.
Предположим, вы просто не знаете размер data.frame заранее. Это может быть несколько рядов или несколько миллионов. Вам нужен какой-то контейнер, который динамически растет. Принимая во внимание мой опыт и все связанные ответы в SO, я прихожу с 4 различными решениями:
rbindlist
к data.frame
Используйте быструю операцию data.table
и соедините ее с удвоением таблицы вручную при необходимости.
Используйте RSQLite
и добавьте к таблице, хранящейся в памяти.
`data.frame' собственная способность выращивать и использовать пользовательскую среду (которая имеет эталонную семантику) для хранения data.frame, чтобы она не копировалась при возврате.
Вот проверка всех методов для небольшого и большого количества добавленных строк. Каждый метод имеет 3 функции, связанные с ним:
create (first_element)
, который возвращает соответствующий объект поддержки с вставленнымfirst_element
.
append (объект, элемент)
, который добавляет element
к концу таблицы (представлен object
).
access (object)
получает data.frame
со всеми вставленными элементами.
rbindlist
к data.frameЭто довольно просто и прямо:
create.1<-function(elems)
{
return(as.data.table(elems))
}
append.1<-function(dt, elems)
{
return(rbindlist(list(dt, elems),use.names = TRUE))
}
access.1<-function(dt)
{
return(dt)
}
data.table :: set
+ вручную удваивает таблицу при необходимости.Я буду хранить истинную длину таблицы в атрибуте rowcount
.
create.2<-function(elems)
{
return(as.data.table(elems))
}
append.2<-function(dt, elems)
{
n<-attr(dt, 'rowcount')
if (is.null(n))
n<-nrow(dt)
if (n==nrow(dt))
{
tmp<-elems[1]
tmp[[1]]<-rep(NA,n)
dt<-rbindlist(list(dt, tmp), fill=TRUE, use.names=TRUE)
setattr(dt,'rowcount', n)
}
pos<-as.integer(match(names(elems), colnames(dt)))
for (j in seq_along(pos))
{
set(dt, i=as.integer(n+1), pos[[j]], elems[[j]])
}
setattr(dt,'rowcount',n+1)
return(dt)
}
access.2<-function(elems)
{
n<-attr(elems, 'rowcount')
return(as.data.table(elems[1:n,]))
}
RSQLite
Это в основном копирование и вставка Карстен В. ответ в аналогичной теме.
create.3<-function(elems)
{
con <- RSQLite::dbConnect(RSQLite::SQLite(), ":memory:")
RSQLite::dbWriteTable(con, 't', as.data.frame(elems))
return(con)
}
append.3<-function(con, elems)
{
RSQLite::dbWriteTable(con, 't', as.data.frame(elems), append=TRUE)
return(con)
}
access.3<-function(con)
{
return(RSQLite::dbReadTable(con, "t", row.names=NULL))
}
data.frame
собственная строка-приложение + пользовательская среда.create.4<-function(elems)
{
env<-new.env()
env$dt<-as.data.frame(elems)
return(env)
}
append.4<-function(env, elems)
{
env$dt[nrow(env$dt)+1,]<-elems
return(env)
}
access.4<-function(env)
{
return(env$dt)
}
Для удобства я буду использовать одну функцию тестирования, чтобы покрыть их всех косвенным вызовом. (Я проверил: использование do.call
вместо непосредственного вызова функций не делает работу кода измеримой дольше).
test<-function(id, n=1000)
{
n<-n-1
el<-list(a=1,b=2,c=3,d=4)
o<-do.call(paste0('create.',id),list(el))
s<-paste0('append.',id)
for (i in 1:n)
{
o<-do.call(s,list(o,el))
}
return(do.call(paste0('access.', id), list(o)))
}
Давайте посмотрим производительность для n = 10 вставок.
Я также добавил функции «плацебо» (с суффиксом «0»), которые ничего не выполняют - просто для измерения накладных расходов на настройку теста.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
Для 1E5 строк (измерения выполнены на процессоре Intel (R) Core (TM) i7-4710HQ при 2,50 ГГц):
nr function time
4 data.frame 228.251
3 sqlite 133.716
2 data.table 3.059
1 rbindlist 169.998
0 placebo 0.202
Похоже, что suluation на основе SQLite, хотя и восстанавливает некоторую скорость для больших данных, далеко не так, как data.table + ручной экспоненциальный рост. Разница почти в два порядка!
Если вы знаете, что добавите довольно небольшое количество строк (n < = 100), продолжайте и используйте самое простое решение: просто назначьте строки для data.frame, используя нотацию скобок, и проигнорируйте тот факт, что data .frame не является предварительно заполненным.*
Для всего остального используйте data.table :: set
и вырастите data.table экспоненциально (например,. используя мой код).*
Поскольку вопрос уже датирован (6 лет), в ответах отсутствует решение с более новыми пакетами tidyr и purrr. Поэтому для людей, работающих с этими пакетами, я хочу добавить решение к предыдущим ответам - все это довольно интересно, особенно .
Самое большое преимущество purrr и tidyr - лучшая читаемость ИМХО . purrr заменяет lapply более гибким семейством map () tidyr предлагает супер-интуитивный метод add_row - просто делает то, что говорит :)
map_df (1: 1000, функция (x) {df% >% add_row (x = x, y = toString (x))})
Это решение короткое и интуитивно понятное для чтения, и оно относительно быстрое:
system.time (
map_df (1: 1000, функция (x) {df% >% add_row (x = x, y = toString (x))})
)
пользовательская система истекла
0,756 0,006 0,766
Он масштабируется почти линейно, поэтому для 1e5 строк производительность равна:
system.time (
map_df (1: 100000, функция (x) {df% >% add_row (x = x, y = toString (x))})
)
пользовательская система истекла
76,035 0,259 76,489
что сделало бы его вторым сразу после data.table (если вы игнорируете плацебо) в тесте @Adam Ryczkowski:
nr function time
4 data.frame 228.251
3 sqlite 133.716
2 data.table 3.059
1 rbindlist 169.998
0 placebo 0.202
Давайте возьмем векторную «точку», которая имеет числа от 1 до 5
point = c (1,2,3,4,5)
если мы хотим добавить число 6 где-нибудь внутри вектора, то под командой может пригодиться
я) векторы
new_var = append (point, 6,after = length (point))
ii) столбцы таблицы
new_var = append (point, 6,after = length (mtcars $ mpg))
Команда append
принимает три аргумента:
просто...!! Извинения в случае любого...!
Более общим решением может быть следующее.
extendDf <- function (df, n) {
withFactors <- sum(sapply (df, function(X) (is.factor(X)) )) > 0
nr <- nrow (df)
colNames <- names(df)
for (c in 1:length(colNames)) {
if (is.factor(df[,c])) {
col <- vector (mode='character', length = nr+n)
col[1:nr] <- as.character(df[,c])
col[(nr+1):(n+nr)]<- rep(col[1], n) # to avoid extra levels
col <- as.factor(col)
} else {
col <- vector (mode=mode(df[1,c]), length = nr+n)
class(col) <- class (df[1,c])
col[1:nr] <- df[,c]
}
if (c==1) {
newDf <- data.frame (col ,stringsAsFactors=withFactors)
} else {
newDf[,c] <- col
}
}
names(newDf) <- colNames
newDf
}
Функция extendDf () расширяет рамку данных n строками.
В качестве примера:
aDf <- data.frame (l=TRUE, i=1L, n=1, c='a', t=Sys.time(), stringsAsFactors = TRUE)
extendDf (aDf, 2)
# l i n c t
# 1 TRUE 1 1 a 2016-07-06 17:12:30
# 2 FALSE 0 0 a 1970-01-01 01:00:00
# 3 FALSE 0 0 a 1970-01-01 01:00:00
system.time (eDf <- extendDf (aDf, 100000))
# user system elapsed
# 0.009 0.002 0.010
system.time (eDf <- extendDf (eDf, 100000))
# user system elapsed
# 0.068 0.002 0.070