Мне нужно отсортировать bed
файл случайным образом 10000 раз и каждый раз брать первые 1000 строк. В настоящее время я использую следующий код:
for i in {1..100}; do
for j in {1..100}; do
sort -R myfile.bed_sorted | tail -n 1000 > myfile.bed.$i.$j.bed
done
done
Это занимает почти 6 часов, чтобы сделать это для каждого файла. У меня есть около 150 из них для разработки. Есть ли более быстрое решение для этого?
Образец данных (myfile.bed_sorted) у меня есть:
chr1 111763899 111766405 peak1424 1000 . 3224.030 -1 -1
chr1 144533459 144534584 peak1537 998 . 3219.260 -1 -1
chr8 42149384 42151246 peak30658 998 . 3217.620 -1 -1
chr2 70369299 70370655 peak16886 996 . 3211.600 -1 -1
chr8 11348914 11352994 peak30334 990 . 3194.180 -1 -1
chr21 26828820 26830352 peak19503 988 . 3187.820 -1 -1
chr16 68789901 68791150 peak11894 988 . 3187.360 -1 -1
chr6 11458964 11462245 peak26362 983 . 3169.750 -1 -1
chr1 235113793 235117308 peak2894 982 . 3166.000 -1 -1
chr6 16419968 16422194 peak26522 979 . 3158.520 -1 -1
chr6 315344 321339 peak26159 978 . 3156.320 -1 -1
chr1 111756584 111759633 peak1421 964 . 3110.520 -1 -1
chrX 12995098 12997685 peak33121 961 . 3100.000 -1 -1
chr9 37408601 37410262 peak32066 961 . 3100.000 -1 -1
chr9 132648603 132651523 peak32810 961 . 3100.000 -1 -1
chr8 146103178 146104943 peak31706 961 . 3100.000 -1 -1
chr8 135611963 135614649 peak31592 961 . 3100.000 -1 -1
chr8 128312253 128315935 peak31469 961 . 3100.000 -1 -1
chr8 128221486 128223644 peak31465 961 . 3100.000 -1 -1
chr8 101510621 101514237 peak31185 961 . 3100.000 -1 -1
chr8 101504210 101508005 peak31184 961 . 3100.000 -1 -1
chr7 8173062 8174642 peak28743 961 . 3100.000 -1 -1
chr7 5563424 5570618 peak28669 961 . 3100.000 -1 -1
chr7 55600455 55603724 peak29192 961 . 3100.000 -1 -1
chr7 35767878 35770820 peak28976 961 . 3100.000 -1 -1
chr7 28518260 28519837 peak28923 961 . 3100.000 -1 -1
chr7 104652502 104654747 peak29684 961 . 3100.000 -1 -1
chr6 6586316 6590136 peak26279 961 . 3100.000 -1 -1
chr6 52362185 52364270 peak27366 961 . 3100.000 -1 -1
chr6 407805 413348 peak26180 961 . 3100.000 -1 -1
chr6 32936987 32941352 peak26978 961 . 3100.000 -1 -1
chr6 226477 229964 peak26144 961 . 3100.000 -1 -1
chr6 157017923 157020836 peak28371 961 . 3100.000 -1 -1
chr6 137422769 137425128 peak28064 961 . 3100.000 -1 -1
chr5 149789084 149793727 peak25705 961 . 3100.000 -1 -1
chr5 149778033 149783125 peak25702 961 . 3100.000 -1 -1
chr5 149183766 149185906 peak25695 961 . 3100.000 -1 -1
split
может, разбить файл на части по 1000 строк в каждой, так что вы получите больше файлов за один вызовsort
. Кроме того, вы проверили, еслиhead
немного быстрее, чемtail
потому, что ему не нужно читать весь файл?head
здесь.sort -R
используется «случайный хэш ключей». Создание хэша - это пустая трата времени и, вероятно, занимает больше времени, чем все остальное. Было бы лучше прочитать строки в массив и затем перемешать их с помощью индексов. Лично я бы использовалperl
для этого; Вы можете сделать это,bash
но вам понадобится функция для генерации случайных чисел.perl
человек! Не могли бы вы помочь мне?shuf
вместоsort -R
, это значительно быстрее. Конечно, выполнение этого в памяти (см. Ответ Perl) побьет все, что требует перечитывания всего файла в оболочке.Ответы:
Предполагая, что у вас достаточно памяти, чтобы сохранить файл, вы можете попробовать
Поскольку вы хотите сделать это 10000 раз, я бы порекомендовал интегрировать повторение в скрипт и перетасовывать индексы вместо самого массива, чтобы ускорить процесс:
Выше было создано 10000 файлов по 1000 строк в каждом из файла, который содержал 37000 строк (ваш пример файла повторялся 1000 раз). Как видите, в моей системе это заняло чуть больше трех минут.
объяснение
use List::Util 'shuffle';
: это импортирует модуль Perl, который предоставляетshuffle()
функцию, которая рандомизирует массив.@l=<>;
: загрузить входной файл (<>
) в массив@l
.for $i (1..10000){}
: запустить это 10000 раз.@r=shuffle(0..$#l);
:$#l
это число элементов в,@l
так@r
что теперь это случайный список порядковых номеров массива@l
(строки входного файла).open(my $fh, ">","file.$i.bed");
: открыть файлfile.$i.bed
для записи.$i
будет принимать значения от 1 до 10000.print $fh @l[@r[0..999]]
: возьмите первые 1000 индексов в перемешанном массиве и напечатайте соответствующие строки (элементы@l
).Другой подход заключается в использовании
shuf
( спасибо @frostschutz ):источник
shuf
как предложено frostschutz:for i in {1..10000}; do shuf -n 1000 file.bed > file.$i.bed; done
. Это заняло ~ 1 минуту в моей системе. Что касается последних 1000 строк, все, что вам нужно, этоtail -n 1000
.sys
времени, которое будет файловым вводом-выводом - оно не должно быть настолько отличным от тогоshuf
, которое имеет ~ 30 сsys
. Итак, я протестировал Perl здесь (cut n 'paste) и O_O он создал 1000 файлов, но все файлы были пусты ...Если вы хотите, чтобы тест показал, как быстро это можно сделать, скопируйте его, вставьте
10kshuffle.cpp
и скомпилируйтеg++ 10kshuffle.cpp -o 10kshuffle
. Затем вы можете запустить его:Где
filename
базовый путь для файлов вывода; они будут названыfilename.0
,filename.1
и т.д. , и каждая из них содержит первые 1000 строк в случайном порядке. Он записывает имя каждого файла по мере его поступления.На одном ядре с частотой 3,5 ГГц это выполняется за ~ 20 секунд:
data.txt
было 37000 строк, продублированных из вопроса. Если вы хотите, чтобы все выходные данные в выходном файле использовались вместо первых 1000 строк, измените строку 54 на:источник
Таким образом, в вашем вопросе есть аспект Unix, но сначала стоит решить фундаментальную проблему, а затем попытаться найти способ реализации этого решения в Unix-y.
Вам нужно создать 10 000 образцов размером 1000 из файла с неизвестным большим количеством строк. Это можно сделать за один проход файла, если вы можете хранить 10 000 x 1000 строк в памяти. Если вы не можете хранить столько строк в памяти, вы все равно можете сделать это за один проход, если знаете, сколько строк содержит ваш файл. Если вы не знаете, сколько строк содержит ваш файл, вам потребуется еще один проход для подсчета количества строк.
Алгоритм, в более сложном случае, когда вы не знаете количество строк, заключается в следующем для каждой выборки (параллельно, сохраняя выборки в памяти):
n > 1000
) включите ее с вероятностью1000 / n
и отбросьте случайную строку из строк, которые вы уже выбрали. (из-за вероятности сброса некоторых строк нам нужно хранить сэмпл в памяти до конца ввода)Элегантный способ реализовать второй шаг - генерировать случайное целое число
k
в[1, n]
. Еслиk <= 1000
затем, включите строку и замените существующуюk
строку. Вот более стандартное описание алгоритма: http://en.wikipedia.org/wiki/Reservoir_samplingЕсли вы знаете количество строк
R
, то:s
из 0(1000 - s) / (R - n + 1)
и вывести ее немедленно (и увеличить размер выборкиs
)Как это сделать на Unix?
awk
кажется, ответ на этот пост в Интернете (я не могу ручаться за его правильность, но код есть) https://news.ycombinator.com/item?id=4840043источник