Zufälliges Abtasten einer Teilmenge einer Datei

38

Gibt es einen Linux-Befehl, mit dem eine Teilmenge einer Datei abgetastet werden kann? Eine Datei enthält beispielsweise eine Million Zeilen, und wir möchten nur eintausend Zeilen aus dieser Datei zufällig auswählen.

Für zufällig meine ich, dass jede Zeile die gleiche Wahrscheinlichkeit hat, ausgewählt zu werden und keine der ausgewählten Zeilen sich wiederholt.

headund tailkann eine Teilmenge der Datei aber nicht zufällig auswählen. Ich weiß, dass ich dazu immer ein Python-Skript schreiben kann, aber ich frage mich, ob es einen Befehl für diese Verwendung gibt.

Clwen
quelle
Zeilen in zufälliger Reihenfolge oder ein zufälliger Block von 1000 aufeinander folgenden Zeilen dieser Datei?
Frostschutz
Jede Zeile hat die gleiche Wahrscheinlichkeit gewählt zu werden. Sie müssen nicht aufeinanderfolgend sein, obwohl die Wahrscheinlichkeit sehr gering ist, dass aufeinanderfolgende Zeilenblöcke zusammen ausgewählt werden. Ich habe meine Frage aktualisiert, um das klarer zu machen. Vielen Dank.
Clwen
Mein github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl tut dies ungefähr, indem er zu einem zufälligen Ort in der Datei sucht und die nächsten Zeilenumbrüche findet.
Barrycarter

Antworten:

65

Der shufBefehl (Teil von coreutils) kann dies tun:

shuf -n 1000 file

Zumindest für nicht-alte Versionen (hinzugefügt in einem Commit von 2013 ), die bei Bedarf eine Reservoir-Abtastung verwenden, was bedeutet, dass der Speicher nicht knapp werden sollte und ein schneller Algorithmus verwendet wird.

derobert
quelle
Laut Dokumentation wird eine sortierte Datei als Eingabe benötigt: gnu.org/software/coreutils/manual/…
mkc
@ Ketan, scheint nicht so
Frostschutz
2
@Ketan es ist nur im falschen Abschnitt des Handbuchs, glaube ich. Beachten Sie, dass auch die Beispiele im Handbuch nicht sortiert sind. Beachten Sie auch, dass sortes sich im selben Abschnitt befindet und eindeutig keine sortierte Eingabe erfordert.
Derobert
2
shufCoreutils wurde in der Version eingeführt 6.0 (2006-08-15), und ob Sie es glauben oder nicht, einige vernünftigerweise gebräuchliche Systeme (insbesondere CentOS 6.5) haben diese Version nicht: - |
offby1
2
@petrelharp shuf -nführt eine Reservoir-Abtastung durch, zumindest wenn die Eingabe größer als 8 KB ist. Die ermittelte Größe ist Benchmarks besser. Siehe den Quellcode (z. B. unter github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Entschuldigen Sie diese sehr späte Antwort. Anscheinend ist das seit 6 Jahren neu.
Derobert
16

Wenn Sie eine sehr große Datei haben (was ein häufiger Grund ist, ein Beispiel zu nehmen), werden Sie feststellen, dass:

  1. shuf erschöpft den Speicher
  2. Die Verwendung $RANDOMfunktioniert nicht richtig, wenn die Datei mehr als 32767 Zeilen enthält

Wenn Sie nicht "genau" n abgetastete Linien benötigen, können Sie ein Verhältnis wie das folgende abtasten:

cat input.txt | awk 'BEGIN {srand()} !/^$/ { if (rand() <= .01) print $0}' > sample.txt

Dies verwendet konstanten Speicher , tastet 1% der Datei ab (wenn Sie die Anzahl der Zeilen der Datei kennen, können Sie diesen Faktor anpassen, um eine begrenzte Anzahl von Zeilen abzutasten) und funktioniert mit jeder Dateigröße, dies wird jedoch nicht der Fall sein Rückgabe eines genauen Anzahl von Zeilen zurück, nur ein statistisches Verhältnis.

Hinweis: Der Code stammt von: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-with-slurping-it-with-unix

Txangel
quelle
Wenn ein Benutzer ungefähr 1% der nicht leeren Zeilen haben möchte , ist dies eine ziemlich gute Antwort. Wenn der Benutzer jedoch eine genaue Anzahl von Zeilen wünscht (z. B. 1000 aus einer 1000000-Zeilendatei), schlägt dies fehl. Wie die Antwort, die Sie erhalten haben, besagt, gibt es nur eine statistische Schätzung. Und verstehst du die Antwort gut genug, um zu sehen, dass sie leere Zeilen ignoriert? In der Praxis mag dies eine gute Idee sein, aber undokumentierte Features sind im Allgemeinen keine gute Idee.
G-Man sagt, dass Monica
1
PS:   Vereinfachte Vorgehensweisen $RANDOMfunktionieren bei Dateien mit mehr als 32767 Zeilen nicht ordnungsgemäß. Die Aussage „Mit $RANDOMwird nicht die gesamte Datei erreicht“ ist etwas weit gefasst.
G-Man sagt, dass Monica
@ G-Man Die Frage scheint über das Erhalten von 10k Linien von einer Million als Beispiel zu sprechen. Keine der Antworten funktionierte für mich (aufgrund der Größe der Dateien und der Hardware-Einschränkungen) und ich schlage dies als vernünftigen Kompromiss vor. Es bringt Ihnen nicht 10.000 Zeilen aus einer Million heraus, aber es könnte für die meisten praktischen Zwecke nah genug sein. Ich habe es nach Ihrem Rat etwas klarer formuliert. Vielen Dank.
Txangel
Dies ist die beste Antwort. Die Zeilen werden zufällig ausgewählt, wobei die chronologische Reihenfolge der Originaldatei beachtet wird, falls dies erforderlich ist. Darüber hinaus awkist ressourcenschonender alsshuf
Polymerase
Wenn Sie eine genaue Zahl benötigen, können Sie immer ... Führen Sie diese mit einem Prozent mehr aus, als Sie benötigen. Zähle das Ergebnis. Entfernen Sie die Zeilen, die dem Unterschied zwischen Zählern und Mods entsprechen.
Bruno Bronosky
6

Ähnlich wie bei der probabilistischen Lösung von @ Txangel, jedoch fast 100x schneller.

perl -ne 'print if (rand() < .01)' huge_file.csv > sample.csv

Wenn Sie eine hohe Leistung und eine genaue Stichprobengröße benötigen und gerne mit einer Stichprobenlücke am Ende der Datei leben, können Sie wie folgt vorgehen (Beispiel: 1000 Zeilen aus einer 1-Meter-Datei):

perl -ne 'print if (rand() < .0012)' huge_file.csv | head -1000 > sample.csv

.. oder in der Tat eine zweite Beispielmethode anstelle von verketten head.

Geotheorie
quelle
5

Wenn der shuf -nTrick bei großen Dateien zu wenig Speicher hat und Sie immer noch ein Beispiel mit fester Größe benötigen und ein externes Dienstprogramm installiert werden kann, versuchen Sie es mit sample :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

Die Einschränkung ist, dass die Probe (1000 Zeilen im Beispiel) in den Speicher passen muss.

Haftungsausschluss: Ich bin der Autor der empfohlenen Software.

hroptatyr
quelle
1
Für diejenigen, die es installieren und ihre /usr/local/binVorgänger /usr/bin/auf dem Weg haben, ist es wichtig, dass macOS einen eingebauten Call-Stack-Sampler enthält sample, der etwas völlig anderes ausführt /usr/bin/.
Denis de Bernardy
2

Ich kenne keinen einzelnen Befehl, der das tun könnte, was Sie verlangen, aber hier ist eine Schleife, die ich zusammengestellt habe, um den Job zu erledigen:

for i in `seq 1000`; do sed -n `echo $RANDOM % 1000000 | bc`p alargefile.txt; done > sample.txt

sedwird bei jedem der 1000 Durchgänge eine zufällige Linie aufnehmen. Möglicherweise gibt es effizientere Lösungen.

mkc
quelle
Ist es bei diesem Ansatz möglich, dieselbe Zeile mehrmals abzurufen?
Clwen
1
Ja, es ist durchaus möglich, dieselbe Zeilennummer mehrmals zu erhalten. Darüber hinaus $RANDOMhat eine Reichweite zwischen 0 und 32767. So werden Sie nicht eine gut Verbreitung Zeilennummern erhalten.
MKC
funktioniert nicht - Zufall wird einmal aufgerufen
Bohdan
2

Sie können den folgenden Code in einer Datei speichern (zum Beispiel randextract.sh) und ausführen als:

randextract.sh file.txt

---- DATEI ANFANGEN ----

#!/bin/sh -xv

#configuration MAX_LINES is the number of lines to extract
MAX_LINES=10

#number of lines in the file (is a limit)
NUM_LINES=`wc -l $1 | cut -d' ' -f1`

#generate a random number
#in bash the variable $RANDOM returns diferent values on each call
if [ "$RANDOM." != "$RANDOM." ]
then
    #bigger number (0 to 3276732767)
    RAND=$RANDOM$RANDOM
else
    RAND=`date +'%s'`
fi 

#The start line
START_LINE=`expr $RAND % '(' $NUM_LINES - $MAX_LINES ')'`

tail -n +$START_LINE $1 | head -n $MAX_LINES

---- END FILE ----

Razzek
quelle
3
Ich bin nicht sicher, was Sie hier mit RAND versuchen, aber $RANDOM$RANDOMes werden keine Zufallszahlen im gesamten Bereich von „0 bis 3276732767“ generiert (z. B. 1000100000, aber nicht 1000099999).
Gilles 'SO- hör auf böse zu sein'
Das OP sagt: „Jede Linie erhält die gleiche Wahrscheinlichkeit, ausgewählt zu werden. … Es besteht eine winzige Wahrscheinlichkeit, dass aufeinanderfolgende Zeilenblöcke zusammen ausgewählt werden. “Ich finde diese Antwort auch kryptisch, aber es sieht so aus, als würde ein 10-zeiliger Block aufeinanderfolgender Zeilen von einem zufälligen Startpunkt extrahiert. Darum bittet das OP nicht.
G-Man sagt, dass Monica
2

Wenn Sie die Anzahl der Zeilen in der Datei kennen (wie 1e6 in Ihrem Fall), können Sie Folgendes tun:

awk -v n=1e6 -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}' < file

Wenn nicht, können Sie immer tun

awk -v n="$(wc -l < file)" -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}' < file

Das würde zwei Durchgänge in der Datei machen, aber immer noch vermeiden, die gesamte Datei im Speicher zu speichern.

Ein weiterer Vorteil gegenüber GNU shuf ist, dass die Reihenfolge der Zeilen in der Datei beibehalten wird.

Beachten Sie, dass davon ausgegangen n wird, wie viele Zeilen die Datei enthält. Wenn Sie paus den ersten n Zeilen der Datei (die möglicherweise mehr Zeilen enthält) drucken möchten , müssen Sie awkan der nth- Zeile wie folgt anhalten :

awk -v n=1e6 -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}
  !n {exit}' < file
Stéphane Chazelas
quelle
2

Ich verwende awk gerne, wenn ich eine Kopfzeile beibehalten möchte und wenn das Beispiel ein ungefährer Prozentsatz der Datei sein kann. Funktioniert für sehr große Dateien:

awk 'BEGIN {srand()} !/^$/ { if (rand() <= .01 || FNR==1) print > "data-sample.txt"}' data.txt
Merlin
quelle
1

Oder so:

LINES=$(wc -l < file)  
RANDLINE=$[ $RANDOM % $LINES ]  
tail -n $RANDLINE  < file|head -1  

Von der Bash-Manpage:

        RANDOM Jedes Mal, wenn auf diesen Parameter verwiesen wird, eine zufällige Ganzzahl
              zwischen 0 und 32767 wird generiert. Die zufällige Reihenfolge
              Nummern können durch Zuweisen eines Wertes zu RAN initialisiert werden
              DOM. Wenn RANDOM nicht gesetzt ist, verliert es seine
              auch wenn es später zurückgesetzt wird.

quelle
Dies schlägt fehl, wenn die Datei weniger als 32767 Zeilen enthält.
1.
Dadurch wird eine Zeile aus der Datei ausgegeben . (Ich denke, Ihre Idee ist es, die obigen Befehle in einer Schleife auszuführen?) Wenn die Datei mehr als 32767 Zeilen enthält, werden diese Befehle nur aus den ersten 32767 Zeilen ausgewählt. Abgesehen von der möglichen Ineffizienz sehe ich bei dieser Antwort kein großes Problem, wenn die Datei weniger als 32767 Zeilen enthält.
G-Man sagt, dass Monica
1

Wenn die Dateigröße nicht sehr groß ist, können Sie die Option Zufällig sortieren verwenden. Das dauert etwas länger als shuf, aber es ordnet die gesamten Daten nach dem Zufallsprinzip. Sie können also ganz einfach Folgendes tun, um head wie gewünscht zu verwenden:

sort -R input | head -1000 > output

Dies würde die Datei nach dem Zufallsprinzip sortieren und Ihnen die ersten 1000 Zeilen geben.

DomainsFeatured
quelle
0

Wie in der akzeptierten Antwort erwähnt, shufunterstützt GNU shuf -nziemlich gut einfache Zufallsstichproben ( ). Wenn Stichprobenmethoden shuferforderlich sind, die über die von unterstützten Methoden hinausgehen , ziehen Sie tsv-sample von eBay TSV Utilities in Betracht . Es werden mehrere zusätzliche Stichprobenmodi unterstützt, einschließlich gewichteter Zufallsstichproben, Bernoulli-Stichproben und eindeutiger Stichproben. Die Leistung ist ähnlich wie bei GNU shuf(beide sind recht schnell). Haftungsausschluss: Ich bin der Autor.

JonDeg
quelle