Dessinez au hasard un certain nombre de lignes à partir d'un fichier de données


13

J'ai une liste de données, comme

12345
23456
67891
-20000
200
600
20
...

Supposons que la taille de cet ensemble de données (c'est-à-dire les lignes de fichier) est N. Je veux tracer au hasard des mlignes à partir de ce fichier de données. Par conséquent, la sortie doit être de deux fichiers, l'un est le fichier comprenant ces mlignes de données et l'autre inclut des N-mlignes de données.

Existe-t-il un moyen de le faire en utilisant une commande Linux?


1
Êtes-vous préoccupé par la séquence des lignes? par exemple. Voulez-vous conserver l'ordre source, ou voulez-vous que cette séquence soit elle-même aléatoire ainsi que le choix des lignes aléatoire?
Peter.O

Réponses:


18

Ce n'est peut-être pas le moyen le plus efficace mais cela fonctionne:

shuf <file> > tmp
head -n $m tmp > out1
tail -n +$(( m + 1 )) tmp > out2

Avec $mcontenant le nombre de lignes.


@userunknown, sort -Rprend soin de l'aléatoire. Je ne sais pas si vous avez dévalorisé la réponse pour cela, mais recherchez d'abord dans la page de manuel.
Rob Wouters

2
Notez que sort -Rne trie pas exactement son entrée au hasard: elle regroupe des lignes identiques. Donc , si l'entrée est par exemple foo, foo, bar, baret m = 2, puis un fichier contiendra les deux foos et l'autre contiendra les deux bars. GNU coreutils a également shuf, qui randomise les lignes d'entrée. De plus, vous n'avez pas besoin d'un fichier temporaire .
Gilles 'SO- arrête d'être méchant'

pourquoi pas shuf <file> |head -n $m?
emanuele

@emanuele: Parce que nous avons besoin de la tête et de la queue dans deux fichiers distincts.
Rob Wouters

5

Ce script bash / awk choisit des lignes au hasard et conserve la séquence d'origine dans les deux fichiers de sortie.

awk -v m=4 -v N=$(wc -l <file) -v out1=/tmp/out1 -v out2=/tmp/out2 \
 'BEGIN{ srand()
         do{ lnb = 1 + int(rand()*N)
             if ( !(lnb in R) ) {
                 R[lnb] = 1
                 ct++ }
         } while (ct<m)
  } { if (R[NR]==1) print > out1 
      else          print > out2       
  }' file
cat /tmp/out1
echo ========
cat /tmp/out2

Sortie, basée sur les données de la question.

12345
23456
200
600
========
67891
-20000
20

4

Comme pour tout ce qui concerne Unix, il existe un utilitaire pour That TM .

Programme du jour: split
splitdivisera un fichier de différentes manières, -boctets, -llignes, -nnombre de fichiers de sortie. Nous utiliserons l' -loption. Puisque vous voulez choisir des lignes aléatoires et pas seulement la première m, nous allons d' sortabord classer le fichier au hasard. Si vous voulez en savoir plus sort, référez-vous à ma réponse ici .

Maintenant, le code réel. C'est assez simple, vraiment:

sort -R input_file | split -l $m output_prefix

Cela fera deux fichiers, un avec des mlignes et un avec des N-mlignes, nommé output_prefixaaet output_prefixab. Assurez-vous que mle fichier est plus gros que vous voulez ou vous obtiendrez plusieurs fichiers de longueur m(et un avecN % m ).

Si vous voulez vous assurer que vous utilisez la bonne taille, voici un petit code pour le faire:

m=10 # size you want one file to be
N=$(wc -l input_file)
m=$(( m > N/2 ? m : N - m ))
sort -R input_file | split -l $m output_prefix

Edit: Il est venu à mon attention que certaines sortimplémentations n'ont pas d' -Rindicateur. Si c'est le cas perl, vous pouvez remplacer perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.


1
Malheureusement, il sort -Rsemble que ce ne soit que dans certaines versions (probablement la version GNU). Pour d'autres plates-formes, j'ai écrit un outil appelé «randline» qui ne fait que randomiser stdin. C'est sur beesbuzz.biz/code pour tous ceux qui en ont besoin. (J'ai tendance à beaucoup mélanger le contenu des fichiers.)
moelleux

1
Notez que sort -Rne trie pas exactement son entrée au hasard: elle regroupe des lignes identiques. Donc , si l'entrée est par exemple foo, foo, bar, baret m = 2, puis un fichier contiendra les deux foos et l'autre contiendra les deux bars. GNU coreutils a également shuf, qui randomise les lignes d'entrée. En outre, vous pouvez choisir les noms des fichiers de sortie en utilisant headet tailau lieu desplit .
Gilles 'SO- arrête d'être méchant'

4

Si cela ne vous dérange pas de réorganiser les lignes et que vous avez des coreutils GNU (c'est-à-dire sur Linux non intégré ou Cygwin, pas trop ancien depuis shufapparu dans la version 6.0), shuf("shuffle") réorganise les lignes d'un fichier de manière aléatoire. Vous pouvez donc mélanger le fichier et répartir les m premières lignes dans un fichier et le reste dans un autre.

Il n'y a pas de moyen idéal pour faire cette dépêche. Vous ne pouvez pas simplement enchaîner headet tailparce headque tamponner à l'avance. Vous pouvez utiliser split, mais vous n'obtenez aucune flexibilité en ce qui concerne les noms des fichiers de sortie. Vous pouvez awkbien sûr utiliser :

<input shuf | awk -v m=$m '{ if (NR <= m) {print >"output1"} else {print} }'

Vous pouvez utiliser sedce qui est obscur mais peut-être plus rapide pour les gros fichiers.

<input shuf | sed -e "1,${m} w output1" -e "1,${m} d" >output2

Ou vous pouvez utiliser teepour dupliquer les données, si votre plate-forme a /dev/fd; c'est ok si m est petit:

<input shuf | { tee /dev/fd/3 | head -n $m >output1; } 3>&1 | tail -n +$(($m+1)) >output2

De manière portable, vous pouvez utiliser awk pour distribuer chaque ligne tour à tour. Notez que awk n'est pas très bon pour initialiser son générateur de nombres aléatoires; le caractère aléatoire n'est pas seulement définitivement inapproprié pour la cryptographie, mais même pas très bon pour les simulations numériques. La graine sera la même pour toutes les invocations awk sur n'importe quel système avec une période d'une seconde.

<input awk -v N=$(wc -l <input) -v m=3 '
    BEGIN {srand()}
    {
        if (rand() * N < m) {--m; print >"output1"} else {print >"output2"}
        --N;
    }'

Si vous avez besoin d'un meilleur caractère aléatoire, vous pouvez faire la même chose en Perl, qui amorce décemment son RNG.

<input perl -e '
    open OUT1, ">", "output1" or die $!;
    open OUT2, ">", "output2" or die $!;
    my $N = `wc -l <input`;
    my $m = $ARGV[0];
    while (<STDIN>) {
        if (rand($N) < $m) { --$m; print OUT1 $_; } else { print OUT2 $_; }
        --$N;
    }
    close OUT1 or die $!;
    close OUT2 or die $!;
' 42

@Gilles: Pour l' awkexemple: -v N=$(wc -l <file) -v m=4... et il n'imprime une ligne "aléatoire" que lorsque la valeur aléatoire est inférieure à $m, plutôt que d'imprimer $mdes lignes aléatoires ... Il semble que perlcela puisse faire la même chose avec rand , mais je ne fais pas ne sais pas perlassez bien pour surmonter une erreur de compilation: erreur de syntaxe à -e ligne 7, près de ") print"
Peter.O

@ Peter.O Merci, c'est ce qui vient de la saisie dans un navigateur et de l'édition négligente. J'ai corrigé le code awk et perl.
Gilles 'SO- arrête d'être méchant'

Les 3 méthodes fonctionnent bien et rapidement .. merci (+1) ... Je me fais lentement une idée de perl ... et c'est un fichier particulièrement intéressant et utile divisé dans l' shufexemple.
Peter.O

Un problème de buffereing?. Suis-je en train de manquer quelque chose? Le head catcombo provoque la perte de données dans le deuxième test suivant 3-4 .... TEST 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } .. TEST 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... les wc -lrésultats pour les sorties de TEST 1-2 sont 5000 5000 (bon), mais pour TEST 3-4 sont 5000 4539 (pas bon) .. La différence varie en fonction de la taille des fichiers impliqués ... Voici un lien vers mon code de test
Peter.O

@ Peter.O Encore une fois, merci. En effet, headlit à l'avance; ce qu'il lit à l'avance et ne s'imprime pas est supprimé. J'ai mis à jour ma réponse avec des solutions moins élégantes mais (je suis raisonnablement sûr) correctes.
Gilles 'SO- arrête d'être méchant'

2

En supposant m = 7et N = 21:

cp ints ints.bak
for i in {1..7}
do
    rnd=$((RANDOM%(21-i)+1))
    # echo $rnd;  
    sed -n "${rnd}{p,q}" 10k.dat >> mlines 
    sed -i "${rnd}d" ints 
done

Remarque: Si vous remplacez 7par une variable comme $1ou $m, vous devez utiliser seq, pas la {from..to}-notation, qui ne fait pas d'extension de variable.

Cela fonctionne en supprimant ligne par ligne du fichier, qui devient de plus en plus court, de sorte que le numéro de ligne, qui peut être supprimé, doit devenir de plus en plus petit.

Cela ne devrait pas être utilisé pour des fichiers plus longs et de nombreuses lignes, car pour chaque numéro, en moyenne, le demi-fichier doit être lu pour le 1er, et le fichier entier pour le 2ème code sed .


Il a également besoin d'un fichier contenant les lignes supprimées.
Rob Wouters

Je pensais que "l'inclusion de ces m lignes de données" devrait signifier including themmais les lignes d'origine aussi - doncincluding , non consisting of, et ne pas utiliser only, mais je suppose que votre interprétation est, ce que signifiait user288609. J'ajusterai mon script en conséquence.
utilisateur inconnu

Cela semble bon. `` ``
Rob Wouters

@user unknown: Vous avez +1le mauvais endroit. Il devrait être rnd=$((RANDOM%(N-i)+1))où N = 21 dans votre exemple. Il provoque actuellement un sedplantage lorsqu'il rndest évalué 0. .. De plus, il ne s'adapte pas très bien avec toute la réécriture de ce fichier. par exemple 123 secondes pour extraire 5 000 lignes aléatoires d'un fichier de 10 000 lignes contre 0,03 secondes pour une méthode plus directe ...
Peter.O

@ Peter.O: Vous avez raison (corrigé) et vous avez raison.
utilisateur inconnu
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.