Как добавить строки в кадр данных R

Я просмотрел StackOverflow, но я не могу найти решение, специфичное для моей проблемы, которое включает в себя добавление строк в кадр данных R.

Я инициализирую пустой кадр данных из двух столбцов следующим образом.

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. Пожалуйста, дайте мне знать, если у вас есть предложения.

Ответ 1

Update

Не зная, что вы пытаетесь сделать, я поделюсь еще одним предложением: предварительно выделите векторы типа, который вы хотите для каждого столбца, вставьте значения в эти векторы, а затем в конце создайте свой data.frame.

Продолжение с Julian 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)

Ответ 2

Позвольте проверить три предложенных решения:

# 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.

Ответ 3

Предположим, вы просто не знаете размер файла data.frame заранее. Это может быть несколько строк или несколько миллионов. У вас должен быть какой-то контейнер, который динамически растет. Принимая во внимание мой опыт и все связанные ответы в SO, я пришел с 4 различными решениями:

  • rbindlist к файлу data.frame

  • Используйте data.table fast set операцию и свяжите ее с удвоением вручную таблицы при необходимости.

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

  • data.frame собственная способность расти и использовать настраиваемую среду (которая имеет ссылочную семантику) для хранения data.frame, поэтому она не будет скопирована при возврате.

Вот тест всех методов как для малого, так и для большого количества присоединенных строк. Каждый метод имеет три связанные с ним функции:

  • create(first_element), который возвращает соответствующий поддерживающий объект с first_element.

  • append(object, element), который добавляет 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,]))
}

SQL должен быть оптимизирован для быстрой вставки записи, поэтому я на самом деле возлагал большие надежды на решение RSQLite

Это в основном копия и вставка Karsten W. answer на аналогичную тему.

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)

Сроки добавления n = 10 строк

Сроки для n = 100 строк Сроки для n = 1000 строк

Для строк 1E5 (измерения выполняются на процессоре Intel (R) Core 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

Похоже, что SQLulite-sulution, хотя и восстанавливает некоторую скорость на больших данных, нигде не приближается к data.table + ручному экспоненциальному росту. Разница почти на два порядка!

Резюме

Если вы знаете, что вы добавите довольно небольшое количество строк (n <= 100), перейдите к использованию и попробуйте самое простое решение: просто назначьте строки в data.frame с использованием скобки и проигнорируйте тот факт, что данные .frame не является предварительно заполненным.

Для всего остального используйте data.table::set и произведите экспоненциальную таблицу data.table(например, используя мой код).

Ответ 4

Возьмем векторную "точку", которая имеет числа от 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 принимает три аргумента:

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

просто...!! Извинения в случае любого...!

Ответ 5

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

    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

Ответ 6

Обновление с purrr, tidyr & dplyr

Поскольку вопрос уже датирован (6 лет), в ответах отсутствует решение с более новыми пакетами tidyr и purrr. Поэтому для людей, работающих с этими пакетами, я хочу добавить решение к предыдущим ответам - особенно интересным.

Самым большим преимуществом мурлыканья и тидира является лучшая читаемость ИМХО. purrr заменяет lapply на более гибкое семейство map(), tidyr предлагает суперинтуитивный метод add_row - просто делает то, что говорит :)

map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })

Это решение короткое и интуитивно понятное, и оно относительно быстрое:

system.time(
   map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
   user  system elapsed 
   0.756   0.006   0.766

Он масштабируется почти линейно, поэтому для 1-5 строк производительность составляет:

system.time(
  map_df(1:100000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
   user  system elapsed 
 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