Comment échantillonner au hasard un sous-ensemble d'un fichier

39

Existe-t-il une commande Linux pouvant être utilisée pour échantillonner un sous-ensemble de fichier? Par exemple, un fichier contient un million de lignes et nous voulons échantillonner de manière aléatoire seulement mille lignes de ce fichier.

Pour aléatoire, je veux dire que chaque ligne a la même probabilité d'être choisie et qu'aucune des lignes choisies n'est répétitive.

headet tailpeut choisir un sous-ensemble du fichier mais pas au hasard. Je sais que je peux toujours écrire un script python pour le faire, mais je me demandais simplement s'il existait une commande pour cet usage.

Clwen
la source
lignes dans un ordre aléatoire, ou un bloc aléatoire de 1000 lignes consécutives de ce fichier?
Frostschutz
Chaque ligne a la même probabilité d'être choisie. Vous n'avez pas besoin d'être consécutif, même s'il existe une faible probabilité qu'un bloc de lignes consécutif soit choisi ensemble. J'ai mis à jour ma question pour clarifier ce sujet. Merci.
Clwen
Mon github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl le fait approximativement en recherchant un emplacement aléatoire dans le fichier et en recherchant les sauts de ligne les plus proches.
barrycarter le

Réponses:

66

La shufcommande (une partie de coreutils) peut faire ceci:

shuf -n 1000 file

Et au moins pour le moment, les versions non anciennes (ajoutées dans un commit de 2013 ), qui utiliseront l'échantillonnage de réservoir le cas échéant, ce qui signifie qu'il ne devrait pas manquer de mémoire et utilise un algorithme rapide.

derobert
la source
Selon la documentation, il faut un fichier trié comme entrée: gnu.org/software/coreutils/manual/…
mkc
@Ketan, ne semble pas comme ça
frostschutz
2
@Ketan c'est juste dans la mauvaise section du manuel, je crois. Notez que même les exemples du manuel ne sont pas triés. Notez également que se sorttrouve dans la même section et qu’il n’est clairement pas nécessaire de trier les entrées.
derobert
2
shufCoreutils a été introduit dans la version 6.0 (2006-08-15), et croyez-le ou non, certains systèmes raisonnablement communs (CentOS 6.5 en particulier) n’ont pas cette version: - |
offby1
2
@petrelharp shuf -neffectue l'échantillonnage de réservoir, du moins lorsque l'entrée est supérieure à 8K, ce qui correspond à la taille qu'ils ont déterminée comme points de référence. Voir le code source (par exemple, à l' adresse github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Désolé pour cette réponse très tardive. Apparemment, c'est nouveau il y a 6 ans.
derobert
16

Si vous avez un très gros fichier (ce qui est une raison courante pour prélever un échantillon), vous constaterez que:

  1. shuf épuise la mémoire
  2. L'utilisation $RANDOMne fonctionnera pas correctement si le fichier dépasse 32 767 lignes.

Si vous n'avez pas besoin "exactement" n lignes échantillonnées, vous pouvez échantillonner un rapport comme celui-ci:

cat input.txt | awk 'BEGIN {srand()} !/^$/ { if (rand() <= .01) print $0}' > sample.txt

Cela utilise une mémoire constante , échantillonne 1% du fichier (si vous connaissez le nombre de lignes du fichier, vous pouvez ajuster ce facteur pour échantillonner un nombre de lignes limité à un nombre limité), et fonctionne avec n’importe quelle taille de fichier, mais cela ne fonctionnera pas. renvoyer un nombre précis de lignes, juste un ratio statistique.

Remarque: le code provient de: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix

Txangel
la source
Si un utilisateur souhaite environ 1% des lignes non vides, la réponse est plutôt bonne. Mais si l'utilisateur veut un nombre exact de lignes (par exemple, 1000 sur un fichier de 1 000 000 lignes), cela échoue. Comme vous l'avez dit, cela ne donne qu'une estimation statistique. Et comprenez-vous suffisamment la réponse pour constater qu’elle ignore les lignes vides? Cela peut être une bonne idée, dans la pratique, mais les fonctionnalités non documentées ne sont généralement pas une bonne idée.
G-Man dit 'Réintégrez Monica'
1
Les approches PS   simplistes$RANDOM ne fonctionneront pas correctement pour les fichiers de plus de 32767 lignes. La déclaration «L’utilisation $RANDOMn’atteint pas l’ensemble du fichier» est un peu large.
G-Man dit 'Réintégrez Monica'
@ G-Man La question semble porter sur l'obtention de 10 000 lignes sur un million, par exemple. Aucune des réponses disponibles n’a fonctionné pour moi (en raison de la taille des fichiers et des limitations matérielles) et je propose cela comme un compromis raisonnable. Cela ne vous donnera pas 10 000 lignes sur un million, mais cela pourrait être assez proche pour la plupart des objectifs pratiques. Je l'ai clarifié un peu plus en suivant vos conseils. Merci.
Txangel
C’est la meilleure réponse, les lignes sont choisies au hasard tout en respectant l’ordre chronologique du fichier original, s’il s’agit là d’une exigence. En plus awkconvivial est plus de ressources queshuf
Polymerase
Si vous avez besoin d'un nombre exact, vous pouvez toujours… Exécuter ceci avec un% de plus que votre besoin. Comptez le résultat. Supprimer les lignes correspondant à la différence de nombre de modulations.
Bruno Bronosky
6

Semblable à la solution probabiliste de @ Txangel mais approchant 100 fois plus vite.

perl -ne 'print if (rand() < .01)' huge_file.csv > sample.csv

Si vous avez besoin de hautes performances, d'une taille d'échantillon exacte et que vous souhaitez vivre avec un espace d'échantillon à la fin du fichier, vous pouvez procéder de la manière suivante (échantillonnez 1000 lignes d'un fichier de 1 m):

perl -ne 'print if (rand() < .0012)' huge_file.csv | head -1000 > sample.csv

.. ou bien enchaîner un deuxième exemple de méthode au lieu de head.

geotheory
la source
5

shuf -nSi l' astuce sur les gros fichiers manque de mémoire et que vous avez toujours besoin d'un échantillon de taille fixe et qu'un utilitaire externe peut être installé, essayez ensuite sample :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

La mise en garde est que l' échantillon (1000 lignes dans l'exemple) doit tenir dans la mémoire.

Déni de responsabilité: Je suis l'auteur du logiciel recommandé.

hroptatyr
la source
1
Pour ceux qui l'installent et qui ont leur /usr/local/binpareil avant /usr/bin/, méfiez-vous que macOS est livré avec un échantillonneur intégré de pile d'appels appelé sample, qui fait quelque chose de complètement différent, dans /usr/bin/.
Denis de Bernardy
2

Pas au courant d'une seule commande qui pourrait faire ce que vous demandez, mais voici une boucle que j'ai mise ensemble qui peut faire le travail:

for i in `seq 1000`; do sed -n `echo $RANDOM % 1000000 | bc`p alargefile.txt; done > sample.txt

sedprendra une ligne au hasard sur chacun des 1000 passages. Peut-être y a-t-il des solutions plus efficaces.

mkc
la source
Est-il possible d’obtenir la même ligne plusieurs fois dans cette approche?
Clwen
1
Oui, il est tout à fait possible d’obtenir le même numéro de ligne plusieurs fois. De plus, $RANDOMla plage est comprise entre 0 et 32767. Ainsi, vous ne recevrez pas de numéros de ligne bien répartis.
Mkc
ne fonctionne pas - le hasard est appelé une fois
Bohdan
2

Vous pouvez enregistrer le code suivant dans un fichier (par exemple randextract.sh) et l'exécuter en tant que:

randextract.sh file.txt

---- Commencer le fichier ----

#!/bin/sh -xv

#configuration MAX_LINES is the number of lines to extract
MAX_LINES=10

#number of lines in the file (is a limit)
NUM_LINES=`wc -l $1 | cut -d' ' -f1`

#generate a random number
#in bash the variable $RANDOM returns diferent values on each call
if [ "$RANDOM." != "$RANDOM." ]
then
    #bigger number (0 to 3276732767)
    RAND=$RANDOM$RANDOM
else
    RAND=`date +'%s'`
fi 

#The start line
START_LINE=`expr $RAND % '(' $NUM_LINES - $MAX_LINES ')'`

tail -n +$START_LINE $1 | head -n $MAX_LINES

---- FICHIER DE FIN ----

razzek
la source
3
Je ne suis pas sûr de ce que vous essayez de faire ici avec RAND, mais je $RANDOM$RANDOMne génère pas de nombres aléatoires dans toute la plage «0 à 3276732767» (par exemple, cela générera 1000100000 mais pas 1000099999).
Gilles 'SO- arrête d'être méchant'
Le PO indique: «Chaque ligne a la même probabilité d'être choisie. … Il y a une probabilité infime qu'un groupe de lignes consécutives soit choisi ensemble. »Je trouve également que cette réponse est cryptée, mais il semble qu'il s'agisse d'extraire un bloc de 10 lignes de lignes consécutives à partir d'un point de départ aléatoire. Ce n'est pas ce que le PO demande.
G-Man dit 'Réintégrez Monica'
2

Si vous connaissez le nombre de lignes dans le fichier (comme 1e6 dans votre cas), vous pouvez faire:

awk -v n=1e6 -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}' < file

Si non, vous pouvez toujours faire

awk -v n="$(wc -l < file)" -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}' < file

Cela ferait deux passes dans le fichier, mais éviterait toujours de stocker le fichier entier en mémoire.

Un autre avantage par rapport à GNU shufest qu’il préserve l’ordre des lignes dans le fichier.

Notez qu'il assume n est le nombre de lignes dans le fichier. Si vous voulez imprimer ples premières n lignes du fichier (qui a potentiellement plus de lignes), vous devez vous arrêter awkà la ntroisième ligne comme ceci:

awk -v n=1e6 -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}
  !n {exit}' < file
Stéphane Chazelas
la source
2

J'aime utiliser awk pour cela lorsque je souhaite conserver une ligne d'en-tête et que l'échantillon peut représenter un pourcentage approximatif du fichier. Fonctionne pour les très gros fichiers:

awk 'BEGIN {srand()} !/^$/ { if (rand() <= .01 || FNR==1) print > "data-sample.txt"}' data.txt
Merlin
la source
1

Ou comme ceci:

LINES=$(wc -l < file)  
RANDLINE=$[ $RANDOM % $LINES ]  
tail -n $RANDLINE  < file|head -1  

De la page de manuel de bash:

        RANDOM Chaque fois que ce paramètre est référencé, un entier aléatoire
              entre 0 et 32767 est généré. La séquence de hasard
              les numéros peuvent être initialisés en attribuant une valeur à RAN‐
              DOM. Si RANDOM n'est pas défini, il perd ses propriétés
              liens, même s'il est réinitialisé par la suite.

la source
Cela échoue mal si le fichier a moins de 32767 lignes.
offby1
Cela produira une ligne du fichier. (Je suppose que votre idée est d'exécuter les commandes ci-dessus dans une boucle?) Si le fichier contient plus de 32 767 lignes, ces commandes choisiront uniquement parmi les 32 767 premières lignes. À part l'inefficacité possible, je ne vois pas de gros problème avec cette réponse si le fichier contient moins de 32767 lignes.
G-Man dit 'Réintégrez Monica'
1

Si la taille du fichier n’est pas énorme, vous pouvez utiliser la méthode de tri aléatoire. Cela prend un peu plus de temps que shuf, mais aléatoirement l’ensemble des données. Donc, vous pouvez facilement faire ce qui suit pour utiliser la tête que vous avez demandée:

sort -R input | head -1000 > output

Cela trierait le fichier au hasard et vous donnerait les 1000 premières lignes.

DomainesRecommandé
la source
0

Comme mentionné dans la réponse acceptée, GNU shufsupporte shuf -nassez bien l’ échantillonnage aléatoire simple ( ). Si des méthodes d'échantillonnage autres que celles prises en charge shufsont nécessaires, utilisez tsv-sample dans TSV Utilities d' eBay . Il prend en charge plusieurs modes d'échantillonnage supplémentaires, notamment l'échantillonnage aléatoire pondéré, l'échantillonnage de Bernoulli et l'échantillonnage distinct. Les performances sont similaires à celles de GNU shuf(les deux sont assez rapides). Disclaimer: je suis l'auteur.

JonDeg
la source