2015-08-07 11 views
6

Diciamo che ho data.frame dfContemporaneamente sottoinsiemi e operano su una colonna specifica di un frame di dati

df<-data.frame(a=1:5,b=101:105,c=201:205) 

Posso chiamare un sottoinsieme di questi dati eseguendo contemporaneamente qualche tipo di modifica (ad esempio, aritmetica) a una delle colonne (o righe) al volo?

Ad esempio, se si desidera restituire la prima e la seconda colonna di df ma restituire il registro dei valori della colonna 1. C'è qualche notazione di modificare df[,1:2] per produrre il seguente tutto al volo ?:

  a b 
>1 0.0000000 101 
>2 0.6931472 102 
>3 1.0986123 103 
>4 1.3862944 104 
>5 1.6094379 105 
+2

il pacchetto 'dplyr' è buono per questo genere di cose. –

+1

@JohnPaul il pacchetto data.table potrebbe anche essere un'alternativa goog, [vedi la mia risposta] (http://stackoverflow.com/a/31885840/2204410) per un'implementazione – Jaap

risposta

10

Questo è un buon esempio per within()

within(df[1:2], a <- log(a)) 
#   a b 
# 1 0.0000000 101 
# 2 0.6931472 102 
# 3 1.0986123 103 
# 4 1.3862944 104 
# 5 1.6094379 105 

O se preferisci non avere <- nella chiamata, puoi usare parentesi

within(df[1:2], { a = log(a) }) 
3
`[`(transform(df, a = log(a)),1:2)  
#   a b 
#1 0.0000000 101 
#2 0.6931472 102 
#3 1.0986123 103 
#4 1.3862944 104 
#5 1.6094379 105 

è possibile chiamare un sottoinsieme nello svolgimento di una funzione. Ma è più un gioco di prestigio che un'operazione simultanea. Ma il dplyr e altri approcci maschereranno essenzialmente lo stesso comportamento. Se è lo spazio e il codice del golf che stai cercando di realizzare, questo dovrebbe aiutare. Mi piace l'aspetto del suggerimento di Mr.Flick ma questo è un po 'più veloce (bit).

+0

Cosa fa il "' ['" ?? – theforestecologist

+2

Perché non solo 'transform (df, a = log (a)) [1: 2]'? – MrFlick

+0

Sembra più pulito, ma l'originale è un vantaggio più veloce –

4

o la versione dplyr:

library(dplyr) 
transmute(df, a = log(a), b = b) 
      a b 
1 0.0000000 101 
2 0.6931472 102 
3 1.0986123 103 
4 1.3862944 104 
5 1.6094379 105 

In dplyr, transmute() restituisce solo le variabili denominate nella chiamata ad esso. Qui, abbiamo solo trasformato una delle due variabili, ma abbiamo incluso la seconda nel risultato creando una copia di essa. A differenza di transmute(), mutate() restituirà l'interezza del frame di dati originale, insieme alle variabili create. Se assegni alle nuove variabili lo stesso nome di quelle esistenti, lo mutate() le sovrascriverà.

Una cosa piacevole circa la versione dplyr è che è facile da miscelare trasformazioni e per ottenere i risultati nomi bello, come questo:

> transmute(df, a.log = log(a), b.sqrt = sqrt(b)) 
     a.log b.sqrt 
1 0.0000000 10.04988 
2 0.6931472 10.09950 
3 1.0986123 10.14889 
4 1.3862944 10.19804 
5 1.6094379 10.24695 
2

Non sono convinto che nessuno di questi sia più veloce del metodo in due passaggi, semplicemente eseguendolo con un numero inferiore di tasti. Qui ci sono alcuni punti di riferimento:

library(microbenchmark) 
microbenchmark(dplyr = {df<-data.frame(a=1:5,b=101:105,c=201:205);df<-transmute(df, a = log(a), b = b)}, 
       transform = {df<-data.frame(a=1:5,b=101:105,c=201:205);df<-transform(df, a = log(a))}, 
       within = {df<-data.frame(a=1:5,b=101:105,c=201:205);df<-within(df[1:2], a <- log(a))}, 
       twosteps = {df<-data.frame(a=1:5,b=101:105,c=201:205);df<-df[,1:2];df[,1]<-log(df[,1])}) 

Unit: microseconds 
     expr  min  lq  mean median  uq  max neval 
    dplyr 1374.710 1438.453 1657.3807 1534.0680 1658.2910 5231.572 100 
transform 489.597 508.413 764.6921 524.9240 569.4680 18127.718 100 
    within 493.436 518.396 593.6254 534.9085 585.7880 1554.420 100 
    twosteps 421.245 438.909 501.6850 450.6210 491.5165 2101.231 100 

Per dimostrare il commento di Gregor sotto, prima con 5 righe, ma mettendo la creazione di oggetti al di fuori del benchmark:

n = 5 
df = data.frame(a = runif(n), b = rnorm(n), c = 1:n) 

microbenchmark(dplyr = {df2 <- transmute(df, a = log(a), b = b)}, 
       subset = {df2 <- `[`(transform(df, a = log(a)),1:2)}, 
       within = {df2 <- within(df[1:2], a <- log(a))}, 
       twosteps = {df2 <- df[,1:2]; df2[,1]<-log(df2[,1])}) 
# twosteps looks much better! 

Ma se si aumenta il numero di righe da grande abbastanza dove potresti preoccuparti delle differenze di velocità:

n = 1e6 
df = data.frame(a = runif(n), b = rnorm(n), c = 1:n) 

microbenchmark(dplyr = {df2 <- transmute(df, a = log(a), b = b)}, 
       subset = {df2 <- `[`(transform(df, a = log(a)),1:2)}, 
       within = {df2 <- within(df[1:2], a <- log(a))}, 
       twosteps = {df2 <- df[,1:2]; df2[,1]<-log(df2[,1])}) 

Le differenze vanno via.

+0

Buono a sapersi !! La mia domanda originale non riguardava la velocità di elaborazione, però. Piuttosto, ero più interessato a ridurre le linee di codice e non dover definire o aggiornare un altro oggetto. Tutte le risposte finora lo realizzano bene. Dalla tua risposta, però, posso vedere che 'within' è probabilmente la strada da seguire quando usi questo approccio. Grazie! – theforestecologist

+3

Con un frame di dati così piccolo (5 righe), una percentuale abbastanza grande del tempo sarà spesa solo nella creazione degli oggetti del frame di dati. Sarebbe un benchmark migliore se avessi creato i dati in anticipo --- e poi vedrai che con un dataframe così piccolo il metodo 'twosteps' è sostanzialmente più veloce, un fattore da 2 a 10 (nel caso lento di dplyr) tempi più veloci. * Tuttavia *, su un esempio così piccolo la differenza è di microsecondi quindi non importa. Se si aumenta la dimensione in un frame di dati di milioni di righe, le differenze spariscono principalmente. – Gregor

+1

@Gregor, buon punto e serbatoi per le modifiche! – jeremycg

6

Un approccio con data.table potrebbe essere la seguente:

library(data.table) 
setDT(df)[, .(a=log(a),b)] 

Un test su grandi insiemi di dati:

library(data.table) 
dt1 <- CJ(a = seq(1, 1e3, by=1), b = sample(1e2L), c = sample(1e2L)) 
df1 <- copy(dt1) 
setDF(df1) 

Il benchmark:

library(rbenchmark) 
benchmark(replications = 10, order = "elapsed", columns = c("test", "elapsed", "relative"), 
      dt = dt1[, .(a=log(a),b)], 
      dplyr = transmute(df1, a = log(a), b = b), 
      transform = transform(df1, a = log(a), b = b), 
      within = within(df1, a <- log(a))[,1:2], 
      twosteps = {df1<-df1[,1:2];df1[,1]<-log(df1[,1])}) 

     test elapsed relative 
5 twosteps 0.249 1.000 
4 within 0.251 1.008 
3 transform 0.251 1.008 
2  dplyr 0.300 1.205 
1  dt 0.462 1.855 

Con mia grande sorpresa, l'approccio data.table è il più lento. Mentre nella maggior parte degli altri casi (ad es .: one, two) è l'approccio più rapido.

+0

'con (df1, data.frame (a = log (a), b = b))' è sempre il più veloce per me, penso sia piuttosto simile al due step, ma restituisce in realtà un df – rawr

+1

@DavidArenburg Not sicuro di cosa stavo pensando. L'ho usato nel benchmark, ma in modo apprezzabile non nella prima parte :-(. Modificato ora. – Jaap

Problemi correlati