web-dev-qa-db-fra.com

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

Existe-t-il une commande Linux que l'on peut utiliser pour échantillonner un sous-ensemble d'un fichier? Par exemple, un fichier contient un million de lignes, et nous voulons échantillonner au hasard 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.

head et tail peuvent 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 demande simplement s'il existe une commande pour cette utilisation.

49
clwen

La commande shuf (qui fait partie de coreutils) peut faire ceci:

shuf -n 1000 file

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

75
derobert

Si vous avez un fichier très volumineux (qui est une raison courante de prendre un échantillon), vous constaterez que:

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

Si vous n'avez pas besoin de "exactement" n lignes échantillonnées vous pouvez échantillonner un ratio comme ça:

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

Cette utilise mémoire constante , échantillonne 1% du fichier (si vous connaître le nombre de lignes du fichier, vous pouvez ajuster ce facteur pour échantillonner un nombre proche d'un nombre limité de lignes), et fonctionne avec n'importe quelle taille de fichier mais il ne renverra pas un nombre précis de lignes, juste une statistique rapport.

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

17
Txangel

Similaire à 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 êtes satisfait de vivre avec un espace d'échantillon à la fin du fichier, vous pouvez faire quelque chose comme ceci (échantillonne 1000 lignes à partir d'un fichier de ligne de 1 m):

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

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

6
geotheory

Dans le cas où le shuf -n astuce sur les gros fichiers manque de mémoire et vous avez toujours besoin d'un échantillon de taille fixe et un utilitaire externe peut être installé, puis essayez exemple :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

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

Avertissement: je suis l'auteur du logiciel recommandé.

5
hroptatyr

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

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

sed récupérera une ligne aléatoire sur chacun des 1000 passages. Il existe peut-être des solutions plus efficaces.

3
mkc

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

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

Sinon, 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 tout de même de stocker tout le fichier en mémoire.

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

Notez qu'il suppose que nis le nombre de lignes dans le fichier. Si vous voulez imprimer p hors des lignes en premiern du fichier (qui a potentiellement plus de lignes), vous devez arrêter awk au ne ligne comme:

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

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 ----

2
razzek

J'aime utiliser awk pour cela lorsque je souhaite conserver une ligne d'en-tête et lorsque l'échantillon peut être 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
2
Merlin

Ou comme ça:

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

Depuis la page de manuel de bash:

 RANDOM Chaque fois que ce paramètre est référencé, un entier aléatoire 
 Compris entre 0 et 32767 est généré. La séquence de nombres aléatoires 
 Peut être initialisée en affectant une valeur à RAN - 
 DOM. Si RANDOM n'est pas défini, il perd ses liens spéciaux - 
, Même s'il est réinitialisé par la suite. 
1
user55518

Pour obtenir un seul bloc aléatoire de lignes adjacentes, utilisez shuf pour obtenir une ligne aléatoire, puis utilisez grep pour obtenir le bloc de lignes après la ligne sélectionnée au hasard.

$ shuf -n 1 file | grep -f - -A 10 file

Cela accédera au fichier deux fois. Le paramètre -f indique à grep d'obtenir le modèle de recherche à partir du fichier, dans ce cas stdin (en utilisant un tiret comme valeur pour le paramètre f) qui est la seule ligne sélectionnée au hasard dans le fichier.

Une fonction simple:

function random-block {
  shuf -n 1 $1 | grep -f - -A $(($2>0?$2-1:0)) $1
}

Exemple d'utilisation:

$ random-block /var/log/syslog 10

En effet, cela ne garantira pas une sortie du nombre de lignes demandé si la sélection aléatoire est si basse qu'il n'y a plus assez de lignes lors de la sélection du bloc.

Une fonction améliorée pourrait ressembler à ceci:

function random-block {
  head -n $(($(wc -l | cut -f1 -d ' ')-$2+1)) $1 | shuf -n 1 | grep -f - -A $(($2>0?$2-1:0)) $1
}

Cela récupérerait toutes les lignes du fichier à l'exception des n dernières lignes, puis mélangerait cette liste de lignes. Cela garantirait que grep peut toujours sélectionner le nombre de lignes demandé.

1
s1037989

Si la taille de votre fichier n'est pas énorme, vous pouvez utiliser Trier au hasard. Cela prend un peu plus de temps que shuf, mais il randomise l'ensemble des données. Ainsi, vous pouvez facilement faire ce qui suit pour utiliser head comme vous l'avez demandé:

sort -R input | head -1000 > output

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

1
DomainsFeatured

Comme mentionné dans la réponse acceptée, GNU shuf prend en charge l'échantillonnage aléatoire simple (shuf -n) plutôt bien. Si des méthodes d'échantillonnage autres que celles prises en charge par shuf sont nécessaires, pensez à tsv-sample from eBay's TSV Utilities . Il prend en charge plusieurs modes d'échantillonnage supplémentaires, y compris l'échantillonnage aléatoire pondéré, l'échantillonnage de Bernoulli et l'échantillonnage distinct. Les performances sont similaires à GNU shuf (les deux sont assez rapides). Avertissement: je suis l'auteur.

0
JonDeg