Come campionare casualmente un sottoinsieme di un file


39

Esiste un comando Linux che è possibile utilizzare per campionare un sottoinsieme di un file? Ad esempio, un file contiene un milione di righe e vogliamo campionare casualmente solo mille righe da quel file.

Per casuale intendo che ogni linea ha la stessa probabilità di essere scelta e nessuna delle linee scelte è ripetitiva.

heade tailpuò scegliere un sottoinsieme del file ma non in modo casuale. So di poter sempre scrivere uno script Python per farlo, ma mi chiedo solo che esiste un comando per questo utilizzo.


righe in ordine casuale o un blocco casuale di 1000 righe consecutive di quel file?
Frostschutz,

Ogni linea ha la stessa probabilità di essere scelta. Non è necessario essere consecutivi, anche se esiste una probabilità molto piccola di scegliere insieme un blocco di linee consecutive. Ho aggiornato la mia domanda per chiarire al riguardo. Grazie.
clwen

Il mio github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl lo fa approssimativamente cercando una posizione casuale nel file e trovando le nuove righe più vicine.
Barrycarter il

Risposte:


66

Il shufcomando (parte di coreutils) può fare questo:

shuf -n 1000 file

E almeno per le versioni non antiche (aggiunte in un commit dal 2013 ), che useranno il campionamento del serbatoio quando appropriato, il che significa che non dovrebbe esaurire la memoria e sta usando un algoritmo veloce.


Secondo la documentazione, ha bisogno di un file ordinato come input: gnu.org/software/coreutils/manual/…
mkc

@Ketan, non sembra così
frostschutz

2
@Ketan è solo nella sezione sbagliata del manuale, credo. Si noti che anche gli esempi nel manuale non sono ordinati. Si noti inoltre che si sorttrova nella stessa sezione e chiaramente non richiede input ordinati.
derobert,

2
shufè stato introdotto in coreutils in versione 6.0 (2006-08-15), e che ci crediate o no, alcuni sistemi ragionevolmente comuni (CentOS 6.5 in particolare) non hanno quella versione: - |
entro il 1 °

2
@petrelharp shuf -nesegue il campionamento del serbatoio, almeno quando l'input è maggiore di 8K, che è la dimensione che hanno determinato è migliore per i benchmark. Vedi il codice sorgente (ad esempio, su github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Ci scusiamo per questa risposta molto tardi. Apparentemente è nuovo a partire da 6 anni fa.
derobert l'

16

Se hai un file molto grande (che è un motivo comune per fare un esempio) scoprirai che:

  1. shuf esaurisce la memoria
  2. L'uso $RANDOMnon funzionerà correttamente se il file supera 32767 righe

Se non hai bisogno di "esattamente" n linee campionate puoi campionare un rapporto come questo:

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

Questo utilizza memoria costante , campiona l'1% del file (se si conosce il numero di righe del file è possibile regolare questo fattore per campionare un numero limitato di righe) e funziona con qualsiasi dimensione del file ma non lo farà restituisce un numero preciso di righe, solo un rapporto statistico.

Nota: il codice proviene da: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix


Se un utente desidera circa l' 1% delle righe non vuote, questa è una risposta abbastanza buona. Ma se l'utente desidera un numero esatto di righe (ad esempio, 1000 su un file di 1000000 righe), ciò non riesce. Come dice la risposta che hai ricevuto, produce solo una stima statistica. E capisci la risposta abbastanza bene da vedere che sta ignorando le righe vuote? Questa potrebbe essere una buona idea, in pratica, ma le caratteristiche prive di documenti non sono, in generale, una buona idea.
G-Man dice "Ripristina Monica" il

1
PS L'   uso di approcci semplicistici$RANDOM non funzionerà correttamente per file di dimensioni superiori a 32767 linee. L'affermazione "L'utilizzo $RANDOMnon raggiunge l'intero file" è un po 'ampia.
G-Man dice 'Reinstate Monica' il

@ G-Man La domanda sembra parlare di come ottenere 10k righe da un milione. Nessuna delle risposte in giro ha funzionato per me (a causa delle dimensioni dei file e delle limitazioni hardware) e propongo questo come un ragionevole compromesso. Non ti farà ottenere 10k linee su un milione ma potrebbe essere abbastanza vicino per la maggior parte degli scopi pratici. L'ho chiarito un po 'di più seguendo i tuoi consigli. Grazie.
Txangel,

Questa è la risposta migliore, le linee vengono scelte casualmente rispettando l'ordine cronologico del file originale, nel caso in cui questo sia un requisito. Inoltre awkè più amico delle risorse dishuf
Polymerase il

Se hai bisogno di un numero esatto, puoi sempre ... Eseguire questo con una percentuale maggiore del tuo bisogno. Conta il risultato. Rimuovi le righe corrispondenti alla differenza mod di conteggio.
Bruno Bronosky,

6

Simile alla soluzione probabilistica di @ Txangel ma si avvicina 100 volte più velocemente.

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

Se hai bisogno di alte prestazioni, dimensioni esatte del campione e sei felice di vivere con un gap campione alla fine del file, puoi fare qualcosa del tipo seguente (campiona 1000 righe da un file di 1m):

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

.. o addirittura concatenare un secondo metodo di esempio anziché head.


5

Nel caso in cui il shuf -ntrucco per i file di grandi dimensioni esaurisca la memoria e sia ancora necessario un campione di dimensioni fisse e sia possibile installare un'utilità esterna, quindi provare l' esempio :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

L'avvertenza è che il campione (1000 righe nell'esempio) deve adattarsi alla memoria.

Disclaimer: sono l'autore del software consigliato.


1
Per coloro che lo installano e hanno i loro /usr/local/binprecedenti /usr/bin/nel loro percorso, diffidare che macOS viene fornito con un campionatore di stack di chiamate sampleincorporato chiamato , che fa qualcosa di completamente diverso, in /usr/bin/.
Denis de Bernardy,

2

Non sono a conoscenza di alcun singolo comando che potrebbe fare ciò che chiedi, ma ecco un ciclo che ho messo insieme che può fare il lavoro:

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

sedprenderà una linea casuale su ciascuno dei 1000 passaggi. Forse ci sono soluzioni più efficienti.


È possibile ottenere la stessa riga più volte in questo approccio?
clwen

1
Sì, è possibile ottenere lo stesso numero di riga più di una volta. Inoltre, $RANDOMha un intervallo compreso tra 0 e 32767. Quindi, non otterrai numeri di riga ben distribuiti.
MK

non funziona - random viene chiamato una volta
Bohdan

2

È possibile salvare il codice seguente in un file (ad esempio randextract.sh) ed eseguire come:

randextract.sh file.txt

---- INIZIA FILE ----

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

---- FILE FINE ----


3
Non sono sicuro di cosa stai cercando di fare qui con RAND, ma $RANDOM$RANDOMnon genera numeri casuali nell'intero intervallo da "0 a 3276732767" (ad esempio, genererà 1000100000 ma non 1000099999).
Gilles 'SO- smetti di essere malvagio' il

L'OP dice: “Ogni linea ha la stessa probabilità di essere scelta. ... c'è una piccola probabilità che un blocco di linee consecutive sia scelto insieme. "Trovo anche questa risposta criptica, ma sembra che stia estraendo un blocco di 10 linee di linee consecutive da un punto di partenza casuale. Non è questo ciò che l'OP chiede.
G-Man dice 'Reinstate Monica' il

2

Se conosci il numero di righe nel file (come 1e6 nel tuo caso), puoi fare:

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

In caso contrario, puoi sempre fare

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

Ciò farebbe due passaggi nel file, ma eviterebbe comunque di memorizzare l'intero file in memoria.

Un altro vantaggio rispetto a GNU shufè che mantiene l'ordine delle linee nel file.

Si noti che presuppone n sia il numero di righe nel file. Se si desidera stampare pfuori dalle prime n righe del file (che ha potenzialmente più linee), avresti bisogno di fermarsi awkal nesima linea come:

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

2

Mi piace usare awk per questo quando voglio preservare una riga di intestazione e quando il campione può essere una percentuale approssimativa del file. Funziona con file molto grandi:

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

1

O così:

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

Dalla pagina man di bash:

        CASUALE Ad ogni riferimento a questo parametro, un numero intero casuale
              tra 0 e 32767 viene generato. La sequenza di casuale
              i numeri possono essere inizializzati assegnando un valore a RAN‐
              DOM. Se RANDOM non è impostato, perde la sua proprietà propria-
              legami, anche se successivamente viene ripristinato.

Ciò non riesce se il file ha meno di 32767 righe.
entro il 1 °

Questo produrrà una riga dal file. (Immagino che la tua idea sia quella di eseguire i comandi sopra in un ciclo?) Se il file ha più di 32767 righe, questi comandi sceglieranno solo dalle prime 32767 righe. A parte la possibile inefficienza, non vedo grossi problemi con questa risposta se il file ha meno di 32767 righe.
G-Man dice "Ripristina Monica" il

1

Se la dimensione del tuo file non è enorme, puoi usare Ordina casuali. Questo richiede un po 'più di tempo di shuf, ma randomizza tutti i dati. Quindi, potresti facilmente fare quanto segue per usare head come richiesto:

sort -R input | head -1000 > output

Questo ordinerebbe il file in modo casuale e ti darebbe le prime 1000 righe.


0

Come menzionato nella risposta accettata, GNU shufsupporta shuf -nabbastanza semplicemente il campionamento casuale ( ). Se shufsono necessari metodi di campionamento oltre a quelli supportati da , prendere in considerazione tsv-sample dalle Utilità TSV di eBay . Supporta diverse modalità di campionamento aggiuntive, tra cui campionamento casuale ponderato, campionamento di Bernoulli e campionamento distinto. Le prestazioni sono simili a GNU shuf(entrambe sono abbastanza veloci). Disclaimer: sono l'autore.

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.