J'essaie de comparer les valeurs de chaque ligne avec des valeurs valides (liste séparée) et si la valeur de la ligne ne correspond pas à des valeurs valides, alors déclenchez un message d'erreur.
Je suis capable de générer une sortie, ce que je veux . Mais je pense que ce n'est pas du tout une manière efficace de le faire.
Ma tentative-
> dt a_check b_check error 1 20 0 A_CHECK must be one of the following values 1-2-3 & NA 2 2 1 NA & NA 3 1 NA NA & B_CHECK must be one of the following values 0-1 4 NA 1 A_CHECK must be one of the following values 1-2-3 & NA 5 0 15 A_CHECK must be one of the following values 1-2-3 & B_CHECK must be one of the following values 0-1
Ma sortie:
set.seed(1234)
dt <- data.frame(a_check=c(20,2,1,NA,0),
b_check=c(0,1,NA,1,15))
valid_values <- list(a_check= c(1,2,3), b_check= c(0,1))
param_names <- colnames(dt)
error_msg <- list()
error <- list()
for(i in 1:nrow(dt)) {
for(j in 1:length(param_names)) {
if(is.na(match(as.character(unlist(dt[param_names[j]]))[i], as.character(unlist(valid_values[j]))))) {
error_msg[j] <- paste0(toupper(param_names[j]), " must be one of the following values ", paste(unlist(valid_values[j]), collapse = '-'))
} else {
error_msg[j] <- NA
}
}
error[i] <- paste(unlist(error_msg), collapse = " & ")
}
final_error <- unlist(error)
dt$error <- final_error
Remarque - strong> Je veux exactement ce que j'obtiens, mais je ne veux pas de NA & NA ni de NA & . Il est facile de le faire pour 2 variables. Mais, j'ai plus de 500 variables.
4 Réponses :
ajoutez une colonne de vérification à votre df et essayez la fonction % in% ? match peut-être avec ifelse pour TRUE | FALSE résultats ...
J'aime la réponse de @Jav, si vous ajoutez juste un remodelage par-dessus (plus précisément avant), vous pouvez avoir toutes les informations dans seulement deux colonnes, fusionnez (c'est-à-dire joignez-le) avec votre table de recherche d'erreur et remodelez-le plus tard en large
exemple de remodelage:
dt_long <- reshape(data = dt, times = names(dt),
direction = 'long', timevar = "type",
varying = list(names(dt)), idvar = "id", v.names = "values")
Vous ne devriez pas utiliser de boucles for dans R - puisque vous avez mentionné plus de 500 variables. C'est pourquoi je vous ai suggéré de vectoriser puis d'attribuer plus tard les messages d'erreur que vous trouvez utiles. vous répétez simplement la même fonction coller à chaque fois dans la boucle for, donc n'ajoutez aucune information utile là-bas. Fusionnez simplement les messages d'erreur à la fin de votre résultat dataframe => et rappelez-vous: essayez de vervtorize
sûr. Je sais, ce n'est pas efficace et ma question se dit d'elle-même. J'ai essayé différentes manières d'y parvenir. mais, l'ajout d'un message d'erreur pour chaque valeur devient difficile. Il serait utile que vous puissiez fournir un code de travail efficace.
Donne moi une minute
En utilisant data.table , vous pouvez le faire de manière plus vectorisée. Boucle sur les colonnes mais pas sur les lignes:
dt[, error := lapply(param_names, function(x) {
((get(x, dt) %in% get(x, valid_values))) %>%
ifelse(., " ", paste(x, "should have valid values like -", paste(get(x, valid_values), collapse = " ")))
}) %>% Reduce(paste, .)]
> dt
a_check b_check error
1: 20 0 a_check should have valid values like - 1 2 3
2: 2 1
3: 1 NA b_check should have valid values like - 0 1
4: NA 1 a_check should have valid values like - 1 2 3
5: 0 15 a_check should have valid values like - 1 2 3 b_check should have valid values like - 0 1
MODIFIER: Attribuer une réponse à une colonne:
library(magrittr)
dt[, wrong_cols := lapply(param_names, function(x) {
(!(get(x, dt) %in% get(x, valid_values))) %>%
ifelse(., x, "")
}) %>% Reduce(paste, .)]
> dt
a_check b_check wrong_cols
1: 20 0 a_check
2: 2 1
3: 1 NA b_check
4: NA 1 a_check
5: 0 15 a_check b_check
EDIT_2
> dt <- as.data.table(dt)
> dt[, paste0(param_names, "_test") := lapply(param_names, function(x){
get(x, dt) %in% get(x, valid_values)
})]
a_check b_check a_check_test b_check_test
1: 20 0 FALSE TRUE
2: 2 1 TRUE TRUE
3: 1 NA TRUE FALSE
4: NA 1 FALSE TRUE
5: 0 15 FALSE FALSE
Mais, comme j'ai 500 colonnes, il sera difficile de garder une trace de 500 colonnes booléennes pour générer une colonne d'erreur.
@Rushabh Vous n'êtes pas obligé d'avoir des colonnes séparées, vous pouvez les affecter à une avec Réduire . magrittr utilisé juste pour rendre le code plus facile à lire. De même, vous pouvez ajouter doit être l'une des valeurs suivantes 1-2-3 à l'intérieur de ifelse si nécessaire
c'est très propre et utile!
J'ai essayé d'ajouter des valeurs valides 1-2-3 à l'intérieur de ifelse , mais cela imprime une par une. J'écris quelque chose comme ceci dt [ error: = lapply (param_names, function (x) {(! (Get (x, dt)% in% get (x, valid_value)))%>% ifelse (. , paste0 (x, "devrait avoir des valeurs valides comme -", get (x, valid_value)), "")})%>% Réduire (coller,.)]
@Rushabh c'est parce que get (x, valid_value) est un vecteur, vous pouvez le réduire en simple avec paste (x, collapse = "") . Voir modifier. Mais pour être honnête, il peut être nettoyé encore plus. Pour le moment, il y a trop d'appels get and paste ...
Wow, cela semble être plus propre. Je suis débutant et apprends. Alors, ne vous inquiétez pas pour les questions de base.
library(purrr)
library(stringr)
compose_err_msg <- function(col)
paste(toupper(col),
"must be one of the following values",
paste(valid_values[[col]], collapse = "-"))
dt$error <-
dt %>%
imap(~ ifelse(
.x %in% valid_values[[.y]],
list(character(0)),
list(compose_err_msg(.y))
)) %>%
transpose() %>%
map(lift(str_c, sep = " & ")) %>%
map_chr(~ if (identical(., character(0))) "" else .)
# a_check b_check error
# 1 20 0 A_CHECK must be one of the following values 1-2-3
# 2 2 1
# 3 1 NA B_CHECK must be one of the following values 0-1
# 4 NA 1 A_CHECK must be one of the following values 1-2-3
# 5 0 15 A_CHECK must be one of the following values 1-2-3 & B_CHECK must be one of the following values 0-1
Note that I don't claim this is is a more efficient or simpler way to do it. There is obviously a lot going on here. The key is imap() that loops over columns (the .x variable) and their names at the same time (.y). The not so important part is using stringr::str_c instead of paste to answer the constraint of no "NA & NA". This adds extra complexity with the need to use character(0) and ultimately replace it with "".
Cela fonctionne également. C'est un peu plus concis / efficace. Je peux vérifier avec microbenchmark plus tard, mais il semble que votre problème soit déjà résolu.
dt$error <- apply(dt_errors, 1 , paste, collapse = " & ")
dt$error <- gsub("( & )\\1+", "\\1", dt$error)
dt$error <- gsub("^ & | & $", "", dt$error)
EDIT: en fait, vous devrez peut-être ajuster le modèle de regex s'il y a plus de deux variables pour supprimer les & supplémentaires. Sinon, il devrait bien évoluer.
L'ajout d'une autre instruction gsub devrait faire l'affaire (en théorie).
dt <- data.frame(a_check=c(20,2,1,NA,0),
b_check=c(0,1,NA,1,15))
valid_values <- list(a_check= c(1,2,3), b_check= c(0,1))
dt_errors <- sapply(1:ncol(dt), function(x) ifelse(!dt[[x]] %in% valid_values[[x]],
paste0(toupper(names(dt)[x]),
" must be one of the following values: ",
paste(valid_values[[x]], collapse = ", ")),
""))
dt$error <- apply(dt_errors, 1 , paste, collapse = " & ")
dt$error <- trimws(gsub("^ &|& $", "", dt$error))
dt
a_check b_check error
1 20 0 A_CHECK must be one of the following values: 1, 2, 3
2 2 1
3 1 NA B_CHECK must be one of the following values: 0, 1
4 NA 1 A_CHECK must be one of the following values: 1, 2, 3
5 0 15 A_CHECK must be one of the following values: 1, 2, 3 & B_CHECK must be one of the following values: 0, 1