Para a limpeza do banco de dados, eu tenho um vetor de pratos, digamos, e quero remover todas as variantes do prato "base", mantendo apenas o prato base. Por exemplo, se eu tiver ...
dishes <- c("DAL BHAT", "DAL BHAT-(SPICY)", "DAL BHAT WITH EXTRA RICE",
"HAMBURGER", "HAMBURGER-BIG", "HAMBURGER2", "PIZZA",
"PIZZA (PROSCIUTO)", "PIZZA_BOLOGNESE")
... Desejo remover todas as entradas que já possuem uma versão correspondente mais curta no vetor. O vetor resultante, portanto, incluiria apenas: "DAL BHAT", "HAMBURGER," PIZZA ".
Usando um aninhado for
Fazer um loop e verificar tudo contra todos os outros funcionará para este exemplo, mas demorará muito para o grande conjunto de dados disponível e, além disso, será uma codificação feia que eu diria.
Pode-se supor que todas as entradas estão em maiúsculas e que o vetor já está classificado. Não se pode presumir que o primeiro item da próxima base seja sempre menor que a entrada anterior.
Alguma sugestão sobre como resolver isso de maneira eficiente?
PERGUNTA DE BÔNUS: Idealmente, eu só quero remover itens dovetor inicial se eles tiverem pelo menos 3 caracteres a mais que sua contraparte mais curta. No caso acima, isso significaria que "HAMBURGER2" também seria retido no vetor resultante.
Respostas:
5 para resposta № 1Aqui está a abordagem que eu tomaria com isso. Eu criei uma função com algumas das condições que eu precisava considerar e usá-la na entrada. Eu adicionei comentários para explicar o que está acontecendo na função.
A função tem 4 argumentos:
invec
: O vetor de caractere de entrada.thresh
: Quantos caracteres podemos usar para determinar o prato "base". Padrão = 5.minlen
: Sua pergunta "BONUS". Padrão = 3.strict
: Lógico. Se houver pratos de base comnchar
mais curto que o seuthresh
, você quer diminuir o thresh ou ser rigoroso sobre o que você está olhando para a base?FALSE
. Veja o último exemplo de comostrict
pode funcionar.
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)
}
Seus dados de amostra:
dishes <- c("DAL BHAT", "DAL BHAT-(SPICY)", "DAL BHAT WITH EXTRA RICE",
"HAMBURGER", "HAMBURGER-BIG", "HAMBURGER2", "PIZZA",
"PIZZA (PROSCIUTO)", "PIZZA_BOLOGNESE")
Aqui está o resultado:
myfun(dishes, minlen = 0)
# [1] "DAL BHAT" "HAMBURGER" "PIZZA"
myfun(dishes)
# [1] "DAL BHAT" "HAMBURGER" "HAMBURGER2" "PIZZA"
Veja mais alguns dados de amostra. Note que em "dishes2" os dados não são mais classificados e há um novo item "DAL", e em "dishes3" você também tem pratos minúsculos.
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!!")
Aqui está a função desses vetores:
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 para resposta № 2
O OP solicitou para remover todas as entradas que já possuem uma versão correspondente mais curta no vetor. Além disso, o OP deseja remover itens do vetor inicial se eles tiverem pelo menos 3 caracteres a mais que sua contraparte mais curta.
o força bruta O método tentaria comparar todas as entradas entre si para descobrir se uma string faz parte da outra string. Isso exigiria n x (n-1) comparações.
A abordagem abaixo tenta reduzir o número de comparações de strings, verificando o número de caracteres de antemão. Isso reduzirá pelo menos o número de chamadas para 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"
Editar
Devido ao junção não equi esta abordagem funciona também sem reordenando DT
antecipadamente:
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"
Isso tem o benefício de que a ordem original de dishes
é mantido (com as "duplicatas" removidas).
Dados
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")
Observe que dois itens foram adicionados para cobrir casos de teste adicionais.
2 para resposta № 3
Uma possível solução usando sapply
com grepl
e colSums
:
dishes[colSums(sapply(dishes, function(x) grepl(x, setdiff(dishes, x)))) > 0]
que dá:
[1] "DAL BHAT" "HAMBURGER" "PIZZA"
O que isto faz:
sapply(dishes, function(x) grepl(x, setdiff(dishes, x)))
compara cada elemento dedishes
com os outros elementos e olha comgrepl
se o elemento específico é uma parte dos outros elementos.Isso retorna uma matriz lógica, onde um
TRUE
valor indica se um nome do prato faz parte de outro nome do prato: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
Tomando a soma da coluna com
colSums
, você obtém um vetor numérico de quantos outros pratos o nome de cada prato contém: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
Apenas o mais curto nomes de prato tem uma contagem maior que zero. Conseqüentemente, comparar o vetor numérico com zero retorna um vetor lógico do qual pratos manter.
- Como alternativa ao uso
> 0
, você também pode usar um sinal de negação duplo (!!
) em frente decolSums
. Isso também seleciona os elementos que possuem uma contagem diferente de zero:dishes[!!colSums(sapply(dishes, function(x) grepl(x, setdiff(dishes, x))))]
.
Se você quiser levar em conta uma diferença máxima no tamanho do personagem, então você pode usar agrepl
ao invés de grepl
onde você pode especificar a diferença máxima de edição em caracteres com o max.distance
-parâmetro:
dishes[colSums(sapply(dishes, function(x) agrepl(x, setdiff(dishes, x), max.distance = 3))) > 0]
que dá:
[1] "DAL BHAT" "HAMBURGER" "HAMBURGER2" "PIZZA"
1 para resposta № 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"