Jak losowo próbkować podzbiór pliku

38

Czy jest jakieś polecenie Linuksa, którego można użyć do próbkowania podzbioru pliku? Na przykład plik zawiera milion wierszy, a my chcemy losowo pobrać próbkę tylko tysiąca wierszy z tego pliku.

Dla losowych mam na myśli to, że każda linia ma takie samo prawdopodobieństwo wyboru i żadna z wybranych linii nie jest powtarzalna.

headi tailmoże wybrać podzbiór pliku, ale nie losowo. Wiem, że zawsze mogę napisać skrypt Pythona, ale zastanawiam się, czy istnieje takie polecenie.

clwen
źródło
wiersze w losowej kolejności, czy losowy blok 1000 kolejnych wierszy tego pliku?
frostschutz
Każda linia ma takie samo prawdopodobieństwo wyboru. Nie muszą być kolejne, chociaż istnieje niewielkie prawdopodobieństwo, że kolejny blok linii zostanie wybrany razem. Zaktualizowałem moje pytanie, aby wyjaśnić tę kwestię. Dzięki.
clwen
Mój github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl robi to w przybliżeniu, szukając losowej lokalizacji w pliku i znajdując najbliższe znaki nowej linii.
barrycarter

Odpowiedzi:

65

shufKomenda (część coreutils) może to zrobić:

shuf -n 1000 file

I przynajmniej na razie nie starożytne wersje (dodane w zatwierdzeniu z 2013 r. ), Które w razie potrzeby będą wykorzystywać próbkowanie zbiornika, co oznacza, że ​​nie powinno zabraknąć pamięci i korzysta z szybkiego algorytmu.

derobert
źródło
Zgodnie z dokumentacją potrzebuje posortowanego pliku jako danych wejściowych: gnu.org/software/coreutils/manual/…
mkc
@Ketan, nie wydaje się w ten sposób
frostschutz
2
@Ketan, jak sądzę, jest po prostu w niewłaściwej części instrukcji. Pamiętaj, że nawet przykłady w podręczniku nie są sortowane. Zauważ też, że sortznajduje się w tej samej sekcji i wyraźnie nie wymaga posortowanych danych wejściowych.
derobert
2
shufzostał wprowadzony do coreutils w wersji 6.0 (2006-08-15)i wierzcie lub nie, niektóre dość powszechne systemy (w szczególności CentOS 6.5) nie mają tej wersji: - |
offby1,
2
@petrelharp shuf -nwykonuje próbkowanie w zbiorniku, przynajmniej wtedy, gdy sygnał wejściowy jest większy niż 8 KB, co oznacza, że ​​rozmiar, który ustalili, jest lepszy w testach porównawczych. Zobacz kod źródłowy (np. Na github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Przepraszam za tę bardzo późną odpowiedź. Najwyraźniej to nowość sprzed 6 lat.
derobert,
16

Jeśli masz bardzo duży plik (co jest częstym powodem do pobrania próbki), przekonasz się, że:

  1. shuf wyczerpuje pamięć
  2. Używanie $RANDOMnie będzie działać poprawnie, jeśli plik przekroczy 32767 linii

Jeśli nie potrzebujesz „dokładnie” n próbkowanych linii , możesz próbkować taki współczynnik :

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

Ten wykorzystuje stałą pamięć , próbki 1% pliku (jeśli wiesz liczbę wierszy w pliku można dostosować ten czynnik do próbki blisko do ograniczonej liczby linii) i współpracuje z dowolnym rozmiarze pliku, ale to nie będzie zwraca dokładną liczbę wierszy, tylko współczynnik statystyczny.

Uwaga: kod pochodzi z: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix

Txangel
źródło
Jeśli użytkownik chce około 1% niepustych wierszy, jest to całkiem dobra odpowiedź. Ale jeśli użytkownik chce dokładnej liczby wierszy (np. 1000 z pliku 1000000 wierszy), to się nie udaje. Jak wynika z otrzymanej odpowiedzi, daje ona jedynie szacunek statystyczny. I czy rozumiesz odpowiedź wystarczająco dobrze, aby zobaczyć, że ignoruje puste linie? W praktyce może to być dobry pomysł, ale nieudokumentowane funkcje nie są na ogół dobrym pomysłem.
G-Man mówi „Przywróć Monikę”
1
PS   Uproszczone podejście przy użyciu $RANDOMnie działa poprawnie dla plików większych niż 32767 linii. Stwierdzenie „Korzystanie $RANDOMnie obejmuje całego pliku” jest nieco ogólne.
G-Man mówi „Przywróć Monikę”
@ G-Man Pytanie wydaje się mówić o pobieraniu 10 000 linii z miliona jako przykład. Żadna z tych odpowiedzi nie działała dla mnie (ze względu na rozmiar plików i ograniczenia sprzętowe) i proponuję to jako rozsądny kompromis. Nie da ci to 10 000 linii na milion, ale może być wystarczająco blisko do większości praktycznych celów. Wyjaśniłem to nieco bardziej zgodnie z twoją radą. Dzięki.
Txangel
To najlepsza odpowiedź, linie są wybierane losowo, z zachowaniem kolejności chronologicznej oryginalnego pliku, na wypadek, gdyby było to wymagane. Ponadto awkjest bardziej przyjazny dla zasobów niżshuf
polimeraza
Jeśli potrzebujesz dokładnej liczby, zawsze możesz… Uruchomić ją z procentem większym niż potrzebujesz. Policz wynik. Usuń linie pasujące do różnicy modów.
Bruno Bronosky
6

Podobne do probabilistycznego rozwiązania @ Txangel, ale zbliża się 100 razy szybciej.

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

Jeśli potrzebujesz wysokiej wydajności, dokładnej wielkości próbki i z przyjemnością żyjesz z przerwą na próbkę na końcu pliku, możesz zrobić coś takiego: (próbkuje 1000 linii z pliku linii 1m):

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

.. lub w rzeczywistości łańcuch drugiej metody próbnej zamiast head.

geotheory
źródło
5

Jeśli w przypadku shuf -ndużych plików zabraknie pamięci i nadal potrzebujesz próbki o stałym rozmiarze i można zainstalować narzędzie zewnętrzne, wypróbuj próbkę :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

Zastrzeżenie polega na tym, że próbka (1000 linii w przykładzie) musi zmieścić się w pamięci.

Oświadczenie: Jestem autorem zalecanego oprogramowania.

hroptatyr
źródło
1
Dla tych, którzy go instalują i mają już /usr/local/binwcześniej /usr/bin/na swojej drodze, uważaj, aby macOS był wyposażony we wbudowany sampler stosów wywołań sample, który robi coś zupełnie innego /usr/bin/.
Denis de Bernardy,
2

Nie znam żadnego pojedynczego polecenia, które mogłoby zrobić to, o co prosisz, ale oto pętla, którą razem stworzyłem:

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

sedodbierze losową linię na każdym z 1000 przejść. Być może istnieją bardziej wydajne rozwiązania.

mkc
źródło
Czy w tym podejściu można wielokrotnie uzyskać tę samą linię?
clwen
1
Tak, całkiem możliwe jest uzyskanie tego samego numeru linii więcej niż jeden raz. Dodatkowo $RANDOMma zakres od 0 do 32767. Tak więc nie otrzymasz dobrze rozłożonych numerów linii.
mkc
nie działa - losowo nazywa się raz
Bohdan
2

Możesz zapisać następujący kod w pliku (na przykład randextract.sh) i wykonać jako:

randextract.sh file.txt

---- ROZPOCZNIJ PLIK ----

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

---- PLIK KOŃCOWY ----

razzek
źródło
3
Nie jestem pewien, co próbujesz tutaj zrobić z RAND, ale $RANDOM$RANDOMnie generuje liczb losowych w całym zakresie od „0 do 3276732767” (na przykład wygeneruje 1000100000, ale nie 1000099999).
Gilles „SO- przestań być zły”
OP mówi: „Każda linia ma takie samo prawdopodobieństwo wyboru. … Istnieje niewielkie prawdopodobieństwo, że wybierzesz razem kolejny blok linii ”. Uważam również, że ta odpowiedź jest tajemnicza, ale wygląda na to, że wyodrębnia 10-liniowy blok kolejnych linii z losowego punktu początkowego. Nie o to prosi OP.
G-Man mówi „Przywróć Monikę”
2

Jeśli znasz liczbę wierszy w pliku (np. 1e6 w twoim przypadku), możesz:

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

Jeśli nie, zawsze możesz to zrobić

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

Spowodowałoby to dwa przejścia do pliku, ale nadal pozwalałoby uniknąć przechowywania całego pliku w pamięci.

Kolejną zaletą w stosunku do GNU shufjest to, że zachowuje kolejność linii w pliku.

Zauważ, że zakłada n ona liczbę wierszy w pliku. Jeśli chcesz drukować pz tych pierwszych n linii pliku (który ma potencjalnie więcej linii), trzeba by zatrzymać awkw nXX linii, takich jak:

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

Lubię używać awk do tego, gdy chcę zachować wiersz nagłówka i kiedy próbka może stanowić przybliżony procent pliku. Działa dla bardzo dużych plików:

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

Lub tak:

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

Ze strony podręcznika użytkownika bash:

        RANDOM Za każdym razem, gdy odwołuje się do tego parametru, losowa liczba całkowita
              generowane jest od 0 do 32767. Sekwencja losowa
              liczby mogą być inicjalizowane poprzez przypisanie wartości do RAN-
              DOM. Jeśli RANDOM jest rozbrojony, traci swoje specjalne właściwości
              więzi, nawet jeśli następnie zostaną zresetowane.

źródło
To kończy się niepowodzeniem, jeśli plik ma mniej niż 32767 linii.
offby1
Spowoduje to wyświetlenie jednego wiersza z pliku. (Myślę, że twoim pomysłem jest wykonanie powyższych poleceń w pętli?) Jeśli plik ma więcej niż 32767 linii, wówczas te polecenia wybiorą tylko z pierwszych 32767 linii. Oprócz możliwej nieefektywności, nie widzę żadnego dużego problemu z tą odpowiedzią, jeśli plik ma mniej niż 32767 linii.
G-Man mówi „Przywróć Monikę”
1

Jeśli rozmiar pliku nie jest ogromny, możesz użyć opcji Sortuj losowo. Trwa to trochę dłużej niż shuf, ale losuje całe dane. Możesz więc z łatwością wykonać następujące czynności, aby użyć głowicy zgodnie z żądaniem:

sort -R input | head -1000 > output

Spowoduje to sortowanie pliku losowo i otrzymanie pierwszych 1000 wierszy.

Domeny Wyróżnione
źródło
0

Jak wspomniano w przyjętej odpowiedzi, GNU całkiem dobrze shufobsługuje proste losowe próbkowanie ( shuf -n). Jeśli shufpotrzebne są metody próbkowania wykraczające poza obsługiwane przez , należy rozważyć tsv-sample z TSV Utilities na eBayu . Obsługuje kilka dodatkowych trybów próbkowania, w tym ważone losowe próbkowanie, próbkowanie Bernoulliego i próbkowanie odrębne. Wydajność jest podobna do GNU shuf(oba są dość szybkie). Oświadczenie: Jestem autorem.

JonDeg
źródło