Cara sampel secara acak bagian dari suatu file

39

Apakah ada perintah Linux yang dapat digunakan untuk sampel subset file? Misalnya, file berisi satu juta baris, dan kami ingin mengambil sampel acak hanya seribu baris dari file itu.

Secara acak saya maksudkan bahwa setiap baris mendapatkan probabilitas yang sama untuk dipilih dan tidak ada baris yang dipilih yang berulang.

headdan taildapat memilih subset file tetapi tidak secara acak. Saya tahu saya selalu bisa menulis skrip python untuk melakukannya tetapi hanya ingin tahu apakah ada perintah untuk penggunaan ini.

Clwen
sumber
baris dalam urutan acak, atau blok acak 1000 baris berturut-turut dari file itu?
frostschutz
Setiap baris mendapat probabilitas yang sama untuk dipilih. Tidak perlu berturut-turut meskipun ada kemungkinan kecil bahwa blok garis berurutan dipilih bersama. Saya telah memperbarui pertanyaan saya untuk lebih jelas tentang itu. Terima kasih.
Clwen
Github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl saya melakukan hal ini dengan mencari lokasi acak dalam file dan menemukan baris baru terdekat.
barrycarter

Jawaban:

66

The shufperintah (bagian dari coreutils) dapat melakukan ini:

shuf -n 1000 file

Dan setidaknya untuk versi non-kuno sekarang (ditambahkan dalam komit dari 2013 ), yang akan menggunakan pengambilan sampel reservoir jika sesuai, artinya tidak boleh kehabisan memori dan menggunakan algoritma cepat.

derobert
sumber
Menurut dokumentasi, diperlukan file yang diurutkan sebagai input: gnu.org/software/coreutils/manual/…
mkc
@Ketan, sepertinya tidak seperti itu
frostschutz
2
@Ketan itu hanya di bagian yang salah dari manual, saya percaya. Perhatikan bahwa bahkan contoh-contoh dalam manual tidak disortir. Perhatikan juga bahwa sortada di bagian yang sama, dan itu jelas tidak memerlukan input yang diurutkan.
derobert
2
shufdiperkenalkan ke coreutils dalam versi 6.0 (2006-08-15), dan percaya atau tidak, beberapa sistem yang cukup umum (CentOS 6.5 khususnya) tidak memiliki versi itu: - |
offby1
2
@petrelharp shuf -nmelakukan sampling reservoir, setidaknya ketika input lebih besar dari 8K, yang merupakan ukuran yang mereka tentukan adalah tolok ukur yang lebih baik. Lihat kode sumber (misalnya, di github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Maaf atas jawaban yang sangat terlambat ini. Rupanya itu baru 6 tahun yang lalu.
derobert
16

Jika Anda memiliki file yang sangat besar (yang merupakan alasan umum untuk mengambil sampel), Anda akan menemukan bahwa:

  1. shuf kehabisan memori
  2. Menggunakan $RANDOMtidak akan berfungsi dengan benar jika file melebihi 32767 baris

Jika Anda tidak membutuhkan "tepat" di baris sampel, Anda dapat mencicipi rasio seperti ini:

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

Ini menggunakan memori konstan , sampel 1% dari file (jika Anda tahu jumlah baris file Anda dapat menyesuaikan faktor ini untuk sampel yang mendekati jumlah baris terbatas), dan bekerja dengan ukuran file berapa pun tetapi tidak akan mengembalikan tepat jumlah baris, hanya rasio statistik.

Catatan: Kode tersebut berasal dari: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix

Txangel
sumber
Jika pengguna menginginkan sekitar 1% dari baris yang tidak kosong, ini adalah jawaban yang cukup bagus. Tetapi jika pengguna menginginkan jumlah baris yang tepat (mis. 1000 dari file 10.00000), ini gagal. Seperti jawaban yang Anda dapatkan dari kata, itu hanya menghasilkan perkiraan statistik. Dan apakah Anda memahami jawabannya dengan cukup baik untuk melihat bahwa ia mengabaikan garis kosong? Ini mungkin ide yang bagus, dalam praktiknya, tetapi fitur yang tidak berdokumen pada umumnya bukan ide yang baik.
G-Man Mengatakan 'Reinstate Monica'
1
Penggunaan pendekatan PS   Simplistic$RANDOM tidak akan berfungsi dengan benar untuk file yang lebih besar dari 32767 baris. Pernyataan "Menggunakan $RANDOMtidak mencapai seluruh file" agak luas.
G-Man Mengatakan 'Reinstate Monica'
@ G-Man Pertanyaannya sepertinya berbicara tentang mendapatkan 10 ribu baris dari satu juta sebagai contoh. Tidak ada jawaban di sekitar yang berhasil bagi saya (karena ukuran file dan keterbatasan perangkat keras) dan saya mengusulkan ini sebagai kompromi yang masuk akal. Ini tidak akan membuat Anda mendapatkan 10 ribu baris dari satu juta, tetapi mungkin cukup dekat untuk sebagian besar tujuan praktis. Saya sudah mengklarifikasi sedikit lebih mengikuti saran Anda. Terima kasih.
Txangel
Ini adalah jawaban terbaik, baris dipilih secara acak dengan tetap menghormati urutan kronologis dari file asli, dalam kasus ini adalah persyaratan. Selain itu awklebih ramah sumber daya daripadashuf
Polymerase
Jika Anda membutuhkan angka pasti, Anda selalu dapat… Jalankan ini dengan% lebih besar dari kebutuhan Anda. Hitung hasilnya. Hapus garis yang cocok dengan perbedaan mod mod.
Bruno Bronosky
6

Mirip dengan solusi probabilistik @ Txangel tetapi mendekati 100x lebih cepat.

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

Jika Anda membutuhkan kinerja tinggi, ukuran sampel yang tepat, dan senang tinggal dengan celah sampel di akhir file, Anda dapat melakukan sesuatu seperti berikut (sampel 1000 baris dari file baris 1m):

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

.. Atau memang rantai metode sampel kedua alih-alih head.

geotheory
sumber
5

Jika shuf -ntrik pada file besar kehabisan memori dan Anda masih perlu sampel ukuran tetap dan utilitas eksternal dapat diinstal kemudian coba sampel :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

Peringatannya adalah bahwa sampel (1000 baris dalam contoh) harus sesuai dengan memori.

Penafian: Saya adalah pembuat perangkat lunak yang direkomendasikan.

hroptatyr
sumber
1
Bagi mereka yang menginstal dan memiliki /usr/local/binsebelum mereka /usr/bin/di jalan mereka, berhati-hatilah bahwa macOS datang dengan built-in call-stack sampler yang disebut sample, yang melakukan sesuatu yang sama sekali berbeda, di /usr/bin/.
Denis de Bernardy
2

Tidak mengetahui adanya perintah tunggal yang bisa melakukan apa yang Anda minta tetapi di sini ada satu loop yang saya kumpulkan yang dapat melakukan pekerjaan:

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

sedakan mengambil garis acak pada masing-masing 1000 pass. Mungkin ada solusi yang lebih efisien.

mkc
sumber
Apakah mungkin mendapatkan garis yang sama beberapa kali dalam pendekatan ini?
Clwen
1
Ya, sangat mungkin untuk mendapatkan nomor baris yang sama lebih dari satu kali. Selain itu, $RANDOMmemiliki rentang antara 0 dan 32767. Jadi, Anda tidak akan mendapatkan nomor baris yang tersebar dengan baik.
mkc
tidak bekerja - acak dipanggil sekali
Bohdan
2

Anda dapat menyimpan kode ikuti dalam file (dengan contoh randextract.sh) dan jalankan sebagai:

randextract.sh file.txt

---- FILE AWAL ----

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

razzek
sumber
3
Saya tidak yakin apa yang Anda coba lakukan di sini dengan RAND, tetapi $RANDOM$RANDOMtidak menghasilkan angka acak di seluruh rentang "0 hingga 3276732767" (misalnya, itu akan menghasilkan 1000100000 tetapi tidak 1000099999).
Gilles 'SANGAT berhenti menjadi jahat'
OP mengatakan, “Setiap baris mendapat probabilitas yang sama untuk dipilih. ... ada kemungkinan kecil bahwa satu blok garis berturut-turut dipilih bersama-sama. ”Saya juga menemukan jawaban ini bersifat samar, tetapi sepertinya itu mengekstraksi blok 10-baris dari garis berurutan dari titik awal yang acak. Bukan itu yang diminta OP.
G-Man Mengatakan 'Reinstate Monica'
2

Jika Anda tahu jumlah baris dalam file (seperti 1e6 dalam kasus Anda), Anda dapat melakukan:

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

Jika tidak, Anda selalu bisa melakukannya

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

Itu akan melakukan dua lintasan dalam file, tetapi masih menghindari menyimpan seluruh file dalam memori.

Keuntungan lain dari GNU shufadalah menjaga urutan baris dalam file.

Perhatikan bahwa diasumsikan n adalah jumlah baris dalam file. Jika Anda ingin mencetak pkeluar dari pertama n baris dari file (yang memiliki potensial lebih baris), Anda akan perlu untuk berhenti awkdi nth baris seperti:

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

Saya suka menggunakan awk untuk ini ketika saya ingin mempertahankan baris tajuk, dan ketika sampel bisa menjadi persentase perkiraan file. Bekerja untuk file yang sangat besar:

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

Atau seperti ini:

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

Dari halaman bash man:

        ACAK Setiap kali parameter ini direferensikan, bilangan bulat acak
              antara 0 dan 32767 dihasilkan. Urutan acak
              angka dapat diinisialisasi dengan menetapkan nilai ke RAN–
              DOM. Jika RANDOM tidak disetel, itu kehilangan hak khusus-
              mengikat, bahkan jika kemudian diatur ulang.

sumber
Ini gagal parah jika file memiliki kurang dari 32767 baris.
offby1
Ini akan menampilkan satu baris dari file. (Saya kira ide Anda adalah menjalankan perintah di atas dalam satu lingkaran?) Jika file memiliki lebih dari 32767 baris, maka perintah ini hanya akan memilih dari 32767 baris pertama. Selain kemungkinan inefisiensi, saya tidak melihat masalah besar dengan jawaban ini jika file memiliki kurang dari 32767 baris.
G-Man Mengatakan 'Reinstate Monica'
1

Jika ukuran file Anda tidak besar, Anda dapat menggunakan Sort secara acak. Ini membutuhkan waktu sedikit lebih lama daripada shuf, tetapi ini mengacak seluruh data. Jadi, Anda dapat dengan mudah melakukan hal berikut untuk menggunakan kepala seperti yang Anda minta:

sort -R input | head -1000 > output

Ini akan mengurutkan file secara acak dan memberi Anda 1000 baris pertama.

DomainsFeatured
sumber
0

Seperti disebutkan dalam jawaban yang diterima, GNU shufmendukung simple random sampling ( shuf -n) dengan cukup baik. Jika diperlukan metode pengambilan sampel di luar yang didukung shuf, pertimbangkan sampel-tsv dari TSV Utilities eBay . Ini mendukung beberapa mode pengambilan sampel tambahan, termasuk pengambilan sampel acak tertimbang, pengambilan sampel Bernoulli, dan pengambilan sampel yang berbeda. Performanya mirip dengan GNU shuf(keduanya cukup cepat). Penafian: Saya penulis.

JonDeg
sumber