Do czyszczenia bazy danych mam wektor, powiedzmy, potraw i chcę usunąć wszystkie warianty naczynia „podstawowego”, pozostawiając tylko naczynie podstawowe. Na przykład, jeśli mam ...
dishes <- c("DAL BHAT", "DAL BHAT-(SPICY)", "DAL BHAT WITH EXTRA RICE",
"HAMBURGER", "HAMBURGER-BIG", "HAMBURGER2", "PIZZA",
"PIZZA (PROSCIUTO)", "PIZZA_BOLOGNESE")
... Chcę usunąć wszystkie wpisy, które mają już krótszą pasującą wersję w wektorze. Wynikowy wektor zawierałby zatem tylko: „DAL BHAT”, „HAMBURGER,„ PIZZA ”.
Korzystanie z zagnieżdżonego for
pętla i sprawdzanie wszystkiego ze wszystkimi innymi będzie działać w tym przykładzie, ale zajmie dużo czasu dla dużego zbioru danych pod ręką, a ponadto jest brzydkim kodowaniem, powiedziałbym.
Można założyć, że wszystkie wpisy są zapisane wielkimi literami i że wektor jest już posortowany. Nie można zakładać, że pierwsza pozycja następnej potrawy bazowej jest zawsze krótsza niż poprzednia pozycja.
Jakieś sugestie, jak rozwiązać ten problem w skuteczny sposób?
BONUSOWE PYTANIE: Idealnie, chcę usunąć tylko elementy zwektor początkowy, jeśli są o co najmniej 3 znaki dłuższe niż ich krótszy odpowiednik. W powyższym przypadku oznaczałoby to, że „HAMBURGER2” również zostałby zachowany w powstałym wektorze.
Odpowiedzi:
5 dla odpowiedzi № 1Oto podejście, które bym przyjął.Stworzyłbym funkcję z niektórymi warunkami, które musiałbym wziąć pod uwagę, i użyłbym tego na wejściu. Dodałem komentarze, aby wyjaśnić, co się dzieje w funkcji.
Funkcja ma 4 argumenty:
invec
: Wektor znaków wejściowych.thresh
: Ilu znaków możemy użyć do określenia „podstawowej” potrawy. Domyślnie = 5.minlen
: Twoje pytanie „BONUS”. Domyślnie = 3.strict
: Logiczne. Jeśli są naczynia podstawowe znchar
krótszy niż twójthresh
, czy chcesz obniżyć próg, czy też dokładnie określić, czego szukasz w podstawie? Default =FALSE
. Zobacz ostatni przykład, aby dowiedzieć się, jak to zrobićstrict
powinno działać.
myfun <- function(invec, thresh = 5, minlen = 3, strict = FALSE) {
# Bookkeeping -- sort, unique, all upper case
invec <- sort(unique(toupper(invec)))
# More bookkeeping -- min should not be longer
# than min base dish unless strict = TRUE
thresh <- if (isTRUE(strict)) thresh else min(min(nchar(invec)), thresh)
# Use `thresh` to get the `stubs``
stubs <- invec[!duplicated(substr(invec, 1, thresh))]
# loop through the stubs and do two things:
# - Match the dish with the stub
# - Return the base dish and any dishes within the minlen
unlist(
lapply(stubs, function(x) {
temp <- grep(x, invec, value = TRUE, fixed = TRUE)
temp[temp == x | nchar(temp) <= nchar(x) + minlen]
}),
use.names = FALSE)
}
Twoje przykładowe dane:
dishes <- c("DAL BHAT", "DAL BHAT-(SPICY)", "DAL BHAT WITH EXTRA RICE",
"HAMBURGER", "HAMBURGER-BIG", "HAMBURGER2", "PIZZA",
"PIZZA (PROSCIUTO)", "PIZZA_BOLOGNESE")
Oto wynik:
myfun(dishes, minlen = 0)
# [1] "DAL BHAT" "HAMBURGER" "PIZZA"
myfun(dishes)
# [1] "DAL BHAT" "HAMBURGER" "HAMBURGER2" "PIZZA"
Oto więcej przykładowych danych. Zwróć uwagę, że w „naczynia2” dane nie są już sortowane i jest „nowy element” „DAL”, a w „potrawach3” są także potrawy pisane małymi literami.
dishes2 <- c("DAL BHAT", "DAL BHAT-(SPICY)", "DAL BHAT WITH EXTRA RICE",
"HAMBURGER", "HAMBURGER-BIG", "HAMBURGER2", "PIZZA",
"PIZZA (PROSCIUTO)", "PIZZA_BOLOGNESE", "DAL")
dishes3 <- c("DAL BHAT", "DAL BHAT-(SPICY)", "DAL BHAT WITH EXTRA RICE",
"HAMBURGER", "HAMBURGER-BIG", "HAMBURGER2", "PIZZA",
"PIZZA (PROSCIUTO)", "PIZZA_BOLOGNESE", "DAL", "pizza!!")
Oto funkcja na tych wektorach:
myfun(dishes2, 4)
# [1] "DAL" "HAMBURGER" "HAMBURGER2" "PIZZA"
myfun(dishes3)
# [1] "DAL" "HAMBURGER" "HAMBURGER2" "PIZZA" "PIZZA!!"
myfun(dishes3, strict = TRUE)
# [1] "DAL" "DAL BHAT" "HAMBURGER" "HAMBURGER2" "PIZZA" "PIZZA!!"
2 dla odpowiedzi nr 2
OP złożył wniosek aby usunąć wszystkie wpisy, które mają już krótszą pasującą wersję w wektorze. Ponadto OP chce usunąć elementy z początkowego wektora, jeśli są one o co najmniej 3 znaki dłuższe niż ich krótszy odpowiednik.
The brutalna siła metoda spróbuje porównać wszystkie wpisy ze sobą, aby znaleźć, czy jeden ciąg jest częścią drugiego ciągu. To wymagałoby n x (n-1) porównania.
Poniższe podejście próbuje zmniejszyć liczbę porównań ciągów, sprawdzając wcześniej liczbę znaków. Spowoduje to co najmniej połowę liczby połączeń do grepl()
.
library(data.table)
# prepare data
DT <- data.table(dish = dishes)[, len := nchar(dish)][order(len)]
DT
dish len 1: NAN 3 2: PIZZA 5 3: DAL BHAT 8 4: HAMBURGER 9 5: HAMBURGER2 10 6: HAMBURGER-BIG 13 7: SLICE OF PIZZA 14 8: PIZZA_BOLOGNESE 15 9: DAL BHAT-(SPICY) 16 10: PIZZA (PROSCIUTO) 17 11: DAL BHAT WITH EXTRA RICE 24
# use non-equi join to find row numbers of "duplicate" entries
tmp <- DT[.(len + 3L, dish), on = .(len > V1), nomatch = 0L, allow = TRUE,
by = .EACHI, .I[grepl(V2, dish)]]
tmp
len V1 1: 8 7 2: 8 8 3: 8 10 4: 11 9 5: 11 11 6: 12 6
# anti-join to remove "duplicates"
DT[!tmp$V1, dish]
[1] "NAN" "PIZZA" "DAL BHAT" "HAMBURGER" "HAMBURGER2"
Edytować
Z powodu non-equi join to podejście działa również bez zmiana kolejności DT
uprzednio:
delta_len <- 3L
DT <- data.table(dish = dishes)[, len := nchar(dish)]
DT[!DT[.(len + delta_len, dish), on = .(len > V1), nomatch = 0L, allow = TRUE,
by = .EACHI, .I[grepl(V2, dish)]]$V1, dish]
[1] "DAL BHAT" "HAMBURGER" "HAMBURGER2" "PIZZA" "NAN"
Ma to tę zaletę, że pierwotna kolejność dishes
jest utrzymywany (z usuniętymi „duplikatami”).
Dane
dishes <- c("DAL BHAT", "DAL BHAT-(SPICY)", "DAL BHAT WITH EXTRA RICE",
"HAMBURGER", "HAMBURGER-BIG", "HAMBURGER2", "PIZZA",
"PIZZA (PROSCIUTO)", "PIZZA_BOLOGNESE", "NAN", "SLICE OF PIZZA")
Zwróć uwagę, że dodano dwa elementy, aby uwzględnić dodatkowe przypadki testowe.
2 dla odpowiedzi nr 3
Możliwe rozwiązanie przy użyciu sapply
z grepl
i colSums
:
dishes[colSums(sapply(dishes, function(x) grepl(x, setdiff(dishes, x)))) > 0]
co daje:
[1] "DAL BHAT" "HAMBURGER" "PIZZA"
Co to robi:
sapply(dishes, function(x) grepl(x, setdiff(dishes, x)))
porównuje każdy elementdishes
z innymi elementami i wygląda zgrepl
czy określony element jest częścią innych elementów.Zwraca to macierz logiczną, gdzie a
TRUE
wartość wskazuje, czy plik nazwa dania jest częścią innego nazwa dania:DAL BHAT DAL BHAT-(SPICY) DAL BHAT WITH EXTRA RICE HAMBURGER HAMBURGER-BIG HAMBURGER2 PIZZA PIZZA (PROSCIUTO) PIZZA_BOLOGNESE [1,] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE [2,] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE [3,] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE [4,] FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE [5,] FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE [6,] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE [7,] FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE [8,] FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
Biorąc sumę kolumny z
colSums
, otrzymujesz liczbowy wektor ilu innych naczynia nazwa każdego danie zawiera:DAL BHAT DAL BHAT-(SPICY) DAL BHAT WITH EXTRA RICE HAMBURGER HAMBURGER-BIG HAMBURGER2 PIZZA PIZZA (PROSCIUTO) PIZZA_BOLOGNESE 2 0 0 2 0 0 2 0 0
Tylko krótszy nazwy dań mają liczbę większą niż zero. W konsekwencji porównanie wektora numerycznego z zerem zwraca logiczny wektor, którego naczynia do zachowania.
- Jako alternatywa do używania
> 0
możesz również użyć podwójnego znaku negacji (!!
) przedcolSums
. Spowoduje to również wybranie elementów, których liczba jest różna od zera:dishes[!!colSums(sapply(dishes, function(x) grepl(x, setdiff(dishes, x))))]
.
Jeśli chcesz wziąć pod uwagę maksymalną różnicę w długości znaków, możesz użyć agrepl
zamiast grepl
gdzie można określić maksymalną różnicę edycji w znakach za pomocą max.distance
-parametr:
dishes[colSums(sapply(dishes, function(x) agrepl(x, setdiff(dishes, x), max.distance = 3))) > 0]
co daje:
[1] "DAL BHAT" "HAMBURGER" "HAMBURGER2" "PIZZA"
1 dla odpowiedzi nr 4
unlist(sapply(split(dishes, substr(dishes, 1, 5)), function(x){
N = nchar(x)
x[(N - N[1]) < 3]
}))
# DAL B HAMBU1 HAMBU2 PIZZA
# "DAL BHAT" "HAMBURGER" "HAMBURGER2" "PIZZA"