Perl en bioinformática

Bruno Contreras-Moreira
Centro de Ciencias Genómicas, UNAM
México

(versión actualizada en www.eead.csic.es/compbio/material/bioinfoPerl)


Índice General


Presentación

Este curso es una introducción a las aplicaciones del lenguaje Perl y su entorno en la bioinformática, el campo donde se aplican herramientas computacionales a problemas biológicos. Es un curso para alumnos que tengan ya algunas nociones de programación, de algoritmos y de manejo de sistemas UNIX*, por lo que apenas se discuten conceptos, más bien se muestran ejemplos y aplicaciones prácticas.

Este curso es parte de la materia "Computación II" de la licenciatura de Ciencias Genómicas de la Universidad Nacional Autónoma de México y cubre 5 semanas. Está escrito principalmente en español, aunque igual encontráis fragmentos en inglés repartidos entre las páginas. De todas maneras, como muchos otros lenguajes de programación, Perl está inspirado en la lengua inglesa, así que un poco de inglés viene muy bien para dominarlo.

Aquí encontraréis este curso en un sólo documento HTML, fácil de guardar o imprimir.

Podéis mandarme comentarios o sugerencias através de la dirección de correo contrera_arroba_ccg.unam.mx.


Material de apoyo en Internet

Perl en español

Perl en inglés

Bioperl

Interfaz DBI

Bases biológicas y experimentales de la bioinformática Interfaz DBI

Búscadores específicos para código fuente


Fundamentos de Perl (1)

El lenguaje Perl

Perl es un acrónimo en inglés para Practical Extraction and Report Language, pero se ha convertido en un lenguaje con múltiples aplicaciones. Podemos consultar perl.org para hacernos una idea sobre las características del lenguaje Perl:

Perl Facts

Supported Operating Systems

Perl Features

Claro está que Perl también tiene defectos y enemigos. A mi me parece que sus virtudes son:

Por otro lado, si no se adoptan ciertas normas de estilo, se pueden escribir en Perl programas difíciles de depurar, con resultados impredecibles o imposibles de comprender. Pero estos problemas son normalmente responsabilidad del programador.

En cuanto a velocidad de ejecución, Perl tiene las desventajas de los lenguajes interpretados, y es por tanto más lento que por ejemplo C. Pero en realidad la velocidad de ejecución no es tan importante en la mayoría de las aplicaciones en bioinformática.

Espero que con este curso aprendáis a usar Perl sin estos problemas.

El intérprete Perl, un terminal y un editor de texto

Para instalar Perl en tu sistema operativo, os sugiero que consultéis las instrucciones dadas en perl.com para cada sistema operativo. Debéis saber que al instalar Perl estáis instalando un programa intérprete, cuya misión es convertir el código Perl de un programador en un código ejecutable por vuestro procesador. El código fuente del intérprete está escrito en C y es público, aquí podéis ver su función principal.

Perl es un lenguaje interpretado, que deber ser compilado cada vez que se desea ejecutar un programa. Esto lo diferencia de lenguajes como C, que se compilan una sola vez y luego se pueden ejecutar N veces. Por esta razón a lenguajes como Perl en inglés se les llama scripting languages y a los programas escritos en Perl se les llama scripts.

Durante la conversión o compilación, el intérprete va revisando la sintaxis del código que le ha pasado el usuario y va informando de los errores que vaya encontrando. Esos mensajes de error son muy útiles para guiaros a lo largo del proceso de programación y prueba de vuestro código, normalmente es imprescindible leerlos.

En este curso programaremos Perl desde un terminal de un sistema UNIX* (incluyendo Linux, MacOS X, BSD y derivados), que sería equivalente a programar bajo Windows con el intérprete MS-DOS.

Además necesitaremos un editor de texto, para ir guardando en archivos el código que vayamos generando. Por ejemplo, bajo Linux yo uso Nedit o vi y bajo Windows WordPad.

Primero comprobemos qué versión de Perl tenemos. Para ellos escribimos en el terminal:

$ perl -v

Que a mi me devuelve:

This is perl, v5.8.5 built for i386-linux-thread-multi
(with 1 registered patch, see perl -V for more detail)

Copyright 1987-2004, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using `man perl' or `perldoc perl'.  If you have access to the
Internet, point your browser at http://www.perl.com/, the Perl Home Page.

Además podéis recordar con qué opciones se puede invocar el intérprete si escribís:

$ perl -h

Las opciones más importantes podrían ser:

Usage: perl [switches] [--] [programfile] [arguments]
  ...
  -d[:debugger]   run program under debugger
  -e program      one line of program (several -e's allowed, omit programfile)
  -v              print version, subversion (includes VERY IMPORTANT perl info)
  -w              enable many useful warnings (RECOMMENDED)
  -W              enable all warnings
  ...

Un primer programa

El proceso de creación de un programa Perl comienza normalmente abriendo un archivo de texto en un editor y escribiendo algún código. Por ejemplo, si creamos un archivo llamado programa1.pl y dentro escribimos:

print "Hola, este es mi primer programa, por supuesto con errores

Ahora podemos guardar el archivo e invocar al intérprete:

$ perl programa1.pl

La respuesta inmediata es:

Can't find string terminator '"' anywhere before EOF at test.pl line 1.

Que nos recuerda que se nos olvidó cerrar la cadena de caracteres impresa con unas comillas. Si corregimos el código, terminando la cadena con una terminador de línea y cerrando la frase con un punto y coma, tenemos:

print "Hola, este es mi primer programa sin errores\n";

Y al ejecutarlo con el intérprete, activando los avisos, obtenemos:

$ perl -w programa1.pl
Hola, este es mi primer programa sin errores

En sistemas UNIX* podemos además averiguar la ruta absoluta (path) del intérprete con el comando which:

$ which perl

Que normalmente devuelve:

/usr/bin/perl

Si ahora modificamos programa1.pl:

#!/usr/bin/perl -w      # ruta absoluta de perl, esto es un comentario 
print "Hola, este es mi primer programa sin errores\n";

Y damos al archivo privilegios de ejecución:

$ chmod +x programa1.pl

Podemos ahora ejecutar este programa directamente:

$ ./programa1.pl

Cómo escribir tus programas

Antes de profundizar en el lenguaje Perl creo importante que discutamos aspectos de lo que podríamos llamar un estilo de programación . Básicamente esto significa que al escribir un programa debemos tener en cuenta si nos podrá ser util otro día y si otras personas podrán utilizarlo y modificarlo. Si la respuesta a alguna de estas preguntas es sí, entonces se hace necesario comentar el código para que quede explicado a lo largo del código qué va haciendo cada segmento de programa.

Como veremos más adelante, otra manera de facilitar la comprensión de un programa es aislar partes del código en procedimientos y funciones. De hecho ésta es toda una estrategia de programación. Podemos escribir en lenguaje natural, por ejemplo como comentarios, las tareas que queremos haga nuestro programa y poco a poco ir sustituyendo esas descripciones de tareas por funciones y procedimientos que en efecto las ejecuten.

Finalmente, dado que el intérprete Perl puede ser demasiado flexible con la sintaxis de los programas que escribimos, os recomiendo que uséis el módulo strict , que, por ejemplo, no os dejara usar variables no declaradas o sin inicializar. De esta manera no obtendremos resultados inexplicables o inesperados en nuestros programas.

Podemos volver a modificar programa1.pl con estas nociones de estilo:

#!/usr/bin/perl -w 
# Programa programa1.pl, escrito por Bruno Contreras en Julio de 2005
# Este programa simplemente imprime un mensaje por la salida estándar

use strict;         # usa módulo strict, intérprete muy estricto con la sintaxis 

print "Hola, este es mi primer programa sin errores\n";

Sintaxis básica de Perl

Como con cualquier lenguaje, debemos conocer al menos parte de la sintaxis de Perl para escribir programas que el intérprete pueda entender. Incluso aunque ya tengas experiencia en Perl, se te pueden olvidar a veces detalles de sintaxis. Como en la guía de referencia de Perl de Rex Swain, podemos dividir la sintaxis en partes. En este curso lo haremos de esta manera:

Vamos a ver qué son estas cosas en el programa sintaxis.pl:

 
#!/usr/bin/perl -w 
# Programa sintaxis.pl, escrito por Bruno Contreras en Julio de 2005
# elementos de la sintaxis de Perl

# llamada al módulo strict
use strict; 

# declaramos la variable $fecha
my $fecha;

# llamada al sistema operativo para ejecutar el programa date y asignar el resultado a $fecha con el operador =
$fecha = `date`; 

# estructura de control if-else para ejecutar partes del programa de forma condicional
# la condición es que dentro del contenido de $fecha encontremos la subcadena
# "mar" seguida de uno ó más espacios \s+ y un dígito \d 
# esto es una expresión regular, fácilmente identificable por / /

if($fecha =~ /mar\s+\d/)
{
	# llamada a la función print con una cadena de caracteres como parámetro 
	# normalmente las funciones reciben parámetros separados por comas entre paréntesis
	print("Estamos en Marzo, el mes de mi cumpleaños\n");
}
else
{	
	# pero perl no siempre requiere los paréntesis en las llamadas a funciones
	print "Todavía queda para mi cumpleaños\n";
}


Variables y tipos

Perl tiene básicamente dos tipos de variables para contener datos: las que contienen un sólo valor y las que pueden contener más de uno.

$escalares (scalars), variables que contienen un sólo valor o dato. Pueden ser cadenas de caracteres, números enteros, números reales e incluso direcciones de memoria de otras variables.

@arreglos (arrays), matrices N-dimensionales (donde $ N\geq1$) que contienen más de un escalar, que no tienen por qué ser del mismo tipo. Permiten accceder a cada valor por medio de subíndices o coordenadas en N. El primer valor de un arreglo tiene siempre subíndice 0.

%tablas_asociativas (hashes), que contienen parejas llave-valor. Estas tablas permiten el acceso a los valores contenidos por medio de las llaves, que a su vez son escalares, por lo que sólo pueden contener un valor para cada llave usada. Si se introducen dos parejas con la misma llave, en la práctica sólo la última en asignarse es almacenada.

Un caso algo especial son las referencias, las direcciones en la memoria del computador que ocupan las variables, dadas en hexadecimal por comodidad.

A pesar de esta flexibilidad, el intérprete sí lleva un control de contexto sobre las variables y protestará si intentas aplicar, por ejemplo, una función de arreglos sobre un escalar.

En ocasiones el intérprete no puede decidir qué tipo de datos está manejando y es necesario que el usuario diga explícitamente en qué contexto deben evaluarse, usando ${},@{},%{} en torno a la variable en cuestión.

Por cierto, las variables que declaréis en Perl deben comenzar por una letra o un subrayado, como en $_variable.

Asignaciones a variables escalares

my $escalar = 1234; # declaración de $escalar y asignación de un entero
$escalar = 1_234;   # el mismo entero, marcando el millar por comodidad
$escalar = 123.4;   # real
$escalar = 0xff;    # hexadecimal
$escalar = 'abc';   # cadena de caracteres

$escalar = "abc\n"; # cadena de caracteres que interpreta caracteres especiales,
                    # como el de nueva línea \n o tabuladores \t

$escalar = \$escalar;                      # referencia, dirección de $escalar
$escalar = `un_comando_sistema_operativo`; # salida de un comando ejecutado en el sistema


Asignaciones a arreglos y consultas

my @arreglo = ();                # declaración de un arreglo vacío
my (@arreglo,$escalar) = ((),0); # declaración de un arreglo vacío y un escalar con valor 0
@arreglo = (1,2,3);              # arreglo de enteros
@arreglo = (1,2,'tres');         # arreglo de enteros y cadenas
@arreglo = (1..25);              # arreglo con los 25 primeros naturales
$arreglo[12];                    # elemento número 13 de @arreglo
$arreglo[12] = 'escalar';        # asigna un escalar al elemento número 13 de @arreglo

@arreglo =[(1,2),(3,4)]; # arreglo con dos dimensiones 2x2
$arreglo[0][1];          # elemento con coordenadas 0,1 de la matriz bidimensional @arreglo

@arreglo = @copia;    # copia de arreglos
$escalar = \@arreglo; # referencia, dirección de @arreglo
$escalar->[12];       # elemento número 13 de @arreglo
$$escalar[12];        # lo mismo, elemento número 13 de @arreglo
@{$escalar};          # es lo mismo que @arreglo
$arreglo[$#arreglo];  # úlimo elemento de @arreglo
@arreglo[3,4,5];      # subarreglo con 3 elementos
scalar(@arreglo);     # en contexto escalar, el número de elementos del arreglo

Asignaciones a tablas asociativas y consultas

my %tabla = ();                             # declaración de un tabla vacía
%tabla = ('A'=>'Alanina', 'C'=>'Cisteína'); # asigna dos parejas llave=>valor a tabla 
%tabla = ('A','Alanina','C','Cisteína');    # lo mismo, asigna dos parejas llave=>valor a tabla 
$tabla{'A'};                                # valor de la pareja cuya llave es 'A', en este caso 'Alanina'
$tabla{'L'} = 'Leucina';                    # asigna una nueva pareja a la tabla, con llave 'L' y valor 'Leucina'

$escalar = \%tabla;                         # referencia, dirección de %tabla
$escalar->{'L'};                            # valor de la pareja cuya llave es 'L'
%{$escalar};                                # lo mismo que %tabla


Variables especiales de Perl

En Perl hay un conjunto de variables globales, accesibles al programas en ejecución, que debemos conocer para aprovechar mejor las capacidades del lenguaje. El listado completo podéis verlo en la guía de referencia de Perl, pero con que conozcáis éstas podréis ir programando:

@ARGV # guarda los argumentos con que se ejecuta un programa perl

Si invocamos en el terminal $ perl programa.pl -f archivo.txt, mientras dure la ejecución del programa @ARGV contendrá:

 
$ARGV[0] contiene '-f'
$ARGV[1] contiene 'archivo.txt'

 
@_      # hace el papel de @ARGV al invocar una subrutina, función o procedimiento

$_      # al leer un archivo desde un programa Perl, cada línea leída se guarda en $_

Si dentro de un programa invocamos a la función print("Hola\n"), la función recogerá el parámetro "Hola\n" como el primer elemento de @_, es decir, $_[0].

Ejercicio con variables

Estudia el siguiente código en Perl y encuentra qué contendrá $resultado al terminar.

 
#!/usr/bin/perl -w 
# Ejercicio escrito por Bruno Contreras en Julio de 2005

use strict; 

my ($dato1,@datos2,%datos3,$resultado);

$datos2[0] = 'A';
$datos2[1] = 'G';
$datos2[2] = 'T';
$datos2[3] = 'C';

$dato1 = scalar(@datos2);

$datos3{ $datos2[0] } = 'ATP';
$datos3{ $datos2[1] } = 'GTP';
$datos3{ $datos2[2] } = 'TTP'; 
$datos3{ $datos2[3] } = 'CTP';

$resultado = \%datos3;

$resultado = $resultado->{ $datos2[$dato1-2] };

print("resultado = $resultado\n");

Operadores

El repertorio completo de operadores de Perl es amplio y podéis consultarlo en la guía de referencia de Perl. Yo os menciono los que más he usado. Se sobreentiende que son operadores binarios, que relacionan dos variables, a menos que se indique lo contrario:

=        # asignación
**       # potencia
+ - * /  # suma, resta, multiplicación y división
%        # resto de la división dividendo%divisor 
|| &&    # OR y AND, disyunción y conjunción lógica
.        # concatenación de dos cadenas
->       # ya lo hemos usado para las referenciar elementos de arreglos y tablas
\        # toma la dirección de una variable (unario)
!        # negación lógica (unario)
++ --    # autoincremento y autodecremento de escalares numéricos (unario)
== !=    # igualdad y desigualdad entre números 
eq ne    # igualdad y desigualdad entre cadenas (equal, not equal)
< >      # mayor o menor numérico
<= >=    # mayor o igual, menor o igual numérico
=~ !~    # operadores de búsqueda de patrones: comprueba que la variable izquierda contiene (no contiene)
         # cierto patrón a la derecha del operador

Perl permite combinar el operador asignación con los operadores + , - , * , / , . , de forma que por ejemplo

$suma = $suma + $sumando es lo mismo que $suma += $sumando.

Estructuras de control

Como los operadores, aquí sólo mencionaré las estructuras de control de Perl que más uso, muy parecidas a las de lenguajes como C. Podéis consultar guía de referencia de Perl para ver toda la variedad.

while(condicion)         # ejecuta un bloque de código mientras se cumpla cierta condición
{
	# bloque de código
	
	last if(condicion2);   # termina el bucle si se da condicion2
	
	if(condicion3)         # si se da condicion3
	{
		# bloque de código
	}
	elsif(condicion4)      # alternativa si se da condicion4, en caso contrario ejecuta else
	{
		# bloque de código alternativo
	}
	else
	{
		# otro bloque de código
	}
	
	next if(condicion5);   # salta a la siguiente iteración si se da condicion5 sin ejecutar bloque4

	# bloque de código 4
}

for($i=0;$i<20;$i++)    # ejecuta el bloque de código 20 veces
{
	# bloque de código
	die if(condicion6)   # termina el programa, no sólo el bucle, si se da condicion6
}

foreach $elemento (@arreglo)  # bucle sobre cada uno de los elementos de un @arreglo
{
	# procesa cada $elemento
}

Ejemplo de estructuras de control

Ahora que hemos visto variables, operadores y estructuras de control, un ejemplo más complicado. ¿Qué contiene $tabla_amino_1_a_3 al final?

 
#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict; 

my @amino = ('A','R','N','D','C','Q','E','G','H','I','L','K','M','F','P','S','T','W','Y','V','X');

my %amino1a3 = ('A','ALA','C','CYS','D','ASP','E','GLU','F','PHE','G','GLY','H','HIS',
					'I','ILE','K','LYS','L','LEU','M','MET','N','ASN' ,'P','PRO','Q','GLN',
					'R','ARG','S','SER','T','THR','V','VAL','W','TRP','Y','TYR'); 

my $tabla_amino_1_a_3 = "";  # declara cadena vacía

my ($aa);  # variable para recorrer un bucle

foreach $aa (@amino)
{
	if($aa eq 'Z' || $aa eq 'B') # B y Z son formas ionizadas de asp y glu
	{
		next;
	}
	elsif($aa eq 'X')
	{
		$tabla_amino_1_a_3 .= "$aa equivale a cualquier aminoácido\n";
	}
	else
	{
		$tabla_amino_1_a_3 .= "$aa equivale a $amino1a3{ $aa }\n";
	}	
}

#print("Tabla de nomenclatura de aminoácidos\n$tabla_amino_1_a_3");

Expresiones regulares

Las expresiones regulares se usan para analizar el contenido de cadenas de caracteres por medio de patrones. Son muy útiles y por ello deberéis aprender a definir vuestros propios patrones.

Como en otras secciones, os remito a la guía de referencia de Perl para ver todo el repertorio de símbolos y que se pueden usar para construir patrones.

Un patrón está delimitado por dos barras inclinadas, /patrón/ y los caracteres que insertéis en él pueden tener un significado literal u otro especial cuando vaya precedido del modificador \. Por ejemplo, /n/ es un patrón que coincidirá con cualquier aparición de un carácter n en una cadena. Sin embargo, /\n/ sólo coincidirá con los caracteres de nueva línea.

Los símbolos más habituales en expresiones regulares podrían ser:

 
/./     # cualquier carácter excepto \n, comodín (wildcard)
/\s/    # es un espacio en blanco (space)
/\t/    # es un tabulador
/\w/    # es un carácter alfanumérico (word), incluyendo '_'
/\W/    # no es un carácter alfanumérico (word)
/\d/    # es un dígito
/\D/    # no es un dígito 

/\A/     # es el principio de una cadena
/\Z/     # es el final de una cadena
/^/      # es el principio de una línea de archivo
/$/      # es el final de una línea de archivo

/\//     # es el carácter / 
/[...]/  # es una clase de carácteres que hay que buscar


Los cuantificadores son:
+ una ó más veces
? cero ó una vez
* cero ó más veces
{n,m} mínimo y máximo de veces  

Los paréntesis se usan:
/(\d\s\w)/   # para agrupar varios símbolos en un sólo patrón
/(A|B|C)/    # para definir un patrón formado por varias alternativas: coincide con 'A', con 'B' o con 'C'

Expresiones más complejas, que memorizan los patrones encontrados:
/(11+)/       # guarda la primera aparición de al menos dos unos seguidos
/(11+)\s+\1/  # guarda la primera aparición de al menos dos unos seguidos y mira a ver si 
              # hay otra igual separada por al menos un espacio en blanco 
				  
              # \1 corresponde al primer (patrón), \2 al segundo y así sucesivamente
              # sus apariciones se almacenan en las variables especiales $1, $2, ...
              # $1, $2, ... son asignadas desde cada patrón agrupado con paréntesis

Veamos unos ejemplos:

if($cadena =~ /\s+/) { # ejecuta este código si hay uno ó más espacios en blanco en $cadena }

if($cadena =~ /\s{2,}/) { # ejecuta este código si hay al menos dos espacios en blanco en $cadena }

if($cadena =~ /(\d|\s)/) { # ejecuta este código si encuentra un dígito o un espacio en blanco en $cadena }

if($cadena =~ /[A-Za-z]/) { # ejecuta este código si encuentra cualquier letra, mayúscula o minúscula, en $cadena }

Otra posible forma de uso:

my $cadena = "34 KDa";
$cadena =~ /^(\d\s+KDa)/;  
print "$1\n";                # en caso de encontrar el (patrón) en $cadena, $1 contendrá su primera aparición

El repertorio de aplicaciones de las expresiones regulares se ha merecido que hayan aparecido libros dedicados sólo a ellas, por lo que no pretendo aquí mostraros todo el potencial que tienen. Valga de ejemplo que se han usado incluso para comprobar si un número es primo mediante (1 x $numero) !~ /^(11+)\1+$/ . Pero sí vamos a ver un par de funciones de Perl, las primeras después de print y scalar, que están asociadas a las expresiones regulares y son muy útiles. Ambas usan el operador =~.

La primera es la sustitución (s), cuya sintaxis simplificada es:

$cadena =~ s/patrón/sustitución/[g|i]

donde g es un modificador opcional para que sustituya todas las apariciones del patrón, no sólo la primera, y i es un modificador opcional para que letras iguales coincidan aunque estén mayúsculas o minúsculas. Veamos un ejemplo:

 
my $cadena = "Letras y espacios \n";
									
$cadena =~ s/\s//;  # patrón es /\s/ y sustitución es // , ahora $cadena contiene "Letrasy espacios \n";

$cadena =~ s/\s//g; # ahora $cadena contiene "Letrasyespacios\n";

La otra función es la traducción (tr) y su sintaxis es muy similar:

$cadena =~ tr/lista_caracteres/lista_caracteres_sustitución/

que reemplaza cada aparición de un carácter de la lista de caracteres de la izquierda por el carácter que ocupa la misma posición en la lista de la derecha. Veamos otro ejemplo:

$DNAcomplementario =~ tr/ATGC/TACG/;

Ejercicios con expresiones regulares

¿Qué hace este trozo de código?

 
#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict; 

my $RNA = "CAUACUAAGAUCGCGAUAUUAUUAGCGAUAUACGACU";

my $stop1 = 'UAA';
my $stop2 = 'UGA';
my $stop3 = 'UAG';

my ($total1,$total2,$total3,$total) = (0,0,0,0);

while( $RNA =~ /$stop1/g )
{
	$total1++;
}

while( $RNA =~ /$stop2/g )
{
	$total2++;
}

while( $RNA =~ /$stop3/g )
{
	$total3++;
}

while( $RNA =~ /($stop1|$stop2|$stop3)/g)
{
	$total++;
}

print "$total1|$total2|$total3|$total\n";


Ejercicio con enzimas de restricción

Ahora os toca a vosotros, la tarea es escribir un programa que busque dentro de una secuencia de ADN dianas de las enzimas de restricción BamHI (diana GGATCC) y EcoRI (diana CC[A/T]GG). Os propongo un borrador de la solución, que en principio podría extenderse a todas las enzimas de restricción que queráis:

 
#!/usr/bin/perl -w 
# Ejercicio escrito por Bruno Contreras en Julio de 2005

use strict; 

my @enz_sitios = ('GGATCC','CC(A|T)GG'); # también sirve 'CC[AT]GG' o 'CC[A|T]GG'
my %enz_nombres = ('GGATCC','BamHI','CC(A|T)GG','EcoRI');
my $DNA = "ATCGCGACCAGGTTAGCGCGCGATTATTATATATCGATATCGCGGCGATATATCTCGAGAGTATATGGATCCTGGATAGCTAGATCATAGA";

# 1) obtengo la secuencia de la hebra de DNA complementaria, no olvidando invertirla con la función reverse

# 2) recorro el vector de enzimas de restricción mediante un bucle foreach, buscando sitios
# para cada enzima, mostrando y contando los sitios encontrados
# PISTA: para mostrar los sitios encontrados usar la variable especial $1 

# 	2.1) primero en la hebra + del DNA
# 	2.2) ahora en la hebra complementaria


Biblioteca de funciones de Perl

Al igual que pasa con otros lenguajes de programación, aprender a programar en Perl implica aprender a usar buena parte de la biblioteca de funciones y procedimientos que el intérprete incorpora por defecto, como la función print, que ya hemos usado.

Vamos a hacer un repaso de algunas funciones esenciales en Perl, y como siempre os remito a la guía de referencia de Perl para que recordéis el repertorio completo cuando estéis programando.


Funciones sobre arreglos

scalar
Devuelve el tamaño en número de elementos de un arreglo.

my @arreglo = (1,2,3,"cuatro");
print scalar(@arreglo); # imprime 4

push
Ya en la sección 2.3.2 vimos cómo asignar elementos a un arreglo usando subíndices. La función push permite añadir elementos o incluso arreglos al final de un arreglo dado.

push(@arreglo,"elemento");
push(@arreglo,$elemento);

my @arreglo = (1,2,3);
my @otro_arreglo = (4,5,6);
push(@arreglo,@otro_arreglo); # ahora @arreglo contiene 1,2,3,4,5,6

unshift
Añade elementos o arreglos al principio de un arreglo dado.

my @arreglo = (1,2,3);
my @otro_arreglo = (4,5,6);
unshift(@arreglo,@otro_arreglo); # ahora @arreglo contiene 4,5,6,1,2,3

pop
Extrae y devuelve el último elemento de un arreglo (un escalar), que reduce en uno su tamaño.

my @arreglo = (1,2,3);
my $ultimo = pop(@arreglo); # $ultimo = 3, @arreglo = (1,2)

shift
Extrae y devuelve el primer elemento de un arreglo (un escalar), que reduce en uno su tamaño.

my @arreglo = (1,2,3);
my $primer = shift(@arreglo); # $primer = 1, @arreglo = (2,3)

reverse
Toma un arreglo y devuelve otro arreglo con los mismos elementos invertidos.

my @arreglo = (1,2,3);
my @invertido = reverse(@arreglo); # @invertido = (3,2,1)

grep
Recibe dos parámetros, una expresión regular y un arreglo, y compara el patrón con cada uno de los elementos del arreglo. Devuelve un arreglo con los elementos que coincidieron con el patrón. Es muy útil para comprobar si ciertos valores están contenidos en un arreglo.

my @arreglo = ("uno","dos","tres");
my @coinc = grep(/s/,@arreglo); # @coinc = ("dos","tres")

sort
Ordena los elementos de un arreglo de menor a mayor alfabéticamente, devolviendo otro arreglo ya ordenado. Se puede cambiar el criterio de ordenación.

my @arreglo = (3,7,1.1,11.8);
my @ordenado = sort(@arreglo);          # @ordenado contiene 1.1, 11.8, 3, 7
@ordenado = sort {$a<=>$b} (@ordenado); # ahora contiene 1.1, 3, 7, 11.8, criterio numérico ascendente
@ordenado = sort {$b<=>$a} (@ordenado); # ahora contiene 11.8, 7, 3, 1.1, criterio numérico descendente

Funciones sobre cadenas

Las cadenas de caracteres en Perl, como sabéis, las manejamos como variables escalares y tienen también unas cuantas funciones específicas asociadas.

substr
Devuelve una subcadena de la cadena pasada como parámetro. Puede recibir dos o tres parámetros. El primero es una cadena, el segundo el punto (offset, empezando en el 0) donde empieza la nueva subcadena y el tercero, opcional, la longitud de la subcadena a partir del punto anterior.

my $cadena = "cadena";
my $subcadena = substr($cadena,0,4); # $subcadena contiene "cade";
$subcadena = substr($cadena,1,4); # $subcadena contiene "aden";
$subcadena = substr($cadena,2); # $subcadena contiene "dena";

index
Devuelve la posición de una subcadena dentro de una cadena, el valor devuelto en caso contrario es -1. Puede recibir un tercer parámetro opcional que marca en qué posición debe empezar la búsqueda.

my $cadena = "cadena";
my ($subc1,$subc2) = ('en','no');
print index($cadena,$subc1); # imprime 3
print index($cadena,$subc1,4); # imprime -1
print index($cadena,$subc2); # imprime -1

length
Devuelve la longitud de una cadena de caracteres.
print length("cadena"); # imprime 6

chomp
Elimina el último terminador de línea (símbolo de nueva línea) \n de una cadena y devuelve 1 si de hecho lo eliminó. Se puede aplicar también a un arreglo de cadenas.

my $cadena = "cadena\n";
my @cadenas=("cadena1\n","cadena2\n");
print chomp($cadena);                     # imprime 1
print chomp(@cadenas);                    # imprime 2

lc, uc
Devuelven versiones en mayúsculas (upper case, uc) o minúsculas (lower case, lc) de la cadena pasada como parámetro.

my $cadena = "May y min";
print lc($cadena);                        # imprime 'may y min'
print uc($cadena);                        # imprime 'MAY Y MIN'

split
Función que recibe un patrón regular y una cadena y trata de extraer las diferentes subcadenas que haya separadas por tal patrón, devolviéndolas en un arreglo. Se puede pasar un tercer parámetro que indica el número máximo de subcadenas deseadas.

my $cadena = "cadena de caracteres|otra cadena\n";

my @subcadenas1 = split(/\s+/,$cadena);
my @subcadenas2 = split(/\|/,$cadena); # separa por el carácter '|'

print scalar(@subcadenas1);   # contiene 4 elementos
print scalar(@subcadenas2);   # contiene 2 elementos

Funciones sobre tablas asociativas

keys
Devuelve un arreglo con las llaves de la tabla asociativa pasada como parámetro. Es muy útil para realizar bucles iterativos sobre tablas.

my $codon;
my %codones = ('GCC'=>'Ala','GCG'=>'Ala','ATG'=>'Met');
my @llaves = keys(%codones);

foreach $codon (@llaves)
{
	print "$codon $codones{ $codon } \n";
}

foreach $codon (sort (@llaves))   # itera sobre las llaves ordenadas alfabéticamente
{
	print "$codon $codones{ $codon } \n";
}

values
Devuelve un arreglo con los valores de la tabla asociativa pasada como parámetro.

my $codon;
my %codones = ('GCC'=>'Ala','ATG'=>'Met');
my @valores = values(%codones);               # contiene 'Ala' y 'Met'                 

Aprovecho aquí para mostraros como se pueden ordenar las tablas asociativas por valor. Como es lógico, la solución pasa por ordenar las llaves por sus valores asociados:

my @llaves_ordenadas = sort {$hash{$a} <=> $hash{$b}} ( keys(%hash) );

Funciones aritméticas

Las funciones aritméticas más usuales son quizás éstas.

abs
Devuelve el valor absoluto del escalar numérico pasado como parámetro.
my $valorabs = abs($escalar);

exp
exp($exponente) devuelve la exponencial de $exponente.

int
Devuelve la parte entera del número real pasado como parámetro.
my $valorint = int(8.2); # $valorint contiene 8

log
Devuelve el logaritmo natural del o escalar numérico.

sqrt
Devuelve la ráiz cuadrada del parámetro escalar numérico. my $raiz = sqrt(25); # $raiz contiene 5

rand
Esta función es un generador de números aleatorios que por defecto devuelve un valor real comprendido entre 0 y 1, aunque puede delimitarse el límite superior pasándolo como parámetro. Funciona con un valor semilla que puedes definir llamando a la función srand, por ejemplo para obtener resultados replicables en tus programas aleatorios. Con la misma semilla, dos llamadas a rand devolverán el mismo valor aleatorio.

my $semilla = 12345;
srand($semilla);
my $azar = rand(14);  # 3.15459917914819   , entre 0 y 14
my $azar = rand();    # 0.919183068533556  , entre 0 y 1

Ejercicios que requieren funciones de Perl

Llegados a este punto creo que podemos ya empezar con algunos ejercicios que tocan de cerca problemas de la bioinformática.


Alineamiento de secuencias y matrices de sustitución

Muchos de vosotros ya sabréis de la importancia de los alineamientos de secuencias en bioinformática, puesto que tienen aplicaciones en cosas tan diferentes como la predicción de genes en genomas o la predicción de estructura secundaria en proteínas.

Los algoritmos de alineamiento de secuencias, como BLAST (Altschul et al., 1997), usan matrices de sustitución para dirigir el proceso de alineamiento. Para proteínas, una de las matrices más conocidas es BLOSUM62 (Henikoff & Henikoff, 1993).


Tabla 2.1: BLOSUM62, muestra estimaciones del valor evolutivo del cambio de un aminoácido por otro en una proteína. Por ejemplo, una mutación de alanina (A) a aspártico (D) está penalizada con un -2.
A R N D C Q E G H I L K M F P S T W Y V B Z X *
A 4 -1 -2 -2 0 -1 -1 0 -2 -1 -1 -1 -1 -2 -1 1 0 -3 -2 0 -2 -1 0 -4
R -1 5 0 -2 -3 1 0 -2 0 -3 -2 2 -1 -3 -2 -1 -1 -3 -2 -3 -1 0 -1 -4
N -2 0 6 1 -3 0 0 0 1 -3 -3 0 -2 -3 -2 1 0 -4 -2 -3 3 0 -1 -4
D -2 -2 1 6 -3 0 2 -1 -1 -3 -4 -1 -3 -3 -1 0 -1 -4 -3 -3 4 1 -1 -4
C 0 -3 -3 -3 9 -3 -4 -3 -3 -1 -1 -3 -1 -2 -3 -1 -1 -2 -2 -1 -3 -3 -2 -4
Q -1 1 0 0 -3 5 2 -2 0 -3 -2 1 0 -3 -1 0 -1 -2 -1 -2 0 3 -1 -4
E -1 0 0 2 -4 2 5 -2 0 -3 -3 1 -2 -3 -1 0 -1 -3 -2 -2 1 4 -1 -4
G 0 -2 0 -1 -3 -2 -2 6 -2 -4 -4 -2 -3 -3 -2 0 -2 -2 -3 -3 -1 -2 -1 -4
H -2 0 1 -1 -3 0 0 -2 8 -3 -3 -1 -2 -1 -2 -1 -2 -2 2 -3 0 0 -1 -4
I -1 -3 -3 -3 -1 -3 -3 -4 -3 4 2 -3 1 0 -3 -2 -1 -3 -1 3 -3 -3 -1 -4
L -1 -2 -3 -4 -1 -2 -3 -4 -3 2 4 -2 2 0 -3 -2 -1 -2 -1 1 -4 -3 -1 -4
K -1 2 0 -1 -3 1 1 -2 -1 -3 -2 5 -1 -3 -1 0 -1 -3 -2 -2 0 1 -1 -4
M -1 -1 -2 -3 -1 0 -2 -3 -2 1 2 -1 5 0 -2 -1 -1 -1 -1 1 -3 -1 -1 -4
F -2 -3 -3 -3 -2 -3 -3 -3 -1 0 0 -3 0 6 -4 -2 -2 1 3 -1 -3 -3 -1 -4
P -1 -2 -2 -1 -3 -1 -1 -2 -2 -3 -3 -1 -2 -4 7 -1 -1 -4 -3 -2 -2 -1 -2 -4
S 1 -1 1 0 -1 0 0 0 -1 -2 -2 0 -1 -2 -1 4 1 -3 -2 -2 0 0 0 -4
T 0 -1 0 -1 -1 -1 -1 -2 -2 -1 -1 -1 -1 -2 -1 1 5 -2 -2 0 -1 -1 0 -4
W -3 -3 -4 -4 -2 -2 -3 -2 -2 -3 -2 -3 -1 1 -4 -3 -2 11 2 -3 -4 -3 -2 -4
Y -2 -2 -2 -3 -2 -1 -2 -3 2 -1 -1 -2 -1 3 -3 -2 -2 2 7 -1 -3 -2 -1 -4
V 0 -3 -3 -3 -1 -2 -2 -3 -3 3 1 -2 1 -1 -2 -2 0 -3 -1 4 -3 -2 -1 -4
B -2 -1 3 4 -3 0 1 -1 0 -3 -4 0 -3 -3 -2 0 -1 -4 -3 -3 4 1 -1 -4
Z -1 0 0 1 -3 3 4 -2 0 -3 -3 1 -1 -3 -1 0 -1 -3 -2 -2 1 4 -1 -4
X 0 -1 -1 -1 -2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -2 0 0 -2 -1 -1 -1 -1 -1 -4
* -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 1


En este ejemplo vamos a construir una matriz de sustitución de aminoácidos, como BLOSUM62, pero basada en el código genético. La hipótesis que vamos a manejar es que los aminoácidos cuyos codones se parecen más son más fácilmente intercambiables, olvidándonos por un momento de sus propiedades bioquímicas.

 
#!/usr/bin/perl -w 
# Ejercicio escrito por Bruno Contreras en Julio de 2005

# Programa que calcula una matriz de sustitución de aminoácidos basándose únicamente en el parecido de 
# sus codones de tRNA

use strict; 

my %tripletes = (
'F' => ['TTT','TTC'],
'L' => ['TTA','TTG','CTT','CTC','CTA','CTG'],
'S' => ['TCT','TCC','TCA','TCG','AGT','AGC'],
'Y' => ['TAT','TAC'],
'C' => ['TGT','TGC'],
'W' => ['TGG'],
'P' => ['CCT','CCC','CCA','CCG'],
'H' => ['CAT','CAC'],
'Q' => ['CAA','CAG'],
'R' => ['CGT','CGC','CGA','CGG','AGA','AGG'],
'I' => ['ATT','ATC','ATA'],
'M' => ['ATG'],
'T' => ['ACT','ACC','ACA','ACG'],
'N' => ['AAT','AAC'],
'K' => ['AAA','AAG'],
'V' => ['GTT','GTC','GTA','GTG'],
'A' => ['GCT','GCC','GCA','GCG'],
'D' => ['GAT','GAC'], 
'E' => ['GAA','GAG'],
'G' => ['GGT','GGC','GGA','GGG'],
'X' => ['XXX'],
);

my @aa = ('A','R','N','D','C','Q','E','G','H','I','L','K','M','F','P','S','T','W','Y','V','X');

my $MAXPUNTUACION = 0; # máximo valor de la matriz de sustitución
                       # es una matriz de costes, con números negativos

my ($aa1,$aa2,$cod1,$cod2,@codones1,@codones2);
my ($dist,$distmedia);

print "#  Matriz de sustitución de aminoácidos basada en el código genético\n";
print "   A  R  N  D  C  Q  E  G  H  I  L  K  M  F  P  S  T  W  Y  V  X\n";
	
foreach $aa1 (@aa)
{
	@codones1 = @{ $tripletes{$aa1} };  # toma todos los codondes correspondientes a $aa1
	print "$aa1 ";
	
	foreach $aa2 (@aa)
	{
		if($aa1 eq $aa2){ print " $MAXPUNTUACION "; }
		else
		{
			@codones2 = @{ $tripletes{$aa2} };   # toma todos los codondes correspondientes a $aa2
			$distmedia = 0;
		
			foreach $cod1 (@codones1)
			{
				foreach $cod2 (@codones2)
				{
					# cuenta el número de cambios que hay que hacer para pasar de un codón a otro (máximo 3)
					$dist = 0;
					if(substr($cod1,0,1) ne substr($cod2,0,1)){ $dist++; }
					if(substr($cod1,1,1) ne substr($cod2,1,1)){ $dist++; }
					if(substr($cod1,2,1) ne substr($cod2,2,1)){ $dist++; }
					
					# recuerda la distancia entre estos dos codones para calcular la media entre aa1 y aa2 
					$distmedia += $dist;
				}
			}
			
			# calcula la distancia media entre aa1 y aa2
			$distmedia /= (scalar(@codones1)*scalar(@codones2));
			$distmedia = int($distmedia);
			
			# imprime el coste medio para pasar de aa1 a aa2
			print "-$distmedia ";
		}
	}
	print "\n";	
}

¿Se te ocurre cómo modificar el algoritmo?


Evaluando alineamientos de secuencias

Ahora os toca a vosotros. La tarea es que uséis la matriz de sustitución generada en el ejemplo anterior para evaluar este alineamiento en formato FASTA:

>unnamed 
------KNATRESTSTLKAWLSEHRKNPYPTKGEKIMLAIITKMTLTQVSTWFANARRRLKKENKMTWTPRNRTDEEGNVYSS------
>PDB1LFU M.musculus DNA-binding protein
MARRKRRNFNKQATEILNEYFYSHLSNPYPSEEAKEELAKKSGITVSQVSNWFGNKRIRYKKN-------IGKFQEEANIYAAKTAVTA

Os sugiero que modifiquéis el código del ejemplo anterior 2.7.5 para convertir la matriz impresa en una tabla asociativa donde la clave sea un par de aminoácidos y el valor su coste. Sería conveniente imprimir el alineamiento con el coste de cada pareja de aminoácidos alineados, para localizar las zonas más confiables del alineamiento.

Algoritmos genéticos: simulando mutaciones sobre cromosomas

Una función de Perl que os puede ser de utilidad es rand, el generador de números aleatorios. Los números aleatorios se han usado en biología para modelar eventos de mutación y de recombinación genética . Pero se han llevado más allá en computación y son la base de varios tipos de algoritmos como los algoritmos genéticos y de Monte Carlo.

En este ejemplo que os propongo vamos a implementar un algoritmo genético muy sencillo que va a recombinar una serie de secuencias de ADN para obtener una secuencia final recombinante con un contenido en GC que se aproxima a uno definido.

#!/usr/bin/perl -w 
# Ejercicio escrito por Bruno Contreras en Julio de 2005

# Programa que recombina una serie de moléculas de ADN que codifican para genes homólogos,
# y obtiene un gen recombinante que optimiza su contenido en %GC

# REQUISITOS:
# los genes deben estar alineados y si tienen diferente longitud deberán incorporar inserciones
# mira http://www.cbs.dtu.dk/services/RevTrans/

use strict; 

@genes = (     # en este ejemplo hay 3 genes homólogos del regulador CRP
#CRP_HAEIN
'ATGTCAAATGAATTAACCGAAATTGATGAAGTTGTAACCTCCTCTCAAGAAGAAGCAACT  
CAACGAGATCCCGTTTTAGATTGGTTTCTTACTCACTGCCATTTGCATAAATATCCTGCA
AAATCAACTTTAATTCATGCTGGGGAAGATGCGACCACGCTGTATTATGTAATTAAAGGT
TCTGTAATGGTATCTTCAAAAGATGATGAAGGCAAAGAGATGATCCTCACTTACTTAGGT
GCAGGACAATTTTTTGGCGAAGCGGGATTATTTGATGAAGGTTCAAAACGATCAGCTTGG
GTAAAAACAAAAACAACATGTGAAATTGCTGAAATTTCCTATAAGAAATATCGCCAGTTG
ATTCAGGCAAACCCTGAAATCTTAATGTTTCTCACTGCACAATTGGCAAGACGTTTGCAA
AATACATCACGTCAAGTCACGAATTTGGCATTTTTAGACGTCGCAGGTCGCATCGCTCAA
ACTTTAATGAACTTAGCTAAACAGCCTGAAGCAATGACGCATCCTGATGGTATGCAAATC
AAAATTACACGCCAAGAAATAGGGCAAATGGTGGGTTGTTCACGAGAAACTGTGGGGCGC
ATTATTAAGATGTTGGAGGATCAGAATCTTATCCACGCTCATGGAAAAACAATCGTTGTA
TATGGCGCAAGA',

#CRP_KLEAE
'ATGGTGCTTGGCAAACCGCAAACA------------------------------------  
------GACCCTACCCTTGAATGGTTCTTGTCTCATTGCCACATTCATAAGTACCCATCA
AAGAGCACGCTGATCCACCAGGGTGAAAAAGCAGAAACGCTGTACTACATCGTTAAAGGC
TCCGTGGCTGTACTCATCAAGGATGAAGAAGGTAAAGAGATGATCCTCTCCTACCTCAAC
CAGGGCGATTTCATCGGTGAATTAGGCCTGTTTGAAGAGGGTCAGGAGCGTAGCGCCTGG
GTACGGGCGAAAACCGCATGTGAAGTGGCCGAAATCTCTTATAAGAAATTCCGTCAGCTG
ATCCAGGTGAACCCGGACATTCTGATGCGTCTCTCTTCGCAAATGGCTCGCCGTCTGCAG
GTCACGTCTGAGAAAGTGGGCAACCTCGCCTTCCTCGACGTGACGGGCCGTATCGCCCAG
ACGCTGCTGAACCTGGCGAAGCAACCGGATGCCATGACCCACCCGGACGGTATGCAAATT
AAAATTACACGCCAGGAAATTGGTCAGATCGTCGGCTGCTCTCGTGAAACCGTTGGTCGT
ATTTTGAAAATGCTGGAAGATCAGAACCTGATTTCCGCGCACGGTAAAACCATCGTCGTC
TACGGCACCCGT',

#CRP_PASMU
'ATGCAAACTACACCATCGATA---------------------------------------
------GATCCTACGTTAGAGTGGTTTCTATCTCACTGCCATATTCACAAATATCCTTCC
AAAAGTACATTGATTCATGCAGGTGAAAAAGCCGAGACGCTGTATTATCTGATTAAAGGT
TCCGTTGCTGTCTTAGTCAAAGATGAAGATGGCAAAGAAATGATTTTGACTTATTTAAGT
CAAGGTGATTTTTTTGGTGAGGCTGGTCTTTTCGAAGAGGGACAATTACGTTCAGCTTGG
ATTAAGGCAAAAAGCCCTTGTGAAATTGCTGAAATTTCTTATAAAAAATTTCGTCAATTA
ATTCAGGTCAACCCAGATATTTTAATGCATTTATCAGCTCAGCTAGCACGTCGCTTACAA
AATACGTCGAGACAAGTAAGTAATCTAGCCTTTTTAGATGTGACGGGGCGTATCGCACAA
ACCTTATTAAATCTGGCTAAAATGCCAGAAGCCATGACCCATCCAGATGGGATGCAAATT
AAAATCACGCGCCAAGAAATTGGGCAAATGGTGGGATGTTCGCGTGAAACGGTAGGACGA
ATCCTCAAAATGTTAGAAGATCAGCATTTAATCTCTGCACATGGTAAAACCATTGTCGTT
TACGGTACAAGA'
);

# 0) establecer el %GC deseado

# 1) abrir un bucle while que recombine aleatoriamente estos genes,
# conservando el marco de lectura y controle el GC de las secuencias
# recombinantes que van saliendo

# 2) calcula el GC con algo como
# my $totalGC = $gen =~ tr/gc/gc/;
# my $GC = $totalGC/length($gen);

# 3) sal del bucle cuando GC lleve n generaciones sin cambiar

# 4) elige al azar dos secuencias para recombinar

# 5) para recombinar usa rand con la longitud del alineamiento
# restringiendo los puntos de sobrecruzamiento a los que sean múltiplos
# de tres

Operaciones con archivos y directorios

Manejo de archivos

En Perl los archivos se manejan como en C, asignando un descriptor de archivo (filehandle) a cada archivo que se abre, que usaremos para manipular su contenido y finalmente cerrarlo.

Si queremos manejar el fichero fichero.txt haríamos:

open(MIFICH,"fichero.txt");   # abrimos un fichero existente para lectura 
                              # y le asignamos el descriptor MIFICH

open(MIFICH,">fichero.txt");  # creamos un nuevo fichero y le asignamos 
                              # el descriptor MIFICH

open(MIFICH,">>fichero.txt"); # abrimos un fichero existente para añadir
                              # contenido al final, conservando el contenido 
                              # original, y le asignamos el descriptor MIFICH

close(MIFICH);                # así cerramos el archivo en cualquier caso 

Una versión más segura hubiera sido:
open(MIFICH,"fichero.txt") || die "lo siento, no puedo encontrar fichero.txt\n";

Para leer un archivo normalmente se usa un bucle while:

open(MIFICH,"fichero.txt")|| die "lo siento, no puedo encontrar fichero.txt\n";   

while(<MIFICH>)               # significa: para cada línea ejecuta el bloque de código 
{                             # entre los corchetes

	print $_;                  # la variable especial $_ contiene la línea actual

	# ahora podríamos usar expresiones regulares para procesar esta línea de texto
}                          
    
close(MIFICH);

Para escribir en un fichero que acabamos de abrir:

open(MIFICH,">fichero.txt")|| die "lo siento, no puedo crear fichero.txt\n";   

print MIFICH "Una línea dentro del archivo\n";
    
close(MIFICH);

Cuando usamos la función print sin especificar un descriptor de archivo, como hemos hecho en varios ejemplos hasta ahora, en realidad estamos escribiendo al archivo de salida por defecto, llamado STDOUT. De la misma manera podemos acceder al teclado, el archivo de entrada por defecto, por medio de <STDIN>.

Por otro lado, existen en Perl operadores especiales para operar sobre ficheros (filetest operators) que pueden ser muy útiles (guía de referencia de Perl):

-r -w -x  # comprueba si hay permiso para leer, escribir o ejecutar un archivo 
-e -z     # comprueba si el archivo existe / tiene tamaño nulo
-s        # compruebe si el archivo tiene tamaño no nulo y devuelve el tamaño
-f -d     # comprueba si el archivo es un fichero / es un directorio

Ejemplo de uso:
if(-r 'mifichero.txt')
{
	# abro y leo el archivo
}

Hay toda una batería de funciones específicas que podéis consultar en la guía de referencia de Perl, pero creo que estas son las esenciales.

Manejo de directorios

Perl también tiene funciones para manejar directorios, como se muestra en el siguiente ejemplo:

$ ls -la ruta/directorio
-rw-r--r--  1 contrera users  52849 jul 20 19:15 archivo.txt
-rw-r--r--  1 contrera users    715 jul 19 11:08 datos
-rw-r--r--  1 contrera users    129 jul  7 16:49 .latex
drwxr-xr-x  2 contrera users   1024 jul  7 13:38 png


opendir(DIR,"ruta/directorio") || die "lo siento, no puedo abrir ruta/directorio\n";

my @entradas = grep {!/^\./} readdir(DIR);  # añade a @entradas archivos y subdirectorios que no empiecen por '.'
                                            # @entradas contiene: archivo.txt, datos, png
closedir(DIR);

Para crear un directorio se usa la función mkdir 'nombre_directorio_nuevo .

Ejercicios que manejan archivos

Leyendo archivos Protein Data Bank (PDB)

El Protein Data Bank (Berman et al., 2000) es un repositorio de descripciones experimentales de las estructuras moleculares de proteínas y ácidos nucleicos resueltos hasta el momento. Cada descripción es un archivo de texto que contiene las coordenadas atómicas de la molécula en cuestión en un formato que se llama PDB.

Aquí podéis ver un ejemplo sencillo, la estructura de una proteína reguladora de ratón en complejo con una molécula de ADN, en el archivo 1lfu.pdb. Podéis desplegar esta molécula con programas como Rasmol y, en cualquier caso, debéis abrir este archivo con vuestro editor de texto para verle las tripas.

Figura 2.1: Representación de Rasmol del archivo PDB 1LFU.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{1lfu}
\end{center}
\end{figure}

En este ejemplo es muestro el programa extraeProtPDB.pl que lee un archivo PDB, como 1lfu.pdb y separa el esqueleto peptídico (backbone) de las proteínas presentes y las deposita en un nuevo archivo.

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

# Programa extraeProtPDB.pl que lee un archivo PDB pasado como argumento, 
# separa el esqueleto peptídico de las proteínas presentes y lo deposita
# en formato PDB en un archivo llamado esquelto.pdb

use strict; 

## variables importantes ###########################################################

my ($archivoPDB,$nombre_atomo,@esqueleto);

## revisa los argumentos pasados al invocar el programa ############################
if(scalar(@ARGV) < 1)
{	
	die "uso: extraeProtPDB.pl <archivoPDB>\n";	
}
else 
{	
	$archivoPDB = $ARGV[0];	# lee el nombre del argumento y asígnalo a  
}


## abre $archivoPDB en modo lectura y lo recorre línea a línea ###############
open(PDB,$archivoPDB) || die "# no puedo abrir $archivoPDB\n";
while(<PDB>)
{
	if(/^ATOM/) # líneas de coordenadas atómicas, si abrís 1lfu.pdb sabréis a qué me refiero
	{
		$nombre_atomo = substr($_,13,4); # toma la parte de la línea que contiene el nombre del átomo
		                                 # en el formato PDB
	
	 	# toma sólo los átomos correspondientes al esqueleto peptídico: N,CA,C,O
		if($nombre_atomo =~ /N / || $nombre_atomo =~ /CA/ || $nombre_atomo =~ /C /|| $nombre_atomo =~ /O /)
	 	{
			push(@esqueleto,$_);
		}
	}
}
close(PDB);

# crea un nuevo archivo para depositar ahí las coordenadas del esqueleto peptídico ###
open(ESQPDB,">esqueleto.pdb") || die "lo siento, no puedo crear esqueleto.pdb\n";

print ESQPDB "HEADER esqueleto peptídico de $archivoPDB\n";
print ESQPDB @esqueleto;

close(ESQPDB);

Ahora, como ejercicio, os pido que modifiquéis este programa para que cree una cadena de caracteres con la secuencia de la proteína 1lfu.pdb en código de una letra. Por ejemplo, Ala deberá ser 'A' y Trp 'W'. El programa deberá además crear un nuevo archivo e imprimir dentro la secuencia en formato FASTA, como el que vísteis en 2.7.5.


Procesando resultados de BLAST

Con el archivo FASTA generado en el ejercicio anterior 1lfu.fas hacemos dos búsquedas BLAST (Altschul et al., 1997) con los siguientes comandos (necesitas tener instalado BLAST localmente y bajarte la biblioteca de secuencias nr):

blastall -p blastp -i 1lfu.fas -d nr -o 1lfu.blast -v 20 -b 20           # -o es el archivo de resultados
blastpgp -i 1lfu.fas -j 2 -Q 1lfu.pssm -d nr -o 1lfu.blast -v 20 -b 20   # -o y -Q son archivos de resultados

Los archivos de resultados generados son 1lfu.blast y 1lfu.blastpgp, donde se encuentra la información relativa a las secuencias potencialmente homólogas encontradas en la biblioteca nr (non redundant), que puedes obtener en el NCBI.

El último archivo de resultados generado es 1lfu.pssm, que contiene información también muy valiosa, entre la que destacamos una matriz de sustitución de aminoácidos específica para la familia de proteínas 1lfu y el contenido en información evolutiva de cada aminoácido de la secuencia de entrada 1lfu.fas.

En el siguiente ejemplo os muestro un trozo de código que extrae de la salida de BLAST (indistintamente 1lfu.blast o 1lfu.blastpgp) la información relevante de cada uno de los alineamientos reportados.

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

# Programa extraeBLAST.pl que lee un archivo generado por BLAST pasado como argumento 
# y extrae la información relevante de cada alineamiento reportado

use strict; 

## variables ######################################################################

my ($archivoBLAST,@hits);

# otras variables manejadas, donde Q significa 'query' y S ' subject',
# las dos secuencias de un alineamiento en BLAST
my ($aux0,$aux1,$aux2,$aux3);  # variables auxiliares
my ($Q,$S);                    # variables booleanas que indican si se está leyendo Q o S
my ($firstQ,$lastQ,$seqQ);     # primer resíduo, último resíduo y secuencia de 'query'
my ($firstS,$lastS,$seqS);
my ($longQ,$name,$id,$ev);     # longitud de 'query', nombre se secuencia, % de identidad y e-value


## revisa los argumentos pasados al invocar el programa ############################
if(scalar(@ARGV) < 1)
{	
	die "uso: extraeBLAST.pl <archivoBLAST>\n";	
}
else 
{	
	$archivoBLAST = $ARGV[0];	# lee el nombre del argumento y asígnalo a  
}


## abre $archivoBLAST en modo lectura y recórrelo línea a línea ####################

$Q = $S = 0;
open(BLAST,$archivoBLAST) || die "# no puedo abrir $archivoBLAST\n";
while(<BLAST>)
{
	if(/Query:/)
	{
		($aux0, $aux1, $aux2, $aux3) = split;  # split separa $_ en 4 escalares separados por espacios en blanco
		
		if(!$Q){	$firstQ = $aux1;	}
		
		$lastQ = $aux3;
		$seqQ .= $aux2;
		$Q++;  # estamos leyendo 'query'        
	}
	elsif(/Sbjct:/)
	{
		($aux0, $aux1, $aux2, $aux3) = split;
	
		if(!$S){	$firstS = $aux1;	}

		$lastS = $aux3;
		$seqS .= $aux2;
		$S++;  # estamos leyendo 'subject'
	}  
	elsif((/^>/)||(/^  Database:/))	## procesa el alineamiento previo
	{ 
		if($Q)
		{
			# crea un arreglo con los datos de este alineamiento y guárdalo en @hits
			# @hits es por tanto un arreglo de arreglos
			push(@hits, [($firstQ,$lastQ,$firstS,$lastS,$name,$id,$ev,$seqQ,$seqS)] );
		}
	
		# reiniciamos valores para el siguiente alineamiento
		$Q = $S = 0;
		$seqQ = $seqS = $name = $id = $ev = "";
		
		# nombre del siguiente alineamiento
		$name = $_;
		chomp($name);
	}
	elsif(/^ Score =/)	
	{	
		if($Q) ## secuencias que tienen varios alineamientos 
		{		
			push(@hits, [($firstQ,$lastQ,$firstS,$lastS,$name,$id,$ev,$seqQ,$seqS)] );
					
			$Q = $S = 0;
			$seqQ = $seqS = $id = ""; 
		}
			
		$ev = (split)[7];
	}
	elsif(/^ Identities/)
	{	
		# toma nota del % de identidad del alineamiento
		$id = (split)[3]; 
		$id =~ s/(\(|\)|\%|,)//g;	
	}
	elsif(/^Sequences/)	# reinicia todo para la siguiente ronda de resultados (sólo blastpgp)
	{
		undef @hits;
		$S = $Q = 0;
		$seqQ = $seqS = $name = $id = $ev = "";
	}
	elsif(/letters\)/)
	{	
		# toma nota de la longitud de la secuencia 'query'
		$longQ = (split)[0];	
		$longQ =~ s/\(//;	
	}
}
close(BLAST);

## ahora como ejemplo muestra el primer alineamiento guardado #################################

# cada alineamiento se guardó así: [($firstQ,$lastQ,$firstS,$lastS,$name,$id,$ev,$seqQ,$seqS)]
# el primer alineamiento será $hits[0], que a su vez es un arreglo, por eso necesitamos otra
# coordenada para llegar a cada elemento

if($hits[0])
{
	print "Posible homólogo:\n$hits[0][4]\n";          
	print "Fragmento de $hits[0][0] a $hits[0][1], e-value $hits[0][6] y \%ID $hits[0][5]\n";    
	print "Alineamiento\n$hits[0][7]\n$hits[0][8]\n";
}

Vuestra tarea es ahora escribir un programita que lea 1lfu.pssm y genere una lista de los aminoácidos cuyo contenido en información evolutiva sea elevado. Os sugiero que uséis la función split para leer el archivo línea a línea.

Entrada, salida y formateo de datos

Cuando queremos que un programa consulte al usuario normalmente necesitaremos leer su respuesta en el archivo estándar de entrada, STDIN . Lo haremos por ejemplo así:

print "Contenido en GC deseado: ";
my $GCusuario = <STDIN>;             # cuando el usuario teclee algo y presione <ENTER> 
                                     # el programa prosigue

Ya hemos visto antes que la salida estándar de datos en Perl es un descriptor de archivo que se llama STDOUT y es a donde escribe print por defecto.

Por último, hay una salida estándar para los errores que se produzcan en tiempo de ejecución que se llama STDERR, a donde podemos hacer que escriban nuestros programas:
print STDERR "Error en mi programa, división por cero\n";
Normalmente STDERR se muestra en el terminal, al igual que STDOUT.

Formateo de datos

Ya hemos usado en diferentes ejemplos la función print. Esta función puede usarse para dar formato a los datos que queremos imprimir en un programa. Una manera práctica muchas veces utilizada en bioinformática es imprimir datos en un archivo por columnas, de forma que todas las líneas tengan el mismo número de columnas. Como separadores de columnas pueden utilizarse carácteres como \t (tabulador), espacios en blanco o comas:

print "$nombreGen \t $GC \t $longitud \t $especie\n";
print "$nombreGen $GC $longitud $especie\n";
print "$nombreGen,$GC,$longitud,$especie\n";

Este tipo de formatos de salida son ideales para que otros programas en Perl, por medio de la función split, puedan leerlos. De hecho son también adecuados para que aplicaciones como Excel u OpenOffice puedan abrir estos archivos.

Sin embargo, a veces es realmente importante, como en el formato PDB, que cada elemento de datos ocupe un grupo de columnas específico de una línea de archivo. En este caso es recomendable el uso de la función printf .

printf es muy similar (no sé si idéntica) a la misma función del lenguaje C y su uso es relativamente sencillo. Su síntaxis es:

printf("cadena formateada %s %1.2f",$escalar1,$escalar2,...);
Dentro de la cadena de caracteres formateada se puede hacer referencia a escalares que queramos formatear. Cada una de estas referencias comienza con el símbolo % y deberá haber tantas como escalares separados por comas en la parte izquierda de la llamada a printf. Además, cada referencia debe especificar el formato de salida deseado:
%c        # el carácter correspondiente a un código ASCII
%s        # una cadena de caracteres (string)
%d        # un número entero
%f        # un número real con coma fija
%g        # un real con decimales
%e        # un real en notación científica exponencial, como 1.204e-23
%x        # un hexadecimal

Modificadores:
-      # justifica a la izquierda
+      # obliga a imprimir el signo
.      # fuerza un mínimo número de columnas para un campo
0      # usa ceros para justificar a la derecha
 
Ejemplos:
 
printf("%1.2f",$real);           # un real de que ocupe al menos 1 columna 
                                 # y tenga un máximo de 2 decimales

printf("%.10s",$cadena);         # cadena de no más de 10 columnas

Finalmente, Perl incluye la posibilidad de definir formatos de otra manera, usando las funciones format y write, que entenderemos mejor con un ejemplo:

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict; 

my ($nombre,$especie,$peso);

## defino un formato y le pongo nombre
# < justifica a la izquierda, > a la derecha , #. definen los campos de números
# | se usaría para centrar contenido
format MIFORMATO =
Proteína=@<<<<<<<<  Organismo=@>>>>>>>>>>>>  Peso molecular=@###.#KDa
           $nombre,             $especie,                   $peso
.			   

## abro el archivo de salida con un descriptor cuyo nombre coincida 
## con el formato de salida
open(MIFORMATO,">salida.txt") || die "no puedo crear salida.txt\n";

## aquí asigno valores a las variables del formato, normalmente será un bucle
$nombre  = "1lfu";
$especie = "M.musculus";
$peso    = "124.456";

write (MIFORMATO);
## imprime en el archivo de salida
## 'Proteína=1lfu       Organismo=   M.musculus  Peso molecular= 124.5KDa'


## cierro el archivo de salida
close(MIFORMATO);

Yo reconozco que nunca uso la combinación format/write, así que os recomiendo, si os interesa usarlos, leer este documento.

Llamadas al sistema operativo

Para pedir al sistema operativo que ejecute otros programas desde un programa en Perl podemos hacerlo de varias maneras. Al hacerlo estamos en realidad llamando al sistema operativo y esperando su respuesta:

## insertando el comando y sus parámetros entre dos `acentos graves`

my $resultado = `ls -l *.fas`;  # muestra todos los archivos FASTA del directorio actual

$resultado = `df -k`;           # muestra el espacio libre en los sistemas de ficheros de tu sistema

$resultado = `cat archivo.pdb | grep ATOM`;  # imprime las líneas 'ATOM' de un PDB

## usando la función system()

$resultado = system("cat archivo.pdb | grep ATOM");

$resultado = system("pwd");                          # devuelve el directorio actual

$resultado system("zcat archivo.fas.gz");            # descomprime un archivo y muestra el contenido

Perl incorpora todo un conjunto de funciones que nos permiten interaccionar con el sistema operativo, que podéis ver íntegro en la guía de referencia de Perl. Yo destacaría:

chdir $otroDir;   # cambia de directorio durante la ejecución de un programa
kill $IDproceso;  # termina otros procesos que hayas creado anteriormente
sleep $segundos;  # para temporalmente la ejecución del programa que la invoca

Hay otra manera muy útil de ejecutar otros programas desde Perl y directamente procesar su salida, usando las tuberías '|' (pipes) de UNIX*. De esta manera, la salida generada por el proceso ejecutado es vista desde Perl como un archivo. Veamos ejemplos:

open(BLAST,"blastpgp -i secuencia.fas |") || die "no puedo ejecutar blastpgp -i secuencia.fas\n";
while(<BLAST>)
{
	# aquí lees la salida de BLAST directamente, sin guardarla en un archivo
}
close(BLAST);


open(ZCAT,"zcat gran_archivo.gz |") || die "no puedo descomprimir gran_archivo.gz\n";
while(<ZCAT>)
{
	# aquí voy leyendo el contenido de gran_archivo, pero el archivo sigue comprimido en el sistema 
	
}
close(ZCAT);


Ejercicios con llamadas al sistema operativo

Os propongo ahora un ejercicio basado en dos programas muy importantes de la bioinformática, BLAST y CLUSTALW(Thompson et al., 1994), que deberéis tener instalados en vuestro sistema. Las tareas que os pido son:

Para terminar este capítulo os muestro un programa que usa el programa externo curl (hay otras posibilidades, como wget) para hacer múltiples peticiones a un servidor web de forma automática. A veces hay que recurrir a este tipo de soluciones, por ejemplo si la versión web es la única accesible para un programa. Pero recordad que antes de hacer este tipo de cosas es una buena idea escribir a la persona responsable de ese servidor web y pedirle permiso. Es posible que os recomienden qué hora del día es mejor para estos trabajos grandes, para no saturar sus máquinas.

 
#!/usr/bin/perl -w
# written by Bruno Contreras, July 2005

# program get_thermo_gibbs_energies.pl, that submits a series of dna sequences in FASTA format
# to the webserver http://wings.buffalo.edu/gsa/dna/dk/WEBTHERMODYN and retrieves their Gibbs 
# energy profile

# This shows how scripts can be written to serialize web form submissions

# For different webservers you need to read the source code of the the submission
# page (rigth mouse button on any browser) to check out the variables involved

# These are the variables for this example, that I got from my browser

#<form  enctype="multipart/form-data" method="POST" action="webthermodyn.cgi">
#<input type=text name=temperature value=37> 
#<input type=text name=saltconc value=10>
#<input type=text name=molecule value="">
#<select name="shape" value="Linear">
#<INPUT TYPE="radio" NAME="inputmode" VALUE="input"> 
#<textarea name="sequence" value="">
#<INPUT TYPE="radio" NAME="allpart" VALUE="ALL"> 
#<input type=text name=step value=50>
#<input type=text value=100 name=windowsize>
#<input type=text value=1 name=marknum>
#<input type=submit value="SUBMIT QUERY">  



use strict;

######################################################################

my $progname = "get_thermo_freeenergies.pl";
my $thermodynCGI = "http://wings.buffalo.edu/gsa/dna/dk/WEBTHERMODYN/webthermodyn.cgi";
my $waittime = 5; # wait 5 seconds between submissions 

my $curl_options = "-F temperature=37 -F sign=positive -F saltconc=10 -F shape=Linear -F inputmode=input -F allpart=ALL -F step=25 -F windowsize=25 -F marknum=5 -F timelimit=90 ";
	
## parse DNA sequences file in fasta ################################

my (@DNA,@NAME);
my ($dnaseq,$dnaseqname,$n_of_seqs) = ("","",0);

open(FASTADNA,$ARGV[0]) || die "#$progname : need a valid DNA fasta file to run properly, exit... \n";
while(<FASTADNA>)
{
	next if(/^$/);
	
	if(/\>/)
	{
		if($dnaseq)
		{
			push(@DNA,$dnaseq);
			push(@NAME,$dnaseqname);
			$dnaseq = ""; 
			$dnaseqname = "";
		}	
		$dnaseqname = substr((split)[0],1);
		$n_of_seqs++;
	}
	else { $dnaseq .= (split)[0];}
}
close(FASTADNA);

print "#$progname : sequences read $n_of_seqs (from $ARGV[0])\n";

## get free energy profiles for each sequence ########################

my ($seq,$s);

for($seq=0;$seq<scalar(@DNA);$seq++)
{
	my $request = $curl_options . "-F sequence=$DNA[$seq] -F molecule=$NAME[$seq]";
	
	open(CURL,"curl $request $thermodynCGI |") || die "#$progname : cannot run curl $request $thermodynCGI ...exit\n";
	while(<CURL>)
	{
		if(/DATADIR/)
		{
			my $results = (split(/\"\>ASCII/,$_))[0];
			$results = (split(/HREF=\"/,$results))[2];
			
			open(RES,"curl $results |" ) || die "#$progname : cannot read $results ...exit\n";
			while(<RES>)
			{
				print;
			}
			close(RES);
		}

	}
	close(CURL);
	
	sleep $waittime;   # wait this time before the next submission is delivered
}


Fundamentos de Perl (2)

Se podría decir que con lo visto hasta ahora podrías ya lanzaros a atacar problemas bioinformáticos con Perl. Sin embargo, todavía no sabéis cómo escribir programas de forma estructurada, con bloques de código separados en forma de subrutinas, ni cómo aprovechar módulos escritos por otra gente y disponibles en Internet. También es útil crear tus propias estructuras de datos, más allá de los arreglos y las tablas, para representar de forma adecuada datos complejos. Por todas estas razones vamos a seguir profundizando un poco más en Perl, para que saquéis el máximo partido al lenguaje y a los millones de líneas de código Perl que hay distribuidas por Internet.

Subrutinas y paso de parámetros

Una subrutina puede definirse como un programa dentro de un programa, un subprograma. Es un trozo de código que tiene vida propia, con sus propio algoritmo y sus propias variables, que puede estar declarado dentro de otro programa Perl, dentro de un módulo o dentro del intérprete Perl, como print, por ejemplo.

Figura 3.1: Posibles ubicaciones para las declaraciones de subrutinas en Perl.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{subrutinas}
\end{center}
\end{figure}

Para ejecutar una subrutina hay que invocarla por su nombre, y si fuera necesario sus parámetros. Por ejemplo ya hemos invocado printf("cadena %s\n",$cadena); antes, donde printf es el nombre de la subrutina y lo que va entre paréntesis sus parámetros. Si invocamos una función y el intérprete no encuentra su declaración, se interrumpirá la ejecución en ese momento con un mensaje de error como este:
Undefined subroutine &main::subrutina_sin_de called at programa.pl line 25.

Declaración de subrutinas y alcance de las variables

Declarar una subrutina en Perl es muy sencillo, es un bloque de código precedido de la palabra reservada sub y un nombre, que, como las variables, debe empezar por un carácter alfanumérico o un '_'.

Puedes declarar subrutinas en cualquier posición de un programa en Perl, pero por cuestión de estilo es costumbre declararlas todas juntas por ejemplo al final del programa principal.

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict; 

mi_subrutina();     # invocación de la subrutina: el intérprete busca 
                    # ahora la declaración de mi_subrutina y la encuentra 
                    # más adelante en el mismo archivo; en ese momento
                    # interrumpe la ejecución del programa principal y
                    # ejecuta el código de la subrutina. El programa
                    # principal se reanuda al terminar mi_subrutina 

######## declaraciones de subrutinas ######################################

sub mi_subrutina     
{
	# código propio de la subrutina
	print "mi primera subrutina\n";
}

Si la ejecución de la subrutina requiere el paso de datos externos a la subrutina, normalmente esos datos se pasan como parámetros en el momento de la invocación:

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict; 

my ($arg1,$arg2) = (1,2);
my @arg3 = (3,4,5);

mi_subrutina($arg1,$arg2,@arg3);  # paso de tres parámetros

######## declaraciones de subrutinas ######################################

sub mi_subrutina     
{
	my($sarg1,$sarg2,@sarg3) = @_;   # así recibe la subrutina los parámetros pasados, 
	                                 # copiándolos del arreglo global @_ a variables locales
	
	print "subrutina con parámetros\n";
}

El paso de parámetros a una subrutina depende de la variable especial @_, que ya mencionamos en 2.3.4, que es un ejemplo de variable global , a la que se puede acceder desde cualquier lugar de un programa.

A diferencia de @_, las variables que se declaran dentro de una subrutina, como my($sarg1,$sarg2,@sarg3) , son por defecto variables locales, que dejan de existir cuando se termina la ejecución de la subrutina y que no pueden ser accedidas si no es desde la misma subrutina.

Aunque en teoría una subrutina puede acceder a variables globales, declaradas fuera de ella, es conveniente limitar el accesos a aquellas variables que se pasen como parámetros, para facilitar la comprensión del código que escribáis y la corrección de posibles errores.

Ejemplo de alcance de variables

A parte de @_ , las subrutinas pueden acceder a otras variables globales. Para probar esto os pido que ejecutéis este programita, observéis la salida y luego lo modifiquéis para demostrar que la subrutina puede hacer exactamente los mismos cálculos recibiendo como parámetros los datos necesarios. ¿No os parece que se hace más fácil comprender el funcionamiento de printBLAST en la versión modificada, ya que no hay que buscar en el código de donde vienen las variables utilizadas?

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict; 

my ($evalue,$identities,$queryseq,$subjectseq);

# 100 líneas de código
$evalue = 4e-34;
$identities = 87;
$queryseq = "MARRKRRNFNKQATEILNEYFYSHLSNPYPSXXXXXXXXXXSGITVSQVSNWFGNKRIRYKKNIGKFQEEANIYAAKTAVTA";
$subjectseq = "MARRKRRNFNKQATEILNEYFYSHLSNPYPSEEAKEELAKKSGITVSQVSNWFGNKRIRYKKNIGKFQEEANIYAAKTAVTA";

# programa principal
my @blastdata = ($evalue,$identities,$queryseq,$subjectseq);

# 100 líneas de código

printBLAST();

# 500 líneas de código

######## declaraciones de subrutinas ######################################

sub printBLAST     
{
	print "evalue $blastdata[0]\n";
	print "alignment:\n$blastdata[2]\n$blastdata[3]\n";                      
}

Subrutinas que devuelven valores

Ya que sabemos cómo declarar e invocar subrutinas, ahora veremos cómo podemos escribir subrutinas que devuelvan valores. Una vez más, creo que un ejemplo deja todo más claro:

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict; 

## variables del programa principal
my $queryseq = "MARRKRRNFNKQATEILNEYFYSHLSNPYPSXXXXXXXXXXSGITVSQVSNWFGNKRIRYKKNIGKFQEEANIYAAKTAVTA";
my $subjectseq = "MARRKRRNFNKQATEILNEYFYSHLSNPYPSEEAKEELAKKSGITVSQVSNWFGNKRIRYKKNIGKFQEEANIYAAKTAVTA";

## llamada a printf que a su vez llama a dos subrutinas que devuelven valores
printf("Alineamiento %2.2f :\n%s\n%s\n%s\n", 
	calculaIdentidad($queryseq,$subjectseq),  # llamada a una subrutina
	decoraAlineamiento($queryseq,$subjectseq));  # llamada a otra subrutina

## no siempre imprimiremos los valores devueltos por una subrutina, a veces simplemente los
## guardaremos:

my ($dev1,$dev2,$dev3) = decoraAlineamiento($queryseq,$subjectseq);


######## declaraciones de subrutinas ######################################

sub calculaIdentidad     
{
	# parámetros
	my ($sec1,$sec2) = @_;
	
	# variables locales
	my ($id,$pos,$long) = (0,0,length($sec1));
	
	for($pos=0;$pos<$long;$pos++)
	{
		if(substr($sec1,$pos,1) eq substr($sec2,$pos,1))
		{
			$id++;
		}
	}
	
	return sprintf("%2.2f",$id/$long);  # devuelve un número real
}

###########################################################################

sub decoraAlineamiento
{
	# parámetros
	my ($sec1,$sec2) = @_;
	
	# variables locales
	my ($decoracion,$pos,$long) = ("",0,length($sec1));
	
	for($pos=0;$pos<$long;$pos++)
	{
		if(substr($sec1,$pos,1) eq substr($sec2,$pos,1))
		{
			$decoracion .= '|';
		}
		else
		{
			$decoracion .= ' ';
		}
	}
	
	return ($sec1,$decoracion,$sec2); # devuelve tres cadenas de caracteres
}

Es común llamar funciones a las subrutinas que devuelven valores y procedimientos a las que no. Si una subrutina contiene la función return, entonces será una función.

A diferencia de otros lenguajes, en Perl una función puede devolver más de un valor, como habéis comprobado con decoraAlineamiento.

A diferencia de otros lenguajes, en Perl los procedimientos pueden invocarse de manera diferente a las funciones, usando &procedimiento; en vez de procedimiento();.

Paso de parámetros por valor y por referencia

En los ejemplos vistos hasta ahora, cuando invocamos una subrutina y le pasamos parámetros en realidad le estamos pasando una lista de datos, que se llama @_ . Cuando pasamos arreglos o tablas como parámetros en realidad los estamos vertiendo elemento por elemento en @_ , lo cual parece un poco estúpido a veces, con el trabajo que nos dio previamente llenarlos. De hecho, si queremos pasar por ejemplo dos arreglos a la vez, será imposible que la subrutina que los reciba sepa donde termina uno y empieza el otro, porque lo que está recibiendo es @_ .

Por esta razón, cuando pasamos varios parámetros y uno de ellos es un arreglo, debemos poner al arreglo en última posición, para poder identificarlos después en la subrutina. Lo mismo se aplica a los valores que devuelve una subrutina con return.

## forma correcta de pasar más de un parámetro y recibir más de un resultado 

my ($result1,@result2) = subrutina($arg1,$arg2,@arg3);

sub subrutina
{
	my($sarg1,$sarg2,@sarg3) = @_; 

	my ($sresult1,@sresult2);

	return ($result1,@result2);
}

## en cambio, esto no funcionaría

my (@result2,$result1) = subrutina($arg1,@arg3,$arg3);  # $result1 queda sin asignar

sub subrutina
{
	my($sarg1,@sarg3,$sarg3) = @_;  # $sarg3 queda sin asignar

	my ($sresult1,@sresult2);

	return (@result2,$result1);
}

La forma más económica de pasar parámetros a subrutinas es pasarlos como referencias (ver sección 2.3). De esta manera no pasamos los datos en sí, si no la dirección que ocupan en memoria. Así no es necesario copiar todos los datos a @_ y la subrutina que reciba las referencias puede acceder a los datos en su organización original, ya sean escalares, arreglos o tablas asociativas.

Otra aplicación de las referencias parámetro es que permiten modificar de forma permanente, no local, los datos pasados como argumento. Finalmente, el paso por referencias permite pasar como parámetro otras subrutinas, lo cual puede ser útil en ocasiones.

Veamos un ejemplo:

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict; 

## variables del programa principal 
my ($escalar , @arreglo1 , @arreglo2);

## asignación de valores
$escalar = 0;
@arreglo1 = (1,2,3);
@arreglo2 = (4,5,6);

## llamada a subrutina y conversión de la referencia recibida a un arreglo
my @resultado = @{ lee_arreglos( $escalar , \@arreglo1 , \@arreglo2 )  }; 

print "resultado: $resultado[0] , $resultado[1] , $resultado[2]\n";


############### subrutinas ##################

sub lee_arreglos
{
	# parámetros pasados
	my ($sarg1,$rarreglo1,$rarreglo2) = @_;

	# variables locales
	my @res = ( $sarg1, $rarreglo1->[0], $rarreglo2->[0] );
	
	return \@res;  # devuelve una referencia a un arreglo como resultado
}

Funciones recursivas

Como en otros lenguajes, en Perl se pueden programar funciones recursivas, que se llaman a si mismas hasta que el problema que intentan resolver tiene un solución obvia. La recursión es un mecanismo de descomposición de problemas que puede hacer más sencillo llegar a una solución, pero a cambio, normalmente, su coste computacional es relativamente grande, ya que implica muchas llamadas a funciones seguidas.

De ejemplo os muestro la función factorial en forma recursiva:

sub factorial
{
	# toma parámetro
	my $fact = $_[0];
	
	if($fact < 0) # error, la función factorial no está definida para negativos
	{ 
		return 0; 
	}
	elsif($fact == 0) # caso trivial
	{
		return 1;
	}
	else # descompón el problema un paso más
	{
		return $fact * factorial($fact -1); 
	}
}


Ventajas de usar subrutinas

Ejercicios con subrutinas

Para aplicar lo que hemos aprendido de subrutinas os pediría que modifiquéis la solución del problema 2.10.1 ahora separando todas las tareas indivisibles en subrutinas. Podéis además comparar la velocidad de ejecución de ambos programas, el viejo y el modificado, usando el programa time en el terminal UNIX*.

Otra manera de valorar el rendimiento de un programa que use subrutinas es ejecutarlo de esta manera: perl -d:DProf programa.pl [argumentos que necesite] . Tras su ejecución se habrá generado un archivo llamado tmon.out que pasaremos como argumento a otro programa: dprofpp tmon.out , generando una salida como esta, donde se ve qué coste empírico tiene cada subrutina durante la ejecución del programa:

Exporter::export has -1 unstacked calls in outer
Exporter::Heavy::heavy_export has 1 unstacked calls in outer
Total Elapsed Time = 34.29820 Seconds
  User+System Time = 16.72820 Seconds
Exclusive Times
%Time ExclSec CumulS #Calls sec/call Csec/c  Name
 64.9   10.85 10.858 124763   0.0000 0.0000  dnalib::get_sq_dist
 23.0   3.852  7.964      3   1.2841 2.6548  dnalib::get_template_contacts
 14.4   2.410  7.221    588   0.0041 0.0123  dnalib::check_base_contact
 6.31   1.055  2.990      3   0.3517 0.9968  dnalib::calc_DNA_complementarity
 3.03   0.507 11.056      3   0.1690 3.6853  dnalib::predict_sites_complex
 0.72   0.121  0.121  28932   0.0000 0.0000  dnalib::tobase4
 0.50   0.084  0.084  30720   0.0000 0.0000  dnalib::num2compbase
 0.42   0.070  0.070      1   0.0700 0.0700  dnalib::extract_BLAST_TF_alignments
 0.41   0.069  0.069    581   0.0001 0.0001  dnalib::calc_Pvalue
 0.41   0.069  0.069    598   0.0001 0.0001  dnalib::calc_family_preferences
 0.24   0.040  0.050      5   0.0080 0.0100  main::BEGIN
 0.23   0.039  8.113      1   0.0391 8.1126  dnalib::build_CM_complexes
 0.20   0.034  0.034  30720   0.0000 0.0000  dnalib::num2base
 0.18   0.030  0.030      3   0.0100 0.0100  dnalib::get_protein_coordinates
 0.17   0.029  0.029    581   0.0001 0.0001  dnalib::calc_def_cost

Estructuras de datos más complicadas

En esta sección veremos cómo crear y manipular estructuras de datos más complejas que las que hemos visto hasta ahora, basadas sobre todo en el uso de arreglos, tablas asociativas y referencias. Como guía alternativa de referencia para este contenido os recomiendo Data structures cookbook.

Arreglos de arreglos

En Perl es muy fácil anidar arreglos dentro de arreglos, por lo que podemos crear matrices N-dimensionales, como ya mencionamos en la sección 2.3. Hay varias maneras de crearlas y consultarlas, que os mostraré con ejemplos:

my @matriz2_3 = ( [1,2,"tres"],  ## declaración con asignación explícita
                  [4,5,"seis"]);

my @matriz;

for($i=0;$i<5;$i++)              ## asignación elemento a elemento desde bucles
{
	for($j=0;$j<10;$j++)
	{
		$matriz[$i][$j] = $i * $j;
	}
}


for $i ( 1 .. 10 )               ## asignación de filas enteras
{
	# ambas expresiones son válidas
	push( @matriz , [ @dimension ];
	$matriz[$i] = [ @dimension ]; 
	# $matriz[$i] contiene una referencia a un arreglo
}

while(<ARCHIVO>)                 ## leyendo un archivo línea por línea
{
	push( @matriz , split(/\s+/,$_);
}

Tablas de tablas, Hashes of hashes

Las tablas de tablas son también sencillas de manejar, de forma muy parecida a los arreglos de arreglos.

my %blosum62 = (                ## declaración con asignación explícita
	'A' => 
	{
		'A' => -4,
		'R' => -1,
		...
		'X' => -1
	}
	
	...
	
	'X' => 
	{
		'A' => --,
		'R' => -1,
		...
		'X' => -1
	}
);


my %tabla;

for($i=0;$i<5;$i++)             ## asignación elemento a elemento desde bucles
{
	for($j=0;$j<10;$j++)
	{
		$tabla{$i}{$j} = $i * $j;
	}
}


for $i ( 1 .. 10 )              ## asignación de subtablas enteras
{
	$tabla[$i] = { %subtabla }; 
	# tabla[$i] contiene una referencia a una tabla
}

Combinaciones de arreglos y tablas

La posibilidad de hacer tablas de arreglos y arreglos de tablas es muy útil en ocasiones, puesto que nos permite combinar las funciones de Perl sobre arreglos, como push, con la facilidad de de uso de las tablas asociativas. La única complicación es que al crear una estructura de datos como esta debemos tener en cuenta sus anidamientos para manipularla, utilizando funciones como keys o values para las subtablas y sort o foreach para los subarreglos:

my @PDBcoordenadas = (

	# una tabla para cada resduo de una proteína PDB
	
	{
		'N'  => 'ATOM    888  N   MET P   0      13.559  11.810 -20.485',
		'CA' => 'ATOM    889  CA  MET P   0      12.699  10.612 -20.293',       
		'C'  => 'ATOM    890  C   MET P   0      12.893  10.012 -18.905',
		'O'  => 'ATOM    891  O   MET P   0      11.938   9.870 -18.140'
	},
	...
);

# podríamos recorrer @PDBcoordenadas recorrería así:

foreach $residuo (@PDBcoordenadas)
{
	# cada valor de $residuo es una referencia a una tabla asociativa
	print $residue->{'CA'};
} 


my %codones = (
	
	# un arreglo para los codones de cada aminoácido	
	
	'F' => ['TTT','TTC'],
	'L' => ['TTA','TTG','CTT','CTC','CTA','CTG'],
	'S' => ['TCT','TCC','TCA','TCG','AGT','AGC'],
	'Y' => ['TAT','TAC'],
	...
	
	# $codones{'L'} sería una referencia a un arreglo
);

# que recorreríamos:
foreach my $amino (keys(%codones)) # recorre llave a llave
{
	print "$amino ";
	foreach my $codon ( @{ $codones{$amino} } )  # recorre los codones asociados a una llave
	{
		print "$codon,";
	}
	print "\n";
}


Registros

Los registros son estructuras de datos que engloban componentes de diferente tipo y equivalen a los que en C serían los objetos struct. Por supuesto puedes crear arreglos y tablas de registros:

my $proteina = {
	NOMBRE => $nombre,
	ANOTACION => $anot,
	PM => $PM,
	PI => $PI,
	NOMBRES_HOMOLOGOS => [ @homologos ],
	ALINEAMIENTOS_HOMOLOGOS => { %alineamientos }
};

# así consultamos algún valor
print $proteina->{'NOMBRE'};

# así asignamos
$proteina->{'NOMBRE'} = 'CRP_ECOLI';

# así la guardamos en un arreglo
push(@proteinas,$proteina);

Colas y pilas como arreglos

Las colas (queue, FIFO, First In First Out) y las pilas (stack, LIFO, Last In First Out) son dos ejemplos clásicos de estructuras de datos en los libros de texto. Por supuesto se pueden implementar en Perl por medio de registros que se apuntan unos a otros, pero lo más sencillo es usar para arreglos y algunas de las funciones vistas en la sección 2.7.1.

Si usamos la combinación shift y unshift, o push y pop, tenemos una pila.

Si usamos shift y push tenemos una cola.

Figura 3.2: Colas y pilas, contenedores FIFO y LIFO.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{colaypila}
\end{center}
\end{figure}

Grafos y árboles

Podemos utilizar los registros que acabamos de ver para representar árboles y, de modo más general, grafos. Cada nodo será un registro que apuntará a sus vecinos según la topología del grafo correspondiente.

Figura 3.3: Ejemplo de árbol binario.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{arbolBin}
\end{center}
\end{figure}

Os muestro un ejemplo muy sencillo para un nodo de un árbol binario, donde cada nodo tiene a lo sumo dos hijos, el derecho y el izquierdo:

my $nodoBin = {
	VALOR => $valor,
	IZQ => $rizq,        # referencia al nodo hijo izquierdo
	DER => $rder         # referencia al nodo hijo derecho
};

Si un nodo puede conectarse a un número indeterminado de nodos, entonces es mejor incluir las referencias a esos nodos en un arreglo.

Os muestro aquí un sencillo programa para manipular árboles binarios de búsqueda, tomado de aquí. ¿Podéis entender el código?¿Qué tiene de especial la subrutina search?

#!/usr/bin/perl -w
# bintree - binary tree demo program
use strict;
my($root, $n);

# first generate 20 random inserts
while ($n++ < 20) { insert($root, int(rand(1000)))}

# now dump out the tree all three ways
print "Pre order:  ";  pre_order($root);  print "\n";
print "In order:   ";  in_order($root);   print "\n";
print "Post order: ";  post_order($root); print "\n";

# prompt until EOF
for (print "Search? "; <>; print "Search? ") { 
    chomp;
    my $found = search($root, $_);
    if ($found) { print "Found $_ at $found, $found->{VALUE}\n" }
    else        { print "No $_ in tree\n" }
}

exit;

#########################################

# insert given value into proper point of
# provided tree.  If no tree provided, 
# use implicit pass by reference aspect of @_
# to fill one in for our caller.
sub insert {
    my($tree, $value) = @_;
    unless ($tree) {
        $tree = {};                         # allocate new node
        $tree->{VALUE}  = $value;
        $tree->{LEFT}   = undef;
        $tree->{RIGHT}  = undef;
        $_[0] = $tree;              # $_[0] is reference param!
        return;
    }
    if    ($tree->{VALUE} > $value) { insert($tree->{LEFT},  $value) }
    elsif ($tree->{VALUE} < $value) { insert($tree->{RIGHT}, $value) }
    else                            { warn "dup insert of $value\n"  }
                                    # XXX: no dups
}

# recurse on left child, 
# then show current value, 
# then recurse on right child.
sub in_order {
    my($tree) = @_;
    return unless $tree;
    in_order($tree->{LEFT});
    print $tree->{VALUE}, " ";
    in_order($tree->{RIGHT});
}

# show current value, 
# then recurse on left child, 
# then recurse on right child.
sub pre_order {
    my($tree) = @_;
    return unless $tree;
    print $tree->{VALUE}, " ";
    pre_order($tree->{LEFT});
    pre_order($tree->{RIGHT});
}

# recurse on left child, 
# then recurse on right child,
# then show current value. 
sub post_order {
    my($tree) = @_;
    return unless $tree;
    post_order($tree->{LEFT});
    post_order($tree->{RIGHT});
    print $tree->{VALUE}, " ";
}

# find out whether provided value is in the tree.
# if so, return the node at which the value was found.
# cut down search time by only looking in the correct
# branch, based on current value.
sub search {
    my($tree, $value) = @_;
    return unless $tree;
    if ($tree->{VALUE} == $value) {
        return $tree;
    }
    search($tree->{ ($value < $tree->{VALUE}) ? "LEFT" : "RIGHT"}, $value)
}

Ejercicio con estructuras de datos

Como ejercicio de esta sección os sugiero que escribáis un programa que tome un grupo de proteínas o de ácidos nucleicos de la misma familia, en formato FASTA, y calcule mediante el programa bl2seq, del paquete BLAST, una matriz de similaridad de todas las secuencias contra todas. Finalmente, basándoos en la matriz, el programa deberá crear un árbol que represente las distancias de todas las secuencias a una elegida por el usuario.

Objetos en Perl

Las estructuras de datos que hemos visto en la sección anterior permiten manejar cómodamente relaciones abstractas entre datos, como por ejemplo un grafo. Los objetos llevan un poco más ella esa abstracción, pues son agregados de variables (atributos) con identidad propia que además incluyen las subrutinas (métodos) que permiten manipularlos.

Figura 3.4: Clase genérica con sus atributos y sus métodos.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{clase}
\end{center}
\end{figure}

Un objeto es una instancia de la clase a la que pertenece, como por ejemplo un Mercedes con placas 1234HJ pertenecería a la clase de los automóviles de lujo, o la proteína 1lfu pertenece a la clase PDB.

Los atributos de un objeto normalmente sólo pueden ser manipulados por medio de los métodos de su clase.

Una clase clase puede heredar componentes de otras clases, por lo que son adecuadas para representar conceptos y objetos de forma jerárquica. Por ejemplo, la clase PDB será seguramente una clase derivada de la clase proteína y ésta a su vez de la clase macromolécula, de donde heredarán el atributo 'secuencia'.

Os recomiendo que miréis en Google si no os resultan familiares estos conceptos, en el libro Advanced Perl Programming, pero recordad que la programación orientada a objetos es un estilo de programación, que tiene sus ventajas e incovenientes, que conviene conocer al menos para poder comprender programas escritos por otras personas. Como ya sabéis, Perl os dejará programar usando programación estructura, orientada a objetos y combinaciones de ambas sin problema.

Atributos e identidad de un objeto (con programación estructura)

Una manera de implementar los atributos de un objeto en Perl sería muy similar a los registros de la sección 3.2.4 y se podrían generar así:

my $pdb1 = constructor_PDB('1lfu',3,'1lfu.pdb');

sub constructor_PDB  # un método que genera nuevos objetos se llama constructor 
{
	# valores de los atributos como parámetros
	my($nombre,$n_de_cadenas,$archivoPDB) = @_;

	my $rPDB = {
		'nombre' => $nombre,
		'ncadenas' => $n_de_cadenas,
		'archivo' => $archivoPDB,
		'coordenadas' => ()
	};
	return $rPDB;   # devuelve una referencia al objeto
}

Métodos de un objeto (con programación estructura)

Los métodos de un objeto permiten manipular sus atributos desde el exterior, sin necesariamente conocer las tripas del objeto:

leer_archivoPDB($pdb1,0);

sub leer_archivoPDB   
{
	# parámetros
	my($rPDB,$comprimido) = @_;
	
	# variables locales
	my $res_leidos = 0;
   
	if($comprimido == 1)
	{
		open(PDB,"zcat $rPDB->{'archivo'} |") || warn "no puedo leer zcat $rPDB->{'archivo'}\n";
		# rellena $rPDB->{'coordenadas'}
		# $res_leidos++;
		close(PDB);
	}
	else
	{
		open(PDB,"$rPDB->{'archivo'}") || warn "no puedo leer $rPDB->{'archivo'}\n";
		# rellena $rPDB->{'coordenadas'}
		# $res_leidos++;
		close(PDB);
	}
	
	return $res_leidos; 
}


Implementando una clase en Perl

Acabamos de implementar un objeto de la clase PDB usando registros y subrutinas, ahora lo haremos usando la sintaxis propia de la programación orientada a objetos. Para ello debemos crear un paquete .pm que contendrá el código de la clase. Como ejemplo os muestro la clase PDB, contenida en el archivo PDB.pm, donde veréis que usamos la función bless que permite distinguir al intérprete entre referencias y objetos:

package PDB;   # se incluye en otro programa con 'use PDB.pm;'

use strict;

sub PDB::constructor  # en inglés este método suele llamarse 'new'
{
	# autoreferencia a la clase y atributos como parámetros
	my($clase,$nombre,$n_de_cadenas,$archivoPDB) = @_;

	# implementación de atributos como una tabla asociativa
	my $objeto = {
		'nombre' => $nombre,              
		'ncadenas' => $n_de_cadenas,
		'archivo' => $archivoPDB,
		'coordenadas' => [],
	};
	
	# podría haber sido como un arreglo, más eficiente, pero menos obvio el contenido 
	#$objeto->[0] = $nombre;
	#$objeto->[1] = $n_de_cadenas;
	#$objeto->[2] = $archivoPDB;
	#$objeto->[3] = [];
		
	bless($objeto,$clase); # la función bless da carácter de objeto a la referencia $objeto

	return $objeto;
}

sub PDB::pon_nombre_archivo
{
	my($objeto,$archivoPDB) = @_;
	$objeto->{'archivo'} = $archivoPDB;
}


sub PDB::leer_archivoPDB   
{
	# autoreferencia al objeto y parámetros
	my($objeto, $comprimido) = @_;
	
	# variables locales
	my @coordenadas = ();
	
	if(!$objeto->{'archivo'}) 
	{ 
		print "'archivo' no está definida\n";
		return 0; 
	}
	
	if($comprimido == 1)
	{
		open(PDB,"zcat $objeto->{'archivo'} |") || warn "no puedo leer zcat $objeto->{'archivo'}\n";
	}
	else
	{
		open(PDB,"$objeto->{'archivo'}") || warn "no puedo leer $objeto->{'archivo'}\n";
	}
	
	while(<PDB>)
	{
		if(/^ATOM/||/^TER/)
		{
		 	push(@coordenadas,$_);
		}
	}
	close(PDB);
	
	$objeto->{'coordenadas'} = [ @coordenadas ];
	
	return scalar(@coordenadas); 
}

sub PDB::imprime_coordenadas
{
	my($objeto) = @_;
	print @{ $objeto->{'coordenadas'} };
}

1;

A continuación os muestro la clase derivada PDBdna, contenida en un archivo llamado PDBdna.pm, que hereda de la clase PDB y sólo modifica el método leer_archivoPDB:

package PDBdna;   # se incluye en otro programa con 'use PDBdna.pm;'

use strict;

# clase derivada de PDB, especializada en representar moléculas de ADN
use PDB;
use vars '@ISA';
@ISA = 'PDB';

# redefino aquí el método leer_archivoPDB, para tener en cuanta sólo los
# átomos de ADN

sub PDBdna::leer_archivoPDB   
{
	# autoreferencia al objeto y parámetros
	my($objeto, $comprimido) = @_;
	
	# variables locales
	my @coordenadas = ();
	
	if(!$objeto->{'archivo'}) 
	{ 
		print "'archivo' no está definida\n";
		return 0; 
	}
	
	if($comprimido == 1)
	{
		open(PDB,"zcat $objeto->{'archivo'} |") || warn "no puedo leer zcat $objeto->{'archivo'}\n";
	}
	else
	{
		open(PDB,"$objeto->{'archivo'}") || warn "no puedo leer $objeto->{'archivo'}\n";
	}
	
	while(<PDB>)
	{
		# lee sólo átomos de ADN
	   if((/^ATOM/ && substr($_,18,1) eq " " && substr($_,19,1) =~ /A|G|C|T/) || /^TER/)
		{
		 	push(@coordenadas,$_);
		}
	}
	close(PDB);
	
	$objeto->{'coordenadas'} = [ @coordenadas ];
	
	return scalar(@coordenadas); 
}


1;

Finalmente, os muestro un ejemplo que deberéis probar donde se utilizan estas clases:

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict;

use PDB;      # incluye PDB.pm
use PDBdna;   # incluye PDBdna.pm, en el mismo directorio

# os sugiero que modifiquéis el código para probar también la clase PDBdna
my $pdb1 = PDB->constructor('1lfu',3,'bioinfoPerl/problemas/1lfu.pdb');
$pdb1->leer_archivoPDB(0);
$pdb1->imprime_coordenadas();

Paquetes y módulos

En la sección 3.3.3 creamos un paquete de Perl en un archivo .pm . En concreto estábamos creando una clase, pero los paquetes de Perl sirven en general para agrupar código, en forma de variables globales, estructuras de datos, subrutinas y programas. De esta forma, tal cómo decíamos en la sección 3.1.5, favorecemos la reutilización y distribución de nuestro código y facilitamos la corrección de errores.

Paquetes y módulos del intérprete Perl

En UNIX*, si tecleáis $ man perlmodlib podéis ver el listado y documentación sobre los paquetes estándar de Perl, entre los que está, ya lo recordaréis, strict. Es muy recomendable que echéis un vistazo aquí antes de poneros a escribir nuevos módulos, no vaya a ser que ya existan. La colección es impresionante, amplia las capacidad que ya conocéis de Perl de forma considerable.


CPAN

En el archivo en red CPAN (Comprehensive Perl Archive Network) podréis encontrar, con fecha de Julio de 2005, más de 8000 paquetes que casi 5000 autores han escrito y documentado para un sinfín de aplicaciones distintas. Este repositorio es sin duda uno de los mayores atractivos de Perl.

Una vez que hayáis encontrado un paquete que os interese, lo descargáis y tendréis un archivo modulo.tar.gz que deberéis descomprimir con tar xvfz modulo.tar.gz. En el subdirectorio creado tenéis un archivo README donde se explican los pasos para instalar y probar el módulo. Sólo para el último de ellos necesitaréis privilegios de administrador, pero con el make y make test ya podréis usarlo.

Si tenéis privilegios de administrador, la manera más fácil es teclear:
perl -MCPAN -e "install 'Modulo'"

Creación de paquetes y módulos

Podemos crear un módulo con subrutinas o estructuras de datos que nos hayan sido especialmente útiles y que sigamos usando de forma repetida. En teoría basta con que creéis un archivo paquete.pm que contenga las siguientes líneas:

package paquete;

# todo lo que venga aquí es el contenido del módulo

1;

Ahora os muestro un ejemplo de paquete genérico que contiene dos módulos:

package paquete;

################### módulo mod1 ########################

$mod1::var1 = 'mod1::variable1';

sub mod1::subrutina1
{
	# código de subrutina 
}

# ...

################### módulo mod2 ########################

$mod2::var1 = 'mod2::variable1';

sub mod2::subrutina1
{
	# código de subrutina 
}

########################################################

1;  # convención para terminar un paquete

Para utilizar este módulo desde otro programa podríamos:

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict;
use paquete;  

print $mod1::var1;  # acceso a una variable del módulo mod1 de paquete

mod2::subrutina1(); # acceso a una subrutina del módulo mod2 de paquete

Al instalar el intérprete Perl viene incluido el programa h2xs que nos ayuda a crear paquetes siguiendo una plantilla genérica. Para crear el paquete ejemplo haríamos:
$ h2xs -Xn ejemplo .

Que a mi me devuelve:

Defaulting to backwards compatibility with perl 5.8.5
If you intend this module to be compatible with earlier perl versions, please
specify a minimum perl version with the -b option.

Writing ejemplo/lib/ejemplo.pm
Writing ejemplo/Makefile.PL
Writing ejemplo/README
Writing ejemplo/t/ejemplo.t
Writing ejemplo/Changes
Writing ejemplo/MANIFEST

El programa crea un subdirectorio con el nombre del paquete y coloca ahí una plantilla ya iniciada de ejemplo.pm donde deberemos ir añadiendo el código que deseemos. Además, el programa genera documentación que acompaña al paquete e instrucciones para compilarlo y finalmente instalarlo en tu sistema.

Si queréis contribuir con algún módulo a CPAN deberán ir en el formato generado por h2xs.

Utilización de paquetes y módulos desde un programa

Como ya vimos, un paquete se incluye en un programa por medio de use paquete; . Cuando el intérprete Perl llega a esa línea, normalmente en el encabezado de un programa, busca un archivo que se llame paquete.pm en una serie de directorios, que incluyen el directorio actual, almacenados en el arreglo especial @INC.

Podemos comprobar el contenido de @INC simplemente imprimiéndolo con print @INC; .

Si queremos utilizar un módulo o paquete que no se encuentra en @INC, deberemos añadir estas líneas antes de llamar a use:

use lib "/home/usuario/modulos/"
use strict;
use mimodulo;

Ejercicio de creación de paquetes

Para que vayáis practicando con estas cosas os voy a pedir que escribáis el paquete contactos.pm. Se trata de un paquete relacionado con el cálculo de mapas de contactos de proteínas. Un mapa de contactos para una proteína con N aminoácidos es una matriz binaria NxN donde en cada celda (coordenadas i,j) habrá un '+' si los aminoácidos i y j están a menos de 7Å uno del otro y un ' ' en caso contrario:

001                       ++++                  +     
002                       +++++                 +
003                          +++++              +
...

Vuestro paquete deberá contener al menos 4 subrutinas que:

Si queréis usar el programa h2xs os recomiendo esta guía.

Una vez que tengáis el módulo listo y documentado, escribid un programa que lo invoque y utilice que sirva de ejemplo.

Algunos paquetes y programas Perl muy útiles

En esta sección veremos ejemplos de algunos paquetes y módulos que me parecen muy útiles para distintas aplicaciones en bioinformática. Algunos vienen incorporados con el intérprete, otros son de CPAN y otros los obtuve directamente de las páginas web de los autores.

Getopt::Std

Este módulo facilita la creación de una interfaz para tu programa en la línea de comando, definiendo las diferentes opciones dispibles y manejando su tratamiento. Lo mostraré mejor con un ejemplo:

#!/usr/bin/perl -w
# program: example.pl
# author: Bruno Contreras Moreira

use strict;
use Getopt::Std;

my %opts;
getopts('hsi:l:', \%opts);

if(($opts{'h'})||(scalar(keys(%opts))==0)) 
{ 
	print "usage: progname.pl [options]\n";
	print "-h \t this message\n";
	print "-i \t input transcription file\n";
	print "-s \t skip preprocessing (optional)\n";
	print "-l \t oligo length (default 1, optional)\n\n";
	exit; 
}

if(!defined($opts{'i'})) 	{ die "# need an input file\n"; }
if(!defined($opts{'l'}) || ($opts{'l'} < 1) ) 	
{ 
	$opts{'l'} = 1; 
}
if(!defined($opts{'s'})) 	
{ 
	$opts{'s'} = 0; 
}

printf("# example.pl -i %s -s %s -l %d\n",$opts{'i'},$opts{'s'},$opts{'l'});

LWP

Esta colección de paquetes de CPAN nos permite desarrollar clientes web en Perl, os recomiendo que investiguéis la documentación en CPAN para ver todas sus posibilidades.

Aquí os mostraré el uso del paquete LWP::Simple para descargarnos una página web, lo cual puede sernos de utilidad alguna vez:

#!/usr/bin/perl -w
# cómo descargarse una página web de forma no interactiva

use strict;
use LWP::Simple;

my $contenido = get("http://www.perl.com");

XML

El XML es un lenguaje descriptivo que sirve para crear documentos estructurados y representar con ellos datos abstractos. Se está usando cada vez más como formato para distribuir documentos e información en bioinformática, ya que se pueden manipular desde muchas plataformas y lenguajes de programación. Esto sería un ejemplo de documento XML:

<?xml version='1.0' standalone='yes' ?>
<documento>
<encabezado>
	<titulo>Módulos sobre XML en Perl</titulo>
	<autor>CPAN</autor>
</encabezado>
<texto>
XML::API
Perl extension for creating XML documents
XML-API-0.06 - 20 Jul 2005 - Mark Lawrence

XML::ASCX12
ASCX12 EDI to XML Module
XML-ASCX12-0.03 - 28 Sep 2004 - Brian Kaney

XML::ASX
An ASX file - methods for everything from <ASX> to </ASX>
XML-ASX-0.01 - 26 Jun 2002 - Allen Day

XML::ApacheFOP
Access Apache FOP from Perl to create PDF files using XSL-FO.
XML-ApacheFOP-0.01 - 01 Jun 2005 - Ken Prows 
...
</texto>
</documento>

En Perl, sólo en CPAN hay sobre 1000 paquetes relacionados con la manipulación de documentos XML. Podéis ver ejemplos en xmlbio.org y en este tutorial.

RemoteRestMap

Módulo para buscar sitios de restricción como cliente del servidor RestrictionMapper, muy sencillo de usar.

#!/usr/bin/perl -w
# program that gets the E.coli restriction maps of a series of E.coli sequences in fasta format
# based on test.pl from http://www.restrictionmapper.org/remote.htm


use lib "/home/compu2/Perl/remoterestmap/";
use strict;
use RemoteRestMap;

my %settings = (
	isoschizomers => "yes",
	enzymelist    => ['EciI','M.Eco67Dam','EcoHAI','EcoNI','Eco29kI','EcoprrI']
	);
	
# instantiation of map object
my $map_obj = new RemoteRestMap("http://www.restrictionmapper.org/cgi-local/sitefind3.pl", "atgc"); 

$map_obj->sequence("ATCGATCGATACGCGATACTAGCGCGATCATCGCGCGCTATATACTCAGCTCGCTCTAGATATCTA");
	
my $map = $map_obj->get_map(\%settings);
my @sites;

while (my $cuts = $map->get_next_enz()) 
{ 
	push( @sites , split(/\,/,$cuts->{CUTLIST}->[0]) );	
}


GD::Graph

Este paquete permite hacer diagramas y gráficas para representar datos numéricos de manera sencilla. Puedes obtenerlo en CPAN y deberás instalar el paquete GD antes de instalar GD::Graph, aunque este es un ejemplo de paquete que puede dar problemas al instalar, porque requiere a su vez otros componentes. Pero dada su utilidad creo que vale la pena el trago.

La documentación para explotar este paquete la encontráis en GD::Graph, en CPAN.

Aquí tenéis el código para hacer un diagrama X/Y de dos variables numéricas, que se muestra en el archivo ejemplo.png:

#!/usr/bin/perl -w 
# Ejemplo escrito por Bruno Contreras en Julio de 2005

use strict;
use GD::Graph::lines;

my @datos = (
[-3,-2,-1,0,1,2,3],            # variable X
[.2,.4,1.9,2.4,1.2,.3,.3]      # variable Y
);

# crea un nueva instancia
my $grafica = new GD::Graph::lines();

# configura el diagrama
$grafica->set( 
	x_label           => 'sigma',
	y_label           => 'frecuencia',
	title             => 'Ejemplo de gráfica X/Y'
) or die $grafica->error;

my $diagrama = $grafica->plot( \@datos );


open(GRAF_PNG,">ejemplo.png") || die "no puedo crear ejemplo.png\n";
binmode GRAF_PNG;
print GRAF_PNG $diagrama->png();
close(GRAF_PNG);


RSPerl: invocar R desde perl

En esta sección veremos como se puede utilizar el poderoso ambiente de programación estadística R, que tantas aplicaciones tiene en el tratamiento estadístico de datos, incluyendo datos biológicos y *ómicos. Para ello debes descargar e instalar el paquete RSPerl, con dos sencillos pasos:

Algunos programas Perl muy útiles

Aquí os muestro algunos programas escritos en Perl que os pueden ser útiles. Están escritos en Perl y podéis por lo tanto curiosear en el código.

El programa mview está escrito en Perl y sirve para representar en forma de alineamiento múltiple la salida de BLAST. Por ejemplo, esta salida de blastpgp 1lfu_nr.blast podemos convertirla a 1lfu_nr.html.

ps_scan.pl es un programa para hacer búsquedas de motivos PROSITE en secuencias de proteínas.

Limitaciones numéricas de Perl

Hasta ahora hemos visto la potencia de Perl para manipular cadenas de caracteres, que de hecho es para lo que se diseñó el lenguaje. Podemos preguntarnos además qué capacidad tiene el lenguaje para las operaciones aritméticas con números reales, algo que normalmente no se ve en el código, más bien queda escondido en los detalles de la compilación. Para entender la cuestión veamos un ejemplo programado en C y en Perl;

/* Ejemplo de Julio Freyre */
#include <stdio.h>
void main() 
{
   for(float x = 0; x < 1; x += .1) 
      printf("%f es menor que 1\n", x);
}

Ahora la versión en Perl:

# este programa muestra el error que acumula perl al sumar reales
for(my $x = 0; $x < 1; $x += .1) 
{
   print("$x es menor que 1\n");
}

# el error es de 1e-16, este programa ya funciona como se espera en arquitecturas i586 y SPARC
my $error = 1e-16;
for(my $x = 0; $x+$error < 1; $x += .1) 
{
   print("$x es menor que 1\n");
}

Si probáis estos programitas veréis la limitación de Perl al manejar números reales. ¿Qué se puede hacer para resolver esto? La solución más sencilla es utilizar un módulo de CPAN que se llama Math::BigFloat, que permite usar una precisión arbitaria, elegida por el usuario, para representar reales y operar con ellos, con un coste en el tiempo de ejecución.

Por cierto, hay otro módulo llamado Math::BigInt que permite extender la capacidad intrínseca del lenguaje y la arquitectura física subyacente para representar enteros.


BIOPERL y DBI

En este capítulo veremos con un poco más de detalle dos paquetes muy importantes, Bioperl y DBI, hechos para facilitarnos la vida en multitud de aplicaciones bioinformáticas y no reinventar la rueda con muchas tareas habituales.

BIOPERL

Bioperl es un proyecto comunitario de código abierto que lleva ya más de 10 años produciendo aplicaciones escritas en Perl para la bioinformática, la genómica y las ciencias biológicas en general. El recurso bioinformático más llamativo hecho con Bioperl sea probablemente la base de datos Ensembl.

Según reza la el tutorial de Bioperl:

Bioperl does provide reusable perl modules that facilitate writing perl scripts for sequence manipulation, accessing of databases using a range of data formats and execution and parsing of the results of various molecular biology programs including Blast, clustalw, TCoffee, genscan, ESTscan and HMMER. Consequently, bioperl enables developing scripts that can analyze large quantities of sequence data in ways that are typically difficult or impossible with web based systems.

El proyecto distribuye programas y paquetes Perl (incluidos en CPAN) y tiene su propio tutorial y su documentación. El listado completo de los 500 módulos de Bioperl puedes encontrarlo aquí y ahí verás además documentación y ejemplos de cada módulo.

En este capítulo vamos a recorrer algunas de las utilidades de Bioperl, sobre todo a base de ejemplos y ejercicios.

Descargando Bioperl

Podéis descargaros la versión más reciente de Bioperl. Para escribir estas páginas yo descargé bioperl-1.5.tar.gz (core) y lo descomprimí con tar xvfz bioperl-1.5.tar.gz en un directorio. Una vez hecho esto puedes correr el programa bptutorial.pl que te muestra algunas de las cosas que puedes hacer con Bioperl, con estos argumentos:

1  => sequence_manipulations
2  => seqstats_and_seqwords
3  => restriction_and_sigcleave
4  => other_seq_utilities
5  => run_perl
6  => searchio_parsing
7  => bplite_parsing
8  => hmmer_parsing
9  => simplealign
10 => gene_prediction_parsing
11 => access_remote_db
12 => index_local_db
13 => fetch_local_db    (NOTE: needs to be run with demo 12)
14 => sequence_annotation
15 => largeseqs
16 => liveseqs
17 => run_struct
18 => demo_variations
19 => demo_xml
20 => run_tree
21 => run_map
22 => run_remoteblast
23 => run_standaloneblast
24 => run_clustalw_tcoffee
25 => run_psw_bl2seq

Una buena manera de iniciarse en las capacidades de Bioperl es revisar el código de bptutorial.pl.

Para instalar el paquete seguid las instrucciones que ya vimos en la sección 3.4.2.

Acceso remoto a bases de secuencias y BLAST

Una de las cosas más útiles de Bioperl es que te permite acceder a bases de datos en la red y a herramientas web desde un programa Perl, permitiendo así hacer análisis bioinformáticos complejos con muy poca infraestructura, tan sólo una conexión a Internet. La única precaución que debes tener es no sobrecargar de forma abusiva los servicios web que utilices, sólo podría perjudicarte.

En este ejemplo os muestro cómo obtener desde la red secuencias de GenBank(Benson et al., 2002) .

Se pueden consultar por esta vía además SwissProt (Boeckmann et al., 2003) , GenPept, EMBL y RefSeq.

#!/usr/bin/perl -w
# Ejemplo donde obtenemos por la red una secuencia de GenBank y la guardamos en formato FASTA

use strict;
use Bio::Perl;

my $gi = "NP_417816";

# ahora una de GenBank
my $secuenciaGenBank = get_sequence( 'genbank', '$gi' );

# escribe la secuencia en formato FASTA a un archivo
write_sequence(">$gi\.fas", 'fasta', $secuenciaGenBank);

Ejercicios con Bioperl

Llegados a este punto, después de tanto rollo, creo que lo mejor es proponer un ejercicio semejante a los problemas que tendréis que resolver alguna vez en vuestros proyectos, donde normalmente la única ayuda disponible será Google o estos tutoriales: este, o este otro.

Anotación automática de secuencias

En este ejercicio os voy a pedir el diseño e implementación de un sistema sencillo que intente anotar de forma automática secuencias de proteínas y ácidos nucleicos según son producidas por un proyecto de secuenciación. Anotar es poner en forma de texto una caracterización de su función, de su historia evolutiva y de sus detalles. El sistema deberá:

Por supuesto, mejor si hacéis todo esto usando herramientas de Bioperl. Quizás este tutorial os sea de utilidad.

DBI

DBI es un conjunto de módulos Perl que puedes descargar de CPAN si no los tienes ya en tu sistema. DBI significa Database Independent Interface , que en español es una interfaz para bases de datos independiente de la plataforma, similar a la interfaz JDBC del lenguaje java, por ejemplo.

Hay todo un mundo de diferentes conceptos e implementaciones de bases de datos. DBI nos permite interaccionar desde Perl con muchas de ellas, ya sean propietarias o de código abierto como MySQL. Uno de las razones del éxito de DBI es que es sencillo escribir los controladores de un tipo de base de datos dado para que DBI pueda interaccionar con ella.

Las aplicaciones de DBI incluyen sobre todo las bases de datos relacionales, construidas a base de tablas y manejadas normalmente por medio de un lenguaje estándar llamado SQL, del inglés Structured Query Language.

DBI es un conjunto de módulos escritos en forma de clases.

En la sección 1 se citan una serie de documentos donde podéis complementar el aprendizaje y los usos de DBI que veamos aquí.

Figura 4.1: Uso de DBI desde Perl y flujo de información desde un programa a una base de datos. DBI incluye controladores para plataformas como Oracle, Informix, mSQL, MySQL, Ingres, Sybase, DB2, Empress, SearchServer y PostgreSQL.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{DBIflux}
\end{center}
\end{figure}

Operaciones básicas con bases de datos relacionales

Figura 4.2: Operaciones básicas con bases de datos y funciones SQL relacionadas.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{BDoperaciones}
\end{center}
\end{figure}

Conexión a una base de datos a través de la red

El primer paso para consultar una base de datos por medio de DBI será conectarse a su servidor desde nuestro programa Perl, normalmente a través de la red. Para hacerlo debemos crear una instancia de la clase DBI y usar su método connect .

La información necesaria para conectarse es:

Si vas a conectarte a un servidor de base de datos Oracle necesitarás el controlador DBD::Oracle, de la misma manera que necesitarás DBD::mysql para conectarte a una máquina que corra mysql. Puedes descargar de CPAN los controladores que necesites, pero antes comprueba qué controladores hay disponibles en tu sistema con:
$ perl -e 'use DBI; print DBI->available_drivers();'

Aquí muestro un ejemplo de código de conexion a la base de datos test del servidor MySQL servidor.ccg.unam.mx :

#!/usr/bin/perl -w
# Ejemplo escrito por Bruno Contreras en Agosto de 2005

use strict;
use DBI;

my $conexion = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave");
if(!$conexion)
{
	print "No me puedo conectar a mysql:test:servidor.ccg.unam.mx\n$DBI::errstr\n";
	exit(-2);
}

$conexion->disconnect() || warn "Fallo al desconectarme de mysql:test:servidor.ccg.unam.mx\n$DBI::errstr\n";

En principio puedes mantener varias conexiones a la misma base de datos, y cada una de ellas se comportará de forma independiente:

#!/usr/bin/perl -w
# Ejemplo escrito por Bruno Contreras en Agosto de 2005

use strict;
use DBI;

my $conexion1 = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave");
if(!$conexion1)
{
	print "Falló conexión1 a mysql:test:servidor.ccg.unam.mx\n$DBI::errstr\n";
	exit(-2);
}

my $conexion2 = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave");
if(!$conexion2)
{
	print "Falló conexión2 a mysql:test:servidor.ccg.unam.mx\n$DBI::errstr\n";
	exit(-3);
}

$conexion1->disconnect() || warn "Falla desconexión1 de mysql:test:servidor.ccg.unam.mx\n$DBI::errstr\n";
$conexion2->disconnect() || warn "Falla desconexión2 de mysql:test:servidor.ccg.unam.mx\n$DBI::errstr\n";

A veces es difícil conectarse a servidores muy cargados de trabajo, por lo que podemos probar a conectarnos varias veces, hasta que lo consigamos:

#!/usr/bin/perl -w
# Ejemplo escrito por Bruno Contreras en Agosto de 2005

use strict;
use DBI;

my $conexion;

until( $conexion = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave") )
{
	warn "No consigo conectarme a a mysql:test:servidor.ccg.unam.mx\nLo intento de nuevo en 1 minuto\n";
	sleep(60);
}

$conexion->disconnect() || warn "Fallo al desconectarme de mysql:test:servidor.ccg.unam.mx\n$DBI::errstr\n";

Manipulación de una base de datos con SQL

Una vez que hemos establecido una conexión con una base de datos podemos utilizar el objeto DBI creado para consultarla y manipularla por medio de comandos SQL, siempre y cuando tengamos los permisos necesarios.

Os recomiendo que miréis el libro Programming the Perl DBI para ver un repertorio más amplio de formas de interacción con bases de datos.

Sentencias SELECT

Usaremos el método DBI::prepare() para crear un objeto manipulador (statement handler) y luego el método execute() de este nuevo objeto para efectivamente manipular la base de datos.

#!/usr/bin/perl -w
# Ejemplo escrito por Bruno Contreras en Agosto de 2005

use strict;
use DBI;

## por Dios, trata de conectarlo, Carlos
my $conexion = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave",{
      PrintError => 0,   ### avisa si hay errores mediante warn
      RaiseError => 1    ### avisa y termina si hay errores mediante die
  }
);

## ahora manipula la base de datos mediante consultas SQL
my $tabla = "blast";
my $consulta="SELECT hit FROM $tabla WHERE evalue < 1e-03";
my $manipulador = $conexion->prepare($consulta);  # normalmente, sólo devuelve un objeto 
                                                  # manipulador si la consulta es válida																  

$manipulador->execute();
while (my @datos=$manipulador->fetchrow_array)    # puedes usar fetchrow_hash con columnas como claves
{
	# haz algo con estos datos, como imprimirlos
	foreach my $dato (@datos)
	{
		print "$dato,";
	}
}
$manipulador->finish();

$conexion->disconnect();

Otra manera útil de capturar la salida de una consulta sería:

use strict;
use DBI;

my $conexion = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave",{PrintError,0,RaiseError,1});

my $consulta="SELECT * FROM blast WHERE evalue < 1e-03";
my $manipulador = $conexion->prepare($consulta);  															  

$manipulador->execute();
my $ref_arreglo = $manipulador->fetchall_arrayref();  # referencia a un arreglo que contiene
                                                      # los resultados

$conexion->disconnect();

Otra manera alternativa de mostrar los resultados de una consulta SQL SELECT podría ser:

use strict;
use DBI;

my $conexion = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave",{PrintError,0,RaiseError,1});

## ahora manipula la base de datos mediante consultas SQL
my $tabla = "blast";
my $consulta="SELECT hit FROM $tabla WHERE evalue < 1e-03";
my $manipulador = $conexion->prepare($consulta);  															  

$manipulador->execute();
# imprime todos los resultados obtenidos de la consulta
$manipulador->dump_results();

# guarda los resultados en un archivo en formato de 80 columnas
$manipulador->execute();
open(CONSULTA,">resultados.txt") || die "no puedo crear resultados.txt\n";
$manipulador->dump_results(80, "\n", ':', \*CONSULTA);

$conexion->disconnect();

Finalmente os muestro una forma de hacer tus programas con DBI más comprensibles, usando el método bind_columns:

use strict;
use DBI;

my $conexion = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave",{PrintError,0,RaiseError,1});

## ahora manipula la base de datos mediante consultas SQL
my ($hit,$evalue); # columnas de $tabla
my $tabla = "blast";
my $consulta="SELECT hit,evalue FROM $tabla WHERE evalue < 1e-03";
my $manipulador = $conexion->prepare($consulta);  															  

$manipulador->execute();

# asocia columnas de salida con sus nombres
$manipulador->bind_col( 1, \$hit );
$manipulador->bind_col( 2, \$evalue );

# procesa la salida
while ($manipulador->fetch()) 
{
	print "$hit  $evalue\n";
}

$conexion->disconnect();

Para manipular variables de tamaño ilimitado en tablas (tipos LONG y LOB) hay que hacer consultas SELECT modificadas, que podéis ver aquí.

Otras sentencias

Para otros comandos SQL, como INSERT, UPDATE, DROP o CREATE, podemos seguir usando la combinación prepare() + execute() , podemos hacerlo también en un sólo paso, con el método do() . Podéis usarlo para sentencias como:

DELETE FROM blast WHERE hit = 'ECOLI_CRP';

UPDATE tabla SET var1 = var1+1 WHERE id = 247;

INSERT INTO variables VALUES ('grafo', 0);

Éste es un ejemplo de uso de do() :

use strict;
use DBI;

my $conexion = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave",{PrintError,0,RaiseError,1});

# filas contiene el número de filas afectadas por la sentencia,
# en caso de error devuelve falso
my $filas = $conexion->do("DELETE FROM blast");
if(!$filas)
{
	print "no pude ejecutar la expresión SQL\n";
}

$conexion->disconnect();

Para las bases de datos basadas en transacciones estas sentencias sólo tendrán efecto si tras cursarlas se ejecuta el comando COMMIT, que por defecto DBI ejecuta automáticamente por nosotros. Pero también podemos hacerlo explícitamente:

use strict;
use DBI;

my $conexion = DBI->connect("dbi:mysql:test:servidor.ccg.unam.mx","usuario","clave",{PrintError,0,RaiseError,1,AutoCommit,0});

# filas contiene el número de filas afectadas por la sentencia,
# en caso de error devuelve falso
my $filas = $conexion->do("INSERT INTO variables VALUES ('grafo', 0)");
if(!$filas)
{
	print "no pude ejecutar la expresión SQL\n";
}
else
{
	# si quieres fijar estos cambios:
	$conexion->commit(); 
	
	# si quisieras descartalos:
	$conexion->rollback();
}

$dbh->rollback();

$conexion->disconnect();

Ejercicios con DBI

Ahora os toca trabajar a vosotros. Podréis usar los ejemplos vistos hasta ahora y esta fuente adicional como inspiración para estos ejercicios:

  1. Selecciona un grupo de proteínas de SwissProt que os resulten de interés.
  2. Guarda en formato texto las entradas SwissProt de las secuencias elegidas.
  3. Diseñar y crear una base de datos en el servidor MySQL del curso para representar y alojar esta información, incluyendo su anotación, sus referencias PubMed, y otros datos que consideréis de opinión.
  4. Hacer un programa PERL con DBI que permita hacer consultas en la línea de comando a la nueva base de datos.


Otras aplicaciones de Perl en bioinformática

Con la excusa de ir aprendiendo diferentes aspectos de Perl, hemos ido viendo algunas de sus aplicaciones en bioinformática, pero nos hemos dejado algunas muy importantes fuera. Creo que este curso se quedaría cojo si no viéramos al menos brevemente temas como:

Vamos a ver ahora ejemplos de cómo Perl puede ser muy útil en estos campos. Os pido que me escribáis (mirar sección 1) si creéis que me dejado fuera alguna aplicación de importancia, trataré de incluirla para la siguienre versión.

Manejo de datos de microarreglos

Dentro del mundo Open Source, la mayoría de los paquetes para tratamiento estadístico de datos de microarreglos se basan en la plataforma R y la biblioteca de paquetes Bioconductor, que no tienen mucho que ver con Perl.

Pero sí hay otros paquetes escritos en Perl para estos fines, como GP3, AMAD: Another Microarray Database, Statistics tools for gene expression o cellwall.stanford.edu/scripts/. Google os dará muchos más, así que, como otras veces, vale la pena ver por ahí que hay hecho antes de lanzarnos a escribir nosotros programas que ya existen.

Cuando hablamos de microarreglos normalmente hablamos de matrices muy grandes donde cada fila normalmente se corresponde con un gen. En Perl es muy sencillo manejar este tipo de datos, como veremos en el siguiente ejemplo.

Ejercicio con datos publicados de microarreglos

Vamos a utilizar los datos de Gutiérrez-Ríos et al. (2004), accesibles desde aquí, para este ejercicio. La idea es que os copiéis el archivo plano con los datos a vuestra máquina local y os pido que escribáis un programa que:

Minería de datos biológicos

Ésta es una de las aplicaciones más prometedoras y quizás menos conocidas de la bioinformática. ¿Pero qué es la minería de datos? En pocas palabras es la búsqueda de conocimiento enterrado en la literatura de forma automatizada. Es una herramienta muy útil para lograr un mejor aprovechamiento de la literatura científica, permitiéndonos acceder a conceptos y contenidos que están ya descritos en las publicaciones científicas, sin ser necesariamente especialistas en ese área. Os recomiendo esta presentación para profundizar sobre este tema. Para haceros una idea de qué se está haciendo ahora mismo en el campo podéis mirar este artículo de Müller et al. (2004).

Hay multitud de herramientas en la web para hacer minería de datos, de alcance general o especializadas en biología, pero aquí vamos a ver cómo aproximarnos a estos problemas desde Perl.

Ejercicio de extracción de palabras clave y frases importantes

En este ejercicio vamos a trabajar con dos conjuntos de datos separados.

El primero consta de 426 resúmenes o abstracts de artículos científicos devueltos por PubMed con la consulta 'CRP + coli + regulation', que podéis encontrar en el archivo CRP_coli_regulation_PubMed.txt.

El segundo es este artículo completo gb-2000-1-2-research0004.xml en formato XML, estraído de BioMedCentral, una de las editoriales que están publicando artículos científicos de libre acceso.

Vuestro trabajo consiste en escribir código Perl que:

Detección de genes

La detección de genes es quizás la tarea más importante del procesamiento de los datos generados por un proyecto de secuenciación. De hecho, las predicciones de genes, correctas o equivocadas, son la base para casi todo el trabajo experimental que se haga después de alcanzar una versión medianamente estable del genoma.

La tarea de localizar genes es complicada y muy costosa, sobre todo por el tamaño de los datos implicados y por la dificultad de distinguir la señal del ruido acumulado en los genomas. En eucariotas se complica todavía más por los enormes genomas que tienen, por la cantidad de secuencias basura que aparentemente no tienen una función biológica y por la presencia de intrones. En otras palabras, es todo un mundo al que podéis iniciaros en www.genefinding.org. Por lo que he visto, Perl no se utiliza demasiado en este campo por razones de eficiencia. Dado el coste computacional del problema, se prefieren lenguajes como C/C++.

Sin embargo, para efectos de este curso, podemos hacer una aproximación un tanto basta al problema desde Perl, aprovechando sus fortalezas en el tratamiento de cadenas. El algoritmo que os propongo consiste en 4 pasos:


Proyecto de trabajo por grupos: manipulación de bases de datos biológicas

Hemos llegado ya a la parte donde os toca aplicar todo lo aprendido previamente para realizar las tareas que se han asignado a cada grupo. Esto será trabajo de equipo y así será evaluado.

Los datos crudos con los que tendréis que trabajar podéis encontrarlos en el directorio del curso en /home/compu2/.

Se os pide que diseñéis e implementéis programas en lenguaje Perl para interaccionar con vuestra base de datos, la de vuestro grupo o proyecto, e ir poblándola con los datos necesarios, según determinásteis en el módulo anterior de vuestro proyecto, tras todo el procesamiento que sea necesario.

Como véis no son instrucciones muy concretas, por lo que tendréis que ir justificando por escrito vuestro trabajo como grupo. Os recomiendo que consultéis readyset para redactar vuestros documentos. Aquí hallaréis plantillas.

La idea de todo esto es enseñaros un concepto nuevo, que no hemos visto antes en el curso: que normalmente vale la pena hacer un poco de planificación antes de ponerse a escribir código. Muchos programadores hacen todo esto mentalmente, sin documentos, si acaso algunas notas. Pero cuando se trata de proyectos en equipo o de cierta envergadura, entonces se hace más necesario recurrir a los documentos.

Por último, cuando vayáis avanzando en vuestro proyecto, debéis tratar de que todo, documentos y código, sean lo más sencillo posible. Que no os importe si los requisitos o las especificaciones ocupan media hoja, si tan pequeño es el proyecto.

Nuestro ciclo de desarrollo particular seguirá este flujo:

Figura 6.1: Ciclo de desarrollo de nuestros proyectos.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{cicloD}
\end{center}
\end{figure}

Documento de requisitos

Sería aconsejable que empecéis con un documento de requisitos software (mirar este ejemplo) donde especifiquéis en más detalle vuestra tarea y cómo pensáis repartir el trabajo. Ésta es una tarea de análisis, que enlaza con el esfuerzo que ya hicísteis en el módulo anterior de vuestro proyecto.

Documento de especificaciones

Después lo lógico es seguir con un documento de especificaciones donde planteéis en cierto detalle como pensáis resolver cada una de las tareas incluidas en el documento de requisitos.

Escritura de código Perl

Ahora sí os ponéis manos a la obra y escribís el código Perl necesario, simplemente siguiendo las especificaciones. Esta parte se puede distribuir fácilmente entre los componentes de cada grupo. Se valorará favorablemente que creéis paquetes y/o módulos con los fragmentos de código que tengan cierta coherencia, si se da el caso, que así serán más fácilmente reutilizables. Por supuesto se requieren todos los comentarios necesarios en el código para facilitar su lectura y comprensión por otros programadores.

Pruebas de código e informe final

Finalmente sería conveniente que elaborárais un informe donde se incluya todo lo anterior y pruebas y ejemplos de utilización de vuestro código, demostrando que sirve para cumplir los requisitos iniciales, con figuras y capturas de pantalla si fuera necesario.


Enlaces relacionados en Internet

Aquí iré poniendo enlaces relacionados con este curso y los que me vayáis enviando vosotros:

Certamen de poesía en Perl
An introduction to bioinformatics algorithms

Bibliografía

Altschul, S. F., Madden, T. L., Schaffer, A. A., Zhang, J., Zhang, Z., Miller, W. & Lipman, D. J. (1997).
Gapped BLAST and PSI-BLAST: a new generation of protein database search programs.
Nucleic Acids Res., 25(17):3389-3402.

Benson, D. A., Karsch-Mizrachi, I., Lipman, D. J., Ostell, J., Rapp, B. A. & Wheeler, D. L. (2002).
GenBank.
Nucl. Acids Res., 30(1):17-20.
URL http://nar.oupjournals.org/cgi/content/abstract/30/1/17.

Berman, H. M., Westbrook, J., Feng, Z., Gilliland, G., Bhat, T. N., Weissig, H., Shindyalov, I. N. & Bourne, P. E. (2000).
The Protein Data Bank.
Nucleic Acids Res., 28(1):235-242.

Boeckmann, B., Bairoch, A., Apweiler, R., Blatter, M.-C., Estreicher, A., Gasteiger, E., Martin, M. J., Michoud, K., O'Donovan, C., Phan, I., Pilbout, S. & Schneider, M. (2003).
The SWISS-PROT protein knowledgebase and its supplement TrEMBL in 2003.
Nucleic Acids Res., 31(1):365-370.

Gutiérrez-Ríos, R., Rosenblueth, D., Loza, J., Huerta, A., Glasner, J., Blattner, F. & Collado-Vides, J. (2004).
Regulatory Network of Escherichia coli: Consistency Between Literature Knowledge and Microarray Profiles.
Genome Res., 13:2435-2443.

Henikoff, S. & Henikoff, J. G. (1993).
Performance evaluation of amino acid substitution matrices.
Proteins, 17(1):49-61.

Müller, H., Kenny, E. & Sternberg, P. (2004).
Textpresso: An Ontology-Based Information Retrieval and Extraction System for Biological Literature.
PLoS Biol., 2(11):e309.

Thompson, J. D., Higgins, D. G. & Gibson, T. J. (1994).
CLUSTAL W: improving the sensitivity of progressive multiple sequence alignment through sequence weighting, position-specific gap penalties and weight matrix choice.
Nucleic Acids Res., 22(22):4673-4680.


Índice de Materias

árboles binarios de búsqueda
Grafos y árboles
abs
Funciones aritméticas
anotar, anotación
Anotación automática de secuencias
arreglo, array
Variables y tipos
atributos
Objetos en Perl
base de datos relacional
DBI
bless
Implementando una clase en
bptutorial.pl
Descargando Bioperl
chomp
Funciones sobre cadenas
clase
Objetos en Perl
CPAN
CPAN
CREATE
Operaciones básicas con bases
curl
Ejercicios con llamadas al
data source name
Conexión a una base
DELETE
Operaciones básicas con bases
descript de archivo, filehandle
Manejo de archivos
die
Estructuras de control
DROP
Operaciones básicas con bases
escalar, scalar
Variables y tipos
esqueleto peptídico, backbone
Leyendo archivos Protein Data
estilo de programación
Cómo escribir tus programas
exp
Funciones aritméticas
FIFO
Colas y pilas como
filetest operators
Manejo de archivos
foreach
Estructuras de control
función
Subrutinas que devuelven valores
GenBank
Acceso remoto a bases
grep
Funciones sobre arreglos
h2xs
Creación de paquetes y
index
Funciones sobre cadenas
INSERT
Operaciones básicas con bases
instancia de una clase
Objetos en Perl
int
Funciones aritméticas
keys
Funciones sobre tablas asociativas
Larry Wall
El lenguaje Perl
last
Estructuras de control
lc, uc
Funciones sobre cadenas
length
Funciones sobre cadenas
lenguaje interpretado
El intérprete Perl, un
LIFO
Colas y pilas como
log
Funciones aritméticas
métodos
Objetos en Perl
mutación
Algoritmos genéticos: simulando mutaciones
Open Source
El lenguaje Perl | El intérprete Perl, un | BIOPERL
path
Un primer programa
pop
Funciones sobre arreglos
printf
Formateo de datos
procedimiento
Subrutinas que devuelven valores
programación orientada a objetos
Objetos en Perl
Protein Data Bank
Leyendo archivos Protein Data
push
Funciones sobre arreglos
queue
Colas y pilas como
rand
Funciones aritméticas
recombinación
Algoritmos genéticos: simulando mutaciones
referencia
Variables y tipos
return
Subrutinas que devuelven valores
reutilización de código
Ventajas de usar subrutinas
reverse
Funciones sobre arreglos
ruta absoluta
Un primer programa
scalar
Funciones sobre arreglos
scripts
El intérprete Perl, un
secuencias basura
Detección de genes
SELECT
Operaciones básicas con bases
shift
Funciones sobre arreglos
sort
Funciones sobre arreglos
split
Funciones sobre cadenas
SQL
DBI
sqrt
Funciones aritméticas
stack
Colas y pilas como
statement handler
Sentencias SELECT
STDERR
Entrada, salida y formateo
STDIN
Entrada, salida y formateo
STDOUT
Manejo de archivos
strict
Cómo escribir tus programas
substr
Funciones sobre cadenas
sustitución
Expresiones regulares
SwissProt
Acceso remoto a bases
tabla asociativa, hash
Variables y tipos
terminal
El intérprete Perl, un
traducción
Expresiones regulares
transacciones
Otras sentencias
tuberías, pipes
Llamadas al sistema operativo
unshift
Funciones sobre arreglos
UPDATE
Operaciones básicas con bases
values
Funciones sobre tablas asociativas
variable global
Declaración de subrutinas y
variables locales
Declaración de subrutinas y
wget
Ejercicios con llamadas al

Sobre este documento...

Perl en bioinformática

This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.70)

Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The command line arguments were:
latex2html bioinfoPerl -split 0 -dir bioinfoPerl1 -no_navigation

The translation was initiated by Bruno Contreras Moreira on 2007-06-15


Bruno Contreras Moreira 2007-06-15