Як довільно вибірки підмножини файлу


38

Чи є якась команда Linux, яку можна використовувати для вибірки підмножини файлу? Наприклад, файл містить один мільйон рядків, і ми хочемо випадково відібрати лише тисячу рядків із цього файлу.

Я випадково маю на увазі, що кожен рядок отримує однакову ймовірність бути обраним, і жоден із обраних рядків не повторюється.

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


рядки у випадковому порядку або випадковий блок з 1000 послідовних рядків цього файлу?
frostschutz

Кожен рядок отримує однакову ймовірність бути обраним. Не потрібно бути послідовними, хоча є невелика ймовірність того, що послідовно блок рядків буде обраний разом. Я оновив своє питання, щоб зрозуміти це. Спасибі.
clwen

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

Відповіді:


65

shufКоманда (частина Coreutils) може зробити це:

shuf -n 1000 file

І принаймні, поки що не стародавні версії (додані в комітеті з 2013 року ), які використовуватимуть вибірку резервуарів, коли це доречно, це означає, що вона не повинна закінчуватися пам'яттю і використовує швидкий алгоритм.


Відповідно до документації, він потребує відсортованого файлу як вхідного: gnu.org/software/coreutils/manual/…
mkc

@Ketan, не здається так
frostschutz

2
@Ketan - це якраз у неправильному розділі керівництва. Зауважте, що навіть приклади в посібнику не відсортовані. Зауважте також, що 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 Питання, мабуть, говорить про те, щоб отримати приклад 10k рядків з мільйона. Жодна відповідь навколо не працювала для мене (через розмір файлів та апаратних обмежень), і я пропоную це як розумний компроміс. Це не отримає 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 постачається із вбудованим вибірковим стеком викликів sample, який називається , який робить щось зовсім інше /usr/bin/.
Дені де Бернарді

2

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

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

sedпідбере випадкову лінію на кожному з 1000 проходів. Можливо, є більш ефективні рішення.


Чи можливо отримати такий самий рядок кілька разів при такому підході?
clwen

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 man":

        RANDOM Щоразу, коли цей параметр посилається, випадкове ціле число
              між 0 і 32767 генерується. Послідовність випадкових
              числа можуть бути ініціалізовані шляхом присвоєння значення RAN-
              DOM. Якщо RANDOM не налаштований, він втрачає особливі властивості
              зв’язки, навіть якщо вони згодом скидаються.

Це не вдається погано, якщо файл містить менше 32767 рядків.
offby1

Це виведе один файл з файла. (Я думаю, ваша ідея полягає у виконанні вищезазначених команд у циклі?) Якщо файл має більше 32767 рядків, то ці команди вибиратимуть лише перші 32767 рядків. Окрім можливої ​​неефективності, я не бачу жодної великої проблеми з цією відповіддю, якщо файл має менше 32767 рядків.
G-Man каже: "Відновіть Моніку"

1

Якщо розмір файлу не величезний, можна використовувати Сортування випадкових. Це займає трохи більше часу, ніж шуф, але воно рандомізує всі дані. Отже, ви можете просто виконати наступне, щоб використовувати голову, як вимагали:

sort -R input | head -1000 > output

Це дозволить сортувати файл випадковим чином і дасть вам перші 1000 рядків.


0

Як згадується у прийнятій відповіді, GNU досить добре shufпідтримує просту випадкову вибірку ( shuf -n). Якщо shufпотрібні методи вибірки, що перевищують ті, що підтримуються , врахуйте tsv-зразок із TSV Utilities eBay . Він підтримує декілька додаткових режимів вибірки, включаючи зважений випадковий вибірковий відбір, відбір Бернуллі та вибіркову вибірку. Продуктивність схожа на GNU shuf(обидва досить швидкі). Відмова: Я автор.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.