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

39

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

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

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

clwen
источник
строки в случайном порядке или случайный блок из 1000 последовательных строк этого файла?
frostschutz
Каждая строка получает одинаковую вероятность выбора. Не нужно быть последовательным, хотя есть небольшая вероятность, что последовательный блок строк будет выбран вместе. Я обновил свой вопрос, чтобы прояснить это. Спасибо.
clwen
Мой github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl делает это приблизительно путем поиска случайного расположения в файле и поиска ближайших новых строк.
Баррикартер

Ответы:

66

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

shuf -n 1000 file

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

derobert
источник
Согласно документации, это необходимо отсортированный файл в качестве входных данных: 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

Txangel
источник
Если пользователь хочет примерно 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.

geotheory
источник
5

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

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

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

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

hroptatyr
источник
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 проходов. Возможно, есть более эффективные решения.

MKC
источник
Можно ли получить один и тот же ряд несколько раз в этом подходе?
'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

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

razzek
источник
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 строк.

DomainsFeatured
источник
0

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

JonDeg
источник