J'ai besoin de trier un bed
fichier au hasard 10000 fois et de prendre les 1000 premières lignes à chaque fois. Actuellement, j'utilise le code suivant:
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
Cela prend presque 6 heures pour cela pour chaque fichier. J'en ai environ 150 à régler. Existe-t-il une solution plus rapide pour cela?
Un échantillon des données (myfile.bed_sorted) que j'ai:
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
peut, euh, diviser un fichier en morceaux de 1000 lignes chacun, de sorte que vous obtiendrez plus de fichiers en un seul appel desort
. De plus, avez-vous vérifié s'ilhead
est légèrement plus rapide quetail
parce qu'il n'a pas besoin de lire l'intégralité du fichier?head
ici.sort -R
utilise un "hachage aléatoire des clés". La création du hachage est une perte de temps totale et prend probablement plus de temps que toute autre chose. Il serait préférable de lire les lignes dans un tableau, puis de mélanger celles-ci à l'aide d'index. Personnellement, j'utiliseraisperl
pour cela; vous pouvez le faire avecbash
mais vous aurez besoin d'une fonction pour générer des nombres aléatoires.perl
personne! Pourriez-vous s'il vous plaît m'aider?shuf
plutôtsort -R
, c'est considérablement plus rapide. Bien sûr, le faire en mémoire (voir la réponse Perl) battra tout ce qui nécessite de relire le fichier entier dans le shell.Réponses:
En supposant que vous disposez de suffisamment de mémoire pour récupérer le fichier, vous pouvez essayer
Puisque vous voulez faire cela 10000 fois, je recommanderais d'intégrer la répétition dans le script et de mélanger les index au lieu du tableau lui-même pour accélérer les choses:
Ce qui précède a créé 10 000 fichiers de 1 000 lignes chacun à partir d'un fichier qui contenait 37 000 lignes (votre exemple de fichier s'est répété 1 000 fois). Comme vous pouvez le voir, cela a pris un peu plus de trois minutes sur mon système.
Explication
use List::Util 'shuffle';
: ceci importe un module Perl qui fournit lashuffle()
fonction qui randomise un tableau.@l=<>;
: charge le fichier d'entrée (<>
) dans le tableau@l
.for $i (1..10000){}
: exécutez ce 10000 fois.@r=shuffle(0..$#l);
:$#l
est le nombre d'éléments dans est@l
donc@r
maintenant une liste aléatoire des numéros d'index du tableau@l
(les lignes du fichier d'entrée).open(my $fh, ">","file.$i.bed");
: ouvre un fichier appeléfile.$i.bed
en écriture.$i
prendra des valeurs de 1 à 10000.print $fh @l[@r[0..999]]
: prenez les 1000 premiers indices du tableau mélangé et imprimez les lignes correspondantes (éléments de@l
).Une autre approche consiste à utiliser
shuf
( merci @frostschutz ):la source
shuf
comme suggéré par Frostschutz:for i in {1..10000}; do shuf -n 1000 file.bed > file.$i.bed; done
. Cela a pris environ 1 minute sur mon système. Quant aux 1000 dernières lignes, tout ce dont vous avez besoin esttail -n 1000
.sys
temps, ce qui serait des E / S de fichiers - cela ne devrait pas être si totalement différent deshuf
celui qui a ~ 30ssys
. J'ai donc testé le Perl ici (couper et coller) et O_O il a créé 1000 fichiers mais tous les fichiers étaient vides ...Si vous voulez qu'un benchmark voie à quelle vitesse il peut être fait, copiez-collez-le dans
10kshuffle.cpp
et compilezg++ 10kshuffle.cpp -o 10kshuffle
. Vous pouvez ensuite l'exécuter:Où
filename
est un chemin de base à utiliser pour les fichiers de sortie; ils seront nommésfilename.0
,filename.1
etc. et chacun contient les 1000 premières lignes d'un shuffle. Il écrit le nom de chaque fichier au fur et à mesure.Sur un seul cœur de 3,5 GHz, cela s'exécute en ~ 20 secondes:
data.txt
était 37000 lignes dupliquées de la question. Si vous voulez le shuffle entier dans le fichier de sortie au lieu des 1000 premières lignes, changez la ligne 54 en:la source
Il y a donc un aspect Unix dans votre question, mais cela vaut la peine de résoudre d'abord votre problème fondamental, puis d'essayer de trouver un moyen Unix-y pour implémenter cette solution.
Vous devez créer 10 000 échantillons de taille 1 000 chacun à partir d'un fichier avec un grand nombre de lignes inconnu. Il est possible de le faire en un seul passage du fichier si vous pouvez conserver 10 000 x 1 000 lignes en mémoire. Si vous ne pouvez pas conserver autant de lignes en mémoire, vous pouvez toujours le faire en une seule passe si vous savez combien de lignes contient votre fichier. Si vous ne savez pas combien de lignes votre fichier contient, vous avez besoin d'une passe supplémentaire pour compter le nombre de lignes.
L'algorithme, dans le cas le plus difficile lorsque vous ne connaissez pas le nombre de lignes, consiste à effectuer les opérations suivantes pour chaque échantillon (en parallèle, conserver les échantillons en mémoire):
n > 1000
), incluez-la avec la probabilité1000 / n
et supprimez une ligne aléatoire parmi les lignes que vous avez déjà sélectionnées. (en raison de la probabilité de supprimer certaines lignes, nous devons conserver l'échantillon en mémoire jusqu'à la fin de l'entrée)Une manière élégante d'implémenter la deuxième étape consiste à générer un entier aléatoire
k
dans[1, n]
. Sik <= 1000
alors incluez la ligne et remplacez lak
-ième ligne existante par elle. Voici une description plus standard de l'algorithme: http://en.wikipedia.org/wiki/Reservoir_samplingSi vous connaissez le nombre de lignes
R
, alors:s
de 0(1000 - s) / (R - n + 1)
et la sortir immédiatement (et incrémenter la taille de l'échantillons
)Comment faire ça sous Unix?
awk
semble être la réponse par ce post sur Internet (je ne peux pas garantir son exactitude, mais le code est là) https://news.ycombinator.com/item?id=4840043la source