Creazione di un dataframe R riga per riga


107

Vorrei costruire un dataframe riga per riga in R. Ho fatto alcune ricerche e tutto ciò che mi è venuto in mente è stato il suggerimento di creare un elenco vuoto, mantenere un indice di elenco scalare, quindi ogni volta aggiungerlo all'elenco un dataframe a riga singola e avanza l'indice della lista di uno. Infine, do.call(rbind,)nell'elenco.

Anche se funziona, sembra molto macchinoso. Non c'è un modo più semplice per raggiungere lo stesso obiettivo?

Ovviamente mi riferisco a casi in cui non riesco a utilizzare qualche applyfunzione e ho bisogno esplicitamente di creare il dataframe riga per riga. Almeno, c'è un modo per arrivare alla pushfine di un elenco invece di tenere esplicitamente traccia dell'ultimo indice utilizzato?


1
Puoi usare append()[che dovrebbe probabilmente essere chiamato insert] o c()per aggiungere elementi alla fine di un elenco, anche se qui non ti sarà d'aiuto.
Hatmatrix

Non ci sono molte funzioni in R che i frame di dati di ritorno a meno che non li restituiscono [row-wise] da lapply(), Map()e così via, ma si potrebbe anche voler dare un'occhiata a aggregate(), dapply() {heR.Misc}e cast() {reshape}per vedere se le attività non possono essere gestiti da questi funzioni (tutti questi restituiscono frame di dati).
hatmatrix

Risposte:


96

Puoi ingrandirli riga per riga aggiungendo o utilizzando rbind().

Questo non significa che dovresti. Le strutture in crescita dinamica sono uno dei modi meno efficienti per codificare in R.

Se puoi, alloca l'intero data.frame in anticipo:

N <- 1e4  # total number of rows to preallocate--possibly an overestimate

DF <- data.frame(num=rep(NA, N), txt=rep("", N),  # as many cols as you need
                 stringsAsFactors=FALSE)          # you don't know levels yet

e poi durante le tue operazioni inserisci una riga alla volta

DF[i, ] <- list(1.4, "foo")

Questo dovrebbe funzionare per data.frame arbitrario ed essere molto più efficiente. Se superi N puoi sempre ridurre le righe vuote alla fine.


6
Non intendevi mettere N invece di 10 e list (1.4, "foo") invece di c (1.4, "foo") per non forzare il 1.4 in modalità carattere?
hatmatrix

Sì, intendevo usare N nella creazione di data.frame. Inoltre, un ottimo modo per catturare la coercizione nella chat - mi era sfuggito.
Dirk Eddelbuettel

1
Sarebbe meglio modificare la risposta piuttosto che lasciarla nei commenti. Ero confuso nel cercare di raggirare questa risposta.
Utente

4
data.tablesembra essere ancora più veloce della pre-allocazione utilizzando data.frames. Test qui: stackoverflow.com/a/11486400/636656
Ari B. Friedman

è ancora vero in R 3.1 dove dovrebbe essere più veloce?
userJT

49

Si possono aggiungere righe a NULL:

df<-NULL;
while(...){
  #Some code that generates new row
  rbind(df,row)->df
}

per esempio

df<-NULL
for(e in 1:10) rbind(df,data.frame(x=e,square=e^2,even=factor(e%%2==0)))->df
print(df)

3
emette una matrice, non un frame di dati
Olga

1
@Olga Solo se leghi righe di elementi di un tipo uguale - A proposito, in quel caso è meglio sapply(o vettorializzare) e trasporre.
MBQ

1
@mbq Esattamente quello che sto facendo. Ho anche scoperto che se lo inizializzi con df <-data.frame (), restituisce un frame di dati.
Olga

9

Questo è uno sciocco esempio di come utilizzare do.call(rbind,)sull'output di Map()[che è simile a lapply()]

> DF <- do.call(rbind,Map(function(x) data.frame(a=x,b=x+1),x=1:3))
> DF
  x y
1 1 2
2 2 3
3 3 4
> class(DF)
[1] "data.frame"

Uso questo costrutto abbastanza spesso.


8

Il motivo per cui Rcpp mi piace così tanto è che non capisco sempre come pensa R Core, e con Rcpp, il più delle volte, non devo.

Parlando filosoficamente, sei in uno stato di peccato rispetto al paradigma funzionale, che cerca di far apparire ogni valore indipendente da ogni altro valore; la modifica di un valore non dovrebbe mai causare una modifica visibile in un altro valore, come si ottiene con i puntatori che condividono la rappresentazione in C.

I problemi sorgono quando la programmazione funzionale segnala alla piccola imbarcazione di spostarsi e la piccola imbarcazione risponde "Sono un faro". Apportare una lunga serie di piccole modifiche a un oggetto di grandi dimensioni che nel frattempo vuoi elaborare ti mette nel territorio del faro.

In C ++ STL, push_back()è uno stile di vita. Non cerca di essere funzionale, ma cerca di adattarsi in modo efficiente agli idiomi di programmazione comuni .

Con un po 'di intelligenza dietro le quinte, a volte puoi organizzare di avere un piede in ogni mondo. I file system basati su snapshot sono un buon esempio (che si è evoluto da concetti come i mount union, che utilizzano anche entrambi i lati).

Se R Core volesse farlo, l'archiviazione del vettore sottostante potrebbe funzionare come un mount union. Un riferimento alla memoria vettoriale potrebbe essere valido per i pedici 1:N, mentre un altro riferimento alla stessa memoria è valido per i pedici 1:(N+1). Potrebbe esserci spazio di archiviazione riservato non ancora validamente referenziato da tutt'altro che conveniente per una rapidapush_back() . Non si viola il concetto di funzionalità quando si aggiunge al di fuori dell'intervallo che qualsiasi riferimento esistente considera valido.

Alla fine, aggiungendo righe in modo incrementale, si esaurisce lo spazio di archiviazione riservato. Avrai bisogno di creare nuove copie di tutto, con lo spazio di archiviazione moltiplicato per un certo incremento. Le implementazioni STL che ho usato tendono a moltiplicare lo storage per 2 quando si estende l'allocazione. Pensavo di aver letto in R Internals che esiste una struttura di memoria in cui lo spazio di archiviazione aumenta del 20%. In entrambi i casi, le operazioni di crescita si verificano con frequenza logaritmica relativa al numero totale di elementi aggiunti. Su base ammortizzata, questo è generalmente accettabile.

Come i trucchi dietro le quinte, ho visto di peggio. Ogni volta che si push_back()inserisce una nuova riga nel dataframe, è necessario copiare una struttura di indice di primo livello. La nuova riga potrebbe essere aggiunta alla rappresentazione condivisa senza influire sui vecchi valori funzionali. Non penso nemmeno che complicherebbe molto il netturbino; poiché non sto proponendo che push_front()tutti i riferimenti siano riferimenti prefissi all'inizio della memoria vettoriale allocata.


2

La risposta di Dirk Eddelbuettel è la migliore; qui ho solo notato che puoi cavartela senza pre-specificare le dimensioni del dataframe oi tipi di dati, che a volte è utile se hai più tipi di dati e molte colonne:

row1<-list("a",1,FALSE) #use 'list', not 'c' or 'cbind'!
row2<-list("b",2,TRUE)  

df<-data.frame(row1,stringsAsFactors = F) #first row
df<-rbind(df,row2) #now this works as you'd expect.

Volevi dire df<-rbind(df, row2)?
Timothy C. Quinn

1

Ho trovato questo modo per creare dataframe da raw senza matrice.

Con nome colonna automatico

df<-data.frame(
        t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
        ,row.names = NULL,stringsAsFactors = FALSE
    )

Con il nome della colonna

df<-setNames(
        data.frame(
            t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
            ,row.names = NULL,stringsAsFactors = FALSE
        ), 
        c("col1","col2","col3")
    )

0

Se hai vettori destinati a diventare righe, concatenali usando c(), passali a una matrice riga per riga e converti quella matrice in un dataframe.

Ad esempio, righe

dummydata1=c(2002,10,1,12.00,101,426340.0,4411238.0,3598.0,0.92,57.77,4.80,238.29,-9.9)
dummydata2=c(2002,10,2,12.00,101,426340.0,4411238.0,3598.0,-3.02,78.77,-9999.00,-99.0,-9.9)
dummydata3=c(2002,10,8,12.00,101,426340.0,4411238.0,3598.0,-5.02,88.77,-9999.00,-99.0,-9.9)

può essere convertito in un frame di dati così:

dummyset=c(dummydata1,dummydata2,dummydata3)
col.len=length(dummydata1)
dummytable=data.frame(matrix(data=dummyset,ncol=col.len,byrow=TRUE))

Certo, vedo 2 limitazioni principali: (1) funziona solo con dati in modalità singola e (2) devi conoscere le tue colonne # finali affinché funzioni (cioè, presumo che tu non stia lavorando con un matrice irregolare la cui massima lunghezza di riga è sconosciuta a priori ).

Questa soluzione sembra semplice, ma dalla mia esperienza con le conversioni di tipo in R, sono sicuro che crei nuove sfide lungo la linea. Qualcuno può commentare questo?


0

A seconda del formato della nuova riga, è possibile utilizzarla tibble::add_rowse la nuova riga è semplice e può essere specificata in "coppie di valori". Oppure si può usare dplyr::bind_rows"un'implementazione efficiente del modello comune di do.call (rbind, dfs)".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.