El mejor método para recopilar una muestra aleatoria de una colección de archivos


23

Supongamos que hay un directorio que contiene 300 archivos de datos. Quiero seleccionar al azar 200 de esos archivos y moverlos a otro directorio. ¿Hay alguna manera de hacerlo bajo Unix / Linux?


R probablemente pueda hacer esto en un abrir y cerrar de ojos con list.files()...
sr_

44
Me conectaría vagamente shufy head(o simplemente usaría shuf -n, debería haber leído la página del manual ...)
Ulrich Schwarz

Respuestas:


32

Si su sistema lo tiene shuf, puede usar esto de manera bastante conveniente (incluso manejando nombres de archivo feos):

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

Si no tiene shufpero tiene una sortque toma -R, esto debería funcionar:

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

77
Ah, sí, porque ¿dónde más buscarías barajar que en una herramienta para clasificar? (Al menos shufno se llama trosporque hace lo contrario de ordenar).
Ulrich Schwarz

2
No existe lo opuesto a la clasificación (en el mismo sentido que no existe tal cosa como "sin clima"). Aleatorio todavía está ordenado, solo está ordenado al azar.
Plutor

1
¿Qué es el "-zen200"? Eso no está en la documentación de shuf, ni en ningún otro lugar de Internet, pero su ejemplo no funciona sin él. Muy místico
SigmaX

2
@SigmaX De hecho, bastante zen, ¿no? Sugerencia: son 3 banderas separadas.
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

Ponga todos los nombres de archivo en una matriz llamada "archivos" en bash:

files=( * )

tamaño de la matriz:

echo ${#files[@]}

defina 2/3 de ellos como tamaño de muestra:

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

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

Esto seleccionará duplicados y se no probado con nombres de archivo con espacios en blanco y tal.

La forma más sencilla de evitar duplicados es iterar sobre todos los archivos y elegir cada uno con una probabilidad de 2/3, pero esto no necesariamente conducirá a 200 archivos.

Esto eliminará un archivo si fue elegido de la lista y cumple con sus requisitos:

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

Puede seleccionar el mismo archivo más de una vez.
Glenn Jackman

Muy buen guión de shell. Para solucionar su problema de no obtener 200 archivos, probablemente desee usar Reservoir Sampling: en.wikipedia.org/wiki/Reservoir_sampling Voy a ser débil y no incluiré un ejemplo de script de shell de esto.
Bruce Ediger

@glennjackman: lo escribí, sí. Necesité algunos minutos para descubrir cómo eliminar entradas de la matriz.
usuario desconocido

Advertencia menor: $RANDOMsolo puede tener valores de 0 a 32767, por lo que esto no funcionará correctamente si tiene más de 32768 archivos. Además, la obtención está sesgada hacia los primeros archivos.
l0b0

@ l0b0: Requisitos donde, para elegir 200 de 300. Si los archivos no están en el directorio actual, sino en un servidor de archivos, tampoco funcionará. Diferentes requisitos, diferentes respuestas.
usuario desconocido

2

Si esto necesita ser estadísticamente aleatorio, no debe usarlo RANDOM % ${#keys[@]}. Considerar:

  1. $RANDOM tiene 32768 valores únicos
  2. La primera selección es 1 de cada 300 elementos.
  3. 32768 = 109 * 300 + 68

Por lo tanto, al seleccionar el primer elemento, hay un 110/32768 ~ = 0.33569% de probabilidad para cada uno de los 68 primeros elementos, y 109/32768 ~ = 0.33264% de probabilidad para cada uno de los otros 232 elementos para ser seleccionados. La selección se repite varias veces con diferentes posibilidades, pero siempre se inclina hacia los primeros elementos.32768 % ${#keys[@]} -ne 0 , por lo que el error se agrava.

Esto debe ser imparcial y funciona con cualquier nombre de archivo:

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 solución de Kevin funciona muy bien! Algo más que he usado mucho porque me resulta más fácil recordar desde la parte superior de mi cabeza es algo como:

cp `ls | shuf -n 200` destination

0

Un forro en bash:

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

Por favor elabora; U&L es una base de conocimiento.
contramode
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.