Как случайным образом выбрать часть файла


39

Есть ли какая-нибудь команда Linux, которую можно использовать для выборки подмножества файла? Например, файл содержит один миллион строк, и мы хотим случайным образом выбрать только одну тысячу строк из этого файла.

Под случайным я подразумеваю, что каждая строка получает одинаковую вероятность выбора, и ни одна из выбранных линий не является повторяющейся.

headи tailможет выбрать подмножество файла, но не случайно. Я знаю, что всегда могу написать для этого скрипт на python, но мне просто интересно, есть ли команда для этого использования.


строки в случайном порядке или случайный блок из 1000 последовательных строк этого файла?
— frostschutz

Каждая строка получает одинаковую вероятность выбора. Не нужно быть последовательным, хотя есть небольшая вероятность, что последовательный блок строк будет выбран вместе. Я обновил свой вопрос, чтобы прояснить это. Спасибо.
— clwen

Мой github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl делает это приблизительно путем поиска случайного расположения в файле и поиска ближайших новых строк.
— Баррикартер

Ответы:


66

Команда shuf(часть coreutils) может сделать это:

shuf -n 1000 file

И, по крайней мере, на данный момент не древние версии (добавленные в коммите от 2013 года ), которые будут использовать выборку из резервуара, когда это уместно, что означает, что она не должна исчерпывать память и использует быстрый алгоритм.


Согласно документации, это необходимо отсортированный файл в качестве входных данных: gnu.org/software/coreutils/manual/...
— MKC

@Ketan, похоже, не так
— frostschutz

2
@ Кетан, я считаю, что это не в том разделе руководства. Обратите внимание, что даже примеры в руководстве не отсортированы. Также обратите внимание, что он sortнаходится в том же разделе, и он явно не требует сортированного ввода.
— Дероберт

2
shufбыла введена в coreutils в версии 6.0 (2006-08-15), и, хотите верьте, хотите нет, некоторые довольно распространенные системы (в частности, CentOS 6.5) не имеют этой версии: - |
— offby1

2
@petrelharp shuf -nвыполняет выборку из резервуара, по крайней мере, когда входное значение больше 8 КБ, что является размером, который они определили, лучше тестов. См. Исходный код (например, на github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Извините за этот очень поздний ответ. По-видимому, это новое на 6 лет назад.
— Дероберт

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% непустых строк, это довольно хороший ответ. Но если пользователь хочет точное количество строк (например, 1000 из файла 1000000 строк), это не удастся. Как говорится в ответе, который вы получили, он дает только статистическую оценку. И вы понимаете ответ достаточно хорошо, чтобы увидеть, что он игнорирует пустые строки? На практике это может быть хорошей идеей, но недокументированные функции, как правило, не очень хорошая идея.
— G-Man говорит: «Восстанови Монику»

1
PS   Использование упрощенных подходов $RANDOMне будет работать правильно для файлов, размер которых превышает 32767 строк. Утверждение «Использование $RANDOMне достигает всего файла» является довольно широким.
— G-Man говорит: «Восстанови Монику»

@ G-Man Вопрос, кажется, говорит о получении 10 тысяч строк из миллиона в качестве примера. Ни один из ответов не сработал для меня (из-за размера файлов и аппаратных ограничений), и я предлагаю это как разумный компромисс. Это не даст вам 10 тысяч строк из миллиона, но может оказаться достаточно близко для большинства практических целей. Я разъяснил это немного больше, следуя вашему совету. Спасибо.
— Txangel

Это лучший ответ, строки выбираются случайным образом при соблюдении хронологического порядка исходного файла, если это требуется. Кроме того, awkэто более дружественный ресурс, чемshuf
— Полимераза

Если вам нужно точное число, вы всегда можете ... Запустите это на% больше, чем нужно. Посчитайте результат. Удалить строки, соответствующие количеству модов.
— Бруно Броноски

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 

Предостережение заключается в том, что образец (1000 строк в примере) должен уместиться в памяти.

Отказ от ответственности: я являюсь автором рекомендуемого программного обеспечения.


1
Для тех, кто устанавливает его и имеет его /usr/local/binранее /usr/bin/в своем пути, будьте осторожны, что MacOS поставляется со встроенным сэмплером call-stack sample, который делает что-то совершенно другое /usr/bin/.
— Дени де Бернарди

2

Не знаю ни одной команды, которая могла бы сделать то, что вы просите, но вот цикл, который я собрал, который может сделать эту работу:

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

sedвыберет случайную линию на каждом из 1000 проходов. Возможно, есть более эффективные решения.


Можно ли получить один и тот же ряд несколько раз в этом подходе?
— '11

1
Да, вполне возможно получить один и тот же номер строки более одного раза. Кроме того, $RANDOMимеет диапазон от 0 до 32767. Таким образом, вы не будете получать номера строк с хорошим спредом.
— MKC

не работает - случайный вызывается один раз
— Богдан

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

---- КОНЕЦ ФАЙЛА ----


3
Я не уверен, что вы пытаетесь сделать здесь с RAND, но $RANDOM$RANDOMне генерирует случайные числа во всем диапазоне от 0 до 3276732767 (например, он сгенерирует 1000100000, но не 1000099999).
— Жиль "ТАК - перестань быть злым"

ОП говорит: «Каждая линия получает одинаковую вероятность выбора. … Есть небольшая вероятность того, что последовательный блок строк будет выбран вместе ». Я также нахожу этот ответ загадочным, но похоже, что он извлекает блок из 10 последовательных строк из случайной начальной точки. Это не то, о чем просит ОП.
— G-Man говорит: «Восстанови Монику»

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  

Со страницы руководства bash:

        RANDOM Каждый раз, когда на этот параметр ссылаются, случайное целое число
              между 0 и 32767 генерируется. Последовательность случайных
              числа могут быть инициализированы путем присвоения значения RAN-
              DOM. Если RANDOM не установлен, он теряет свое специальное свойство
              связи, даже если он впоследствии сбрасывается.

Это плохо работает, если файл содержит менее 32767 строк.
— offby1

Это выведет одну строку из файла. (Я полагаю, ваша идея состоит в том, чтобы выполнить вышеуказанные команды в цикле?) Если файл имеет более 32767 строк, то эти команды будут выбирать только из первых 32767 строк. Помимо возможной неэффективности, я не вижу большой проблемы с этим ответом, если файл имеет менее 32767 строк.
— G-Man говорит: «Восстанови Монику»

1

Если размер вашего файла не очень велик, вы можете использовать сортировку в случайном порядке. Это занимает немного больше времени, чем shuf, но случайным образом разбирает все данные. Таким образом, вы можете легко сделать следующее, чтобы использовать head, как вы просили:

sort -R input | head -1000 > output

Это отсортирует файл случайным образом и даст вам первые 1000 строк.


0

Как уже упоминалось в принятом ответе, GNU довольно хорошо shufподдерживает простую случайную выборку ( shuf -n). Если shufтребуются методы выборки, выходящие за рамки поддерживаемых , рассмотрите tsv-sample из утилит TSV eBay . Он поддерживает несколько дополнительных режимов выборки, включая взвешенную случайную выборку, выборку Бернулли и отдельную выборку. Производительность похожа на GNU shuf(оба довольно быстрые). Отказ от ответственности: я автор.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.