วิธีสุ่มตัวอย่างชุดย่อยของไฟล์


38

มีคำสั่ง Linux ใดบ้างที่สามารถใช้เพื่อตัวอย่างชุดย่อยของไฟล์? ตัวอย่างเช่นไฟล์มีหนึ่งล้านบรรทัดและเราต้องการสุ่มตัวอย่างเพียงหนึ่งพันบรรทัดจากไฟล์นั้น

สำหรับการสุ่มฉันหมายความว่าทุก ๆ เส้นมีความน่าจะเป็นเหมือนกันในการเลือกและไม่มีเส้นใดที่เลือกซ้ำ

headและtailสามารถเลือกไฟล์ย่อย แต่ไม่สุ่ม ฉันรู้ว่าฉันสามารถเขียนสคริปต์ python ได้ตลอดเวลา แต่สงสัยว่าจะมีคำสั่งสำหรับการใช้งานนี้หรือไม่


บรรทัดในลำดับแบบสุ่มหรือบล็อกสุ่ม 1,000 บรรทัดต่อเนื่องกันของไฟล์นั้น?
frostschutz

ทุกบรรทัดจะมีโอกาสได้รับการเลือกเท่ากัน ไม่จำเป็นต้องต่อเนื่องกันแม้ว่าจะมีความเป็นไปได้น้อยมากที่จะเลือกบล็อกเส้นต่อเนื่องกัน ฉันได้อัปเดตคำถามของฉันเพื่อให้ชัดเจนยิ่งขึ้น ขอบคุณ
clwen

github.com/barrycarter/bcapps/tree/master/bc-fastrand.plของฉันทำสิ่งนี้โดยประมาณโดยการค้นหาตำแหน่งสุ่มในไฟล์และค้นหา newlines ที่ใกล้ที่สุด
barrycarter

คำตอบ:


65

shufคำสั่ง (ส่วนหนึ่งของ coreutils) สามารถทำเช่นนี้:

shuf -n 1000 file

และอย่างน้อยสำหรับรุ่นที่ไม่ใช่ของโบราณในปัจจุบัน (เพิ่มในคอมมิชชันจาก 2013 ) ที่จะใช้การสุ่มตัวอย่างอ่างเก็บน้ำเมื่อเหมาะสมหมายความว่ามันไม่ควรมีหน่วยความจำไม่เพียงพอและใช้อัลกอริธึมที่รวดเร็ว


ตามเอกสารมันต้องการไฟล์ที่เรียงลำดับเป็นอินพุต: gnu.org/software/coreutils/manual/…
mkc

@ Ketan ดูเหมือนจะไม่เป็นเช่นนั้น
frostschutz

2
@ Ketan เป็นเพียงส่วนที่ผิดของคู่มือผมเชื่อว่า โปรดทราบว่าแม้ตัวอย่างในคู่มือจะไม่ถูกจัดเรียง โปรดทราบว่าsortอยู่ในส่วนเดียวกันและชัดเจนว่าไม่จำเป็นต้องป้อนข้อมูลเรียง
Derobert

2
shufได้รับการแนะนำให้รู้จักกับ coreutils ในเวอร์ชั่น6.0 (2006-08-15)และเชื่อหรือไม่ว่าระบบที่ใช้กันทั่วไปบางระบบ (โดยเฉพาะอย่างยิ่ง CentOS 6.5) ไม่มีเวอร์ชั่นนั้น: - |
offby1

2
@petrelharp shuf -nทำการสุ่มตัวอย่างอ่างเก็บน้ำอย่างน้อยที่สุดเมื่ออินพุตมากกว่า 8K ซึ่งเป็นขนาดที่พวกเขาพิจารณาว่าเป็นเกณฑ์มาตรฐานที่ดีกว่า ดูซอร์สโค้ด (เช่นที่github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ) ขออภัยสำหรับคำตอบที่ล่าช้านี้ เห็นได้ชัดว่าเป็นของใหม่เมื่อ 6 ปีที่แล้ว
Derobert

16

หากคุณมีไฟล์ที่มีขนาดใหญ่มาก (ซึ่งเป็นเหตุผลทั่วไปในการรับตัวอย่าง) คุณจะพบว่า:

  1. shuf หน่วยความจำหมด
  2. การใช้$RANDOMจะไม่ทำงานอย่างถูกต้องหากไฟล์เกิน 32767 บรรทัด

หากคุณไม่จำเป็นต้องใช้ "ตัวอย่าง" n บรรทัดตัวอย่างคุณสามารถลองอัตราส่วนดังนี้:

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

วิธีนี้ใช้หน่วยความจำคงที่ตัวอย่าง 1% ของไฟล์ (หากคุณรู้จำนวนบรรทัดของไฟล์ที่คุณสามารถปรับปัจจัยนี้เพื่อสุ่มตัวอย่างใกล้เคียงกับจำนวนบรรทัดที่ จำกัด ) และทำงานกับไฟล์ทุกขนาดแต่จะไม่ส่งคืนจำนวนบรรทัดที่แม่นยำเพียงอัตราส่วนทางสถิติ

หมายเหตุ: รหัสนั้นมาจาก: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix


หากผู้ใช้ต้องการประมาณ 1% ของบรรทัดที่ไม่ว่างนี่เป็นคำตอบที่ดีทีเดียว แต่หากผู้ใช้ต้องการจำนวนบรรทัดที่แน่นอน (เช่น 1,000 จากไฟล์ 1000000 บรรทัด) สิ่งนี้จะล้มเหลว ตามคำตอบที่คุณได้รับจากพูดว่ามันให้ผลเพียงประมาณการทางสถิติ และคุณเข้าใจคำตอบดีพอที่จะเห็นว่ามันไม่สนใจบรรทัดว่างหรือไม่ นี่อาจเป็นความคิดที่ดีในทางปฏิบัติ แต่โดยทั่วไปคุณลักษณะที่ไม่มีเอกสารนั้นไม่ใช่ความคิดที่ดี
G-Man กล่าวว่า 'Reinstate Monica'

1
วิธีPS   แบบเรียบง่ายที่ใช้$RANDOMจะไม่ทำงานอย่างถูกต้องสำหรับไฟล์ที่มีขนาดใหญ่กว่า 32767 บรรทัด คำสั่ง“ การใช้$RANDOMไม่ถึงไฟล์ทั้งหมด” ค่อนข้างกว้าง
G-Man พูดว่า 'Reinstate Monica'

@ G-Man คำถามดูเหมือนจะพูดคุยเกี่ยวกับการรับสาย 10k จากล้านเป็นตัวอย่าง ไม่มีคำตอบใดที่เหมาะกับฉัน (เนื่องจากขนาดของไฟล์และข้อ จำกัด ด้านฮาร์ดแวร์) และฉันเสนอว่านี่เป็นการประนีประนอมที่สมเหตุสมผล มันจะไม่ทำให้คุณมี 10k บรรทัดจากหนึ่งล้านเส้น แต่มันอาจจะใกล้พอสำหรับการใช้งานจริงส่วนใหญ่ ฉันได้ชี้แจงเพิ่มเติมเล็กน้อยตามคำแนะนำของคุณ ขอบคุณ
Txangel

นี่คือคำตอบที่ดีที่สุดบรรทัดจะถูกสุ่มเลือกโดยคำนึงถึงลำดับของไฟล์ต้นฉบับในกรณีที่มีความต้องการ นอกจากนี้ยังawkมีทรัพยากรที่เป็นมิตรมากกว่าshuf
Polymerase

หากคุณต้องการตัวเลขที่แน่นอนคุณสามารถ ... เรียกใช้ด้วย% ที่มากกว่าความต้องการของคุณ นับจำนวนผลลัพธ์ ลบบรรทัดที่ตรงกับความแตกต่างของตัวนับการนับ
Bruno Bronosky

6

คล้ายกับโซลูชันความน่าจะเป็นของ @ Txangel แต่ใกล้เร็วขึ้น 100 เท่า

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

หากคุณต้องการประสิทธิภาพสูงขนาดตัวอย่างที่แน่นอนและยินดีที่จะอยู่กับช่องว่างตัวอย่างที่ท้ายไฟล์คุณสามารถทำสิ่งต่อไปนี้ (ตัวอย่าง 1000 บรรทัดจากไฟล์บรรทัด 1 ม.):

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

.. headหรือแน่นอนโซ่วิธีวินาทีตัวอย่างแทน


5

ในกรณีที่shuf -nเคล็ดลับในไฟล์ขนาดใหญ่หน่วยความจำไม่เพียงพอและคุณยังต้องการตัวอย่างขนาดคงที่และสามารถติดตั้งยูทิลิตี้ภายนอกจากนั้นลองตัวอย่าง :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

ข้อแม้คือตัวอย่าง (1,000 บรรทัดในตัวอย่าง) ต้องพอดีกับหน่วยความจำ

ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียนซอฟต์แวร์ที่แนะนำ


1
สำหรับผู้ที่ติดตั้งและมีของพวกเขา/usr/local/binก่อนที่จะ/usr/bin/อยู่ในเส้นทางของพวกเขาต้องระวังว่า MacOS มาพร้อมกับ built-in ตัวอย่างโทรสแต็คที่เรียกว่าซึ่งทำบางสิ่งที่แตกต่างอย่างสิ้นเชิงในsample /usr/bin/
เดนิสเดอเบอร์นาดี

2

ไม่ทราบคำสั่งเดียวที่สามารถทำสิ่งที่คุณถาม แต่นี่คือวงฉันใส่กันซึ่งสามารถทำงาน:

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

sedจะรับสายสุ่มในแต่ละรอบ 1,000 อาจมีโซลูชั่นที่มีประสิทธิภาพมากกว่า


เป็นไปได้ไหมที่จะได้รับบรรทัดเดียวกันหลาย ๆ ครั้งในแนวทางนี้?
clwen

1
ใช่เป็นไปได้ค่อนข้างมากที่จะได้รับหมายเลขบรรทัดเดียวกันมากกว่าหนึ่งครั้ง นอกจากนี้$RANDOMมีช่วงระหว่าง 0 ถึง 32767 ดังนั้นคุณจะไม่ได้รับหมายเลขบรรทัดการแพร่กระจายที่ดี
mkc

ไม่ทำงาน - การสุ่มเรียกว่าครั้งเดียว
Bohdan

2

คุณสามารถบันทึกรหัสติดตามในไฟล์ (โดยตัวอย่าง randextract.sh) และดำเนินการดังนี้:

randextract.sh file.txt

---- เริ่มต้นไฟล์ ----

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


3
ฉันไม่แน่ใจว่าคุณกำลังทำอะไรกับ RAND ที่นี่ แต่$RANDOM$RANDOMไม่ได้สร้างตัวเลขสุ่มในช่วงทั้งหมด“ 0 ถึง 3276732767” (ตัวอย่างเช่นมันจะสร้าง 1,000100000 แต่ไม่ใช่ 10,00099999)
Gilles 'หยุดความชั่วร้าย'

OP กล่าวว่า“ ทุกบรรทัดมีความเป็นไปได้ที่จะเลือกเหมือนกัน …มีความเป็นไปได้น้อยที่บล็อกของเส้นต่อเนื่องจะถูกเลือกด้วยกัน” ฉันยังพบว่าคำตอบนี้เป็นความลับ แต่ดูเหมือนว่ามันจะแยกบล็อก 10 บรรทัดของบรรทัดที่ต่อเนื่องกันออกจากจุดเริ่มต้นแบบสุ่ม นั่นไม่ใช่สิ่งที่ OP ขอมา
G-Man พูดว่า 'Reinstate Monica'

2

หากคุณรู้จำนวนบรรทัดในไฟล์ (เช่น 1e6 ในกรณีของคุณ) คุณสามารถทำได้:

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

ถ้าไม่คุณสามารถทำได้

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

ที่จะทำสองผ่านในไฟล์ แต่ยังคงหลีกเลี่ยงการเก็บไฟล์ทั้งหมดในหน่วยความจำ

ข้อดีอีกประการของ GNU shufก็คือมันจะรักษาลำดับของบรรทัดในไฟล์

โปรดทราบว่ามันถือว่าn เป็นจำนวนบรรทัดในไฟล์ หากคุณต้องการที่จะพิมพ์pออกมาจากครั้งแรกที่ nสายของไฟล์ (ซึ่งมีสายที่อาจเกิดขึ้นมากกว่า) คุณจะต้องหยุดawkในnวันที่บรรทัดที่ชอบ:

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

2

ฉันชอบใช้ awk สำหรับสิ่งนี้เมื่อฉันต้องการรักษาแถวส่วนหัวและเมื่อตัวอย่างสามารถเป็นเปอร์เซ็นต์โดยประมาณของไฟล์ ใช้งานได้กับไฟล์ที่มีขนาดใหญ่มาก:

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

1

หรือเช่นนี้

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

จากหน้าคนทุบตี:

        RANDOM ทุกครั้งที่มีการอ้างอิงพารามิเตอร์นี้จำนวนเต็มแบบสุ่ม
              สร้างขึ้นระหว่าง 0 ถึง 32767 ลำดับของการสุ่ม
              หมายเลขอาจถูกเตรียมใช้งานโดยการกำหนดค่าให้ RAN‐
              DOM หาก RANDOM ไม่ได้ตั้งค่ามันจะสูญเสียความเหมาะสมเป็นพิเศษ ‐
              เสมอแม้ว่ามันจะถูกรีเซ็ตในภายหลัง

สิ่งนี้จะล้มเหลวอย่างรุนแรงหากไฟล์มีจำนวนน้อยกว่า 32767 บรรทัด
offby1

สิ่งนี้จะเอาท์พุทหนึ่งบรรทัดจากไฟล์ (ฉันเดาความคิดของคุณคือการรันคำสั่งดังกล่าวข้างต้นในวง?) ถ้าไฟล์ที่มีมากขึ้นกว่า 32,767 สายแล้วคำสั่งเหล่านี้จะเลือกเฉพาะจาก 32767 บรรทัดแรก นอกเหนือจากความไร้ประสิทธิภาพที่เป็นไปได้ฉันไม่เห็นปัญหาใหญ่กับคำตอบนี้หากไฟล์มีจำนวนน้อยกว่า 32767 บรรทัด
G-Man กล่าวว่า 'Reinstate Monica'

1

หากขนาดไฟล์ของคุณไม่ใหญ่คุณสามารถใช้การเรียงลำดับแบบสุ่ม สิ่งนี้ใช้เวลานานกว่า shuf เล็กน้อย แต่จะสุ่มข้อมูลทั้งหมด ดังนั้นคุณสามารถใช้หัวต่อไปนี้ตามที่คุณร้องขอได้อย่างง่ายดาย:

sort -R input | head -1000 > output

นี่จะเรียงลำดับไฟล์แบบสุ่มและให้ 1,000 บรรทัดแรกให้คุณ


0

ดังที่กล่าวไว้ในคำตอบที่ยอมรับ GNU shufสนับสนุนการสุ่มตัวอย่างแบบง่าย ( shuf -n) ค่อนข้างดี หากสุ่มตัวอย่างวิธีการนอกเหนือจากที่ได้รับการสนับสนุนโดยshufมีความจำเป็นต้องพิจารณาTSV ตัวอย่างจากอีเบย์ TSV ยูทิลิตี้ สนับสนุนโหมดการสุ่มตัวอย่างเพิ่มเติมหลายประการรวมถึงการสุ่มแบบถ่วงน้ำหนักการสุ่มตัวอย่างเบอร์นูลีและการสุ่มตัวอย่างที่แตกต่างกัน ประสิทธิภาพคล้ายกับ GNU shuf(ทั้งสองค่อนข้างเร็ว) ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.