web-dev-qa-db-fra.com

Obtenir un échantillon aléatoire de la liste tout en maintenant la commande des articles?

J'ai une liste triée, disons: (ce n'est pas vraiment que des nombres, c'est une liste d'objets qui sont triés avec un algorithme compliqué prenant beaucoup de temps)

mylist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ,9  , 10 ]

Y at-il une fonction python qui me donnera N des éléments, mais gardera l'ordre?

Exemple:

randomList = getRandom(mylist,4)
# randomList = [ 3 , 6 ,7 , 9 ]
randomList = getRandom(mylist,4)
# randomList = [ 1 , 2 , 4 , 8 ]

etc...

74
Yochai Timmer

Le code suivant générera un échantillon aléatoire de taille 4:

import random

sample_size = 4
sorted_sample = [
    mylist[i] for i in sorted(random.sample(range(len(mylist)), sample_size))
]

(note: avec Python 2, mieux vaut utiliser xrange au lieu de range)

Explication

random.sample(range(len(mylist)), sample_size)

génère un échantillon aléatoire de indices de la liste d'origine.

Ces index sont ensuite triés pour préserver l'ordre des éléments dans la liste d'origine.

Enfin, la compréhension de la liste extrait les éléments réels de la liste d'origine, en fonction des indices échantillonnés.

114
mhyfritz

Simple à coder de manière O (N + K * log (K))

Prélevez un échantillon au hasard sans remplacer les index, triez-les et prenez-les à partir de l'original.

indices = random.sample(range(len(myList)), K)
[myList[i] for i in sorted(indices)]

Ou plus concement:

[x[1] for x in sorted(random.sample(enumerate(myList),K))]

Optimisé temps O (N), O (1) - voie de l'espace auxiliaire

Vous pouvez également utiliser une astuce mathématique et parcourir de manière itérative myList de gauche à droite, en choisissant des nombres avec une probabilité de changement dynamique (N-numbersPicked)/(total-numbersVisited). L'avantage de cette approche est qu'il s'agit d'un algorithme O(N) puisqu'il ne nécessite pas de tri!

from __future__ import division

def orderedSampleWithoutReplacement(seq, k):
    if not 0<=k<=len(seq):
        raise ValueError('Required that 0 <= sample_size <= population_size')

    numbersPicked = 0
    for i,number in enumerate(seq):
        prob = (k-numbersPicked)/(len(seq)-i)
        if random.random() < prob:
            yield number
            numbersPicked += 1

Preuve de concept et test que les probabilités sont correctes:

Simulé avec 1 billion d’échantillons pseudo-aléatoires sur une période de 5 heures:

>>> Counter(
        Tuple(orderedSampleWithoutReplacement([0,1,2,3], 2))
        for _ in range(10**9)
    )
Counter({
    (0, 3): 166680161, 
    (1, 2): 166672608, 
    (0, 2): 166669915, 
    (2, 3): 166667390, 
    (1, 3): 166660630, 
    (0, 1): 166649296
})

Les probabilités s'écartent des probabilités réelles d'un facteur moins égal à 1.0001. Lancer à nouveau ce test a abouti à un ordre différent, ce qui signifie qu'il n'est pas biaisé en faveur d'un ordre. L'exécution du test avec moins d'échantillons pour [0,1,2,3,4], k=3 et [0,1,2,3,4,5], k=4 a donné des résultats similaires.

edit: Vous ne savez pas pourquoi les gens votent pour de faux commentaires ou craignent de passer à un vote supérieur ... NON, il n'y a rien de mal à cette méthode. =)

(Également une note utile de l’utilisateur tegan dans les commentaires: s’il s’agit de python2, vous voudrez utiliser xrange, comme d’habitude, si vous vous souciez vraiment de l’espace supplémentaire.)

edit: Preuve: Compte tenu de la distribution uniforme (sans remplacement) du choix d'un sous-ensemble de k sur une population seq de taille len(seq), nous pouvons considérer une partition à un point arbitraire i dans 'gauche' (0, 1, ..., i-1) et 'right' (i, i + 1, ..., len (seq)). Étant donné que nous avons sélectionné numbersPicked dans le sous-ensemble connu de gauche, le reste doit provenir de la même distribution uniforme dans le sous-ensemble inconnu de droite, bien que les paramètres soient maintenant différents. En particulier, la probabilité que seq[i] contienne un élément choisi est #remainingToChoose/#remainingToChooseFrom ou (k-numbersPicked)/(len(seq)-i), aussi nous simulons cela et recurse sur le résultat. (Cela doit prendre fin puisque si #remainingToChoose == #remainingToChooseFrom, toutes les probabilités restantes sont égales à 1.) Ceci est similaire à un arbre de probabilités généré dynamiquement. Fondamentalement, vous pouvez simuler une distribution de probabilité uniforme en conditionnant des choix antérieurs (lorsque vous développez l’arbre de probabilité, vous choisissez la probabilité de la branche actuelle de telle sorte qu’elle soit aposteriori identique aux congés précédents, c’est-à-dire conditionnée à des choix antérieurs; cette probabilité est uniformément exactement N/k).

edit: Timothy Shields mentionne Échantillonnage de réservoir , qui est la généralisation de cette méthode lorsque len(seq) est inconnu (comme avec une expression génératrice). Spécifiquement, celui noté comme "algorithme R" est O(N) et O(1) espace si cela est fait sur place; cela implique de prendre le premier élément N et de le remplacer lentement (un indice de preuve inductive est également donné). Il existe également des variantes distribuées utiles et diverses variantes d'échantillonnage de réservoir disponibles sur la page wikipedia.

edit: Voici un autre moyen de le coder ci-dessous d'une manière plus sémantique.

from __future__ import division
import random

def orderedSampleWithoutReplacement(seq, sampleSize):
    totalElems = len(seq)
    if not 0<=sampleSize<=totalElems:
        raise ValueError('Required that 0 <= sample_size <= population_size')

    picksRemaining = sampleSize
    for elemsSeen,element in enumerate(seq):
        elemsRemaining = totalElems - elemsSeen
        prob = picksRemaining/elemsRemaining
        if random.random() < prob:
            yield element
            picksRemaining -= 1

from collections import Counter         
Counter(
    Tuple(orderedSampleWithoutReplacement([0,1,2,3], 2))
    for _ in range(10**5)

)

86
ninjagecko

Peut-être pouvez-vous simplement générer un échantillon d'index, puis collecter les éléments de votre liste.

randIndex = random.sample(range(len(mylist)), sample_size)
randIndex.sort()
Rand = [mylist[i] for i in randIndex]
7
Howard

Apparemment, random.sample a été introduit dans Python 2.3

donc pour la version en dessous, on peut utiliser shuffle (exemple pour 4 éléments):

myRange =  range(0,len(mylist)) 
shuffle(myRange)
coupons = [ bestCoupons[i] for i in sorted(myRange[:4]) ]
4
Yochai Timmer

random.sample l'implémente.

>>> random.sample([1, 2, 3, 4, 5],  3)   # Three samples without replacement
[4, 1, 5]
0
xiao