R 데이터 프레임에 행을 추가하는 방법


121

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성공하지. 제안 사항이 있으면 알려주십시오.


2
나는 R이 어떻게 사용되는지 알 것이라고 생각하지 않지만 매 반복마다 색인을 업데이트하는 데 필요한 추가 코드 줄을 무시하고 싶었고 데이터 프레임의 크기를 쉽게 미리 할당 할 수 없습니다. 궁극적으로 얼마나 많은 행이 걸릴지 모릅니다. 위의 내용은 재현 가능한 장난감 예제 일뿐입니다. 어느 쪽이든, 제안 해 주셔서 감사합니다!
Gyan Veda

답변:


115

최신 정보

무엇을 하려는지 모르기 때문에 한 가지 더 제안을 공유하겠습니다. 각 열에 대해 원하는 유형의 벡터를 미리 할당하고 해당 벡터에 값을 삽입 한 다음 마지막에 data.frame.

지금까지 가장 빠른 옵션 으로 Julian 's 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)

6
감사! 그것은 내 문제를 해결합니다. 이것이 "정말 좋은 생각이 아닌"이유는 무엇입니까? 그리고 for 루프에서 x와 y는 어떤 방식으로 혼합됩니까?
Gyan Veda

5
@ user2932774, R에서 이러한 방식으로 개체를 성장시키는 것은 매우 비효율적입니다. 개선 된 (하지만 여전히 최선의 방법은 아님) data.frame예상 한 최종 크기의 a를 미리 할당하고 [추출 / 대체로 값을 추가하는 것 입니다.
A5C1D2H2I1M1N2O1R2T1

1
고마워, 아난다. 나는 일반적으로 사전 할당을 사용하지만 이것이 실제로 좋은 생각이 아니라는 데 동의하지 않습니다. 상황에 따라 다릅니다. 제 경우에는 작은 데이터를 다루고 있으며 대안은 코딩에 더 많은 시간이 소요될 것입니다. 또한 매 반복마다 사전 할당 된 데이터 프레임의 적절한 부분을 채우기 위해 숫자 인덱스를 업데이트하는 데 필요한 코드에 비해 더 우아한 코드입니다. 궁금한 점이 있다면이 작업을 수행하는 "가장 좋은 방법"이 무엇이라고 생각하십니까? 나는 사전 할당이 최선이라고 생각했을 것입니다.
Gyan Veda

2
@ user2932774, 멋지다. 당신의 관점도 고맙습니다. 저는 큰 데이터 세트로 작업하지도 않습니다. 즉, 함수 등을 작성하는 작업을 할 경우 가능한 한 더 나은 속도를 얻기 위해 일반적으로 코드를 수정하는 데 약간의 추가 노력을 기울일 것입니다. 상당히 큰 속도 차이의 예는 내 업데이트를 참조하십시오.
A5C1D2H2I1M1N2O1R2T1

1
우와, 그것은 큰 차이입니다! 시뮬레이션을 실행하고 microbenchmark 패키지에 대해 알려 주셔서 감사합니다. 나는 그 여분의 노력을 기울이는 것이 좋다는 당신의 의견에 확실히 동의합니다. 내 특별한 경우에는 다시 실행할 필요가 없을 수도있는 일부 코드에서 빠르고 더러운 것을 원했던 것 같습니다. :)
Gyan Veda

34

제안 된 세 가지 솔루션을 벤치마킹 해 보겠습니다.

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


감사! 나는 Ananda의 제안에 동의하지 않지만. 문자를 요인 수준으로 변환할지 여부는 출력으로 수행하려는 작업에 따라 다릅니다. 제안한 솔루션으로 stringsAsFactors를 FALSE로 설정해야한다고 생각합니다.
Gyan Veda

시뮬레이션 주셔서 감사합니다. 사전 할당이 처리 속도 측면에서 가장 좋다는 것을 알고 있지만이 코딩 결정을 내릴 때 고려한 유일한 요소는 아닙니다.
Gyan Veda

1
f1에서는 문자열을 숫자 형 벡터 x에 할당하여 혼동했습니다. 올바른 줄 :df <- rbind(df, data.frame(x = i, y = toString(i)))
Eldar Agalarov 2014-06-01

14

단순히 data.frame의 크기를 미리 모른다고 가정합니다. 몇 개의 행 또는 수백만 개가 될 수 있습니다. 동적으로 커지는 일종의 컨테이너가 필요합니다. 내 경험과 모든 관련 답변을 고려하여 다음과 같은 4 가지 솔루션을 제공합니다.

  1. rbindlist data.frame에

  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 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. 답변 의 복사 및 붙여 넣기입니다 .

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 (TM) i7-4710HQ CPU @ 2.50GHz에서 측정) :

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 기반 솔루션은 대용량 데이터에서 약간의 속도를 되찾았지만 data.table + 수동 기하 급수적 성장에 가까운 곳이 아닙니다. 그 차이는 거의 두 배입니다!

요약

다소 적은 수의 행 (n <= 100)을 추가한다는 것을 알고 있다면 가능한 가장 간단한 솔루션을 사용하십시오. 대괄호 표기법을 사용하여 data.frame에 행을 할당하고 data.frame이 있다는 사실을 무시하십시오. 미리 채워지지 않았습니다.

다른 모든 경우 data.table::set에는 data.table을 기하 급수적으로 사용 하고 확장합니다 (예 : 내 코드 사용).


2
SQLite가 느린 이유는 각 INSERT INTO에서 REINDEX가 필요하기 때문입니다. 이는 O (n)이며 여기서 n은 행 수입니다. 이것은 한 번에 한 행씩 SQL 데이터베이스에 삽입하는 것이 O (n ^ 2)임을 의미합니다. SQLite는 전체 data.frame을 한 ​​번에 삽입하면 매우 빠를 수 있지만 한 줄씩 성장하는 데는 최고가 아닙니다.
Julian Zucker 17

5

purrr, tidyr 및 dplyr로 업데이트

질문이 이미 날짜가 지정 되었기 때문에 (6 년), 답변에는 최신 패키지가 깔끔하고 깔끔한 솔루션이 없습니다. 따라서 이러한 패키지로 작업하는 사람들을 위해 이전 답변에 대한 솔루션을 추가하고 싶습니다. 특히.

purrr 및 tidyr의 가장 큰 장점은 더 나은 가독성 IMHO입니다. 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

거의 선형 적으로 확장되므로 1e5 행의 경우 성능은 다음과 같습니다.

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 

@Adam Ryczkowski의 벤치 마크에서 data.table (위약을 무시하는 경우) 바로 다음으로 2 위를 차지합니다.

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

을 사용할 필요가 없습니다 add_row. 예 : map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) }).
user3808394

@ user3808394 감사합니다. 흥미로운 대안입니다! 누군가가 처음부터 데이터 프레임을 생성하려는 경우 귀하의 데이터 프레임이 더 짧으므로 더 나은 솔루션입니다. 이미 데이터 프레임이있는 경우에는 물론 내 솔루션이 더 좋습니다.
Agile Bean

이미 dataframe이있는 경우, 당신은 할 것이다 bind_rows(df, map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) }))사용하는 대신 add_row.
user3808394

2

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. 값이 추가되는 아래 첨자.

단순한...!! 어떤 경우에 사과 ...!


1

보다 일반적인 솔루션은 다음과 같습니다.

    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

0

내 솔루션은 원래 답변과 거의 동일하지만 저에게 효과가 없습니다.

그래서 열에 이름을 지정했고 작동합니다.

painel <- rbind(painel, data.frame("col1" = xtweets$created_at,
                                   "col2" = xtweets$text))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.