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.
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.
Si vous avez un fichier très volumineux (qui est une raison courante de prendre un échantillon), vous constaterez que:
shuf
épuise la mémoire$RANDOM
Ne fonctionnera pas correctement si le fichier dépasse 32 767 lignesSi 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 =
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
.
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é.
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.
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 n
is 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 n
e ligne comme:
awk -v n=1e6 -v p=1000 '
BEGIN {srand()}
Rand() * n-- < p {p--; print}
!n {exit}' < file
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 ----
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
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.
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é.
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.
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.