Die beste Methode zum Sammeln einer Zufallsstichprobe aus einer Dateisammlung


23

Angenommen, es gibt ein Verzeichnis mit 300 Datendateien. Ich möchte 200 dieser Dateien zufällig auswählen und in ein anderes Verzeichnis verschieben. Gibt es eine Möglichkeit, dies unter Unix / Linux zu tun?


R kann dies wahrscheinlich im Handumdrehen mit list.files()...
sr_

4
Ich würde vage zusammenstecken shufund head(oder einfach benutzen shuf -n, hätte die Manpage lesen sollen ...)
Ulrich Schwarz

Antworten:


32

Wenn Ihr System dies hat shuf, können Sie dies ganz bequem verwenden (sogar mit hässlichen Dateinamen umgehen):

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

Wenn Sie keine haben, shufaber eine haben sort, die braucht -R, sollte dies funktionieren:

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

7
Ach ja, denn wo sonst würde man nach Mischen suchen als in einem Werkzeug zum Sortieren. (Zumindest shufwird es nicht aufgerufen, trosweil es das Gegenteil von Sortieren bewirkt.)
Ulrich Schwarz

2
Es gibt kein Gegenteil von Sortieren (im gleichen Sinne wie "kein Wetter"). Zufall ist immer noch sortiert, es ist nur zufällig sortiert.
Plutor

1
Was ist das "-zen200"? Das steht in keiner Dokumentation für shuf oder irgendwo im Internet, aber Ihr Beispiel funktioniert ohne das nicht. Ziemlich mystisch.
SigmaX

2
@ SigmaX In der Tat, ziemlich zen, nicht wahr? Hinweis: Es sind 3 separate Flags.
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

Fügen Sie alle Dateinamen in ein Array mit dem Namen "files" in bash ein:

files=( * )

Größe des Arrays:

echo ${#files[@]}

Definieren Sie 2/3 davon als Stichprobengröße:

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

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

Dies wählt Duplikate aus und wird nicht mit Dateinamen mit Leerzeichen und dergleichen getestet.

Die einfachste Möglichkeit, Duplikate zu vermeiden, besteht darin, alle Dateien zu durchlaufen und jede mit einer Wahrscheinlichkeit von 2/3 auszuwählen. Dies führt jedoch nicht unbedingt zu 200 Dateien.

Dadurch wird eine Datei entfernt, wenn sie aus der Liste ausgewählt wurde und Ihren Anforderungen entspricht:

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

Sie können dieselbe Datei mehrmals auswählen.
Glenn Jackman

Sehr schönes Shell-Skript. Um das Problem zu umgehen, dass Sie nicht 200 Dateien erhalten, möchten Sie wahrscheinlich Reservoir Sampling verwenden: en.wikipedia.org/wiki/Reservoir_sampling Ich werde schwach sein und kein Shell-Skript-Beispiel dafür enthalten.
Bruce Ediger

@glennjackman: Ich habe es geschrieben, ja. Es dauerte einige Minuten, um herauszufinden, wie Einträge aus dem Array entfernt werden.
Benutzer unbekannt

Kleine Einschränkung: $RANDOMKann nur Werte von 0 bis 32767 haben, daher funktioniert dies nicht richtig, wenn Sie mehr als 32768 Dateien haben. Das Abrufen ist auch auf die ersten Dateien ausgerichtet.
10.

@ l0b0: Voraussetzungen für die Auswahl von 200 von 300. Wenn sich die Dateien nicht im aktuellen Verzeichnis befinden, sondern auf einem Dateiserver, funktioniert dies auch nicht. Unterschiedliche Anforderungen, unterschiedliche Antworten.
Benutzer unbekannt

2

Wenn dies statistisch zufällig sein muss, sollten Sie nicht verwenden RANDOM % ${#keys[@]}. Erwägen:

  1. $RANDOM hat 32768 eindeutige Werte
  2. Die erste Auswahl ist 1 von 300 Elementen
  3. 32768 = 109 * 300 + 68

Bei der Auswahl des ersten Elements besteht daher für jedes der 68 ersten Elemente eine Chance von 110/32768 ~ = 0,33569% und für jedes der anderen 232 Elemente eine Chance von 109/32768 ~ = 0,33264%. Die Kommissionierung wird mehrmals mit unterschiedlichen Chancen wiederholt, jedoch immer in Richtung der ersten Elemente verschoben 32768 % ${#keys[@]} -ne 0, sodass sich der Fehler addiert.

Dies sollte unvoreingenommen sein und funktioniert mit jedem Dateinamen:

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

Kevins Lösung funktioniert großartig! Etwas anderes, das ich oft benutzt habe, weil es mir leichter fällt, mich über den Kopf zu erinnern, ist etwas wie:

cp `ls | shuf -n 200` destination

0

Ein Liner in der Bash:

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

Bitte erläutern Sie; U & L ist eine Wissensbasis.
Gegenmodus
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.