Cara menambahkan baris ke bingkai data R.

121

Saya telah melihat-lihat StackOverflow, tetapi saya tidak dapat menemukan solusi khusus untuk masalah saya, yang melibatkan penambahan baris ke bingkai data R.

Saya menginisialisasi bingkai data 2 kolom kosong, sebagai berikut.

df = data.frame(x = numeric(), y = character())

Kemudian, tujuan saya adalah untuk mengulang melalui daftar nilai dan, di setiap iterasi, menambahkan nilai ke akhir daftar. Saya mulai dengan kode berikut.

for (i in 1:10) {
    df$x = rbind(df$x, i)
    df$y = rbind(df$y, toString(i))
}

Saya juga berusaha fungsi c, appenddan mergetanpa hasil. Tolong beri tahu saya jika Anda punya saran.

Gyan Veda
sumber
2
Saya tidak mengira tahu bagaimana R dimaksudkan untuk digunakan, tetapi saya ingin mengabaikan baris kode tambahan yang diperlukan untuk memperbarui indeks pada setiap iterasi dan saya tidak dapat dengan mudah mengalokasikan ukuran bingkai data karena saya tidak tidak tahu berapa banyak baris yang pada akhirnya akan dibutuhkan. Ingatlah bahwa di atas hanyalah contoh mainan yang dimaksudkan agar dapat direproduksi. Bagaimanapun, terima kasih atas saran Anda!
Gyan Veda

Jawaban:

115

Memperbarui

Tidak tahu apa yang Anda coba lakukan, saya akan membagikan satu saran lagi: Lakukan pra-alokasi vektor dari jenis yang Anda inginkan untuk setiap kolom, masukkan nilai ke dalam vektor tersebut, dan kemudian, pada akhirnya, buat data.frame.

Melanjutkan dengan Julian f3(yang dialokasikan sebelumnya data.frame) sebagai opsi tercepat sejauh ini, yang didefinisikan sebagai:

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

Berikut adalah pendekatan yang serupa, tetapi pendekatan yang data.framedibuat sebagai langkah terakhir.

# 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)
}

microbenchmarkdari paket "microbenchmark" akan memberi kita wawasan yang lebih komprehensif daripada 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()(pendekatan di bawah) sangat tidak efisien karena seberapa sering ia memanggil data.framedan karena menumbuhkan objek seperti itu umumnya lambat di R. f3()jauh lebih baik karena pra-alokasi, tetapi data.framestruktur itu sendiri mungkin menjadi bagian dari hambatan di sini. f4()mencoba melewati kemacetan itu tanpa mengorbankan pendekatan yang ingin Anda ambil.


Jawaban asli

Ini sebenarnya bukan ide yang bagus, tetapi jika Anda ingin melakukannya dengan cara ini, saya rasa Anda dapat mencoba:

for (i in 1:10) {
  df <- rbind(df, data.frame(x = i, y = toString(i)))
}

Perhatikan bahwa dalam kode Anda, ada satu masalah lain:

  • Anda harus menggunakan stringsAsFactorsjika Anda ingin karakter tidak diubah menjadi faktor. Menggunakan:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
A5C1D2H2I1M1N2O1R2T1
sumber
6
Terima kasih! Itu memecahkan masalah saya. Mengapa ini "benar-benar bukan ide yang bagus"? Dan dengan cara apa x dan y dicampur di loop for?
Gyan Veda
5
@ user2932774, Sangat tidak efisien untuk menumbuhkan objek dengan cara ini di R. Sebuah perbaikan (tapi masih belum tentu cara terbaik) akan mengalokasikan data.frameukuran akhir yang Anda harapkan dan menambahkan nilai dengan [ekstraksi / penggantian.
A5C1D2H2I1M1N2O1R2T1
1
Terima kasih, Ananda. Saya biasanya memilih pra-alokasi, tetapi saya tidak setuju bahwa ini sebenarnya bukan ide yang bagus. Tergantung situasinya. Dalam kasus saya, saya berurusan dengan data kecil dan alternatifnya akan lebih memakan waktu untuk membuat kode. Plus, ini adalah kode yang lebih elegan dibandingkan dengan yang diperlukan untuk memperbarui indeks numerik untuk mengisi bagian yang sesuai dari bingkai data yang dialokasikan sebelumnya pada setiap iterasi. Hanya ingin tahu, menurut Anda apa "cara terbaik" untuk menyelesaikan tugas ini? Saya akan berpikir bahwa pra-alokasi akan menjadi yang terbaik.
Gyan Veda
2
@ user2932774, itu keren. Saya juga menghargai perspektif Anda - saya juga tidak pernah benar-benar bekerja dengan kumpulan data besar. Yang mengatakan, jika saya akan bekerja untuk menulis suatu fungsi atau sesuatu, saya biasanya akan menghabiskan sedikit usaha ekstra untuk mengubah kode untuk mendapatkan kecepatan yang lebih baik bila memungkinkan. Lihat pembaruan saya untuk contoh perbedaan kecepatan yang cukup besar.
A5C1D2H2I1M1N2O1R2T1
1
Wah, itu perbedaan besar! Terima kasih telah menjalankan simulasi itu dan mengajari saya tentang paket microbenchmark. Saya sangat setuju dengan Anda bahwa senang melakukan upaya ekstra itu. Dalam kasus khusus saya, saya rasa saya hanya menginginkan sesuatu yang cepat dan kotor pada beberapa kode yang mungkin tidak perlu saya jalankan lagi. :)
Gyan Veda
34

Mari kita tolak ukur tiga solusi yang diusulkan:

# 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

Solusi terbaik adalah mengalokasikan ruang terlebih dahulu (seperti yang dimaksudkan di R). Solusi terbaik berikutnya adalah menggunakan list, dan solusi terburuk (setidaknya berdasarkan hasil waktu ini) tampaknya rbind.

Julián Urbano
sumber
Terima kasih! Meskipun saya tidak setuju dengan saran Ananda. Apakah saya ingin karakter dikonversi ke level faktor atau tidak akan bergantung pada apa yang ingin saya lakukan dengan hasilnya. Meskipun saya rasa dengan solusi yang Anda usulkan, Anda perlu menyetel stringsAsFactors ke FALSE.
Gyan Veda
Terima kasih atas simulasinya. Saya menyadari bahwa pra-alokasi adalah yang terbaik dalam hal kecepatan pemrosesan, tetapi itu bukan satu-satunya faktor yang saya pertimbangkan dalam membuat keputusan pengkodean ini.
Gyan Veda
1
Di f1 Anda bingung dengan menetapkan string ke vektor numerik x. Baris yang benar adalah:df <- rbind(df, data.frame(x = i, y = toString(i)))
Eldar Agalarov
14

Misalkan Anda tidak mengetahui ukuran data.frame sebelumnya. Bisa jadi beberapa baris, atau beberapa juta. Anda perlu memiliki semacam wadah, yang tumbuh secara dinamis. Mempertimbangkan pengalaman saya dan semua jawaban terkait di SO saya datang dengan 4 solusi berbeda:

  1. rbindlist ke data.frame

  2. Gunakan operasi data.tablecepat setdan pasangkan dengan menggandakan tabel secara manual bila diperlukan.

  3. Gunakan RSQLitedan tambahkan ke tabel yang disimpan dalam memori.

  4. data.framekemampuan sendiri untuk mengembangkan dan menggunakan lingkungan kustom (yang memiliki semantik referensi) untuk menyimpan data.frame sehingga tidak akan disalin saat kembali.

Berikut adalah pengujian semua metode untuk jumlah baris yang ditambahkan kecil dan besar. Setiap metode memiliki 3 fungsi yang terkait dengannya:

  • create(first_element)yang mengembalikan objek pendukung yang sesuai dengan first_elementdimasukkan.

  • append(object, element)yang menambahkan elementakhir tabel (diwakili oleh object).

  • access(object)mendapatkan data.framedengan semua elemen yang disisipkan.

rbindlist ke data.frame

Itu cukup mudah dan tidak berbelit-belit:

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 + menggandakan tabel secara manual saat diperlukan.

Saya akan menyimpan panjang sebenarnya dari tabel dalam rowcountatribut.

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 harus dioptimalkan untuk penyisipan rekaman cepat, jadi saya awalnya memiliki harapan tinggi untuk RSQLitesolusi

Ini pada dasarnya adalah salin & tempel jawaban Karsten W. di utas serupa.

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.framelingkungan kustom + penambahan baris sendiri.

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)
}

Rangkaian pengujian:

Untuk kenyamanan, saya akan menggunakan satu fungsi uji untuk mencakup semuanya dengan panggilan tidak langsung. (Saya memeriksa: menggunakan do.callalih-alih memanggil fungsi secara langsung tidak membuat kode berjalan terukur lebih lama).

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)))
}

Mari kita lihat kinerja untuk n = 10 penyisipan.

Saya juga menambahkan fungsi 'plasebo' (dengan akhiran 0) yang tidak melakukan apa-apa - hanya untuk mengukur overhead pengaturan pengujian.

r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)

Pengaturan waktu untuk menambahkan n = 10 baris

Pengaturan waktu untuk n = 100 baris Pengaturan waktu untuk n = 1000 baris

Untuk 1E5 baris (pengukuran dilakukan pada 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

Sepertinya sulusi berbasis SQLite, meskipun mendapatkan kembali beberapa kecepatan pada data besar, tidak jauh dari pertumbuhan eksponensial data.table + manual. Perbedaannya hampir dua kali lipat!

Ringkasan

Jika Anda tahu bahwa Anda akan menambahkan jumlah baris yang agak kecil (n <= 100), lanjutkan dan gunakan solusi yang paling sederhana: cukup tetapkan baris ke data.frame menggunakan notasi braket dan abaikan fakta bahwa data.frame adalah tidak diisi sebelumnya.

Untuk semua yang lain gunakan data.table::setdan kembangkan data.table secara eksponensial (misalnya menggunakan kode saya).

Adam Ryczkowski
sumber
2
Alasan SQLite lambat adalah bahwa pada setiap INSERT INTO, ia harus REINDEX, yaitu O (n), di mana n adalah jumlah baris. Ini berarti bahwa memasukkan ke dalam database SQL satu baris pada satu waktu adalah O (n ^ 2). SQLite bisa sangat cepat, jika Anda memasukkan seluruh data.frame sekaligus, tetapi itu bukan yang terbaik dalam mengembangkan baris demi baris.
Julian Zucker
5

Perbarui dengan purrr, tidyr & dplyr

Karena pertanyaannya sudah bertanggal (6 tahun), jawabannya tidak ada solusi dengan paket yang lebih baru tidyr dan purrr. Jadi bagi orang-orang yang bekerja dengan paket ini, saya ingin menambahkan solusi ke jawaban sebelumnya - semuanya cukup menarik, khususnya.

Keuntungan terbesar dari purrr dan tidyr adalah IMHO keterbacaan yang lebih baik. purrr menggantikan lapply dengan keluarga map () yang lebih fleksibel, tidyr menawarkan metode super-intuitif add_row - lakukan saja apa yang dikatakan :)

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

Solusi ini singkat dan intuitif untuk dibaca, dan relatif cepat:

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

Ini berskala hampir linier, jadi untuk 1e5 baris, kinerjanya adalah:

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 

yang akan membuatnya berada di peringkat kedua setelah data.table (jika Anda mengabaikan plasebo) dalam tolok ukur oleh @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
Kacang Agile
sumber
Anda tidak perlu menggunakan add_row. Sebagai contoh: map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) }).
user3808394
@ user3808394 terima kasih, itu alternatif yang menarik! jika seseorang ingin membuat kerangka data dari awal, milik Anda lebih pendek sehingga solusi yang lebih baik. jika Anda sudah memiliki kerangka data, solusi saya tentu saja lebih baik.
Agile Bean
Jika Anda sudah memiliki dataframe, Anda akan melakukannya bind_rows(df, map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) }))daripada menggunakan add_row.
pengguna3808394
2

Mari kita ambil 'titik' vektor yang memiliki angka dari 1 sampai 5

point = c(1,2,3,4,5)

jika kita ingin menambahkan angka 6 di mana saja di dalam vektor maka perintah di bawah ini mungkin berguna

i) Vektor

new_var = append(point, 6 ,after = length(point))

ii) kolom tabel

new_var = append(point, 6 ,after = length(mtcars$mpg))

Perintah tersebut appendmembutuhkan tiga argumen:

  1. vektor / kolom yang akan diubah.
  2. nilai untuk dimasukkan dalam vektor yang dimodifikasi.
  3. subskrip, setelah itu nilainya akan ditambahkan.

sederhana...!! Permintaan maaf jika terjadi ...!

Praneeth Krishna
sumber
1

Solusi yang lebih umum untuk mungkin adalah sebagai berikut.

    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
}

Fungsi extensionDf () memperluas bingkai data dengan n baris.

Sebagai contoh:

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
Pisca46
sumber
0

Solusi saya hampir sama dengan jawaban asli tetapi tidak berhasil untuk saya.

Jadi, saya memberi nama untuk kolom dan berhasil:

painel <- rbind(painel, data.frame("col1" = xtweets$created_at,
                                   "col2" = xtweets$text))
Brun Ijbh
sumber