Como complemento a la entrada anterior de Álvaro os paso un enlace al programa
CD-HIT, que es una opción muy cómoda y eficiente para eliminar redundancia dentro de un conjunto de secuencias de proteínas o de DNA. El artículo donde se describe la primera versión de CD-HIT se publicó en Bioinformatics en 2006.
Aunque hay una serie de servidores web que pueden ayudarnos en esta tarea, si tienes un volumen realmente grande de secuencias lo más probable es que necesites descargar el código fuente (en C++) y compilarlo con make. Una vez compilado, el programa tiene múltiples opciones, pero su uso es muy sencillo. El algoritmo se basa en calcular la identidad de todas las secuencias (previamente ordenadas por longitud) por parejas para ir generando clusters de secuencias con una identidad superior al umbral deseado. Por defecto el umbral de identidad es del 90%, medida a lo largo de toda la secuencia (global), no sólo la alineada. Por supuesto se puede modificar este comportamiento y elegir por ejemplo umbrales más bajos o identidades locales.
Terminaré con un ejemplo de uso. Por ejemplo, dado el mismo archivo FASTA de la entrada anterior de Álvaro, para obtener el subconjunto no redundante al 80% sería necesario el siguiente comando en el terminal Linux:
$ cd-hit/cd-hit -i redundante.faa -o noredundante.faa -c 0.8
Como resultado, además del fichero noredundante.faa, se obtienen otros dos archivos (noredundante.faa.clstr y noredundante.faa.bak.clstr) que contienen
los clusters encontrados y las secuencias redundantes observadas.
Ideas y código para problemas de genómica de plantas, biología computacional y estructural
13 de julio de 2010
5 de julio de 2010
Eliminar secuencias redundantes y transcritos repetidos en genomas y proteomas
Una situación cada vez más habitual en genómica y proteómica es la redundancia de datos. Con las modernas técnicas de secuenciación de alto rendimiento se generan gigas y gigas de datos que es necesario filtrar y corregir para obtener resultados inteligibles.
El problema más habitual y sencillo es la redundancia de secuencias, cuando en un mismo fichero aparecen secuencias idénticas con identificadores diferentes. En otras ocasiones el problema es el contrario, identificadores idénticos apuntan a secuencias parciales de una misma secuencia (especialmente en datos de proteómica o transcriptómica).
El siguiente script filtra un fichero de sequencias en formato FASTA, dos tipos de filtro son posibles: 1) filtrar las secuencias redundantes (repetidas), 2) filtrar las secuencias de transcritos parciales (recuperando únicamente los transcritos de mayor secuencia en ficheros de proteínas)
An example FASTA file to test the script:
El problema más habitual y sencillo es la redundancia de secuencias, cuando en un mismo fichero aparecen secuencias idénticas con identificadores diferentes. En otras ocasiones el problema es el contrario, identificadores idénticos apuntan a secuencias parciales de una misma secuencia (especialmente en datos de proteómica o transcriptómica).
El siguiente script filtra un fichero de sequencias en formato FASTA, dos tipos de filtro son posibles: 1) filtrar las secuencias redundantes (repetidas), 2) filtrar las secuencias de transcritos parciales (recuperando únicamente los transcritos de mayor secuencia en ficheros de proteínas)
#!/usr/bin/perl
my ($infile) = @ARGV;
my $outfile = filter_fasta_file($infile,['longtrans','nonredundant']);
# Create a FASTA file with custom filtered sequences
sub filter_fasta_file {
my ($infile, $filter, $outfile) = @_;
if (!defined($outfile)){$outfile=$infile.'.filtered'}
open(INFILE,"$infile");
open(OUTFILE,">$outfile");
my ($sequences,$names,$headers) = read_FASTA_file($infile);
my (@previous_sequences,@previous_names,@previous_headers);
# my (@filtered_sequences,@filtered_names,@fitered_headers);
for (my $i=0; $i<=$#{$headers}; $i++){
my $write=1;
if (in_array($filter,'nonredundant')){
my ($found,$posic)=in_array($sequences, $sequences->[$i], 1);
foreach my $pos (@{$posic}){
if ($i != $pos && $i > $pos){
$write = 0; last;
}
}
}
if (in_array($filter,'longtrans')){
$names->[$i] =~ /([^\.]+)\.?(\d?)/;
my $name = $1;
my $variant = $2;
my ($found,$posic)=in_array($names, "/$1/", 1);
foreach my $pos (@{$posic}){
if ($i != $pos){
if (length($sequences->[$i]) < length($sequences->[$pos])) {
$write = 0; last;
}
if (length($sequences->[$i]) == length($sequences->[$pos]) && $sequences->[$i] eq $sequences->[$pos]){
$names->[$pos] =~ /([^\.]+)\.?(\d?)/;
my $name2 = $1;
my $variant2 = $2;
if ($variant2 < $variant){
$write = 0; last;
}
}
}
}
}
if ($write) {
print OUTFILE $headers->[$i]."\n".$sequences->[$i]."\n";
}
}
close INFILE;
close OUTFILE;
return $outfile;
}
#################################################################################
# Create 3 array references with sequences, names and headers from a FASTA file
sub read_FASTA_file
{
my($FASTAfile,$full_header,$allow_empty_sequences,$allow_multi_rows) = @_;
if(!$full_header){ $full_header = $allow_empty_sequences = 0 }
if(!$allow_empty_sequences){ $allow_empty_sequences = 0 }
if(!$allow_multi_rows){ $allow_multi_rows = 0 }
my (@sequences,@names,@headers,$seq,$seqname,$header);
my $headerOK = 0;
open(FASTA,$FASTAfile) || die "# read_FASTA_file : cannot find $FASTAfile\n";
$seq = '';
while(<FASTA>){
next if(/^#/); # allow comments
s/\012\015?|\015\012?|^\s+|\s+$//;
if(/>/){
$headerOK++;
if($seq || ($allow_empty_sequences && $seqname)){
if(!$allow_multi_rows){
$seq =~ s/\d|\s//g;
}
push(@sequences,$seq); # store this FASTA sequence
push(@names,$seqname);
push(@headers,$header);
$seq='';
}
if($full_header){
$seqname = substr($_,1);
}else { # get next sequence name
if (/^>\s*([^\t|^\||^;]+)/){
$header = $_;
$seqname = $1;
if (length($seqname)>32 && $seqname =~ /^([^\s]+)+\s/){
$seqname = $1;
}
$seqname =~ s/^\s+|\s+$// ;
}
}
}else{
if(!$allow_multi_rows){
s/\s+//g;
}
$seq .= $_;
}
}
close(FASTA);
# take care of last FASTA sequence
push(@sequences,$seq);
push(@names,$seqname);
push(@headers,$header);
if(!$headerOK){ @sequences = @names = @headers = (); } #Aug2007
return (\@sequences,\@names,\@headers);
}
#################################################################################
# Searchs a string inside an array
sub in_array {
my ($array,$search_for,$search_posic) = @_;
# my %items = map {$_ => 1} @$array; # create a hash out of the array values
# my $exist = (exists($items{$search_for}))?1:0;
# return (exists($items{$search_for}))?1:0;
my @posic;
# If search a pattern
if ($search_for =~ /^\/.+\/$/){
$search_for =~ s/^\/|\/$//g;
@posic = grep { $array->[$_] =~ /$search_for/ } 0 .. $#{$array};
} else {
@posic = grep { $array->[$_] eq $search_for } 0 .. $#{$array};
}
if (defined($search_posic) && $search_posic){
(scalar @posic)?return (1,\@posic):return (0);
} else {
(scalar @posic)?return 1:return 0;
}
}
An example FASTA file to test the script:
>AT1G51370.2 | Symbols: | F-box family protein | chr1:19045615-19046748 FORWARD
MVGGKKKTKICDKVSHEEDRISQLPEPLISEILFHLSTKDSVRTSALSTKWRYLWQSVPG
LDLDPYASSNTNTIVSFVESFFDSHRDSWIRKLRLDLGYHHDKYDLMSWIDAATTRRIQH
LDVHCFHDNKIPLSIYTCTTLVHLRLRWAVLTNPEFVSLPCLKIMHFENVSYPNETTLQK
LISGSPVLEELILFSTMYPKGNVLQLRSDTLKRLDINEFIDVVIYAPLLQCLRAKMYSTK
NFQIISSGFPAKLDIDFVNTGGRYQKKKVIEDILIDISRVRDLVISSNTWKEFFLYSKSR
PLLQFRYISHLNARFYISDLEMLPTLLESCPKLESLILVMSSFNPS*
>AT1G70790.1 | Symbols: | C2 domain-containing protein | chr1:26700724-26702127 FORWARD
MEDKPLGILRVHVKRGINLAIRDATTSDPYVVITLANQKLKTRVINNNCNPVWNEQLTLS
IKDVNDPIRLTVFDKDRFSGDDKMGDAEIDFRPFLEAHQMELDFQKLPNGCAIKRIRPGR
TNCLAEESSITWSNGKIMQEMILRLKNVECGEVELMLEWTDGPGCKGLGREGSKKTPWMP
TKRLD*
>AT1G50920.1 | Symbols: | GTP-binding protein-related | chr1:18870555-18872570 FORWARD
MVQYNFKRITVVPNGKEFVDIILSRTQRQTPTVVHKGYKINRLRQFYMRKVKYTQTNFHA
KLSAIIDEFPRLEQIHPFYGDLLHVLYNKDHYKLALGQVNTARNLISKISKDYVKLLKYG
DSLYRCKCLKVAALGRMCTVLKRITPSLAYLEQIRQHMARLPSIDPNTRTVLICGYPNVG
KSSFMNKVTRADVDVQPYAFTTKSLFVGHTDYKYLRYQVIDTPGILDRPFEDRNIIEMCS
ITALAHLRAAVLFFLDISGSCGYTIAQQAALFHSIKSLFMNKPLVIVCNKTDLMPMENIS
EEDRKLIEEMKSEAMKTAMGASEEQVLLKMSTLTDEGVMSVKNAACERLLDQRVEAKMKS
KKINDHLNRFHVAIPKPRDSIERLPCIPQVVLEAKAKEAAAMEKRKTEKDLEEENGGAGV
YSASLKKNYILQHDEWKEDIMPEILDGHNVADFIDPDILQRLAELEREEGIREAGVEEAD
MEMDIEKLSDEQLKQLSEIRKKKAILIKNHRLKKTVAQNRSTVPRKFDKDKKYTTKRMGR
ELSAMGLDPSSAMDRARSKSRGRKRDRSEDAGNDAMDVDDEQQSNKKQRVRSKSRAMSIS
RSQSRPPAHEVVPGEGFKDSTQKLSAIKISNKSHKKRDKNARRGEADRVIPTLRPKHLFS
GKRGKGKTDRR*
>AT1G70795.1 | Symbols: | C2 domain-containing protein | chr1:26700724-26702127 FORWARD
MEDKPLGILRVHVKRGINLAIRDATTSDPYVVITLANQKLKTRVINNNCNPVWNEQLTLS
IKDVNDPIRLTVFDKDRFSGDDKMGDAEIDFRPFLEAHQMELDFQKLPNGCAIKRIRPGR
TNCLAEESSITWSNGKIMQEMILRLKNVECGEVELMLEWTDGPGCKGLGREGSKKTPWMP
TKRLD*
>AT1G70790.2 | Symbols: | C2 domain-containing protein | chr1:26700724-26702127 FORWARD
MEDKPLGILRVHVKRGINLAIRDATTSDPYVVITLANQKLKTRVINNNCNPVWNEQLTLS
IKDVNDPIRLTVFDKDRFSGDDKMGDAEIDFRPFLEAHQMELDFQKLPNGCAIKRIRPGR
TNCLAEESSITWSNGKIMQEMILRLKNVECGEVELMLEWTDGPGCKGLGREGSKKTPWMP
TKRLD*
>AT1G36960.1 | Symbols: | unknown protein | chr1:14014796-14015508 FORWARD
MTRLLPYKGGDFLGPDFLTFIDLCVQVRGIPLPYLSELTVSFIAGTLGPILEMEFNQDTS
TYVAFIRVKIRLVFIDRLRFFRREEAAASNTITDQTHMTSSNSSDISPASPISQPPLPAS
LPSHDSYFDAGIQASRLVNPRAISQHHFSSSYSDFKGKEKAKIKIGECSKRKKDKQVDSG
T*
>AT1G51370.1 | Symbols: | F-box family protein | chr1:19045615-19047141 FORWARD
MVGGKKKTKICDKVSHEEDRISQLPEPLISEILFHLSTKDSVRTSALSTKWRYLWQSVPG
LDLDPYASSNTNTIVSFVESFFDSHRDSWIRKLRLDLGYHHDKYDLMSWIDAATTRRIQH
LDVHCFHDNKIPLSIYTCTTLVHLRLRWAVLTNPEFVSLPCLKIMHFENVSYPNETTLQK
LISGSPVLEELILFSTMYPKGNVLQLRSDTLKRLDINEFIDVVIYAPLLQCLRAKMYSTK
NFQIISSGFPAKLDIDFVNTGGRYQKKKVIEDILIDISRVRDLVISSNTWKEFFLYSKSR
PLLQFRYISHLNARFYISDLEMLPTLLESCPKLESLILEMVKNQSTRRHGEKREPNVMVS
TVPWCLVSSLKFVELKRSIPRYEGEMELVRYVLTNSTVLKKLRLNVYYTKKAKCAFLTEL
VAIPRCSSTCVVLVL*
1 de julio de 2010
A qué grupo taxonómico pertenece una especie?
Desde hace ya tiempo hay más secuencias genómicas disponibles que las que podemos recordar y a menudo nos encontramos con nombres de organismos que no podemos ubicar, no sabemos si son bacterias aguas termales o artrópodos, por ejemplo. El siguiente programa puede ayudarnos precisamente a colocar dentro de la clasificación taxonómica vigente del NCBI un nombre de especie, o en general, cualquier taxón de nombre aceptado. Antes de ejecutarlo debes acceder al servidor FTP en ftp://ftp.ncbi.nih.gov/pub/taxonomy y descomprimir los archivos de nombres y nodos, como se explica en la cabecera del código Perl, haciendo apuntar la variable global $TAXONOMYPATH al directorio donde se encuentren. Una vez hechos estos ajustes el código ya está listo para usarse desde el terminal. Por ejemplo, si consultamos la taxonomía de Oryza sativa obtendremos esta respuesta, que podemos comparar con la figura, tomada de www.biomedcentral.com/1471-2164/8/283:
Éste es el código:
$ ./prog6.pl Oryza sativa # input taxon: Oryza sativa # selected taxonID: 4530 \\ URL: http://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id=4530 # variants: > Oryza sativa > Oryza sativa L. # lineage : Oryza sativa, Oryza, Oryzeae, Ehrhartoideae, BEP clade, Poaceae, Poales,commelinids, Liliopsida, Magnoliophyta, Spermatophyta, Euphyllophyta, Tracheophyta, Embryophyta, Streptophytina, Streptophyta, Viridiplantae, Eukaryota, cellular organisms # rank : species, genus, tribe, subfamily, no_rank, family, order, subclass, class, no_rank, no_rank, no_rank, no_rank, no_rank, no_rank, phylum, kingdom, superkingdom, no_rank,
Éste es el código:
#!/usr/bin/perl -w
# prog6
# programa que devuelve informacion taxonomica para un taxon de entrada,
# que se apoya en la version texto de la clasificacion Taxonomy del NCBI,
# que se pueden descargar de ftp://ftp.ncbi.nih.gov/pub/taxonomy , en
# particular el fichero taxdump.tar.gz, que debes descomprimir para obtener
# los dos ficheros que se utilizan aqui
# Bruno Contreras Moreira, Junio de 2010
use strict;
$|=1;
if(!$ARGV[0])
{
die "# usage: $0 <taxon name from superkingdom to species, such as:\n".
" 'Arabidopsis thaliana' or 'Viridiplantae' but not 'thaliana'>\n";
}
my $VERBOSE = 0 ;
my $TAXONOMYPATH = '/path/taxonomy/'; # directorio de taxdump.tar.gz
my $NAMESFILE = $TAXONOMYPATH . 'names.dmp';
my $NODESFILE = $TAXONOMYPATH . 'nodes.dmp';
my $NCBIURL = 'http://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id=';
###################################################################
my ($n_of_taxa,$taxonID,$taxon,$ID,$name,$rank,$parentID) = (0,'','');
my (@names,%node,@lineage,@sorted_lineage,%lineage_name);
my ($lineage_names,$lineage_ranks) = ('','');
## 1) captura el nombre del taxon de entrada, como Arabidopsis thaliana, y
## sustituye caracteres '_' por espacios si los hubiera
if(scalar(@ARGV) > 1){ @ARGV = @ARGV[0,1]; } # corta tercera palabra < especie
else{ @ARGV = split(/_/,$ARGV[0]); @ARGV = @ARGV[0,1]; }
$taxon = join(' ',@ARGV);
$taxon =~ s/^\s+//g; # elimina espacios iniciales y finales
$taxon =~ s/\s+$//g;
print "# input taxon: $taxon\n\n";
## 2) busca el taxonID del taxon de entrada
open(NAMES,$NAMESFILE) || die "# $0 : cannot open $NAMESFILE\n";
while(<NAMES>)
{
next if(!/$taxon/i);
#3702 | Arabidopsis thaliana | | scientific name
if(/^(\d+)\s+\|\s+(.*?)\|/)
{
($ID,$name) = ($1,$2);
$name =~ s/^\s+//g;
$name =~ s/\s+$//g;
# comprueba que el taxon buscado coincide con el principio de $name,
# evita aceptar cadenas que en realidad coinciden con la especie,
# que a veces se pueden encontrar en generos muy distintos
next if(index($name,$taxon) != 0);
# si el taxon buscado contiene una sola palabra evita
# aceptar cadenas de especies, que tendran dos palabras
next if($taxon !~ /\s+/ && $name =~ /\s+/);
if(!$n_of_taxa || $ID eq $taxonID) # guarda primer taxonID
{
$taxonID = $ID;
push(@names,$name);
$n_of_taxa++;
if($VERBOSE){ print }
}
else
{
if($VERBOSE)
{
# muestra otras posibles apariciones del taxon
print "# skip taxon: $ID\n";
}
else{ last } # termina la lectura del fichero
}
}
}
close(NAMES);
if(!$n_of_taxa){ die "# cannot find taxon $taxon in $NAMESFILE , exit\n"; }
else
{
print "# selected taxonID: $taxonID URL: $NCBIURL$taxonID\n";
print "\n# variants:\n";
foreach $name (@names){ print "> $name\n"; }
}
## 3) busca taxon padre del taxonID encontrado
open(NODES,$NODESFILE) || die "# $0 : cannot open $NODESFILE\n";
while(<NODES>)
{
#132596 | 79512 | genus...
#tax_id -- node id in GenBank taxonomy database
#parent tax_id -- parent node id in GenBank taxonomy
#rank -- rank of this node (superkingdom, kingdom, ...)
next if(/forma/ || /varietas/); # evita rangos < especie
if(/^$taxonID\t/)
{
my @data = split(/\t|\t/,$_);
$parentID = $data[2];
$rank = $data[4];
if($VERBOSE){ print }
last;
}
}
close(NODES);
if(!$parentID){ die "\n# cannot find taxon '$taxon' in $NODESFILE , exit\n"; }
## 4) lee arbol taxonomico del nivel de especie hacia arriba
## (son en 2010 35MB, caben en cualquier RAM)
open(NODES,$NODESFILE) || die "# $0 : cannot open $NODESFILE\n";
while(<NODES>)
{
next if(/forma/ || /varietas/);
if(/^(\d+)\t\|\t(\d+)\t\|\t(\S+)/)
{
$node{$1}{'p'} = $2; # p = parent node
$node{$1}{'r'} = $3; # r = rank
}
}
close(NODES);
## 5) reconstruye linaje de $taxonID en terminos de IDs
# 5.1) primero guarda datos de $taxonID
push(@lineage,$taxonID);
$lineage_name{$taxonID} = $rank;
$node{$taxonID}{'r'} = $rank;
# 5.2) ahora guarda los de los taxones superiores
while($parentID ne $taxonID && $parentID > 1)
{
push(@lineage,$parentID);
$parentID = $node{$parentID}{'p'};
}
@sorted_lineage = sort{$a<=>$b}(@lineage);
$taxon = shift(@sorted_lineage);
## 6) recupera los nombres del linaje de $taxonID leyendo NAMES de nuevo,
## que contiene los taxones ordenados de menor a mayor
open(NAMES,$NAMESFILE) || die "# $0 : cannot open $NAMESFILE\n";
while(<NAMES>)
{
next if(!/scientific name/);
if(/^(\d+)\s+\|\s+(.*?)\|/)
{
($ID,$name) = ($1,$2);
next if($ID ne $taxon);
$name =~ s/^\s+//g;
$name =~ s/\s+$//g;
$lineage_name{$ID} = $name;
if(@sorted_lineage){ $taxon = shift(@sorted_lineage) }
else{ last } # termina si no quedan mas IDs
}
}
close(NAMES);
## 7) enumera el linaje de $taxoID
foreach $ID (@lineage)
{
$rank = $node{$ID}{'r'};
if($rank eq 'no'){ $rank = 'no_rank' }
$lineage_names .= "$lineage_name{$ID}, ";
$lineage_ranks .= "$rank, ";
}
print "\n# lineage : $lineage_names\n# rank : $lineage_ranks\n";
17 de junio de 2010
Qué ha hecho la Bioinformática por nosotros?
Aunque esto quedaría mejor en el blog de mi colega José Maria, os paso este enlace en inglés que me ha llegado por la lista de correo del Protein Data Bank, donde puedes proponer y elegir la contribución más importante de la Bioinformática a la ciencia. Alguien ha propuesto ya la comparación entre el genoma humano y el del chimpancé, por si estabas pensando en esto, saludos.
Clusters de proteínas ortólogas mediante el algoritmo COGs
Tras leer el artículo que sale hoy en Nature sobre la expansión del espacio de secuencias de proteínas, que se asemeja a la expansión del Universo tal como la imaginaba Hubble, he vuelto a encontrarme con el algoritmo COGs (Clusters of Orthologous Groups), originalmente creado por Tatusov en 1997 y actualizado por David Kristensen hace unas semanas. En esencia este algoritmo permite definir grupos de secuencias (potencialmente) ortólogas entre 3 o más genomas a partir de los resultados de BLAST de todos contra todos. Para ello utiliza el concepto de los 'bidirectional best hits', que se dibuja como flechas bidireccionales en esta figura, tomada del paper de Kristensen:
El código que publico hoy permite calcular COGs a partir de un directorio con archivos FASTA con secuencias de proteínas, utilizando los binarios del algoritmo COGtriangles, que se pueden obtener de SourceForge, y los de BLAST, que podemos descargar del NCBI.
El código que publico hoy permite calcular COGs a partir de un directorio con archivos FASTA con secuencias de proteínas, utilizando los binarios del algoritmo COGtriangles, que se pueden obtener de SourceForge, y los de BLAST, que podemos descargar del NCBI.
#!/usr/bin/perl -w
# prog5.pl
# Script que ejecuta el algoritmo COGtriangle sobre los archivos FASTA contenidos
# en el directorio -d, basado en el código original de David Kristensen (Jun 2010).
# El programa asume que cada archivo .faa contiene CDS de genomas distintos
# y puede agrupar la misma secuencia en mas de un cluster, cuando sea multidominio.
use strict 'subs';
use Getopt::Std;
use File::Basename;
## 0) global variables, including binaries to be installed
## and parameters that affect the whole algorithm
# URL: ftp://ftp.ncbi.nih.gov/blast/executables/
my $BLASTPATH = '/home/whatever/blast-2.2.18/';
my $FORMATDBEXE = $BLASTPATH . 'bin/formatdb';
my $BLASTPGPEXE = $BLASTPATH . 'bin/blastpgp -I T -m 9 -b 1000 -v 1000 -z 100000000';
# URL: http://sourceforge.net/projects/cogtriangles/files/
my $COGPATH = '/home/soft/COGsoft/';
my $MAKEHASHEXE = $COGPATH . "COGmakehash/COGmakehash -s=',' -n=1";
my $READBLASTEXE= $COGPATH . 'COGreadblast/COGreadblast -e=10 -q=2 -t=2';
my $LSEEXE = $COGPATH . 'COGlse/COGlse';
my $COGTRIANGLESEXE=
$COGPATH . "COGtriangles/COGtriangles -t=0.5 -e=10 -n='cluster' -s=1";
# the default COGtriangle algorithm requires two BLAST searches
my $TWOBLASTRUNS=1; # set to 0 to perform only a filtered BLAST search
# extension of valid files in -d directories
my $FAAEXT = '.faa';
## 1) process command-line arguments ##############################
my ($INP_FASTAdir,$INP_skipBLAST,$INP_BLASToptions,$newDIR,$id) = ('',0,'');
my ($n_of_sequences,$pwd,$genome,$clusterid,%sequences,%genomeused,%COGs) = (0);
my ($allfaafile,$p2ofilename,$lsefilename,$lseoutfilename,$coglogfilename) =
('all.faa','all.p2o.csv','all.lsejob.csv','all.lse.csv','all.cog.clusters.log');
my ($cogedgesfilename,$edgesfilename) = ('cog-edges.txt','all-edges.txt');
getopts('hsb:d:', \%opts);
if(($opts{'h'})||(scalar(keys(%opts))==0))
{
print "usage: $0 [options]\n";
print " -h this message\n";
print " -d input directory with FASTA .faa files (required, example -d genomes)\n";
print " -s skip BLAST and re-use previous searches (optional)\n";
die " -b extra blastpgp options (optional, example -b '-a 2' to use 2 CPUs)\n";
}
if(!defined($opts{'d'})
|| !-d $opts{'d'}){ die "# $0 : need a valid FASTA directory, exit\n"; }
else
{
$INP_FASTAdir = $opts{'d'};
if(substr($INP_FASTAdir,length($INP_FASTAdir)-1,1) eq '/'){ chop $INP_FASTAdir }
$pwd = `pwd`; chomp $pwd; $pwd .= '/';
$newDIR = $pwd.basename($INP_FASTAdir)."_COGs";
mkdir($newDIR) if(!-d $newDIR);
}
if(defined($opts{'s'})){ $INP_skipBLAST = 1 }
if(defined($opts{'b'}) && !$INP_skipBLAST){ $INP_BLASToptions = $opts{'b'} }
print "# results_directory=$newDIR\n\n";
## 2) concatenate $FAAEXT files and create csv file with protein2organism indexing
opendir(FAADIR,$INP_FASTAdir) || die "# $0 : cannot list $INP_FASTAdir, exit\n";
@faafiles = grep {/$FAAEXT/} readdir(FAADIR);
close(FAADIR);
die "# $0 : need at least two $FAAEXT files in $INP_FASTAdir\n"
if(scalar(@faafiles)<2);
open(ALLFAA,">$newDIR/$allfaafile")
|| die "# $0 : cannot create $newDIR/$allfaafile\n";
open(PROTORG,">$newDIR/$p2ofilename")
|| die "# $0 : cannot create $newDIR/$p2ofilename, exit\n";
foreach my $faafile (@faafiles)
{
print "# reading $INP_FASTAdir/$faafile\n";
my $rhash_FASTA = read_FASTA_sequence("$INP_FASTAdir/$faafile");
foreach my $seq (sort {$a<=>$b} (keys(%$rhash_FASTA)))
{
$n_of_sequences++;
print PROTORG "$n_of_sequences,$faafile\n"; # csv file
#concatenated FASTA with numbers as headers
print ALLFAA ">$n_of_sequences\n$rhash_FASTA->{$seq}{'SEQ'}\n";
#store number - real header equivalence, could be done with BerkeleyDB
$sequence_data{$n_of_sequences} = $rhash_FASTA->{$seq}{'NAME'};
$sequence_data{$n_of_sequences.'PROT'} = $rhash_FASTA->{$seq}{'SEQ'};
}
$genomeused{$faafile} = 1;
}
close(PROTORG);
close(ALLFAA);
## 3) run BLASTs searches #########################################
if(!$INP_skipBLAST)
{
print "# Formatting BLAST database...\n";
system("$FORMATDBEXE -i $newDIR/$allfaafile -o T");
if(-s 'formatdb.log'){ system("mv -f formatdb.log $newDIR") }
if($TWOBLASTRUNS)
{
print "# Running unfiltered BLAST...\n";
mkdir($newDIR.'/BLASTno');
system("$BLASTPGPEXE -d $newDIR/$allfaafile -i $newDIR/$allfaafile -F F -t F ".
"-o $newDIR/BLASTno/all.tab $INP_BLASToptions 2> " .
"$newDIR/BLASTno/blastlog_unfiltered");
}
print "# Running filtered BLAST...\n"; # see PMID: 20439257
mkdir($newDIR.'/BLASTff');
system("$BLASTPGPEXE -d $newDIR/$allfaafile -i $newDIR/$allfaafile -F T -t T ".
"-o $newDIR/BLASTff/all.tab $INP_BLASToptions 2> " .
"$newDIR/BLASTff/blastlog_unfiltered");
}
## 4) calculate COG triangles ######################################
print "\n# making COG hash...\n";
mkdir($newDIR.'/BLASTconv');
system("$MAKEHASHEXE -i=$newDIR/$p2ofilename -o=$newDIR/BLASTconv");
print "# reading BLAST files...\n";
if($TWOBLASTRUNS)
{
system("$READBLASTEXE -d=$newDIR/BLASTconv -u=$newDIR/BLASTno " .
" -f=$newDIR/BLASTff -s=$newDIR/BLASTno");
}
else{ system("$READBLASTEXE -d=$newDIR/BLASTconv -u=$newDIR/BLASTff ".
"-f=$newDIR/BLASTff -s=$newDIR/BLASTff"); }
print "# checking Lineage-specific expansions...\n";
open (LSEIN, ">$newDIR/$lsefilename")
|| die "# $0 : cannot create $newDIR/$lsefilename, exit\n";
foreach $genome (sort(keys(%genomeused)))
{
foreach my $genome2 (sort(keys(%genomeused)))
{
next if($genome eq $genome2);
print LSEIN "$genome,$genome2\n";
}
}
close LSEIN;
mkdir($newDIR.'/tmp');
system("$LSEEXE -t=$newDIR/tmp -d=$newDIR/BLASTconv -j=$newDIR/$lsefilename ".
" -p=$newDIR/$p2ofilename -o=$newDIR/$lseoutfilename > /dev/null");
print "# making COGs...\n"; # genera all-edges.txt , cog-edges.txt
system("$COGTRIANGLESEXE -i=$newDIR/BLASTconv -q=$newDIR/$p2ofilename " .
" -l=$newDIR/$lseoutfilename -o=$newDIR/$coglogfilename");
# $COGTRIANGLESEXE puts them in cwd, Jun2010!
if(-e $cogedgesfilename){ system("mv -f $cogedgesfilename $newDIR") }
if(-e $edgesfilename){ system("mv -f $edgesfilename $newDIR") }
## 5) group sequences sharing COG #################################
open(COGS,"$newDIR/$cogedgesfilename") ||
die "# $0 : cannot read $newDIR/$cogedgesfilename, the program failed\n";
while(<COGS>)
{
#cluster00332,912,Buch_aph_Bp.faa,405,Buch_aphid_Sg.faa
my @data = split(/,/,$_);
($clusterid,$id,$genome) = @data[0,1,2];
push(@{$COGs{$clusterid}{'genomes'}},$genome)
if(!grep(/^$genome$/,@{$COGs{$clusterid}{'genomes'}}));
push(@{$COGs{$clusterid}{'ids'}},$id)
if(!grep(/^$id$/,@{$COGs{$clusterid}{'ids'}}));
}
close(COGS);
print "\n# cluster list:\n";
mkdir($newDIR.'/clusters');
foreach $clusterid (sort {substr($a,7)<=>substr($b,7)} (keys(%COGs)))
{
print ">$newDIR/clusters/$clusterid.faa sequences=".
scalar(@{$COGs{$clusterid}{'ids'}}).
" genomes=".scalar(@{$COGs{$clusterid}{'genomes'}})."\n";
open(COGFAA,">$newDIR/clusters/$clusterid.faa") ||
die "# $0 : cannot create $newDIR/cogs/$clusterid.faa, exit\n";
foreach $id (@{$COGs{$clusterid}{'ids'}})
{
print COGFAA ">$sequence_data{$id} ($clusterid)\n$sequence_data{$id.'PROT'}\n";
}
close(COGFAA);
}
print "\n# total COGs = ".scalar(keys(%COGs))." in folder $newDIR/clusters\n";
exit(0);
################################################################
sub read_FASTA_sequence
{
my ($infile) = @_;
my ($n_of_sequences,%FASTA) = (0);
open(FASTA,$infile) || die "# read_FASTA_sequence: cannot read $infile $!:\n";
while(<FASTA>)
{
if(/^\>(.*?)\n/)
{
$n_of_sequences++;
$FASTA{$n_of_sequences}{'NAME'} = $1;
}
else
{
$FASTA{$n_of_sequences}{'SEQ'} .= $_;
$FASTA{$n_of_sequences}{'SEQ'} =~ s/[\s|\n|\d]//g;
}
}
close(FASTA);
return(\%FASTA);
}
Suscribirse a:
Entradas (Atom)