7 de junio de 2010

Regresión lineal e intervalo de confianza

Una cuestión habitual en bioinformática es la de buscar e interpretar posibles correlaciones entre variables de un conjunto de datos. Cuando la correlación entre dos variables es significativa a menudo es interesante modelar la relación entre ellas por medio de una función lineal de regresión. Como todo modelo, este tipo de funciones tienen asociado un grado de error o incertidumbre, que podrá o no ser tolerable según el uso que queramos darle.



En este ejemplo mostramos gráficamente el error de una función de regresión, partiendo de un conjunto de datos, que guardaremos en un fichero llamado datos_regresion.tab, con datos separados con tabuladores:

days y2000 y2010
1 33.61 20.58
5 31.96 23.70
9 40.42 23.30
15 35.43 33.12
18 46.13 31.13
22 39.19 36.30
25 42.72 33.97
29 46.72 31.26
31 42.62 32.33
36 45.09 34.10 
39 55.05 38.17
43 46.89 37.46
46 54.98 40.08
51 51.83 40.68
54 54.64 40.68
57 49.87 39.47
60 51.33 39.57

De las tres variables vamos a trabajar con las dos primeras, days, que cuenta los días desde el inicio de lexperimento, y y2000, que se corresponde con las mediciones de la variableX en el año 2000. El siguiente código en R muestra como calcular la correlación, el coeficiente de determinación (y su P-valor) y el intervalo de confianza al 95% de la recta de regresión:

1:  # prog4.R , inspirado por ejemplos de   
2:  # http://wwwmaths.anu.edu.au/~johnm/r-book/2edn/scripts/exploit.R  
3:    
4:  # función: grafica el intervalo de confianza 0.95 en torno a la regresión  
5:  curvasIC <- function(form=varX$y2010~varX$days,data=varX,lty=2,  
6:     col=3,lwd=2,newdata=data.frame(days=varX$days))  
7:  {   
8:    varX.lm <- lm(form,data=data)   
9:    predmod <- predict(varX.lm,newdata=newdata,interval="confidence")   
10:    lines(spline(newdata$days,predmod[,"fit"]),lwd=3)   
11:    lines(spline(newdata$days,predmod[,"lwr"]),lty=lty,lwd=lwd,col=col)   
12:    lines(spline(newdata$days,predmod[,"upr"]),lty=lty,lwd=lwd,col=col)   
13:  }  
14:    
15:  varX = read.table("datos_regresion.tab",header=T)  
16:    
17:  ## nos centramos en la serie 2000   
18:  lm2000 = lm(varX$y2000~varX$days) # calcula regresión lineal  
19:  sum = summary(lm2000)  
20:    
21:  ## imprime diagrama XY   
22:  title = sprintf("year 2000 (R^2=%.2f)",sum$r.squared)  
23:  plot(varX$days,varX$y2000,ylab="variableX [arbitrary units]",  
24:     xlab="(days from experiment start)",main=title )  
25:    
26:  ## imprime regresión, intervalos de confianza y función ajustada  
27:  curvasIC(form=varX$y2000~varX$days)  
28:  text(40,35,sprintf("variableX (days) = %1.2f + %1.2f days",  
29:     lm2000$coefficients[1],lm2000$coefficients[2]))  
30:  text(40,32,sprintf("P-value = %1.3g",sum$coefficients[2,4]))  
31:    
32:  ## crea copia en formato PDF  
33:  dev.copy(pdf, file="regresion2000.pdf")  
34:  dev.off()  

4 de junio de 2010

Evaluación de un alineamiento de proteínas

Un alineamiento de secuencias proteicas se puede evaluar numéricamente comparando las identidades entre los aminoácidos alineados. Se puede asignar una puntuación a cada par de aminoácidos usando una matriz de sustitución, que describe el ritmo al que un aminoácido en una secuencia cambia a otro a lo largo de la evolución natural.

Las matrices de sustitución más utilizadas son las PAM y las BLOSUM. Éstas matrices son las que utiliza el famoso programa de alineamiento BLAST.

En el siguiente ejemplo se mostrará una sencilla  subrutina en Perl que evalúa un alineamiento de proteínas utilizando la matriz BLOSUM62. Las dos secuencias alineadas se pasan como sendas cadenas de caracteres:


1:  # prog3.pl Evalúa un alineamiento
2:  sub score_alignment {  
3:       my ($type,$score_matrix,$seq1,$seq2)=@_;  
4:       my (@indices,@matrix,@score_results);  
5:       if ($score_matrix eq 'blosum62'){  
6:            @indices = ('C', 'S', 'T', 'P', 'A', 'G', 'N', 'D', 'E', 'Q', 'H', 'R', 'K', 'M', 'I', 'L', 'V', 'F', 'Y', 'W');  
7:            @matrix = (  
8:                 [9, -1, -1, -3, 0, -3, -3, -3, -4, -3, -3, -3, -3, -1, -1, -1, -1, -2, -2, -2],  
9:                 [-1, 4, 1, -1, 1, 0, 1, 0, 0, 0, -1, -1, 0, -1, -2, -2, -2, -2, -2, -3],  
10:                 [-1, 1, 4, 1, -1, 1, 0, 1, 0, 0, 0, -1, 0, -1, -2, -2, -2, -2, -2, -3],  
11:                 [-3, -1, 1, 7, -1, -2, -1, -1, -1, -1, -2, -2, -1, -2, -3, -3, -2, -4, -3, -4],  
12:                 [0, 1, -1, -1, 4, 0, -1, -2, -1, -1, -2, -1, -1, -1, -1, -1, -2, -2, -2, -3],  
13:                 [-3, 0, 1, -2, 0, 6, -2, -1, -2, -2, -2, -2, -2, -3, -4, -4, 0, -3, -3, -2],  
14:                 [-3, 1, 0, -2, -2, 0, 6, 1, 0, 0, -1, 0, 0, -2, -3, -3, -3, -3, -2, -4],  
15:                 [-3, 0, 1, -1, -2, -1, 1, 6, 2, 0, -1, -2, -1, -3, -3, -4, -3, -3, -3, -4],  
16:                 [-4, 0, 0, -1, -1, -2, 0, 2, 5, 2, 0, 0, 1, -2, -3, -3, -3, -3, -2, -3],  
17:                 [-3, 0, 0, -1, -1, -2, 0, 0, 2, 5, 0, 1, 1, 0, -3, -2, -2, -3, -1, -2],  
18:                 [-3, -1, 0, -2, -2, -2, 1, 1, 0, 0, 8, 0, -1, -2, -3, -3, -2, -1, 2, -2],  
19:                 [-3, -1, -1, -2, -1, -2, 0, -2, 0, 1, 0, 5, 2, -1, -3, -2, -3, -3, -2, -3],  
20:                 [-3, 0, 0, -1, -1, -2, 0, -1, 1, 1, -1, 2, 5, -1, -3, -2, -3, -3, -2, -3],  
21:                 [-1, -1, -1, -2, -1, -3, -2, -3, -2, 0, -2, -1, -1, 5, 1, 2, -2, 0, -1, -1],  
22:                 [-1, -2, -2, -3, -1, -4, -3, -3, -3, -3, -3, -3, -3, 1, 4, 2, 1, 0, -1, -3],  
23:                 [-1, -2, -2, -3, -1, -4, -3, -4, -3, -2, -3, -2, -2, 2, 2, 4, 3, 0, -1, -2],  
24:                 [-1, -2, -2, -2, 0, -3, -3, -3, -2, -2, -3, -3, -2, 1, 3, 1, 4, -1, -1, -3],  
25:                 [-2, -2, -2, -4, -2, -3, -3, -3, -3, -3, -1, -3, -3, 0, 0, 0, -1, 6, 3, 1],  
26:                 [-2, -2, -2, -3, -2, -3, -2, -3, -2, -1, 2, -2, -2, -1, -1, -1, -1, 3, 7, 2],  
27:                 [-2, -3, -3, -4, -3, -2, -4, -4, -3, -2, -2, -3, -3, -1, -3, -2, -3, 1, 2, 11]  
28:            );  
29:       }  
30:       if (length($seq1)==length($seq2)){  
31:            my @seq1 = split(//,$seq1);  
32:            my @seq2 = split(//,$seq2);  
33:            for (my $i=0; $i<=$#seq1; $i++) {  
34:                 # Find letters position in matrix  
35:                 my ($pos1) = grep { $indices[$_] eq $seq1[$i] } 0 .. $#indices;  
36:                 my ($pos2) = grep { $indices[$_] eq $seq2[$i] } 0 .. $#indices;  
37:                 if (defined($pos1) && defined($pos2)){  
38:                      push (@score_results, $matrix[$pos1][$pos2]);  
39:                 } else {  
40:                      push (@score_results, '-');  
41:                 }  
42:            }  
43:       } else {  
44:            die "# Both sequences must have the same length to score the alignment\n";  
45:       }  
46:       return @score_results;  
47:  }  

2 de junio de 2010

Zotero - Gestor de bibliografía web 2.0

Zotero es un gestor de bibliografía gratuito que funciona como extensión de Firefox. Su funcionamiento es similar al de otros gestores de pago como EndNote o ProCite y además tiene todas las ventajas de poder ser consultado online como CiteULike
Permite su uso tanto online como offline, además permite importar y exportar referencias en múltiples formatos (RIS, TeX...). Y lo más importante, sus plugins permiten insertar referencias bibliográficas tanto en Microsoft Word como en OppenOffice con gran facilidad y con cientos de estilos diferentes para elegir.
Otra gran característica de este gestor bibliográfico es su posibilidad de leer citas bibliográficas en páginas web como Amazon, Google Scholar o Pubmed e incorporarlas a nuestra propia librería.
En definitiva, Zotero es una de las opciones más interesantes en gestores bibliográficos en la actualidad.
Más información:

    Gráfico sencillo de dominios sobre secuencia (glifo)

    El problema que nos ocupa hoy es el de representar gráficamente dominios o regiones sobre una secuencia de referencia. Quizás el ejemplo más típico sea el de mapear los resultados de una búsqueda BLAST sobre la secuencia de partida, que no siempre alinean contra la secuencia completa.
    Por ejemplo, dada una secuencia de 935 aminoácidos, BLAST me devuelve un alineamiento que solapa desde la posición 23 a la 512. Cómo podemos representar esto gŕaficamente de manera sencilla? Es posible usar BioPerl para este fin, como se explica en el taller de (bio)perl, pero si queremos un diagrama como este,


    que representa la longitud total de la secuencia y marca el segmento alineado a escala, es mejor recurrir al módulo GD, como se muestra en este programa:

    1:  #!/usr/bin/perl -w
    2: # prog2.pl: crea barra PNG con uno o más dominios sobre secuencia de referencia
    3:
    4: use strict;
    5: use GD;
    6:
    7: # variables globales que definen el tamaño de la figura rectangular
    8: my $ANCHOFIGURA = 100;
    9: my $ALTOFIGURA = 8;
    10: my @COLORDOMINIO= (150,150,150); # en 3 canales RGB
    11:
    12: # ejemplo: secuencia de 935 aminoácidos, con un dominio reconocido del 23 al 512
    13: if(pinta_dominios_secuencia(935,'23:512','panel.png'))
    14: {
    15: print "# archivo de salida: panel.png\n";
    16: }
    17:
    18: sub pinta_dominios_secuencia
    19: {
    20: # argumentos:
    21: # 1) tamaño de secuencia de referencia
    22: # 2) cadena con dominios separados por compas, como: '23:78,123:189'
    23: # 3) nombre de archivo PNG de salida
    24: my ($length,$domain_definitions,$outPNGfilename) = @_;
    25:
    26: my ($n_of_domains,$dom,@domains,$startd,$endd) = (0);
    27:
    28: # comprueba los dominios
    29: foreach $dom (split(/,/,$domain_definitions))
    30: {
    31: if($dom =~ /(\d+):(\d+)/)
    32: {
    33: ($startd,$endd) = ($1,$2);
    34: if($startd<1 || $endd > $length)
    35: {
    36: print "# pinta_dominios_secuencia: dominio incorrecto ($dom)\n";
    37: next;
    38: }
    39:
    40: push(@domains,[$startd,$endd]);
    41: $n_of_domains++;
    42: }
    43: else{ print "# pinta_dominios_secuencia: dominio incorrecto ($dom)\n"; }
    44: }
    45:
    46: if(@domains)
    47: {
    48: my $imagen = GD::Image->new($ANCHOFIGURA,$ALTOFIGURA);
    49:
    50: # define colores básicos en 3 canales RGB
    51: my $blanco= $imagen->colorAllocate(255,255,255);
    52: my $negro = $imagen->colorAllocate(0,0,0);
    53: my $color = $imagen->colorAllocate(@COLORDOMINIO);
    54:
    55: # crea panel rectangular con dimensiones globales $ANCHOFIGURA y $ALTOFIGURA
    56: $imagen->filledRectangle(0, 0, $ANCHOFIGURA, $ALTOFIGURA, $blanco); # fondo
    57: $imagen->rectangle(0, 0, $ANCHOFIGURA-1, $ALTOFIGURA-1, $negro); # borde
    58:
    59: # añade dominios
    60: foreach $dom (@domains)
    61: {
    62: # calcula dimensiones del dominio a escala, eje X
    63: $startd = int( $ANCHOFIGURA * ( $dom->[0] / $length ) );
    64: if($startd < 1){ $startd = 1 } # evita pisar el borde
    65: $endd = int( $ANCHOFIGURA * ( $dom->[1] / $length ) );
    66: if($endd > $ANCHOFIGURA-2){ $endd = $ANCHOFIGURA-2 } # evita pisar el borde
    67: # con 1 y -2 evitamos el borde en eje Y
    68: $imagen->filledRectangle($startd, 1, $endd, $ALTOFIGURA-2, $color);
    69: }
    70:
    71: open(PNG,">$outPNGfilename")
    || die "# pinta_dominios_secuencia: no puedo crear $outPNGfilename\n";
    72: binmode PNG;
    73: print PNG $imagen->png();
    74: close(PNG);
    75: }
    76:
    77: return $n_of_domains;
    78: }

    1 de junio de 2010

    Extraer secuencias intergénicas de archivos GenBank

    La tarea de hoy consiste en leer un archivo GenBank, por ejemplo de un genoma circular bacteriano, con el fin de extraer las secuencias intergénicas. Para esta tarea vamos a utilizar código de BioPerl y antes necesitaremos descargar al menos un fichero GenBank, como se explica por ejemplo en el taller de (bio)perl. Una vez tengamos los archivos de entrada necesarios, podemos resolver el problema de las secuencias intergénicas con una subrutina como ésta:

    1:  use Bio::SeqIO;
    2:
    3: # prog1.pl
    4: # recibe hasta 4 argumentos:
    5: # 1) archivo entrada en formato GenBank
    6: # 2) nombre del archivo de salida en formato FASTA
    7: # 3) longitud mínima de las regiones intergénicas (opcional, por defecto toma todas)
    8: # 4) longitud máxima de las regiones intergénicas (opcional)
    9:
    10: sub extract_intergenic_from_genbank
    11: {
    12: my ($infile,$out_intergenic_file,$min_intergenic_size,$max_intergenic_size) = @_;
    13:
    14: my ($n_of_intergenic,$gi,$start,$end,$length,$strand,$taxon) = (0);
    15: my $in = new Bio::SeqIO(-file => $infile, -format => 'genbank' );
    16:
    17: open(FNA,">$out_intergenic_file") ||
    18: die "# extract_intergenic_from_genbank : cannot create $out_intergenic_file\n";
    19:
    20: while( my $seq = $in->next_seq) # scan all sequences found in $input
    21: {
    22: my ($gbaccession,$sequence,$gen,@genes) = ( $seq->accession() );
    23: $sequence = $seq->primary_seq()->seq() ||
    24: 'no encuentro la secuencia primaria, necesito un fichero GenBank completo!';
    25: $taxon = '';
    26:
    27: for my $f ($seq->get_SeqFeatures)
    28: {
    29: if($f->primary_tag =~ /CDS|rRNA|tRNA/) # campos de 'genes'
    30: {
    31: $gi = '';
    32: if($f->has_tag('db_xref'))
    33: {
    34: my $crossrefs = join(',',sort $f->each_tag_value('db_xref'));
    35: if($crossrefs =~ /(GI\:\d+)/){ $gi = $1 }
    36: }
    37: elsif($f->has_tag('locus_tag'))
    38: {
    39: if($gi eq '' && $f->has_tag('locus_tag'))
    40: {
    41: $gi = "ID:".join(',',sort $f->each_tag_value('locus_tag'));
    42: }
    43: }
    44:
    45: next if($gi eq '');
    46:
    47: $start = $f->start();
    48: $end = $f->end();
    49: $strand = $f->location()->strand();
    50: push(@genes,[$start,$end,$gi,$strand]);
    51: }
    52: elsif($f->primary_tag() =~ /source/)
    53: {
    54: if($f->has_tag('organism'))
    55: {
    56: foreach my $element ($f->each_tag_value('organism'))
    57: {
    58: $taxon .= "[$element],";
    59: }
    60: chop $taxon;
    61: }
    62: }
    63: }
    64:
    65: # calcular ORFs vecinos y corta las secuencias intergenicas
    66: for($gen=1;$gen<scalar(@genes);$gen++)
    67: {
    68: next if($genes[$gen-1][1] >= $genes[$gen][0]); #no solapa asume genes en orden
    69: next if( defined($min_intergenic_size) && $min_intergenic_size > 0 &&
    70: $genes[$gen][0]-$genes[$gen-1][1]+1 < $min_intergenic_size); # evita cortas
    71: next if( defined($max_intergenic_size) && $max_intergenic_size > 0 &&
    72: $genes[$gen][0]-$genes[$gen-1][1]+1 > $max_intergenic_size); # evita largas
    73:
    74: $n_of_intergenic++;
    75:
    76: # calcula bordes de intergenes
    77: $start = $genes[$gen-1][1] + 1;
    78: $end = $genes[$gen][0] - 1;
    79: $length = $end - $start + 1;
    80:
    81: print FNA ">intergenic$n_of_intergenic|$gbaccession|coords:$start..$end|".
    82: "length:$length|neighbours:$genes[$gen-1][2]($genes[$gen-1][3]),".
    83: "$genes[$gen][2]($genes[$gen][3])|$taxon\n";
    84: print FNA substr($sequence,$start-1,$length)."\n";
    85: }
    86: }
    87:
    88: close(FNA);
    89:
    90: return $n_of_intergenic;
    91: }