2013-02-25 32 views
11

Ho un dataframe con una sequenza di numeri simile al di sotto:trovare e sostituire sequenza numerica in r

data <- c(1,1,1,0,0,1,1,2,2,2,0,0,0,2,1,1,0,1,0,2) 

cosa ho bisogno è qualcosa di individuare tutte le istanze di 1, 2 o 3 ripetizioni di 0 dove la procedendo e seguendo numeri sono identici - cioè sia 1 o entrambi 2 (per esempio 1,0,1 o 2,0,0,2 ma NON 2,0,1).

Quindi ho bisogno di riempire gli zeri solo con il valore circostante.

sono riuscito a individuare e contare zeri consecutivi

consec <- (!data) * unlist(lapply(rle(data)$lengths, seq_len)) 

poi ho trovato la riga in cui questi zeri consecutivi iniziano con:

consec <- as.matrix(consec) 
first_na <- which(consec==1,arr.ind=TRUE) 

Ma stumped con il processo di sostituzione

Gradirei davvero il vostro aiuto con questo!

Carl

risposta

2

Poiché sembra esserci molto interesse nella risposta a questa domanda, ho pensato di scrivere un metodo di espressioni regolari alternative per i posteri.

Utilizzando la funzione 'gregexpr', è possibile cercare i modelli e utilizzare le corrispondenze di posizione risultanti e le lunghezze di corrispondenza per richiamare i valori da modificare nel vettore originale. Il vantaggio di usare le espressioni regolari è che possiamo essere espliciti riguardo esattamente quali pattern vogliamo abbinare e, di conseguenza, non avremo casi di esclusione di cui preoccuparci.

Nota: il seguente esempio funziona come scritto, poiché assumiamo valori a una cifra. Potremmo facilmente adattarlo per altri pattern, ma possiamo prendere una piccola scorciatoia con caratteri singoli. Se volessimo farlo con possibili valori a più cifre, vorremmo aggiungere un carattere di separazione come parte della prima funzione di concatenazione ('incolla').


Il Codice

str.values <- paste(data, collapse="") # String representation of vector 
str.matches <- gregexpr("1[0]{1,3}1", str.values) # Pattern 101/1001/10001 
data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 1 # Replace zeros with ones 
str.matches <- gregexpr("2[0]{1,3}2", str.values) # Pattern 202/2002/20002 
data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 2 # Replace zeros with twos 

Fase 1: fare una singola stringa di tutti i valori dei dati.

str.values <- paste(data, collapse="") 
# "11100112220002110102" 

Questo riduce i dati in una stringa lunga, quindi è possibile utilizzare un'espressione regolare su di esso.

Passaggio 2: applicare un'espressione regolare per trovare le posizioni e le lunghezze di qualsiasi corrispondenza all'interno della stringa.

str.matches <- gregexpr("1[0]{1,3}1", str.values) 
# [[1]] 
# [1] 3 16 
# attr(,"match.length") 
# [1] 4 3 
# attr(,"useBytes") 
# [1] TRUE 

In questo caso, stiamo usando un'espressione regolare per cercare il primo modello, da uno a tre zeri ([0]{2,}) con quelli su entrambi i lati (1[0]{1,3}1). Dovremo abbinare l'intero schema, al fine di evitare di dover verificare se vi sono abbinamenti o twos finali. Sottraggeremo quelle estremità nel passaggio successivo.

Passaggio 3: Scrivere quelli in tutte le posizioni corrispondenti nel vettore originale.

data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 1 
# 1 1 1 1 1 1 1 2 2 2 0 0 0 2 1 1 1 1 0 2 

Stiamo facendo alcuni passaggi tutti insieme. Innanzitutto, stiamo creando un elenco di sequenze numeriche dai numeri che corrispondono all'espressione regolare. In questo caso, ci sono due partite, che iniziano agli indici 3 e 16 e sono lunghe 4 e 3 voci, rispettivamente. Ciò significa che i nostri zeri si trovano negli indici (3 + 1) :(3-2 + 4), o 4: 5 e in (16 + 1) :(16-2 + 3) o 17:17. Concateniamo ("incolla") queste sequenze usando di nuovo l'opzione "Collapse", nel caso ci siano più corrispondenze. Quindi, usiamo una seconda concatenazione per inserire le sequenze all'interno di una combinazione (c()). Usando le funzioni 'eval' e 'parse', trasformiamo questo testo in codice e lo passiamo come valori di indice alla matrice [dati]. Scriviamo tutti quelli in quei luoghi.

Punto x: ripetere per ciascun motivo. In questo caso, dobbiamo eseguire una seconda ricerca e trovare da uno a tre zeri con due su ciascun lato e quindi eseguire la stessa istruzione del passaggio 3, ma assegnando due, anziché uno.

str.matches <- gregexpr("2[0]{1,3}2", str.values) 
# [[1]] 
# [1] 10 
# attr(,"match.length") 
# [1] 5 
# attr(,"useBytes") 
# [1] TRUE 

data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 2 
# 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2 

Aggiornamento: ho capito che il problema originale, ha detto per abbinare uno a tre zeri di fila, piuttosto che il "due o più" che ho scritto nel codice originale. Ho aggiornato le espressioni regolari e la spiegazione, anche se il codice rimane lo stesso.

+0

quindi, alla fine ho optato per questo, mi è piaciuta la possibilità di avere il controllo sugli schemi, ma ho apprezzato tutti i suggerimenti. Terrò comunque nota di questi diversi metodi per diverse circostanze. Lo apprezzo davvero. –

1

Ci può essere una soluzione senza un ciclo for, ma si può provare questo:

tmp <- rle(data) 
val <- tmp$values 
for (i in 2:(length(val)-1)) { 
    if (val[i]==0 & val[i-1]==val[i+1]) val[i] <- val[i-1] 
} 
tmp$values <- val 
inverse.rle(tmp) 

che dà:

[1] 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2 
+0

Penso che si possa "stringere" questo facendo 'rle (as.logical (data))' che riempirà il tuo 'tmp' con lunghezze di 'zero' e 'non-zero', dopo di che puoi sostituire ogni corsa di zeri con qualcosa come 'val [i-1] * (val [i-1] == val [i + 1])'. (Nel caso in cui l'ho svitato, l'intento è di sostituire gli zeri con 'val [i-1]' ma solo quando il controllo di uguaglianza è VERO) - pero 'dovrebbe essere piuttosto accurato :-(un-rle -ed –

+0

@CarlWitthoft Hmm se usi 'rle (as.logical (data))' non puoi usare i tuoi 'rle $ values' per verificare più l'uguaglianza dei valori? – juba

+0

Nevvamind - La risposta di Andrie fa quello che stava pensando a un modo ancora più compatto (e affidabile). –

14

Ecco una soluzione loopless utilizzando rle() e inverse.rle().

data <- c(1,1,1,0,0,1,1,2,2,2,0,0,0,2,1,1,0,1,0,2) 

local({ 
    r <- rle(data) 
    x <- r$values 
    x0 <- which(x==0) # index positions of zeroes 
    xt <- x[x0-1]==x[x0+1] # zeroes surrounded by same value 
    r$values[x0[xt]] <- x[x0[xt]-1] # substitute with surrounding value 
    inverse.rle(r) 
}) 

[1] 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2 

PS. Io uso local() come un semplice meccanismo per non rovinare lo spazio di lavoro con un sacco di nuovi oggetti temporanei. È possibile creare un function invece di utilizzare local - Ho appena trovato che uso oggi lo local per questo tipo di attività.


PPS. Dovrai modificare questo codice per escludere zero iniziali o finali nei tuoi dati originali.

+0

Questo è esattamente il modo in cui la funzione 'rle' dovrebbe essere utilizzata, e sono contento che tu abbia scritto così chiaramente. Anche la funzione "locale" è un buon consiglio. Faccio all'incirca la stessa cosa avvolgendo un sacco di codice nelle mie funzioni (utile anche per il debug), e penso che sia una buona cosa che le persone imparino in generale. Bel lavoro, Andrie. – Dinre

Problemi correlati