Bir dosyanın alt kümesini rastgele örnekleme


38

Bir dosyanın altkümesini örneklemek için kullanılabilecek herhangi bir Linux komutu var mı? Örneğin, bir dosya bir milyon satır içeriyor ve rastgele o dosyadan yalnızca bin satırını örneklemek istiyoruz.

Rastgele olarak, her çizginin seçilmek için aynı olasılığı elde ettiği ve seçilen çizgilerin hiçbirinin tekrarlayan olmadığı anlamına gelir.

headve taildosyanın bir alt kümesini seçebilir, ancak rasgele seçemez. Bunu yapmak için her zaman bir python betiği yazabileceğimi biliyorum ama sadece merak ediyorum bu kullanım için bir komut var.


rasgele sırayla satırları veya bu dosyanın ardışık 1000 satır rastgele blok?
frostschutz

Her çizginin seçilmesi için aynı olasılık var. Ardışık olmak gerekmez, ancak ardışık bir satır bloğunun birlikte seçilebilmesi için küçük bir olasılık vardır. Bu konuyu netleştirmek için sorumu güncelledim. Teşekkürler.
saat

Benim github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl bunu yaklaşık olarak dosyada rastgele bir yer arayarak ve en yakın yeni satırları bularak yapar.
barrycarter

Yanıtlar:


65

shufKomutu (coreutils parçası) yapabilirsiniz:

shuf -n 1000 file

Ve en azından şimdilik (uygun olmayan durumlarda rezervuar örneklemesini kullanacak olan eski olmayan sürümleri ( 2013'ten yapılan bir taahhütte eklendi ), hafızasının tükenmemesi gerektiği ve hızlı bir algoritma kullandığı anlamına gelir.


Belgelere göre, girdi olarak sınıflandırılmış bir dosyaya ihtiyacı var: gnu.org/software/coreutils/manual/…
mkc

@Ketan, öyle görünmüyor
frostschutz

2
@Ketan kılavuzun sadece yanlış bölümünde olduğuna inanıyorum. Kılavuzdaki örneklerin bile sıralanmadığını unutmayın. Ayrıca sort, aynı bölümde olduğunu ve açıkça sıralanmış bir giriş gerektirmediğini unutmayın.
derobert

2
shufsürümünde coreutil'lere tanıtıldı 6.0 (2006-08-15)ve ister inan ister inanma, bazı yaygın sistemler (özellikle CentOS 6.5) bu sürüme sahip değil: - |
offby1

2
@petrelharp shuf -n, en azından girdi 8 K'dan büyük olduğunda rezervuar örneklemesi yapar, belirledikleri boyut daha iyi ölçütlerdir. Kaynak koduna bakınız (örneğin, github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 adresinde ). Bu çok geç cevap için özür dilerim. Görünüşe göre bu 6 yıl önce yeni.
derobert

16

Eğer bir varsa çok büyük bir dosya olduğunu göreceksiniz (bir örneği almak için yaygın nedeni olan):

  1. shuf bellek tüketen
  2. $RANDOMDosya 32767 satırını aşarsa, kullanma düzgün çalışmaz

Örneklenen satırlara "tam olarak" ihtiyacınız yoksa, şöyle bir oranın tadına varabilirsiniz :

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

Bu kullanır sabit bellek ,% 1 dosyasının örnekleri (dosyanın satır sayısını biliyorsanız çizgilerin sınırlı sayıda bir kapanış örneklemek için bu faktörünü ayarlayabilirsiniz) ve herhangi bir boyutu ile çalışmalarını dosyanın ama olmaz Sadece istatistiksel bir oranla kesin bir çizgi sayısı döndür.

Not: Kod, şunlardan gelir: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix


Bir kullanıcı boş olmayan satırların yaklaşık % 1'ini isterse , bu oldukça iyi bir cevaptır. Ancak kullanıcı, tam bir sayıda satır isterse (örneğin, 1000000 satırlık bir dosyanın 1000'i gibi), bu başarısız olur. Aldığınız cevabın dediği gibi, sadece istatistiksel bir tahmin verir. Ve cevabı boş satırları görmezden geldiğini görecek kadar iyi anlıyor musunuz? Bu pratikte iyi bir fikir olabilir, ancak belgelenmemiş özellikler genel olarak iyi bir fikir değildir.
G-Man

1
PS   Basit yaklaşımlar $RANDOM, 32767 satırından büyük dosyalar için düzgün çalışmaz. “Kullanma $RANDOMtüm dosyaya ulaşmıyor” ifadesi biraz geniş.
G-Man

@ G-Man Soru, bir milyondan 10k satır almaktan söz ediyor gibi görünüyor. Buradaki cevapların hiçbiri benim için işe yaramadı (dosyaların boyutu ve donanım sınırlamaları nedeniyle) ve bunu makul bir uzlaşma olarak öneriyorum. Sizi bir milyondan 10 bin çizgiye çıkarmayacak, ancak en pratik amaçlar için yeterince yakın olabilir. Tavsiyene uyarak biraz daha açıklığa kavuşturdum. Teşekkürler.
Txangel

Bu en iyi cevap, bir gereklilik olması durumunda, orijinal dosyanın kronolojik sırasına göre satırlar rasgele toplanır. Buna ek awkolarak daha kaynak daha dostudurshuf
Polimeraz

Kesin bir sayıya ihtiyacınız varsa, her zaman yapabilirsiniz ... Bunu ihtiyacınız olandan%% daha fazla çalıştırın. Sonucu sayın. Sayım mod farkı eşleşen satırları kaldırın.
Bruno Bronosky

6

@ Txangel'in olasılıksal çözümüne benzer ancak 100 kat daha hızlı yaklaşır.

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

Yüksek performansa, kesin bir örneklem boyutuna ihtiyacınız varsa ve dosyanın sonunda örnek bir boşlukla yaşamaktan memnunsanız, aşağıdaki gibi bir şey yapabilirsiniz (1m satırlık bir dosyadan 1000 satır örnekler):

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

.. ya da gerçekten zincir yerine ikinci bir örnek yöntem head.


5

Durumda shuf -nbüyük dosyalar üzerinde hile bellek biterse ve hala sabit bir boyut örneğine ihtiyacım ve harici bir yarar daha sonra deneyin takılabilir örnek :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

Uyarı, örneğin ( örnekteki 1000 satır) belleğe sığması gerektiğidir.

Yasal Uyarı: Önerilen yazılımın yazarıyım.


1
Yükleyenler ve daha /usr/local/binönce /usr/bin/kendi yollarına sahip olanlar için , macOS'un içinde sampletamamen farklı bir şey yapan dahili bir çağrı yığını örnekleyici ile geldiğine dikkat edin /usr/bin/.
Denis de Bernardy

2

İstediğin şeyi yapabilecek tek bir komutun farkında değilsin ama işte bir araya getirdiğim işi yapabilecek bir döngü:

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

sed1000 geçişin her birinde rastgele bir çizgi belirleyecektir. Muhtemelen daha verimli çözümler var.


Bu yaklaşımda aynı çizgiyi birden çok kez almak mümkün müdür?
saat

1
Evet, aynı hat numarasını bir kereden fazla almak oldukça mümkün. Ek olarak, $RANDOM0 ile 32767 arasında bir aralık vardır. Dolayısıyla, iyi bir yayılma satır numarası elde edemezsiniz.
mkc

çalışmıyor - rastgele bir kez denir
Bohdan

2

Aşağıdaki kodu bir dosyaya kaydedebilirsiniz (örneğin randextract.sh) ve aşağıdaki gibi çalıştırılabilir:

randextract.sh file.txt

---- BAŞLANGIÇ DOSYASI ----

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

---- SON DOSYA ----


3
Burada RAND ile ne yapmaya çalıştığınızdan emin değilim, ancak $RANDOM$RANDOM“0 - 3276732767” aralığında rasgele sayılar üretmiyor (örneğin, 1000100000 üretecek ancak 1000099999 üretmeyecek).
Gilles 'SO- kötülük'

OP, “Her satırda aynı olasılık seçiliyor. … Ardışık bir satır bloğunun birlikte seçilebilmesi için küçük bir olasılık var. ”Bu cevabı şifreli buluyorum ama 10 satırlık ardışık satırları rasgele bir başlangıç ​​noktasından çıkarmak gibi görünüyor. OP'nin istediği şey bu değil.
G-Man

2

Dosyadaki satır sayısını biliyorsanız (durumunuzdaki 1e6 gibi), şunları yapabilirsiniz:

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

Değilse, her zaman yapabilirsiniz

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

Bu dosyada iki geçiş yapar, ancak yine de tüm dosyayı bellekte saklamaktan kaçının.

GNU'ya göre bir diğer avantaj shuf, dosyadaki satırların sırasını korumasıdır.

O varsayar Not n olan dosyadaki satırların sayısı. Yazdırmak istediğiniz takdirde pdışarı birinci n (potansiyel olarak daha fazla çizgiler vardır) dosyanın hatları, durdurmak gerekiyordu awkde ninci gibi sıraya:

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

2

Başlık satırını korumak istediğimde ve örneğin dosyanın yaklaşık bir yüzdesi olabildiğinde bunun için awk kullanmayı seviyorum. Çok büyük dosyalar için çalışıyor:

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

1

Veya bunun gibi:

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

Beşinci adam sayfasından:

        RANDOM Bu parametreye her referansta, rastgele bir tamsayı
              0 ile 32767 arasında üretilir. Rastgele dizisi
              RAN‐'ya bir değer atanarak sayılar başlatılabilir.
              DOM. RANDOM ayarlanmamışsa, özel özelliğini kaybeder‐
              Daha sonra sıfırlansa bile bağlar.

Dosya 32767'den az satır içeriyorsa, bu başarısız olur.
offby1

Bu dosyadan bir satır çıkacaktır . (Ben senin fikrin bir döngüde yukarıdaki komutları yürütmek olduğunu tahmin?) Dosyası varsa daha fazla 32767 çizgiler, daha sonra bu komutlar ilk 32767 hatlarından sadece seçecektir. Olası verimsizlik dışında, dosyada 32767'den az satır varsa, bu yanıtla ilgili büyük bir sorun görmüyorum.
G-Man

1

Dosya boyutunuz büyük değilse, Rastgele sıralamayı kullanabilirsiniz. Bu shuf'tan biraz daha uzun sürüyor, ancak tüm verileri rasgele hale getiriyor. Böylece, istediğiniz gibi baş kullanmak için kolayca aşağıdakileri yapabilirsiniz:

sort -R input | head -1000 > output

Bu dosyayı rastgele sıralar ve ilk 1000 satırı verirdi.


0

Kabul edilen cevapta belirtildiği gibi, GNU shufbasit rastgele örneklemeyi ( shuf -n) oldukça iyi desteklemektedir. Tarafından desteklenen ötesinde örnekleme yöntemleri ise shufihtiyaç vardır, düşünün tsv-numuneyi gelen eBay'in TSV Utilities . Ağırlıklı rasgele örnekleme, Bernoulli örneklemesi ve farklı örnekleme dahil olmak üzere birkaç ek örnekleme modunu destekler. Performans GNU'ya benzer shuf(her ikisi de oldukça hızlı). Yasal Uyarı: Ben yazarım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.