2013-02-25 13 views
11

Mam dataframe z sekwencją liczb podobny do poniżej:znaleźć i zastąpić sekwencję liczbową w R

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

Co potrzebne jest coś, aby znaleźć wszystkie wystąpienia 1, 2 lub 3 powtórzeń z 0, gdzie na postępowanie i następujące numery są identyczne - tzn. zarówno 1, jak i 2 (np. 1,0,1 lub 2,0,0,2, ale NIE 2,0,1).

Następnie muszę wypełnić zer tylko otaczającą wartością.

udało mi się zlokalizować i policzyć kolejnych zer

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

potem znalazłem wiersz, gdzie te kolejnych zer początku:

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

Ale jestem zakłopotany z procesem zastępowania

Byłbym wdzięczny za pomoc w tej sprawie!

Carl

Odpowiedz

2

Ponieważ wydaje się, że jest duże zainteresowanie odpowiedzią na to pytanie, pomyślałem, że wypiszę alternatywną metodę wyrażeń regularnych dla potomności.

Za pomocą funkcji "gregexpr" można wyszukiwać wzorce i używać wynikowych dopasowań lokalizacji i długości pasujących, aby wywołać wartości, które mają zostać zmienione w oryginalnym wektorze. Zaletą korzystania z wyrażeń regularnych jest to, że możemy dokładnie określić, które wzory chcemy dopasować, w związku z czym nie będziemy mieli żadnych przypadków wykluczenia, o które moglibyśmy się martwić.

Uwaga: Poniższy przykład działa tak, jak został napisany, ponieważ przyjmujemy wartości jednocyfrowe. Możemy łatwo dostosować go do innych wzorów, ale możemy zrobić mały skrót z pojedynczymi znakami. Jeśli chcemy to zrobić z możliwymi wielocyfrowymi wartościami, chcielibyśmy dodać znak separacji jako część pierwszej funkcji łączenia ("wklejania").


Kodeks

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 

Krok 1: Złóż pojedynczy ciąg wszystkich wartości danych.

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

to wali w dół dane w jeden długi ciąg, dzięki czemu możemy użyć wyrażenia regularnego na nim.

Krok 2: Zastosuj wyrażenie regularne, aby znaleźć lokalizacje i długości wszystkich dopasowań w ciągu znaków.

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

W tym przypadku używamy wyrażenia regularnego szukać pierwszego wzoru, od jednego do trzech zer ([0]{2,}) z nich po obu stronach (1[0]{1,3}1). Będziemy musieli dopasować cały wzór, aby zapobiec konieczności sprawdzania pasujących lub dwójek na końcach. Odejmiemy te końce w następnym kroku.

Krok 3: Napisz je we wszystkich zgodnych lokalizacjach w oryginalnym wektorze.

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 

Robimy tu kilka kroków naraz. Najpierw tworzymy listę sekwencji liczb z liczb pasujących do wyrażenia regularnego. W tym przypadku są dwa dopasowania, które rozpoczynają się od indeksów 3 i 16 i mają odpowiednio długość 4 i 3 elementy. Oznacza to, że nasze zera znajdują się w indeksach (3 + 1) :(3-2 + 4) lub 4: 5 i przy (16 + 1) :(16-2 + 3) lub 17:17. Łączymy ("wklejamy") te sekwencje, ponownie korzystając z opcji "zwiń", na wypadek gdyby było kilka dopasowań. Następnie używamy drugiej konkatenacji, aby umieścić sekwencje wewnątrz funkcji kombajnu (c()). Korzystając z funkcji "eval" i "parsuj", przekształcamy ten tekst w kod i przekazujemy go jako wartości indeksu do tablicy [data]. Piszemy wszystkie w tych lokalizacjach.

Krok x: Powtórz dla każdego wzoru. W takim przypadku musimy wykonać drugie wyszukiwanie i odszukać od jednego do trzech zer po dwóch stronach po obu stronach, a następnie uruchomić to samo zdanie, co w kroku 3, ale przydzielając dwa, zamiast jednego.

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 

Aktualizacja: ja sobie sprawę, że oryginalny problem powiedział, aby dopasować jeden do trzech zer w rzędzie, raczej niż „dwa lub więcej”, który napisałem do oryginalnego kodu. Zaktualizowałem wyrażenia regularne i wyjaśnienia, chociaż kod pozostaje ten sam.

+0

Tak więc, faktycznie poszłam na tę w końcu, uwielbiałam możliwość kontrolowania wzorów - ale doceniam wszystkie sugestie. Będę jednak pamiętać o tych różnych metodach w różnych okolicznościach. Naprawdę to doceniam. –

1

Nie może być rozwiązanie bez for pętli, ale można spróbować to:

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) 

Co daje:

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

Myślę, że możesz "zacieśnić" to, wykonując 'rle (as.logical (data)), które wypełni twoje' tmp' długością "zero" i "nie-zera", po czym możesz zastąpić każdy zera zer z czymś w rodzaju 'val [i-1] * (val [i-1] == val [i + 1])'. (W przypadku, gdy to skręciłem, zamiarem jest zastąpienie zer przez "val [i-1]", ale tylko wtedy, gdy kontrola równości ma wartość PRAWDA) - choćby "musiało to być raczej ostrożnie :-(un-rle -ed. –

+0

@CarlWitthoft Hmm jeśli użyjesz 'rle (as.logical (data))' nie możesz użyć swoich 'rle $ values' aby przetestować równość wartości już więcej – juba

+0

Nevvamind - odpowiedź Andrie robi to, co ja myślał jeszcze bardziej zwięźle (i niezawodnie). –

14

Oto rozwiązanie loopless użyciu rle() i 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. Używam local() jako prostego mechanizmu, aby nie zwijać obszaru roboczego z mnóstwem nowych tymczasowych obiektów. Można utworzyć function zamiast używać local - Po prostu uważam, że obecnie używam local dla tego typu zadań.


PPS. Będziesz musiał zmodyfikować ten kod, aby wykluczyć początkowe lub końcowe zera w oryginalnych danych.

+0

Dokładnie w ten sposób należy użyć funkcji "rle" i cieszę się, że napisałeś ją tak wyraźnie. Funkcja "lokalna" również jest dobrą wskazówką. Robię w przybliżeniu to samo, owijając wiele funkcji w moim kodzie (również dobrym do debugowania) i myślę, że ogólnie rzecz biorąc, ludzie powinni się uczyć. Dobra robota, Andrie. – Dinre