Je veux randomiser / mélanger un vecteur. Certains des éléments vectoriels sont identiques. Après mélange, les éléments identiques doivent avoir une distance minimale de trois (c'est-à-dire que deux autres éléments doivent être entre des éléments identiques).
Prenons l'exemple de vecteur suivant dans R:
set.seed(53135) sample(x) # sample() function puts same elements too close # [1] "B" "A" "E" "D" "C" "C" "E" "A" "B" "C" "D" "E" "A" "D" "B"
Si je mélange mon vecteur à l'aide de la fonction exemple, certains des éléments identiques peuvent être trop proches les uns des autres. Par exemple, si j'utilise le code R suivant, l'élément "C" apparaît directement l'un après l'autre aux positions 5 et 6:
x <- rep(LETTERS[1:5], 3) # Create example vector x # [1] "A" "B" "C" "D" "E" "A" "B" "C" "D" "E" "A" "B" "C" "D" "E"
Comment puis-je m'assurer que des éléments identiques ont une distance minimale de trois?
3 Réponses :
Donc, fondamentalement, nous devons échantillonner conditionnellement un élément du vecteur x qui n'a pas été sélectionné dans les min.dist-1
. En utilisant la réduction de Purrr, nous pouvons y parvenir:
shuffler <- function(x, min.dist=2){ while(any(unlist(lapply(unique(tl<-tail(l<-shuffle(x, min.dist=min.dist), 2*min.dist)), function(x) diff(which(tl==x))<=min.dist)))){} l }
shuffler <- function(x, min.dist=2){ while(!length(unique(print(tail(l<-shuffle(x, min.dist=min.dist), min.dist+1))))==min.dist+1){} l } table(print(shuffler(rep(LETTERS[1:5], 3),2))) [1] "A" "B" "C" "E" "B" "C" "D" "A" "C" "D" "A" "E" "B" "D" "E" A B C D E 3 3 3 3 3 table(print(shuffler(rep(LETTERS[1:5], 3),2))) [1] "D" "C" "C" [1] "C" "C" "E" [1] "C" "A" "C" [1] "D" "B" "D" [1] "B" "E" "D" [1] "C" "A" "E" "D" "A" "B" "C" "E" "A" "B" "D" "C" "B" "E" "D" A B C D E 3 3 3 3 3
Emballé dans une fonction
shuffle <- function(x, min.dist=2){ stopifnot(min.dist < length(unique(x))) reduce(integer(length(x)-1), function(.x, ...){ # whether the value is in the tail of the aggregated vector in.tail <- x %in% tail(.x, min.dist) # whether a value still hasn't reached the max frequency freq.got <- x %in% names(t<-table(x[x%in%.x]) > table(.x))[t] # whether a value isn't in the aggregated vector yet <- !x %in% .x # the if is there basically to account for the cases when we don't have enough vars to space out the vectors c(.x, if(any((!in.tail & freq.got) | yet )) sample(x[(!in.tail & freq.got) | yet ], 1) else x[which(freq.got)[1]] ) }, .init=sample(x,1)) }
shuffle <- function(x, min.dist=2){ stopifnot(min.dist < length(unique(x))) reduce(integer(length(x)-1), ~ c(.x, sample(x[!x %in% tail(.x, min.dist) &( x %in% names(t <- table(x[x%in%.x]) > table(.x))[t] | !x %in% .x)], 1)), .init=sample(x,1)) } > table(shuffle(rep(LETTERS[1:5], 3),2)) A B C D E 3 3 3 3 3 > table(shuffle(rep(LETTERS[1:5], 3),2)) Error in sample.int(length(x), size, replace, prob) : invalid first argument
après @ 27Ï • 9 commentaire:
> shuffle(x, 3) [1] "A" "C" "B" "D" "E" "A" "B" "C" "E" "D" "A" "B" "C" "E" "A" > shuffle(x, 3) [1] "A" "B" "D" "E" "C" "A" "B" "D" "E" "C" "A" "D" "E" "C" "A" > shuffle(x, 4) [1] "C" "E" "D" "A" "B" "C" "E" "D" "A" "B" "C" "E" "D" "A" "B" > shuffle(x, 4) [1] "A" "B" "D" "E" "C" "A" "B" "D" "E" "C" "A" "B" "D" "E" "C" > shuffle(x, 2) [1] "E" "A" "D" "E" "B" "D" "A" "E" "C" "D" "A" "E" "C" "A" "B" > shuffle(x, 2) [1] "B" "A" "D" "C" "B" "A" "E" "B" "A" "E" "B" "C" "D" "A" "E"
Après quelques essais et erreurs, en regardant le fait que vous n'aurez pas toujours assez d'éléments pour espacer la liste min.dist
J'ai trouvé une solution, ce code est le plus expliqué parmi ceux ci-dessus:
shuffle <- function(x, min.dist=2){ stopifnot(min.dist < length(unique(x))) reduce(integer(length(x)-1), ~c(.x, sample(x[!x %in% tail(.x, min.dist)], 1)), .init=sample(x,1)) }
maintenant la table(shuffle(rep(LETTERS[1:5], 3),2))
retournera toujours 3 pour toutes les variables et nous pouvons dire avec une certaine certitude que dans le vecteur les variables sont espacées d'une distance minimale de 2 . la seule façon de garantir qu'aucun élément n'est dupliqué est d'utiliser min.dist=length(unique(x))-1
sinon il y aura des instances où au maximum r < min.dist
éléments ne sont pas distancés min.dist
de leur dernier occurrences, et si de tels éléments existent, ils seront dans le sous-ensemble length(x) + 1 - 1:min.dist
du vecteur résultant.
Juste pour être complètement sûr en utilisant une boucle pour vérifier si la queue du vecteur de sortie a des valeurs uniques: (supprimez l'instruction d'impression que je l'ai utilisée juste à des fins de démonstration)
[1] "A" "E" "D" "B" "A" "D" "E" "C" "D" "A" "C" "E" "B" "A" "E"
min.dist <- 2 reduce(integer(length(x)-1), ~c(.x, sample(x[!x %in% tail(.x, min.dist)], 1)), .init=sample(x,1))
cette nouvelle version fait un test rigoureux pour savoir si les éléments dans la queue du vecteur sont min.dist
, la version précédente fonctionne pour min.dist=2
, cependant cette nouvelle version fait de meilleurs tests.
@ 27ϕ9 merci pour la remarque, elle m'a échappé au début. pouvez-vous vérifier la modification.
Tester cela avec shuffle(rep(letters[1:2], c(2, 3)), 1)
J'obtiens parfois "ababb" qui n'est pas valide; la seule permutation possible qui pourrait être échantillonnée devrait être "babab".
@MikkoMarttila Merci pour la remarque, tout ce que vous avez à faire est de remplacer l'appel par shuffler
.
@AbdessabourMtk Je suis vraiment désolé pour la réponse tardive! Je voulais appliquer votre code à mes données réelles et j'ai dû faire quelques préparatifs avant. Je viens d'appliquer votre code à mes données et cela fonctionne parfaitement. Merci beaucoup pour tous vos efforts et tests / développement du code !!
@JoachimSchork mon plaisir
J'espère que cette réponse fonctionne bien pour vous. C'est fait avec la base R, mais ça marche. Je quitte l'impression si vous souhaitez vérifier ligne par ligne:
x <- rep(LETTERS[1:5], 3) # Create example vector shuffle <- function(x, min_dist=3){ #init variables result<-c() # result vector count<-0 vec_use<-x vec_keep<-c() for(i in 1:length(x)){ # print(paste0("iteration =", i)) if (count>min_dist){ valback<-vec_keep[1] # print(paste0("value to be returned:", valback)) ntimes_valback<-(table(vec_keep)[valback]) vec_use<- c(vec_use,rep(valback,ntimes_valback)) # print(paste0("vec_use after giving back valbak =", valback)) # print(paste0(vec_use,",")) vec_keep <- vec_keep[!vec_keep %in% valback] # print(paste0("vec_keep after removing valback =", valback)) # print(paste0(vec_keep,",")) } val<-sample(vec_use,1) # print(paste0("val = ",val))#remove value vec_keep<- c(vec_keep,x[x %in% val]) vec_keep<-vec_keep[1:(length(vec_keep)-1)]#removing 1 letter # print(paste0("vec_keep =")) # print(paste0(vec_keep,",")) vec_use <- vec_use[!vec_use %in% val] # print(paste0("vec_use =")) # print(paste0(vec_use,",")) result[i]<-val count<-count+1 } return(result) } shuffle(x) "C" "D" "B" "E" "C" "A" "B" "D" "E" "A" "C" "D" "B" "E" "C"
Merci beaucoup pour votre code. Cela fonctionne parfaitement bien avec mes données! J'ai donné la "réponse acceptée" à Abdessabour Mtk, puisqu'il a répondu le premier. Mais votre solution fonctionne également.
Pas de soucis!, Je pense que celui-ci était plus clair sur la logique (mais heureux d'avoir aidé!)
Si vos données sont volumineuses, il est peut-être (beaucoup) plus rapide de s'appuyer sur la probabilité pour faire ce genre de tâche.
Voici un exemple:
library(microbenchmark) x = rep(c(letters, LETTERS), 10) length(x) #> [1] 520 microbenchmark(prob_shuffler(x, 1), shuffler_am(x, 1), shuffler_csf(x, 1), times = 10) #> Unit: microseconds #> expr min lq mean median uq max neval #> prob_shuffler(x, 1) 87.001 111.501 155.071 131.801 192.401 264.401 10 #> shuffler_am(x, 1) 17218.100 18041.900 20324.301 18740.351 22296.301 26495.200 10 #> shuffler_csf(x, 1) 86771.401 88550.501 118185.581 95582.001 98781.601 341826.701 10 microbenchmark(prob_shuffler(x, 2), shuffler_am(x, 2), shuffler_csf(x, 2), times = 10) #> Unit: microseconds #> expr min lq mean median uq max neval #> prob_shuffler(x, 2) 140.1 195.201 236.3312 245.252 263.202 354.101 10 #> shuffler_am(x, 2) 18886.2 19526.901 22967.6409 21021.151 26758.800 29133.400 10 #> shuffler_csf(x, 2) 86078.1 92209.901 97151.0609 97612.251 99850.101 107981.401 10 microbenchmark(prob_shuffler(x, 3), shuffler_am(x, 3), shuffler_csf(x, 3), times = 10) #> Unit: microseconds #> expr min lq mean median uq max neval #> prob_shuffler(x, 3) 318.001 450.402 631.5312 573.352 782.2 1070.401 10 #> shuffler_am(x, 3) 19003.501 19622.300 23314.4808 20784.551 28281.5 32885.101 10 #> shuffler_csf(x, 3) 87692.701 96152.202 101233.5411 100925.201 108034.7 113814.901 10
Même si le while
en boucle semble convergence effrayante, dans la pratique est rapide. Bien sûr, plus la probabilité d'avoir deux caractères à min.dist
distance min.dist
, plus la convergence est rapide.
Les solutions actuelles de @Abdessabour Mtk et @Carles Sans Fuentes fonctionnent mais, en fonction de la taille des données d'entrée, deviennent rapidement trop lentes. Voici une référence:
prob_shuffler = function(x, min.dist = 2){ n = length(x) res = sample(x) OK = FALSE # We loop until we have a solution while(!OK){ OK = TRUE for(i in 1:min.dist){ # We check if identical elements are 'i' steps away pblm = res[1:(n-i)] == res[-(1:i)] if(any(pblm)){ if(sum(pblm) >= (n - i)/2){ # back to square 1 res = sample(x) } else { # we pair each identical element with # an extra one extra = sample(which(!pblm), sum(pblm)) id_reshuffle = c(which(pblm), extra) res[id_reshuffle] = sample(res[id_reshuffle]) } # We recheck from the beginning OK = FALSE break } } } res }
Nous pouvons remarquer deux choses: a) en toute logique, la vitesse de prob_shuffler
dépend de min.dist
tandis que les autres méthodes pas tellement, b) prob_shuffler
est environ 100 fois plus rapide pour seulement 520 observations (et il évolue).
Bien sûr, si la probabilité d'avoir deux caractères identiques à la liste min.dist
. Est extrêmement élevée, les méthodes récursives devraient être plus rapides. Mais dans la plupart des cas pratiques, la méthode des probabilités est plus rapide.
C'est une approche vraiment intéressante! Peut-être qu'un argument max.iter
ou similaire serait utile, au cas où l'entrée est quelque chose où il n'est pas possible d'atteindre la convergence.
@ LaurentBergé Merci beaucoup pour cette méthode alternative! En fait, j'ai remarqué que les autres approches ont pris un certain temps lorsque j'ai augmenté la liste min. Même si mes données ne sont pas encore si importantes. Je pense donc que votre approche est géniale pour tous ceux qui ont des données plus volumineuses.
@MikkoMarttila: bien sûr, et l'algorithme peut être amélioré, mais c'était juste pour donner une alternative rapide et sale.
Vous pouvez toujours effectuer un échantillonnage de rejet. En fonction de votre vecteur d'entrée, cela peut prendre beaucoup de temps.
Veuillez fournir plus d'informations sur le vecteur que vous essayez de mélanger. Il ne suffit pas de dire que "certains des éléments vectoriels sont identiques", car votre procédure d'échantillonnage risque de se retrouver dans une impasse. Par exemple, si vous avez un vecteur de 9 éléments dont 4 sont identiques les uns aux autres, vous ne pouvez jamais obtenir un échantillon satisfaisant votre contrainte de position. Au minimum, nous avons besoin de connaître le nombre de fréquences pour chaque type d'éléments dans votre vecteur.
@Roland Merci beaucoup pour l'astuce sur l'échantillonnage de rejet. Cela pourrait être un peu exagéré cependant. J'espérais une fonction qui a un argument tel que min.distance. Quoi qu'il en soit, si une telle fonction n'existe pas, je vais essayer de le faire avec un échantillonnage de rejet, alors merci beaucoup!
@ekoam Merci de m'avoir répondu! Je veux appliquer cette "méthode de mélange" à différents vecteurs, donc je ne peux pas vous dire la longueur exacte. Cependant, je peux vous dire que mes vecteurs auront une longueur d'environ 100 à 200 éléments et que tous les éléments auront au moins un jumeau. L'élément avec le plus d'occurrences existera environ 10 fois dans le vecteur. J'espère que cela clarifie ma question!
@JoachimSchork y a-t-il un problème avec les réponses? si c'est le cas, n'hésitez pas à le signaler
@AbdessabourMtk Je suis désolé pour la réponse tardive! (voir mon commentaire ci-dessous votre réponse)