2016-05-04 11 views
7

Immaginate Ho un dataframe come questo (o i nomi di tutti i mesi)sottraendo ogni due colonne

set.seed(1) 
mydata <- data.frame() 
mydata <- rbind(mydata,c(1,round(runif(20),3))) 
mydata <- rbind(mydata,c(2,round(runif(20),3))) 
mydata <- rbind(mydata,c(3,round(runif(20),3))) 
colnames(mydata) <- c("id", paste0(rep(c('Mary', 'Bob', 'Dylan', 'Tom', 'Jane', 'Sam', 'Tony', 'Luke', 'John', "Pam"), each=2), 1:2)) 

.

id Mary1 Mary2 Bob1 Bob2 Dylan1 Dylan2 Tom1 Tom2 Jane1 Jane2 Sam1 Sam2 Tony1 Tony2 Luke1 Luke2 John1 John2 Pam1 Pam2 
1 0.266 0.372 0.573 0.908 0.202 0.898 0.945 0.661 0.629 0.062 0.206 0.177 0.687 0.384 0.770 0.498 0.718 0.992 0.380 0.777 
2 0.935 0.212 0.652 0.126 0.267 0.386 0.013 0.382 0.870 0.340 0.482 0.600 0.494 0.186 0.827 0.668 0.794 0.108 0.724 0.411 
3 0.821 0.647 0.783 0.553 0.530 0.789 0.023 0.477 0.732 0.693 0.478 0.861 0.438 0.245 0.071 0.099 0.316 0.519 0.662 0.407 

Di solito con molte più colonne.

E voglio aggiungere colonne (sta a voi decidere di aggiungerli a destra, oppure creare una nuova dataframe con queste nuove colonne) sottraendo ogni due .. (*)

id, Mary1-Mary2, Bob1-Bob2, Dylan1-Dylan2, Tom1-Tom2, Jane1-Jane2,... 

Questo l'operazione è abbastanza comune

Mi piacerebbe farlo per nome, non per posizione, per evitare problemi se non sono consecutivi. Potrebbe anche succedere che alcune colonne non abbiano la colonna "doppia", basta andarsene così com'è o ignorare questa complicazione ora.

(*) I nomi delle colonne hanno un prefisso e un numero. Invece di solo sottrarre due colonne, potrei avere gruppi di 5 e potrei voler fare qualcosa come aggiungere tutti i numeri. Una soluzione generica sarebbe grandiosa.

Prima ho provato a farlo convertendolo in formato lungo, in seguito operato con aggregato e convertendolo di nuovo in formato grande, ma forse è molto più facile farlo direttamente in formato grande. So che il problema riguarda principalmente l'uso di espressioni regolari in modo efficiente.

R, data.table or dplyr, long format splitting colnames

non mi dispiace la velocità, ma la soluzione più semplice. Qualsiasi pacchetto è benvenuto.

PD: tutti i codici falliscono se aggiungo una colonna solitaria. set.seed (1)

mydata <- data.frame() 
mydata <- rbind(mydata,c(1,round(runif(21),3))) 
mydata <- rbind(mydata,c(2,round(runif(21),3))) 
mydata <- rbind(mydata,c(3,round(runif(21),3))) 
colnames(mydata) <- c(c("id", paste0(rep(c('Mary', 'Bob', 'Dylan', 'Tom', 'Jane', 'Sam', 'Tony', 'Luke', 'John', "Pam"), each=2), 1:2)),"Lola") 

so che potrei filtrare manualmente, ma sarebbe meglio se il risultato è la differenza (*) di ogni coppia e lasciare solo la colonna solitaria. (In caso di differenze di gruppi di due dimensioni)

L'opzione migliore non sarebbe rimuovere manualmente la prima colonna ma dividere tutte le colonne in colonne singole e multiple.

+0

'potrebbe avere gruppi di 5' - in modo che quando i nomi hanno gruppi di X, saranno tutti i nomi hanno una quantità X di colonne? o avresti dei casi in cui solo Bob ha 5 colonne e Mary ha solo 2 colonne? – zx8754

+0

Ma a seconda dell'operazione potrebbe essere possibile o meno. Ad esempio, è possibile calcolare la media di gruppi di dimensioni diverse, ma alcune altre operazioni non possono essere definite. – skan

+0

Puoi mantenerlo semplice se vuoi. Tranne ciò che ho detto: "Potrebbe anche accadere che alcune colonne non abbiano la doppia colonna". Se una colonna è sola (dimensione del gruppo = 1) lasciala così com'è. Diciamo che tutti gli altri hanno le stesse dimensioni. Quindi, abbiamo size = 1 o size = N, con N fisso. – skan

risposta

3

Come sull'utilizzo di base R:

cn <- unique(gsub("\\d", "", colnames(mydata)))[-1] 
sapply(cn, function(x) mydata[[paste0(x, 1)]] - mydata[[paste0(x, 2)]]) 

è possibile utilizzare questo approccio per qualsiasi numero arbitrario di gruppi. Per esempio, questo sarebbe tornato le somme riga lungo i nomi con il suffisso 1 o 2 .:

sapply(cn, function(x) rowSums(mydata[, paste0(x, 1:2)])) 

Questo approccio pasta potrebbe essere sostituito da espressioni regolari per le applicazioni più generali.

+0

C'è qualche vantaggio di gsub su sub in questo problema? – skan

+0

In questo caso, non riesco a capire perché ci sarebbe una differenza. – Raad

+0

Come modificheresti la tua soluzione per aggiungere una "d" come prefisso di ogni nuova colonna, come dMay, dBob, dDylan ...? Ho provato ad assegnare paste0 ("d", cn) a colnames() ma forse c'è un modo più diretto all'interno del tuo sapply. – skan

1

Questa soluzione gestisce un numero arbitrario di gemelli.

## return data frame 
twin.vars <- function(prefix, df) { 
    df[grep(paste0(prefix, '[0-9]+$'), names(df))] 
} 

pfx <- unique(sub('[0-9]*$', '', names(mydata[-1]))) 

tmp <- lapply(pfx, function(x) Reduce(`-`, twin.vars(x, mydata))) 
cbind(id=mydata$id, as.data.frame(setNames(tmp, pfx))) 
+0

Potresti spiegare il vantaggio della tua soluzione contro NBATrends? – skan

+0

Il vantaggio è che la mia soluzione funziona anche quando si hanno colonne non abbinate o più di due colonne con lo stesso prefisso. –

2

Si può fare qualcosa di simile,

sapply(unique(sub('\\d', '', names(mydata[,-1]))), 
     function(i) Reduce('-', mydata[,-1][,grepl(i, sub('\\d', '', names(mydata[,-1])))])) 
#  Mary Bob Dylan Tom Jane Sam Tony Luke John Pam 
#[1,] -0.106 -0.335 -0.696 0.284 0.567 0.029 0.303 0.272 -0.274 -0.397 
#[2,] 0.723 0.526 -0.119 -0.369 0.530 -0.118 0.308 0.159 0.686 0.313 
#[3,] 0.174 0.230 -0.259 -0.454 0.039 -0.383 0.193 -0.028 -0.203 0.255 

Come per il tuo commento, possiamo facilmente ordinare le colonne e quindi applicare la formula di cui sopra,

sorted.names <- names(mydata)[order(nchar(names(mydata)), names(mydata))] 
mydata <- mydata[,sorted.names] 
+0

Sembra più compatto della soluzione NBATrends ma se alcune posizioni delle colonne sono invertite, restituisce il risultato negativo. prova con mydata <- cbind (mydata [-2], mydata [2]) – skan

1

OK, ho scelta soluzione @NBATrends perché funziona bene quasi sempre e lui è stato il primo.

In ogni caso, aggiungo il mio piccolo contributo, nel caso in cui qualcuno è interessato:

runs <- rle(sort(sub('\\d$', '', names(mydata)))) 

sapply(runs[[2]][runs[[1]]>1], function(x) mydata[[paste0(x, 1)]] - mydata[[paste0(x, 2)]]) 

L'unico "problema" è che cambia l'ordine finale, ma non c'è bisogno di rimuovere manualmente colonne isolate e funziona anche per le colonne disordinate.

Sono perplesso perché nessuno ha postato una soluzione con dplyr o data.table :)

Problemi correlati