Cách lấy mẫu ngẫu nhiên một tập hợp con của tệp


38

Có bất kỳ lệnh Linux nào người ta có thể sử dụng để lấy mẫu tập hợp con của một tập tin không? Chẳng hạn, một tệp chứa một triệu dòng và chúng tôi muốn lấy mẫu ngẫu nhiên chỉ một nghìn dòng từ tệp đó.

Đối với ngẫu nhiên, tôi có nghĩa là mọi dòng đều có cùng xác suất được chọn và không có dòng nào được chọn là lặp đi lặp lại.

headtailcó thể chọn một tập hợp con của tệp nhưng không ngẫu nhiên. Tôi biết tôi luôn có thể viết một kịch bản python để làm như vậy nhưng chỉ cần tự hỏi là có một lệnh cho việc sử dụng này.


các dòng theo thứ tự ngẫu nhiên, hoặc một khối ngẫu nhiên 1000 dòng liên tiếp của tập tin đó?
frostschutz

Mỗi dòng có cùng xác suất được chọn. Không cần phải liên tiếp mặc dù có một xác suất nhỏ rằng một khối dòng liên tiếp được chọn cùng nhau. Tôi đã cập nhật câu hỏi của mình để rõ hơn về điều đó. Cảm ơn.
clwen

Github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl của tôi thực hiện điều này bằng cách tìm kiếm một vị trí ngẫu nhiên trong tệp và tìm dòng mới gần nhất.
barrycarter

Câu trả lời:


65

Các shuflệnh (một phần của coreutils) có thể làm điều này:

shuf -n 1000 file

Và ít nhất là đối với các phiên bản không cổ xưa (được thêm vào trong một cam kết từ năm 2013 ), sẽ sử dụng lấy mẫu hồ chứa khi thích hợp, nghĩa là nó không nên hết bộ nhớ và đang sử dụng thuật toán nhanh.


Theo tài liệu, nó cần một tệp được sắp xếp làm đầu vào: gnu.org/software/coreutils/manual/ mẹo
mkc

@Ketan, không có vẻ như vậy
frostschutz

2
@Ketan nó chỉ nằm trong phần sai của hướng dẫn, tôi tin thế. Lưu ý rằng ngay cả các ví dụ trong hướng dẫn cũng không được sắp xếp. Cũng lưu ý rằng đó sortlà trong cùng một phần, và rõ ràng nó không yêu cầu đầu vào được sắp xếp.
derobert

2
shufđã được giới thiệu với coreutils trong phiên bản 6.0 (2006-08-15)và tin hay không, một số hệ thống khá phổ biến (cụ thể là CentOS 6.5) không có phiên bản đó: - |
off1

2
@petrelharp shuf -nthực hiện lấy mẫu hồ chứa, ít nhất là khi đầu vào lớn hơn 8K, kích thước họ xác định là điểm chuẩn tốt hơn. Xem mã nguồn (ví dụ: tại github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Xin lỗi vì câu trả lời rất muộn này. Rõ ràng đó là mới từ 6 năm trước.
derobert

16

Nếu bạn có một tệp rất lớn (đó là lý do phổ biến để lấy mẫu), bạn sẽ thấy rằng:

  1. shuf cạn kiệt bộ nhớ
  2. Sử dụng $RANDOMsẽ không hoạt động chính xác nếu tệp vượt quá 32767 dòng

Nếu bạn không cần "chính xác" n dòng được lấy mẫu, bạn có thể lấy mẫu theo tỷ lệ như sau:

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

Điều này sử dụng bộ nhớ không đổi , lấy mẫu 1% tệp (nếu bạn biết số dòng của tệp, bạn có thể điều chỉnh hệ số này để lấy mẫu gần với số lượng dòng giới hạn) và hoạt động với mọi kích thước tệp nhưng sẽ không trả về một số dòng chính xác , chỉ là một tỷ lệ thống kê.

Lưu ý: Mã đến từ: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix


Nếu người dùng muốn khoảng 1% số dòng không trống, đây là một câu trả lời khá hay. Nhưng nếu người dùng muốn có một số dòng chính xác (ví dụ: 1000 trong số 1000000 tệp), thì điều này không thành công. Như câu trả lời bạn nhận được từ nó nói, nó chỉ mang lại một ước tính thống kê. Và bạn có hiểu câu trả lời đủ tốt để thấy rằng nó đang bỏ qua các dòng trống? Đây có thể là một ý tưởng tốt, trong thực tế, nhưng các tính năng không có giấy tờ nói chung, không phải là một ý tưởng tốt.
G-Man nói 'Phục hồi Monica'

1
PS   Cách tiếp cận đơn giản bằng cách sử dụng $RANDOMsẽ không hoạt động chính xác cho các tệp lớn hơn 32767 dòng. Tuyên bố sử dụng $RANDOMkhông tiếp cận được toàn bộ tập tin.
G-Man nói 'Phục hồi Monica'

@ G-Man Câu hỏi dường như nói về việc lấy 10 nghìn dòng từ một triệu làm ví dụ. Không có câu trả lời nào xung quanh làm việc cho tôi (vì kích thước của các tệp và giới hạn phần cứng) và tôi đề xuất đây là một sự thỏa hiệp hợp lý. Nó sẽ không giúp bạn kiếm được 10 nghìn trong số một triệu nhưng nó có thể đủ gần cho hầu hết các mục đích thực tế. Tôi đã làm rõ hơn một chút theo lời khuyên của bạn. Cảm ơn.
Txangel

Đây là câu trả lời tốt nhất, các dòng được chọn ngẫu nhiên trong khi tôn trọng thứ tự thời gian của tệp gốc, trong trường hợp đây là một yêu cầu. Ngoài ra, awknó còn thân thiện với tài nguyên hơnshuf
polymerase

Nếu bạn cần một con số chính xác, bạn luôn có thể Chạy Run với số% lớn hơn nhu cầu của bạn. Đếm kết quả. Xóa dòng phù hợp với sự khác biệt đếm mod.
Bruno Bronosky

6

Tương tự như giải pháp xác suất của @ Txangel nhưng tiếp cận nhanh hơn 100 lần.

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

Nếu bạn cần hiệu suất cao, kích thước mẫu chính xác và sẵn lòng sống với khoảng cách mẫu ở cuối tệp, bạn có thể thực hiện một số thao tác như sau (lấy 1000 dòng từ tệp dòng 1m):

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

.. hoặc thực sự chuỗi một phương pháp mẫu thứ hai thay vì head.


5

Trong trường hợp shuf -nlừa trên các tệp lớn hết bộ nhớ và bạn vẫn cần một mẫu có kích thước cố định và một tiện ích bên ngoài có thể được cài đặt, sau đó thử mẫu :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

Thông báo trước là mẫu (1000 dòng trong ví dụ) phải vừa với bộ nhớ.

Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của phần mềm được đề xuất.


1
Đối với những người cài đặt nó và có /usr/local/bintrước đó /usr/bin/trong đường dẫn của họ, hãy cảnh giác rằng macOS đi kèm với một bộ lấy mẫu ngăn xếp cuộc gọi tích hợp được gọi sample, có chức năng hoàn toàn khác /usr/bin/.
Denis de Bernardy

2

Không biết bất kỳ lệnh nào có thể làm những gì bạn yêu cầu nhưng đây là một vòng lặp tôi có thể thực hiện công việc:

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

sedsẽ chọn một dòng ngẫu nhiên trên mỗi 1000 lượt. Có thể có những giải pháp hiệu quả hơn.


Có thể có được cùng một dòng nhiều lần trong phương pháp này?
clwen

1
Có, hoàn toàn có thể nhận được cùng một số dòng nhiều lần. Ngoài ra, $RANDOMcó phạm vi từ 0 đến 32767. Vì vậy, bạn sẽ không nhận được số dòng trải đều.
mkc

không hoạt động - ngẫu nhiên được gọi một lần
Bohdan

2

Bạn có thể lưu mã theo dõi trong một tệp (ví dụ randextract.sh) và thực thi như sau:

randextract.sh file.txt

---- BẮT ĐẦU 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

---- KẾT THÚC HẾT ----


3
Tôi không chắc chắn những gì bạn đang cố gắng thực hiện ở đây với RAND, nhưng $RANDOM$RANDOMkhông tạo ra các số ngẫu nhiên trong toàn bộ phạm vi 0 đến 3276732767 (ví dụ: nó sẽ tạo ra 1000100000 chứ không phải 1000099999).
Gilles 'SO- ngừng trở nên xấu xa'

OP nói, hàng Mỗi dòng đều có cùng xác suất được chọn. Có một khả năng rất nhỏ là một khối liên tiếp được chọn cùng nhau. Tôi cũng thấy câu trả lời này là khó hiểu, nhưng có vẻ như nó đang trích xuất một khối 10 dòng liên tiếp từ một điểm bắt đầu ngẫu nhiên. Đó không phải là những gì OP đang yêu cầu.
G-Man nói 'Phục hồi Monica'

2

Nếu bạn biết số lượng dòng trong tệp (như 1e6 trong trường hợp của bạn), bạn có thể làm:

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

Nếu không, bạn luôn có thể làm

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

Điều đó sẽ thực hiện hai lần trong tệp, nhưng vẫn tránh lưu trữ toàn bộ tệp trong bộ nhớ.

Một ưu điểm khác so với GNU shuflà nó bảo toàn thứ tự các dòng trong tệp.

Lưu ý rằng nó giả sử n số lượng dòng trong tệp. Nếu bạn muốn in pra những dòng đầu tiên n của tệp (có khả năng nhiều dòng hơn), bạn cần dừng lại awkở dòng nthứ như:

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

2

Tôi thích sử dụng awk cho việc này khi tôi muốn duy trì một hàng tiêu đề và khi mẫu có thể là một tỷ lệ phần trăm xấp xỉ của tệp. Hoạt động cho các tệp rất lớn:

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

1

Hoặc như thế này:

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

Từ trang bash man:

        RANDOM Mỗi lần tham số này được tham chiếu, một số nguyên ngẫu nhiên
              từ 0 đến 32767 được tạo ra. Trình tự ngẫu nhiên
              số có thể được khởi tạo bằng cách gán giá trị cho RAN‐
              DOM. Nếu RANDOM không được đặt, nó sẽ mất tính đặc biệt của nó‐
              quan hệ, ngay cả khi nó được thiết lập lại sau đó.

Điều này không thành công nếu tập tin có ít hơn 32767 dòng.
off1

Điều này sẽ xuất một dòng từ tệp. (Tôi đoán ý tưởng của bạn là thực hiện các lệnh trên trong một vòng lặp?) Nếu tệp có nhiều hơn 32767 dòng, thì các lệnh này sẽ chỉ chọn từ 32767 dòng đầu tiên. Ngoài khả năng không hiệu quả, tôi không thấy bất kỳ vấn đề lớn nào với câu trả lời này nếu tệp có ít hơn 32767 dòng.
G-Man nói 'Phục hồi Monica'

1

Nếu kích thước tệp của bạn không lớn, bạn có thể sử dụng Sắp xếp ngẫu nhiên. Điều này mất nhiều thời gian hơn shuf, nhưng nó ngẫu nhiên toàn bộ dữ liệu. Vì vậy, bạn có thể dễ dàng thực hiện các thao tác sau để sử dụng đầu như bạn yêu cầu:

sort -R input | head -1000 > output

Điều này sẽ sắp xếp các tập tin ngẫu nhiên và cung cấp cho bạn 1000 dòng đầu tiên.


0

Như đã đề cập trong câu trả lời được chấp nhận, GNU shufhỗ trợ lấy mẫu ngẫu nhiên đơn giản ( shuf -n) khá tốt. Nếu các phương pháp lấy mẫu ngoài các phương pháp được hỗ trợ shuflà cần thiết, hãy xem xét mẫu tsv từ Tiện ích TSV của eBay . Nó hỗ trợ một số chế độ lấy mẫu bổ sung, bao gồm lấy mẫu ngẫu nhiên có trọng số, lấy mẫu Bernoulli và lấy mẫu riêng biệt. Hiệu suất tương tự như GNU shuf(cả hai đều khá nhanh). Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.