Meilleure méthode pour collecter un échantillon aléatoire à partir d'une collection de fichiers


23

Supposons qu'il existe un répertoire contenant 300 fichiers de données. Je souhaite sélectionner au hasard 200 de ces fichiers et les déplacer dans un autre répertoire. Existe-t-il un moyen de le faire sous Unix / Linux?


R peut probablement le faire en un clin d'œil avec list.files()...
sr_

4
Je me brancherais vaguement shufet head(ou j'utiliserais simplement shuf -n, j'aurais dû lire la page de manuel ...)
Ulrich Schwarz

Réponses:


32

Si votre système le possède shuf, vous pouvez l'utiliser très facilement (même en gérant les noms de fichiers laids):

shuf -zen200 source/* | xargs -0 mv -t dest

Si vous n'avez pas , shufmais un sortqui prend -R, cela devrait fonctionner:

find source -type f -print0 | sort -Rz | cut -d $'\0' -f-200 | xargs -0 mv -t dest

7
Ah oui, car où chercherait-on le brassage autrement que dans un outil de tri. (Au moins shufn'est pas appelé troscar il fait le contraire du tri.)
Ulrich Schwarz

2
Il n'y a rien de tel que l'opposé du tri (dans le même sens qu'il n'y a pas de «pas de temps»). Aléatoire est toujours trié, il est simplement trié au hasard.
Plutor

1
Qu'est-ce que le "-zen200"? Ce n'est pas dans la documentation de shuf, ou n'importe où sur Internet, mais votre exemple ne fonctionne pas sans lui. Assez mystique.
SigmaX

2
@SigmaX En effet, assez zen, n'est-ce pas. Astuce: c'est 3 drapeaux distincts.
Kevin

2
files=(*)
for (( i=0; i<200; i++ )); do
    keys=("${!files[@]}")
    rnd=$(( RANDOM % ${#keys[@]} ))
    key=${keys[$rnd]}
    mv "${files[$key]}" "$otherdir"
    unset files[$key]
done

2

Mettez tous les noms de fichiers dans un tableau nommé "fichiers" dans bash:

files=( * )

taille du tableau:

echo ${#files[@]}

définissez 2/3 d'entre eux comme taille d'échantillon:

take=$((2*${#files[@]}/3)) 

for i in $(seq 1 $take)
do
    r=$((RANDOM%${#files[@]})) 
    echo ${files[r]}
done

Cela sélectionnera les doublons et n'est pas testé avec les noms de fichiers avec des blancs et autres.

Le moyen le plus simple d'éviter les doublons est d'itérer sur tous les fichiers et de choisir chacun avec 2/3 de chance, mais cela ne conduira pas nécessairement à 200 fichiers.

Cela supprimera un fichier s'il a été choisi dans la liste et répondra à vos besoins:

#!/bin/bash
files=( * )
# define 2/3 of them as sample size:
take=$((2*${#files[@]}/3)) 

while (( i < $take ))
do
    r=$((RANDOM%${#files[@]})) 
    f=${files[r]}
    if [[ -n $f ]]
    then 
        i=$((i+1))    
        echo ${files[r]}
        unset files[r]    
    fi
done

Vous pouvez sélectionner le même fichier plusieurs fois.
glenn jackman

Très bon script shell. Pour contourner votre problème de ne pas obtenir 200 fichiers, vous voudrez probablement utiliser l'échantillonnage de réservoir: en.wikipedia.org/wiki/Reservoir_sampling Je vais être faible et ne pas inclure d'exemple de script shell.
Bruce Ediger

@glennjackman: Je l'ai écrit, oui. Il a fallu quelques minutes pour comprendre comment supprimer les entrées du tableau.
utilisateur inconnu

Avertissement mineur: $RANDOMne peut avoir que des valeurs de 0 à 32767, donc cela ne fonctionnera pas correctement si vous avez plus de 32768 fichiers. De plus, la récupération est biaisée vers les premiers fichiers.
l0b0

@ l0b0: Exigences où, pour choisir 200 parmi 300. Si les fichiers ne sont pas dans le répertoire courant, mais sur un serveur de fichiers, cela ne fonctionnera pas trop. Des exigences différentes, des réponses différentes.
utilisateur inconnu

2

Si cela doit être statistiquement aléatoire, vous ne devriez pas l'utiliser RANDOM % ${#keys[@]}. Considérer:

  1. $RANDOM a 32768 valeurs uniques
  2. La première sélection est 1 sur 300 éléments
  3. 32768 = 109 * 300 + 68

Ainsi, lors de la sélection du premier élément, il y a 110/32768 ~ = 0,33569% de chance pour chacun des 68 premiers éléments, et 109/32768 ~ = 0,33264% de chance pour chacun des 232 autres éléments à sélectionner. La sélection est répétée plusieurs fois avec des chances différentes, mais biaisée vers les premiers éléments à chaque fois 32768 % ${#keys[@]} -ne 0, donc l'erreur se complique.

Cela doit être non biaisé et fonctionne avec n'importe quel nom de fichier:

while IFS= read -r -d '' -u 9
do
    mv -- "$REPLY" /target/dir
done 9< <(find /source/dir -mindepth 1 -print0 | shuf -n 200 -z)

2

La solution de Kevin fonctionne très bien! Quelque chose d'autre que j'ai beaucoup utilisé parce qu'il est plus facile de se souvenir du haut de ma tête est quelque chose comme:

cp `ls | shuf -n 200` destination

0

Un liner en bash:

ls original_directory/|sort -R|head -number_of_files_to_move|while read file; do cp "new_directory/"$file test; done

Veuillez développer; U&L est une base de connaissances.
contre-mode le
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.