Как добавить строки в таблице данных 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.

продолжение (заранее выделенном 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 различными решениями:

  1. rbindlist для сведения.кадр

  2. использовать data.tableбыстро set операция и соедините ее с ручным удвоением стол, когда это необходимо.

  3. использовать RSQLite и добавить к таблице, хранящейся в памяти.

  4. 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)

Timings for adding n=10 rows

Timings for n=100 rows Timings for n=1000 rows

для строк 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 принимает три аргумента:

  1. столбец вектора/к модифицированный.
  2. значение, которое будет включено в измененный вектор.
  3. индекс, после которого значения должны быть добавлены.

простой...!! Извиняюсь на всякий случай...!


более общим решением для Может быть следующее.

    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