Как добавить строки в таблице данных R
Я посмотрел вокруг 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 без успеха. Пожалуйста, дайте мне знать, если у вас есть какие-либо предложения.
5 ответов:
обновление
не зная, что вы пытаетесь сделать, я поделюсь еще одним предложением: предварительно выделите векторы типа, который вы хотите для каждого столбца, вставьте значения в эти векторы, а затем, в конце концов, создайте свой
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 = character(), 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.
предположим, что вы просто не знаете размер данных.кадр заранее. Это может быть несколько строк или несколько миллионов. Вам нужно иметь какой-то контейнер, который растет динамически. Принимая во внимание мой опыт и все связанные с ним ответы, поэтому я пришел с 4 различными решениями:
rbindlistдля сведения.кадриспользовать
data.tableбыстроsetоперация и соедините ее с ручным удвоением стол, когда это необходимо.использовать
RSQLiteи добавить к таблице, хранящейся в памяти.
data.frameсобственная способность расти и использовать пользовательскую среду (которая имеет ссылочную семантику) для хранения данных.кадр, поэтому он не будет скопирован по возвращении.вот тест всех методов для малого и большого количества добавленных строк. Каждый метод имеет 3 функции связанные с ним:
create(first_element)это возвращает соответствующий объект поддержки сfirst_elementположить в.
append(object, element)что добавляетelementдо конца таблицы (представленоobject).
access(object)получаетdata.frameвсе элементы.
rbindlistдля сведения.кадрэто довольно легко и прямо вперед:
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+ ручное удвоение таблицы при необходимости.я сохраню истинную длину таблицы в .
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,])) }SQL должен быть оптимизирован для быстрой вставки записей, поэтому я изначально возлагал большие надежды на
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 CPU @ 2.50 GHz):
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похоже, что SQLite-based sulution, хотя и восстанавливает некоторую скорость на больших данных, нигде не находится рядом с данными.таблица + ручной экспоненциальный рост. Разница составляет почти два порядка величину!
резюме
если вы знаете, что вы добавите довольно небольшое количество строк (n
для всего остального использовать
data.table::setи растут данные.таблица экспоненциально (например, с помощью моего кода).
давайте возьмем вектор "точка", которая имеет номера от 1 до 5
point = c(1,2,3,4,5)если мы хотим добавить номер 6 в любом месте внутри вектора, то ниже команда может пригодиться
i)векторы
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



Comments