Il metodo migliore per raccogliere un campione casuale da una raccolta di file


23

Supponiamo che ci sia una directory che contiene 300 file di dati. Voglio selezionare casualmente 200 di quei file e spostarli in un'altra directory. C'è un modo per farlo sotto Unix / Linux?


R probabilmente può farlo in un batter d'occhio con list.files()...
sr

4
Vorrei collegarmi vagamente shufe head(o semplicemente usare shuf -n, avrei dovuto leggere la pagina man ...)
Ulrich Schwarz,

Risposte:


32

Se il tuo sistema ha shuf, puoi usarlo abbastanza comodamente (anche gestendo nomi di file brutti):

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

Se non hai shufma ne hai uno sortche richiede -R, questo dovrebbe funzionare:

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

7
Ah sì, perché dove altro si potrebbe cercare di mescolare che in uno strumento per l'ordinamento. (Almeno shufnon viene chiamato trosperché fa il contrario dell'ordinamento.)
Ulrich Schwarz,

2
Non esiste il contrario dell'ordinamento (nello stesso senso in cui non esiste "nessun tempo"). Il casuale è ancora ordinato, è semplicemente ordinato in modo casuale.
Plutor,

1
Che cos'è il "-zen200"? Non è presente nella documentazione di shuf, né in qualsiasi altra parte di Internet, ma il tuo esempio non funziona senza di essa. Abbastanza mistico.
SigmaX,

2
@SigmaX In effetti, abbastanza zen, non è vero. Suggerimento: sono 3 flag separati.
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

Inserisci tutti i nomi di file in un array chiamato "file" in bash:

files=( * )

dimensione dell'array:

echo ${#files[@]}

definirne 2/3 come dimensione del campione:

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

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

Questo selezionerà i duplicati e non verrà testato con nomi di file con spazi vuoti e simili.

Il modo più semplice per evitare i duplicati è quello di scorrere su tutti i file e sceglierli ciascuno con probabilità 2/3, ma ciò non porta necessariamente a 200 file.

Questo rimuoverà un file se è stato scelto dall'elenco e soddisfa i tuoi requisiti:

#!/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

È possibile selezionare lo stesso file più di una volta.
Glenn Jackman,

Script di shell molto bello. Per ovviare al problema di non ottenere 200 file, probabilmente si desidera utilizzare Reservoir Sampling: en.wikipedia.org/wiki/Reservoir_sampling Sto per essere debole e non includere un esempio di script shell di questo.
Bruce Ediger,

@glennjackman: l'ho scritto sì. Sono stati necessari alcuni minuti per capire come rimuovere le voci dall'array.
utente sconosciuto

Avvertenza minore: $RANDOMpuò avere solo valori compresi tra 0 e 32767, quindi non funzionerà correttamente se si dispone di più di 32768 file. Inoltre, il recupero è distorto verso i primi file.
l0b0

@ l0b0: requisiti in cui, per scegliere 200 tra 300. Se i file non si trovano nella directory corrente, ma su un file server, non funzionerà anche. Requisiti diversi, risposta diversa.
utente sconosciuto

2

Se questo deve essere statisticamente casuale, non dovresti usarlo RANDOM % ${#keys[@]}. Ritenere:

  1. $RANDOM ha 32768 valori univoci
  2. La prima selezione è 1 su 300 elementi
  3. 32768 = 109 * 300 + 68

Pertanto, quando si seleziona il primo elemento, esiste una probabilità del 110/32768 ~ = 0,33569% per ciascuno dei 68 primi elementi e 109/32768 ~ = 0,33264% della probabilità per ciascuno degli altri 232 elementi da selezionare. La raccolta viene ripetuta più volte con diverse possibilità, ma distorta verso i primi elementi ogni volta 32768 % ${#keys[@]} -ne 0, quindi l'errore si aggrava.

Questo dovrebbe essere imparziale e funziona con qualsiasi nome di file:

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 soluzione di Kevin funziona alla grande! Qualcos'altro che ho usato molto perché trova più facile ricordare dalla parte superiore della mia testa è qualcosa di simile:

cp `ls | shuf -n 200` destination

0

Una fodera in bash:

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

Si prega di elaborare; U&L è una base di conoscenza.
Contromodalità
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.