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 का उपयोग कैसे किया जाना था, लेकिन मैं कोड की अतिरिक्त पंक्ति को अनदेखा करना चाहता था जिसे हर पुनरावृत्ति पर सूचकांक को अद्यतन करने की आवश्यकता होगी और मैं आसानी से डेटा फ्रेम के आकार का प्रचार नहीं कर सकता क्योंकि मैं डॉन 'न जाने कितनी पंक्तियाँ अंततः लगेंगी। याद रखें कि उपरोक्त केवल एक खिलौना उदाहरण है जिसका अर्थ प्रजनन योग्य है। किसी भी तरह से, आपके सुझाव के लिए धन्यवाद!
ज्ञान वेद

जवाबों:


115

अपडेट करें

यह नहीं जानते कि आप क्या करने की कोशिश कर रहे हैं, मैं एक और सुझाव साझा करूंगा: प्रत्येक कॉलम के लिए इच्छित प्रकार के प्रोलोकेट वैक्टर, उन वैक्टर में मान डालें, और फिर, अंत में, अपना बनाएं 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"माइक्रोबेनमार्क" पैकेज से हमें और अधिक व्यापक जानकारी मिलेगी 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और क्योंकि बढ़ती हुई वस्तुएं जिस तरह से आम तौर पर आर में धीमी होती हैं 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
धन्यवाद! यह मेरी समस्या का हल करता है। यह "वास्तव में एक अच्छा विचार क्यों नहीं है"? और किस तरह से x और y को लूप के लिए मिलाया जाता है?
ज्ञानवेद

5
@ user2932774, आर। में एक वस्तु को इस तरह से विकसित करना अविश्वसनीय रूप से अक्षम है। एक सुधार (लेकिन अभी भी जरूरी नहीं कि सबसे अच्छा तरीका) एक data.frameपरम आकार का प्रचार करना होगा जो आप अपेक्षा करते हैं और [निष्कर्षण / प्रतिस्थापन के साथ मूल्यों को जोड़ते हैं ।
a5C1D2H2I1M1N2O1R2T1

1
धन्यवाद, आनंद। मैं सामान्य रूप से प्रचार के साथ जाता हूं, लेकिन मैं असहमत हूं कि यह वास्तव में एक अच्छा विचार नहीं है। यह स्थिति पर निर्भर करता है। मेरे मामले में, मैं छोटे डेटा के साथ काम कर रहा हूं और विकल्प कोड के लिए अधिक समय लेने वाला होगा। साथ ही, प्रत्येक पुनरावृत्ति पर पूर्व-आवंटित डेटा फ़्रेम के उपयुक्त भागों को भरने के लिए संख्यात्मक सूचकांकों को अद्यतन करने के लिए आवश्यक तुलना में यह अधिक सुरुचिपूर्ण कोड है। बस जिज्ञासु, आपकी राय में इस कार्य को पूरा करने के लिए "सबसे अच्छा तरीका" क्या है? मैंने सोचा होगा कि उपदेश सबसे अच्छा होगा।
ज्ञानवेद

2
@ user2932774, यह अच्छा है। मैं आपके दृष्टिकोण की भी सराहना करता हूं - मैं वास्तव में कभी भी बड़े डेटासेट के साथ काम नहीं करता हूं। उस ने कहा, अगर मैं एक फ़ंक्शन या कुछ लिखने पर काम करने जा रहा हूं, तो मैं आमतौर पर जब भी संभव हो बेहतर गति प्राप्त करने के लिए कोड को ट्विक करने की कोशिश में थोड़ा अतिरिक्त प्रयास खर्च करूंगा। एक बहुत बड़ी गति अंतर के उदाहरण के लिए मेरा अपडेट देखें।
a5C1D2H2I1M1N2O1R2T1

1
वाह, यह बहुत बड़ा अंतर है! उस सिमुलेशन को चलाने और मुझे माइक्रोबेनचमार्क पैकेज के बारे में सिखाने के लिए धन्यवाद। मैं आपसे निश्चित रूप से सहमत हूं कि उस अतिरिक्त प्रयास में लगाना अच्छा है। मेरे विशेष मामले में, मुझे लगता है कि मुझे बस कुछ कोड पर कुछ क्विक और गंदा चाहिए था जो मुझे फिर कभी नहीं चलाना पड़ सकता है। :)
ज्ञान वेद

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

सबसे अच्छा उपाय अंतरिक्ष को पूर्व-आवंटित करना है (जैसा कि आर में इरादा है)। अगला-सर्वश्रेष्ठ समाधान उपयोग करना है list, और सबसे खराब समाधान (कम से कम इन समय परिणामों के आधार पर) प्रतीत होता है rbind


धन्यवाद! हालांकि मैं आनंद के सुझाव से असहमत हूं। मैं चाहता हूं कि पात्रों को एक कारक के स्तरों में परिवर्तित किया जाए या नहीं, यह इस बात पर निर्भर करेगा कि मैं आउटपुट के साथ क्या करना चाहता हूं। हालांकि मुझे लगता है कि आपके द्वारा प्रस्तावित किए गए समाधान के साथ, स्ट्रिंग्सएफ़एक्टर्स को FALSE करना आवश्यक है।
ज्ञान वेद

अनुकरण के लिए धन्यवाद। मुझे एहसास है कि प्रसंस्करण गति के संदर्भ में उपदेश सबसे अच्छा है, लेकिन यह एकमात्र कारक नहीं है जिसे मैंने इस कोडिंग निर्णय लेने में माना था।
ज्ञानवेद

1
F1 में आपने सांख्यिक वेक्टर x को स्ट्रिंग निर्दिष्ट करके भ्रमित किया है। सही लाइन है:df <- rbind(df, data.frame(x = i, y = toString(i)))
एल्डार एलारोव

14

मान लीजिए कि आप बस पहले से data.frame का आकार नहीं जानते हैं। यह अच्छी तरह से कुछ पंक्तियों, या कुछ लाखों हो सकता है। आपको किसी प्रकार के कंटेनर की आवश्यकता होती है, जो गतिशील रूप से बढ़ता है। मेरे अनुभव और एसओ में सभी संबंधित उत्तरों को ध्यान में रखते हुए मैं 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 + जरूरत पड़ने पर मैन्युअल रूप से तालिका को दोगुना करना।

मैं तालिका की वास्तविक लंबाई को एक 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,]))
}

एसक्यूएल को तेज रिकॉर्ड प्रविष्टि के लिए अनुकूलित किया जाना चाहिए, इसलिए मुझे शुरू में 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)

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- आधारित कमजोर पड़ने जैसा दिखता है, हालांकि बड़े डेटा पर कुछ गति प्राप्त करता है, डेटाटैब + मैनुअल घातीय वृद्धि कहीं नहीं है। अंतर परिमाण के लगभग दो क्रम है!

सारांश

यदि आप जानते हैं कि आप छोटी संख्या में पंक्तियों को जोड़ेंगे (n <= 100), तो आगे बढ़ें और सबसे सरल संभव समाधान का उपयोग करें: बस डेटा को पंक्तियों को असाइन करें। ब्रैकेट नोटेशन का उपयोग करके और इस तथ्य को अनदेखा करें कि डेटा.फ्रेम है पहले से आबाद नहीं।

बाकी सब के data.table::setलिए डेटा का उपयोग करें और विकसित करें। तेजी से (जैसे मेरे कोड का उपयोग करके)।


2
SQLite धीमा होने का कारण यह है कि प्रत्येक INSERT INTO पर, इसे REINDEX है, जो O (n) है, जहाँ n पंक्तियों की संख्या है। इसका मतलब यह है कि एक समय में एक SQL डेटाबेस एक पंक्ति में सम्मिलित हे (n ^ 2) है। यदि आप एक बार में एक संपूर्ण data.frame सम्मिलित करते हैं, तो SQLite बहुत तेज़ हो सकता है, लेकिन यह लाइन द्वारा लाइन बढ़ने पर सबसे अच्छा नहीं है।
जूलियन जुकर

5

Purrr, tidyr और dplyr के साथ अपडेट करें

जैसा कि प्रश्न पहले ही दिनांक (6 वर्ष) है, उत्तर नए पैकेजों के साथ एक समाधान याद कर रहे हैं tidyr और purrr। इसलिए इन पैकेजों के साथ काम करने वाले लोगों के लिए, मैं पिछले उत्तरों का समाधान जोड़ना चाहता हूं - विशेष रूप से सभी काफी दिलचस्प।

Purrr और tidyr का सबसे बड़ा लाभ बेहतर पठनीयता IMHO है। purrr ने अधिक लचीले मानचित्र () परिवार के साथ भव्य रूप से प्रतिस्थापित किया, 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 द्वारा बेंचमार्क में:

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 धन्यवाद, यह एक दिलचस्प विकल्प है! अगर कोई स्क्रैच से डेटाफ़्रेम बनाना चाहता है, तो आपका समाधान बेहतर है। यदि आपके पास पहले से ही डेटाफ्रेम है, तो मेरा समाधान निश्चित रूप से बेहतर है।
एजाइल बीन

यदि आपके पास पहले से ही डेटाफ़्रेम है, तो आप 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
}

फ़ंक्शन का विस्तार करें () एन पंक्तियों के साथ एक डेटा फ़्रेम का विस्तार करता है।

उदहारण के लिए:

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))
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.