You are on page 1of 227

A PRIMER FOR

COMPUTATIONAL
BIOLOGY

Shawn T. O’Neil
Oregon State University
Oregon State University
A Primer for Computational Biology

Shawn T. O’Neil
This text is disseminated via the Open Education Resource (OER) LibreTexts Project (https://LibreTexts.org) and like the hundreds
of other texts available within this powerful platform, it is freely available for reading, printing and "consuming." Most, but not all,
pages in the library have licenses that may allow individuals to make changes, save, and print this book. Carefully
consult the applicable license(s) before pursuing such effects.
Instructors can adopt existing LibreTexts texts or Remix them to quickly build course-specific resources to meet the needs of their
students. Unlike traditional textbooks, LibreTexts’ web based origins allow powerful integration of advanced features and new
technologies to support learning.

The LibreTexts mission is to unite students, faculty and scholars in a cooperative effort to develop an easy-to-use online platform
for the construction, customization, and dissemination of OER content to reduce the burdens of unreasonable textbook costs to our
students and society. The LibreTexts project is a multi-institutional collaborative venture to develop the next generation of open-
access texts to improve postsecondary education at all levels of higher learning by developing an Open Access Resource
environment. The project currently consists of 14 independently operating and interconnected libraries that are constantly being
optimized by students, faculty, and outside experts to supplant conventional paper-based books. These free textbook alternatives are
organized within a central environment that is both vertically (from advance to basic level) and horizontally (across different fields)
integrated.
The LibreTexts libraries are Powered by MindTouch® and are supported by the Department of Education Open Textbook Pilot
Project, the UC Davis Office of the Provost, the UC Davis Library, the California State University Affordable Learning Solutions
Program, and Merlot. This material is based upon work supported by the National Science Foundation under Grant No. 1246120,
1525057, and 1413739. Unless otherwise noted, LibreTexts content is licensed by CC BY-NC-SA 3.0.
Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not
necessarily reflect the views of the National Science Foundation nor the US Department of Education.
Have questions or comments? For information about adoptions or adaptions contact info@LibreTexts.org. More information on our
activities can be found via Facebook (https://facebook.com/Libretexts), Twitter (https://twitter.com/libretexts), or our blog
(http://Blog.Libretexts.org).

This text was compiled on 03/17/2023


TABLE OF CONTENTS
Licensing

Materia Frontal
TitlePage
InfoPage
Tabla de Contenidos
Acerca del Autor

01: Introducción a Unix


1: Introducción a Unix/Linux
1.1: Contexto
1.2: Inicio de sesión
1.3: La línea de comandos y el sistema de archivos
1.4: Trabajar con archivos y directorios
1.5: Permisos y Ejecutables
1.6: Instalación de software (Bioinformática)
1.7: Línea de comandos BLAST
1.8: Las corrientes estándar
1.9: Clasi cación, Primera y Última Líneas
1.10: Filas y Columnas
1.11: Patrones (Expresiones Regulares)
1.12: Miscelánea

2: Programación en Python
2.1: Hola, Mundo
2.2: Tipos de datos elementales
2.3: Colecciones y Looping- Listas y para
2.4: Entrada y salida de archivos
2.5: Flujo de control condicional
2.6: Funciones de Python
2.7: Interfaz de línea de comandos
2.8: Diccionarios
2.9: Knick-knacks bioinformáticos y expresiones regulares
2.10: Variables y Alcance
2.11: Objetos y Clases
2.12: Interfaces de Programación de Aplicaciones, Módulos, Paquetes, Azúcar Sintáctico
2.13: Algoritmos y estructuras de datos

3: Programación en R
3.1: Una introducción
3.2: Variables y Datos
3.3: Vectores
3.4: Funciones R
3.5: Listas y Atributos
3.6: Marcos de datos
3.7: Carácter y Datos Categóricos

1 https://espanol.libretexts.org/@go/page/157032
3.8: Dividir, Aplicar, Combinar
3.9: Remodelación y unión de marcos de datos
3.10: Programación Procesal
3.11: Objetos y Clases en R
3.12: Datos de trazado y ggplot2

Index
Glossary
Detailed Licensing

Volver Materia
Índice
Glosario

2 https://espanol.libretexts.org/@go/page/157032
Licensing
A detailed breakdown of this resource's licensing can be found in Back Matter/Detailed Licensing.

1 https://espanol.libretexts.org/@go/page/157033
CHAPTER OVERVIEW
Materia Frontal
TitlePage
InfoPage
Tabla de Contenidos
Acerca del Autor

Materia Frontal is shared under a not declared license and was authored, remixed, and/or curated by LibreTexts.

1
Universidad Estatal de Oregón
Una cartilla para la biología computacional

Shawn T. O'Neil
TitlePage is shared under a not declared license and was authored, remixed, and/or curated by LibreTexts.
TitlePage has no license indicated.
This text is disseminated via the Open Education Resource (OER) LibreTexts Project (https://LibreTexts.org) and like the hundreds
of other texts available within this powerful platform, it is freely available for reading, printing and "consuming." Most, but not all,
pages in the library have licenses that may allow individuals to make changes, save, and print this book. Carefully
consult the applicable license(s) before pursuing such effects.
Instructors can adopt existing LibreTexts texts or Remix them to quickly build course-specific resources to meet the needs of their
students. Unlike traditional textbooks, LibreTexts’ web based origins allow powerful integration of advanced features and new
technologies to support learning.

The LibreTexts mission is to unite students, faculty and scholars in a cooperative effort to develop an easy-to-use online platform
for the construction, customization, and dissemination of OER content to reduce the burdens of unreasonable textbook costs to our
students and society. The LibreTexts project is a multi-institutional collaborative venture to develop the next generation of open-
access texts to improve postsecondary education at all levels of higher learning by developing an Open Access Resource
environment. The project currently consists of 14 independently operating and interconnected libraries that are constantly being
optimized by students, faculty, and outside experts to supplant conventional paper-based books. These free textbook alternatives are
organized within a central environment that is both vertically (from advance to basic level) and horizontally (across different fields)
integrated.
The LibreTexts libraries are Powered by MindTouch® and are supported by the Department of Education Open Textbook Pilot
Project, the UC Davis Office of the Provost, the UC Davis Library, the California State University Affordable Learning Solutions
Program, and Merlot. This material is based upon work supported by the National Science Foundation under Grant No. 1246120,
1525057, and 1413739. Unless otherwise noted, LibreTexts content is licensed by CC BY-NC-SA 3.0.
Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not
necessarily reflect the views of the National Science Foundation nor the US Department of Education.
Have questions or comments? For information about adoptions or adaptions contact info@LibreTexts.org. More information on our
activities can be found via Facebook (https://facebook.com/Libretexts), Twitter (https://twitter.com/libretexts), or our blog
(http://Blog.Libretexts.org).

This text was compiled on 03/17/2023

InfoPage is shared under a not declared license and was authored, remixed, and/or curated by LibreTexts.
TABLE OF CONTENTS
Licensing

Materia Frontal
TitlePage
InfoPage
Tabla de Contenidos
Acerca del Autor

01: Introducción a Unix


1: Introducción a Unix/Linux
1.1: Contexto
1.2: Inicio de sesión
1.3: La línea de comandos y el sistema de archivos
1.4: Trabajar con archivos y directorios
1.5: Permisos y Ejecutables
1.6: Instalación de software (Bioinformática)
1.7: Línea de comandos BLAST
1.8: Las corrientes estándar
1.9: Clasi cación, Primera y Última Líneas
1.10: Filas y Columnas
1.11: Patrones (Expresiones Regulares)
1.12: Miscelánea

2: Programación en Python
2.1: Hola, Mundo
2.2: Tipos de datos elementales
2.3: Colecciones y Looping- Listas y para
2.4: Entrada y salida de archivos
2.5: Flujo de control condicional
2.6: Funciones de Python
2.7: Interfaz de línea de comandos
2.8: Diccionarios
2.9: Knick-knacks bioinformáticos y expresiones regulares
2.10: Variables y Alcance
2.11: Objetos y Clases
2.12: Interfaces de Programación de Aplicaciones, Módulos, Paquetes, Azúcar Sintáctico
2.13: Algoritmos y estructuras de datos

3: Programación en R
3.1: Una introducción
3.2: Variables y Datos
3.3: Vectores
3.4: Funciones R
3.5: Listas y Atributos
3.6: Marcos de datos
3.7: Carácter y Datos Categóricos

1 https://espanol.libretexts.org/@go/page/55080
3.8: Dividir, Aplicar, Combinar
3.9: Remodelación y unión de marcos de datos
3.10: Programación Procesal
3.11: Objetos y Clases en R
3.12: Datos de trazado y ggplot2

Index
Glossary
Detailed Licensing

Volver Materia
Índice
Glosario

Tabla de Contenidos is shared under a not declared license and was authored, remixed, and/or curated by LibreTexts.

2 https://espanol.libretexts.org/@go/page/55080
Acerca del Autor

This page is a draft and is under active development.

Shawn T. O'Neil obtuvo una licenciatura en informática de la Universidad del Norte de Michigan, y posteriormente una maestría y
un doctorado en la misma materia por la Universidad de Notre Dame. Su investigación pasada y actual se centra en la
bioinformática. O'Neil ha desarrollado e impartido diversos cursos de biología computacional tanto en Notre Dame como en la
Universidad Estatal de Oregón, donde actualmente trabaja en el Centro de Investigación del Genoma y Biocomputación.

Acerca del Autor is shared under a not declared license and was authored, remixed, and/or curated by LibreTexts.
About the Author has no license indicated.

1 https://espanol.libretexts.org/@go/page/55088
01: Introducción a Unix
Esta página se ha generado automáticamente porque un usuario ha creado una subpágina de esta página.

01: Introducción a Unix is shared under a not declared license and was authored, remixed, and/or curated by LibreTexts.

01.1 https://espanol.libretexts.org/@go/page/55171
CHAPTER OVERVIEW
1: Introducción a Unix/Linux
La línea de comandos es el “entorno natural” de la computación científica, y esta parte abarca una amplia gama de temas,
incluyendo el registro, el trabajo con archivos y directorios, la instalación de programas y escritura de scripts, y el poderoso
operador “pipe” para la manipulación de archivos y datos.
1.1: Contexto
1.2: Inicio de sesión
1.3: La línea de comandos y el sistema de archivos
1.4: Trabajar con archivos y directorios
1.5: Permisos y Ejecutables
1.6: Instalación de software (Bioinformática)
1.7: Línea de comandos BLAST
1.8: Las corrientes estándar
1.9: Clasificación, Primera y Última Líneas
1.10: Filas y Columnas
1.11: Patrones (Expresiones Regulares)
1.12: Miscelánea

Miniaturas: Esmoquin el pingüino. (El titular de los derechos de autor de este archivo permite a cualquier persona usarlo para
cualquier propósito, siempre que el titular de los derechos de autor sea atribuido correctamente; Larry Ewing)

This page titled 1: Introducción a Unix/Linux is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

1
1.1: Contexto
Líneas de Comando y Sistemas Operativos
Muchos sistemas operativos, incluyendo Microsoft Windows y Mac OS X, incluyen una interfaz de línea de comandos (CLI) así
como la interfaz gráfica de usuario estándar (GUI). En este libro, nos interesan principalmente las interfaces de línea de comandos
incluidas como parte de un sistema operativo derivado del entorno históricamente natural para la computación científica, Unix,
incluyendo las diversas distribuciones de Linux (por ejemplo, Ubuntu Linux y Red Hat Linux), BSD Unix y Mac OS X.
Aun así, es útil comprender los sistemas operativos informáticos modernos y cómo interactúan con el hardware y otro software. Un
sistema operativo se considera vagamente como el conjunto de software que administra y asigna el hardware subyacente,
dividiendo la cantidad de tiempo que cada usuario o programa puede usar en la unidad central de procesamiento (CPU), por
ejemplo, o guardar los archivos secretos de un usuario en el disco duro y protegerlos del acceso por otros usuarios. Cuando un
usuario inicia un programa, ese programa es “propiedad” del usuario en cuestión. Si un programa desea interactuar con el hardware
de alguna manera (por ejemplo, leer un archivo o mostrar una imagen a la pantalla), debe canalizar esa solicitud a través del
sistema operativo, que generalmente manejará esas solicitudes de tal manera que ningún programa pueda monopolizar la atención
del sistema operativo o el hardware.

La figura anterior ilustra los cuatro principales recursos “consumibles” disponibles para las computadoras modernas:
1. La CPU. Algunas computadoras tienen múltiples CPU y algunas CPU tienen múltiples “núcleos” de procesamiento.
Generalmente, si hay n núcleos totales y k programas ejecutándose, entonces cada programa puede acceder hasta n/k potencia
de procesamiento por unidad de tiempo. La excepción es cuando hay muchos procesos (digamos, unos pocos miles); en este
caso, el sistema operativo debe dedicar una cantidad considerable de tiempo simplemente cambiando entre los diversos
programas, reduciendo efectivamente la cantidad de potencia de procesamiento disponible para todos los procesos.
2. Discos duros u otro “almacenamiento persistente”. Dichas unidades pueden almacenar grandes cantidades de datos, pero el
acceso es bastante lento en comparación con la velocidad a la que se ejecuta la CPU. El almacenamiento persistente suele estar

1.1.1 https://espanol.libretexts.org/@go/page/55186
disponible a través de unidades remotas “mapeadas” a través de la red, lo que hace que el acceso sea aún más lento (pero quizás
proporcionando mucho más espacio).
3. RAM, o memoria de acceso aleatorio. Debido a que los discos duros son tan lentos, todos los datos deben copiarse en la RAM
de “memoria de trabajo” para que pueda acceder la CPU. La RAM es mucho más rápida pero también mucho más cara (y por lo
tanto generalmente proporciona menos almacenamiento total). Cuando se llena la RAM, muchos sistemas operativos recurrirán
a tratar de usar el disco duro como si fuera RAM (conocido como “intercambio” porque los datos se intercambian
constantemente dentro y fuera de la RAM). Debido a la diferencia de velocidad, puede parecerle al usuario como si la
computadora se hubiera estrellado, cuando en realidad simplemente está trabajando a un ritmo glacial.
4. La conexión de red, que proporciona acceso al mundo exterior. Si varios programas desean acceder a la red, deben compartir
tiempo en la conexión, al igual que para la CPU.
Debido a que las interfaces de software que usamos todos los días, las que nos muestran nuestros íconos de escritorio y nos
permiten iniciar otros programas, son tan omnipresentes, que a menudo pensamos en ellas como parte del sistema operativo.
Técnicamente, sin embargo, estos son programas que son ejecutados por el usuario (generalmente de forma automática al iniciar
sesión o al inicio) y deben realizar solicitudes del sistema operativo, al igual que cualquier otro programa. Los sistemas operativos
como Microsoft Windows y Mac OS X son en realidad sistemas operativos integrados con amplias suites de software de usuario.

Una breve historia


La historia completa de los sistemas operativos utilizados por los investigadores computacionales es larga y compleja, pero un
breve resumen y explicación de varios términos y acrónimos de uso común como BSD, “open source” y GNU pueden ser de
interés. (Los lectores impacientes pueden en este punto saltar adelante, aunque algunos conceptos en esta subsección pueden
ayudar a comprender la relación entre el hardware y el software de la computadora).
La investigación fundamental sobre cómo los componentes físicos que componen la maquinaria informática deben interactuar con
los usuarios a través del software se realizó ya en las décadas de 1950 y 1960. En estas décadas, las computadoras eran raras,
máquinas del tamaño de una habitación y eran compartidas por un gran número de personas. A mediados de la década de 1960,
investigadores de Bell Labs (entonces propiedad de AT&T), el Instituto de Tecnología de Massachusetts y General Electric
desarrollaron un novedoso sistema operativo conocido como Multics, abreviatura de Multiplexed Information and Computing
Service. Multics introdujo una serie de conceptos importantes, incluidos los avances en cómo se organizan los archivos y cómo se
asignan los recursos a múltiples usuarios.
A principios de la década de 1970, varios ingenieros de Bell Labs no estaban contentos con el tamaño y la complejidad de Multics,
y decidieron reproducir la mayor parte de la funcionalidad en una versión reducida que llamaron Unics, esta vez abreviatura de
Uniplexed Information and Computing Service, un juego con el nombre de Multics pero sin denotar un gran diferencia en la
estructura. A medida que avanzaba el trabajo, el sistema operativo pasó a llamarse Unix. Otros desarrollos permitieron que el
software se tradujera (o portara) fácilmente para su uso en hardware de computadora de diferentes tipos. Estas primeras versiones
de Multics y Unix también fueron pioneras en el intercambio automático y simultáneo de recursos de hardware (como el tiempo de
CPU) entre usuarios, así como archivos protegidos que pertenecen a un usuario de otros, características importantes cuando
muchos investigadores deben compartir una sola máquina. (Estas mismas características nos permiten realizar múltiples tareas en
computadoras de escritorio modernas).
Durante este tiempo, AT&T y su subsidiaria Bell Labs tuvieron prohibido por la legislación antimonopolio comercializar cualquier
proyecto que no estuviera directamente relacionado con la telefonía. Como tal, los investigadores licenciaron, de forma gratuita,
copias del software Unix a cualquier parte interesada. La combinación de una tecnología robusta, fácil portabilidad y costo gratuito
aseguraron que hubiera un gran número de usuarios interesados, particularmente en la academia. En poco tiempo, se escribieron
muchas aplicaciones para operar sobre el framework Unix (muchas de las cuales usaremos en este libro), lo que representa un
poderoso entorno informático incluso antes de la década de 1980.
A principios de la década de 1980, se resolvió la demanda antimonopolio contra AT&T, y AT&T era libre de comercializar Unix, lo
que hicieron con lo que solo podemos suponer que era entusiasmo. Como era de esperar, los nuevos términos y costos no fueron
favorables para la base de usuarios de Unix, en gran parte académica y centrada en la investigación, causando gran preocupación
para muchos tan fuertemente invertidos en la tecnología.
Afortunadamente, un grupo de investigadores de la Universidad de California (UC), Berkeley, llevaba algún tiempo trabajando en
su propia investigación con Unix, rediseñándola lentamente de adentro hacia afuera. Al final de la demanda antimonopolio de
AT&T, habían producido un proyecto que parecía y funcionaba como Unix de AT&T: BSD (para Berkeley Systems Distribution)

1.1.2 https://espanol.libretexts.org/@go/page/55186
Unix. BSD Unix fue lanzado bajo una nueva licencia de software conocida como la licencia BSD: cualquier persona era libre de
copiar el software de forma gratuita, usarlo, modificarlo y redistribuirlo, siempre y cuando cualquier cosa redistribuida también se
liberara bajo la misma licencia BSD y se le diera crédito a UC Berkeley (esta última cláusula fue posterior caído). Las versiones
modernas de BSD Unix, aunque no se usan mucho en la academia, se consideran sistemas operativos robustos y seguros, aunque en
consecuencia a menudo carecen de características de vanguardia o experimentales.
En el mismo año en que AT&T buscó comercializar Unix, el informático Richard Stallmann respondió fundando la organización
sin fines de lucro Free Software Foundation (FSF), que se dedicó a la idea de que el software debía ser libre de propiedad, y que los
usuarios debían ser libres de usarlo, copiarlo, modificarlo y redistribuirlo. También inició el proyecto de sistema operativo GNU,
con el objetivo de recrear el entorno Unix bajo una licencia similar a la de BSD Unix. (GNU significa No Unix de GNU: un
acrónimo recursivo y autorreferenciado que ejemplifica el peculiar humor de los informáticos).
El proyecto GNU implementó un esquema de licencias que difería algo de la licencia BSD. El software GNU debía ser licenciado
bajo términos creados específicamente para el proyecto, llamados GPL, o GNU Public License. La GPL permite a cualquier
persona usar el software de la manera que considere conveniente (incluyendo distribuir de forma gratuita o vender cualquier
programa construido con él), siempre que también ponga a disposición el código legible por humanos que haya creado y lo licencie
bajo la GPL también (la esencia del “código abierto” [1]). Es como si la Ford Motor Company regalara los planos de un auto nuevo,
con el requisito de que cualquier auto diseñado con esos planos también venga con planos propios y reglas similares. Por esta
razón, el software escrito bajo la GPL tiene una tendencia natural a propagarse y crecer. Irónica e ingeniosamente, Richard
Stallmann y el grupo BSD utilizaron el sistema de licencias, generalmente destinado a proteger la difusión de la propiedad
intelectual y provocando la crisis de Unix de la década de 1980, para garantizar la libertad perpetua de su trabajo (y con ello, el
legado de Unix).
Si bien Stallmann y la FSF lograron recrear la mayor parte del software que componía el entorno estándar Unix (el software
incluido), no volvieron a crear inmediatamente el núcleo del sistema operativo (también llamado kernel). En 1991, el estudiante de
informática Linus Torvalds comenzó a trabajar en este componente básico con licencia de la GPL, al que llamó Linux (pronunciado
“lin-ucks”, según lo prescrito por el propio autor). Muchos otros desarrolladores contribuyeron rápidamente al proyecto, y ahora
Linux está disponible en una variedad de “distribuciones”, como Ubuntu Linux y Red Hat Linux, incluyendo tanto el kernel de
Linux como una colección de software GPL compatible con Unix (y ocasionalmente no GPL). Las distribuciones de Linux difieren
principalmente en qué paquetes de software vienen incluidos con el kernel y cómo se instalan y administran estos paquetes.
Hoy en día, un número significativo de proyectos de software se emiten bajo las licencias GPL, BSD o similares “abiertas”. Estos
incluyen tanto los proyectos Python como R, así como la mayoría de las otras piezas de software cubiertas en este libro. De hecho,
la idea también se ha popularizado para proyectos sin código, con muchos documentos (incluido este) publicados bajo licencias
abiertas como Creative Commons, que permiten a otros utilizar materiales de forma gratuita, siempre que se sigan ciertas
disposiciones.

1. El software moderno se escribe inicialmente usando “código fuente” legible por humanos, luego se compila en software legible
por máquina. Dado el código fuente, es fácil producir software, pero lo contrario no es necesariamente cierto. Las distinciones
entre las licencias BSD y GPL son, por lo tanto, significativas.

This page titled 1.1: Contexto is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil (OSU
Press) .

1.1.3 https://espanol.libretexts.org/@go/page/55186
1.2: Inicio de sesión
Este libro asume que tiene acceso a una cuenta en un sistema operativo similar a Unix (como Linux) que puede usar directamente o
iniciar sesión de forma remota a través del protocolo de inicio de sesión SSH (Secure-shell). Las cuentas de este tipo están
frecuentemente disponibles en universidades e instituciones de investigación, aunque también puedes considerar usar o instalar
Linux en tu propio hardware. Adicionalmente, CyVerse Collaborative proporciona acceso gratuito de línea de comandos a los
investigadores biológicos a través de su sistema Atmosphere; consulte el final de este capítulo para obtener información sobre
cómo acceder a este sistema usando su navegador web.
Antes de explicar algo en detalle, vamos a cubrir el proceso real de iniciar sesión en una computadora remota a través de SSH. Para
ello, necesitarás cuatro cosas:
1. Software de cliente en su propia computadora.
2. La dirección de la computadora remota, llamada su “nombre de host” o, alternativamente, su dirección IP (protocolo de
Internet).
3. Un nombre de usuario en el equipo remoto con el que iniciar sesión.
4. Una contraseña correspondiente en la máquina remota con la que iniciar sesión.
Si está utilizando una computadora con Mac OS X, el software cliente estará orientado a la línea de comandos y será accesible
desde la utilidad Terminal. El Terminal se encuentra en la carpeta Utilidades, dentro de la carpeta Aplicaciones. [1]

En la ventana que se abre, verá un prompt para ingresar comandos. En mi computadora se ve así:

En esta solicitud, ingrese lo siguiente: ssh @ <username><hostname or ip>. Tenga en cuenta que en realidad no escribe los
corchetes angulares; los corchetes angulares son solo algunas nomenclaturas de uso común para indicar un campo que necesita
especificar. Para iniciar sesión en mi cuenta con el nombre de usuario oneils en la computadora principal Linux de la
Universidad Estatal de Oregon ( shell.onid.oregonstate.edu ), por ejemplo, escribía:

Para iniciar sesión en una instancia de CyVerse con dirección IP 128.196.64.193 (y nombre de usuario oneils ), sin
embargo, usaría:

Después de presionar Enter para ejecutar el comando, es posible que se le pida “verificar la huella digital clave” del equipo remoto.
Cada computadora que ejecuta el sistema de inicio de sesión SSH utiliza un par único de “claves” con fines criptográficos: la clave
pública es accesible al mundo, y solo la computadora remota conoce la clave privada. Los mensajes cifrados con la clave pública

1.2.1 https://espanol.libretexts.org/@go/page/55207
solo se pueden descifrar con la clave privada; si le importaba verificar con el propietario del equipo remoto que la “huella digital”
de la clave pública era correcta, entonces podría estar seguro de que nadie entre usted y el equipo remoto correcto podría ver su
sesión de inicio de sesión. A menos que tenga una razón para sospechar algún tipo de espionaje, generalmente es seguro ingresar
sí en este aviso. Normalmente, se le pedirá una vez por cada huella digital, a menos que su computadora local olvide la huella
dactilar o el administrador del sistema la cambie (¡o de hecho hay un intento de espionaje!).
En cualquier caso, a continuación se le pedirá que ingrese su contraseña. Tenga en cuenta que a medida que escriba la contraseña,
no verá ningún carácter que se muestre en la pantalla. Esta es otra característica de seguridad, para que ningún transeúnte pueda
ver el contenido o incluso la longitud de tu contraseña. Sin embargo, hace que la entrada de contraseña sea más difícil, así que
tenga cuidado al ingresarla. Después de iniciar sesión en una computadora remota, el símbolo del sistema generalmente cambiará
para reflejar el inicio de sesión:

Si está ejecutando Microsoft Windows, deberá descargar el software cliente SSH de la web, instalarlo y ejecutarlo. Una opción es
PuTTy.exe, que está disponible en http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html.
Una vez descargado, deberá instalarse y ejecutarse. En el campo Nombre de host (o dirección IP) , ingrese el
nombre de host o la dirección IP, como se discutió anteriormente. Deje el número de puerto al valor predeterminado de 22 y
haga clic en Abrir .

1.2.2 https://espanol.libretexts.org/@go/page/55207
Si ha ingresado correctamente el nombre de host o IP, se le pedirá su nombre de usuario y contraseña (y potencialmente para
verificar la huella digital de la clave).
Por último, si ya estás ejecutando un sistema operativo Linux u otro tipo UNIX, los pasos para iniciar sesión remotamente a través
de SSH serán similares a los de Mac OS X, aunque necesitarás encontrar la utilidad Terminal por tu cuenta. Si está utilizando
CyVerse Atmosphere, entonces puede utilizar una ventana de terminal directamente en el navegador web haciendo clic en la
pestaña Access By Shell o en el enlace Open Web Shell. En todos los casos, la interfaz basada en texto será idéntica, porque el
equipo remoto, en lugar de su escritorio local, determina la pantalla.

Iniciar sesión más allá, cambiar su contraseña


Dependiendo de dónde esté iniciando sesión, es posible que no haya terminado con el proceso de inicio de sesión. En muchas
universidades y centros de investigación, el administrador preferiría que no hagas ningún trabajo en la computadora en la que
iniciaste sesión inicialmente, porque esa computadora puede estar reservada solo para inicios de sesión iniciales por decenas o
incluso cientos de investigadores. Como resultado, es posible que deba “verificar” una computadora secundaria interna para sus
necesidades computacionales reales. En ocasiones esto se puede hacer mediante SShing en la línea de comandos a la computadora
secundaria, pero en algunas instituciones, se le pedirá que ejecute otros comandos para este proceso de checkout. Consulte con el
administrador de su sistema local.

1.2.3 https://espanol.libretexts.org/@go/page/55207
Además, es posible que desee cambiar su contraseña después de su inicio de sesión inicial. Ejecutar el comando passwd
generalmente es suficiente, y se le pedirá que ingrese sus contraseñas antiguas y nuevas. Como es habitual, por razones de
seguridad, no aparecerán caracteres al ingresar. Además, los buenos administradores del sistema nunca piden tu contraseña, y no
pueden recuperarla si se pierde. Lo mejor que pueden hacer los administradores es restablecerlo a uno temporal.

SSH: Shell seguro


Uno podría preguntarse con razón: ¿qué acabamos de lograr con todo este inicio de sesión? En nuestra computadora de escritorio,
usamos un programa llamado cliente para conectarnos a otro programa, llamado servidor. Un servidor es un programa que espera
en segundo plano a que otro programa (un cliente) se conecte a él. [2] Esta conexión suele ocurrir a través de una red, pero también
pueden ocurrir conexiones entre programas en la misma computadora. Un cliente es un programa que generalmente es ejecutado
por un usuario según sea necesario y se conecta a un servidor. Si bien es más correcto definir un servidor como un programa que
espera una conexión de un cliente, coloquialmente, las computadoras que ejecutan principalmente programas de servidor también
se conocen como servidores.

El servidor SSH y el cliente SSH se comunican usando lo que se conoce como el “protocolo” SSH, simplemente un formato
acordado para la transferencia de datos. El protocolo SSH es bastante ligero: debido a que todo el cálculo real ocurre en la máquina
remota, la única información que necesita ser transferida al servidor son las pulsaciones de teclas escritas por el usuario, y la única
información que necesita ser transferida al cliente son los caracteres a mostrar al usuario. Como su nombre lo indica, el protocolo
SSH es muy seguro debido a su dependencia de la criptografía avanzada de clave pública. [3]
El servidor SSH puede no ser el único programa de servidor que se ejecuta en el equipo remoto. Por ejemplo, los servidores web
permiten que las computadoras remotas sirvan páginas web a clientes (como Mozilla Firefox y Safari de OS X) usando HTTP
(protocolo de transferencia de hipertexto). Pero debido a que solo hay un nombre de host o dirección IP asociada a la computadora
remota, se requiere un bit extra (byte, en realidad) de información, conocido como el “número de puerto”. A modo de analogía, si
la computadora remota fuera un edificio de departamentos, los números de puerto serían números de departamento. Por
convención, SSH se conecta en el puerto 22 y HTTP conecta en el puerto 80, aunque estos puertos pueden ser cambiados por
administradores que deseen ejecutar sus servicios de formas no estándar. Esto explica por qué se especifica el puerto 22 cuando se
conecta a través de Putty (y es el predeterminado cuando se usa ssh de línea de comandos, pero se puede ajustar con un
parámetro).

1.2.4 https://espanol.libretexts.org/@go/page/55207
Otros protocolos de interés incluyen FTP (protocolo de transferencia de archivos) y su versión segura, SFTP (protocolo de
transferencia segura de archivos), diseñado específicamente para transferir archivos.

Acceso a la línea de comandos con CyVerse Atmosphere


Idealmente, los lectores de este libro tendrán acceso a un sistema operativo basado en UNIX (por ejemplo, Linux) con acceso a la
línea de comandos. Esto suele ser el caso de individuos en universidades y otras instituciones de investigación o educativas.
Muchos usuarios tienen acceso a dichos sistemas pero ni siquiera se dan cuenta, ya que el servicio no suele ser ampliamente
publicitado.
Para quienes no tienen acceso institucional, existen algunas alternativas. Primero, las máquinas Mac OS X están basadas en Unix y
vienen con una interfaz de línea de comandos (a través de la aplicación Terminal), aunque las herramientas de línea de comandos
difieren ligeramente de las herramientas basadas en GNU que se encuentran en la mayoría de las distribuciones de Linux. Una
búsqueda en la web de “OS-X GNU core utils” arrojará algunas instrucciones para remediar esta discrepancia. En segundo lugar, es
posible instalar una distribución Linux (como Ubuntu Linux) en la mayoría de los escritorios y laptops configurando la
computadora para “arranque dual” Linux y el sistema operativo principal. Alternativamente, las distribuciones Linux se pueden
instalar dentro del software de “máquina virtual”, como VirtualBox (http://virtualbox.org).
Una adición bastante emocionante y relativamente reciente a estas opciones es el sistema Atmosphere, dirigido por la colaborativa
CyVerse (anteriormente iPlant), un proyecto de ciberinfraestructura fundado en 2008 para apoyar las necesidades computacionales
de los investigadores en las ciencias de la vida. CyVerse en su conjunto alberga una variedad de proyectos, desde recursos
educativos hasta análisis bioinformáticos guiados. El sistema Atmosphere es el más relevante para nosotros aquí, ya que
proporciona acceso basado en la nube a sistemas que ejecutan Linux. Para comenzar a usar uno de estos sistemas, navegue a
http://cyverse.org/atmosphere y haga clic en el enlace para “Crear una cuenta” o “Lanzar atmósfera”. Si necesita crear una nueva
cuenta, hágalo: Cyverse solicita una variedad de información para la creación de cuentas para ayudar a medir el interés de los
usuarios y obtener apoyo financiero. También puede necesitar solicitar acceso especial al sistema Atmosphere a través de su cuenta
en la página principal de Atmosphere, ya que Atmosphere consume más recursos que otras herramientas proporcionadas por
CyVerse.

Después de hacer clic en el enlace “Lanzar”, tendrá la oportunidad de ingresar su nombre de usuario y contraseña (si se le ha
otorgado uno).

1.2.5 https://espanol.libretexts.org/@go/page/55207
El sistema Atmosphere funciona dividiendo clústeres de computadoras físicas (ubicadas en uno de los diversos proveedores del
país) en máquinas virtuales accesibles para el usuario de varios tamaños. Al realizar un cálculo que requiera muchos núcleos de
CPU, por ejemplo, uno podría desear acceder a una nueva “instancia” con 16 CPU, 64 gigabytes (GB) de RAM y 800 GB de
espacio en disco duro. Por otro lado, para fines de aprendizaje, es probable que solo necesites una pequeña instancia con 1 CPU y 4
GB de RAM. Esta es una consideración importante, ya que CyVerse limita a los usuarios a una cierta cuota de recursos. Los
usuarios están limitados por la cantidad de “unidades de atmósfera” (AU) que pueden usar por mes, definidas aproximadamente
como usar una sola CPU durante una hora. Los usuarios también están limitados en el número total de CPU y la cantidad total de
RAM que pueden usar simultáneamente.
Después de determinar el tamaño de instancia necesario, es necesario determinar qué “imagen” del sistema operativo debe cargarse
en la máquina virtual. Todos los usuarios pueden crear tales imágenes; algunos usuarios crean imágenes con software preinstalado
para analizar datos de secuenciación de ARN, realizar ensamblajes de genoma de novo, etc. Hemos creado una imagen
específicamente para acompañar este libro: es bastante simple e incluye NCBI Blast+ (la versión más moderna de BLAST
producida por el Centro Nacional de Información Biotecnológica), R, Python, git y algunas otras herramientas. Se llama
“Imagen APCB”.
Para activar una nueva instancia con esta imagen, haga clic en el botón “Nuevo -> Instancia” en la interfaz Atmosphere. Es posible
que primero necesites crear un “Proyecto” para que la instancia viva. Puedes buscar “Imagen APCB” en el cuadro de búsqueda de
tipos de instancias. Aquí está la vista de mi proyecto APCB después de crear e iniciar la instancia:

Después de crear la instancia, puede estar en uno de varios estados; generalmente estará “en ejecución” (es decir, disponible para
iniciar sesión y consumir recursos) o “suspendida” (efectivamente pausada y no consumiendo recursos). La interfaz para una
instancia dada tiene botones para suspender o reanudar una instancia suspendida, así como botones para “Detener” (cerrar una
instancia), “Reiniciar” (reiniciar la instancia) y “Eliminar” (eliminar la instancia y todos los datos almacenados en ella).

1.2.6 https://espanol.libretexts.org/@go/page/55207
Una vez que una instancia está en funcionamiento, hay varias formas de acceder a ella. En primer lugar, es accesible vía SSH en la
dirección IP proporcionada. Tenga en cuenta que es probable que esta dirección IP cambie cada vez que se reanude la instancia.
Arriba, la dirección IP se muestra como 128.196.64.36 , por lo que podríamos acceder a ella desde la aplicación OS X
Terminal:
[soneil @mbp ~] $ ssh oneils @128 .196.64.36
La interfaz web Atmosphere también proporciona un botón “Open Web Shell”, que proporciona acceso a la línea de comandos
directamente en su navegador. Cuando termines de trabajar con tu instancia de Atmosphere, es importante suspender la instancia,
de lo contrario estarás desperdiciando recursos computacionales que otros podrían estar usando (así como tu propia cuota).

Cerrar sesión
Una vez terminado de trabajar con el equipo remoto, debemos cerrar la sesión de SSH. El cierre de sesión se realiza ejecutando el
comando exit en la línea de comandos hasta que se devuelve al escritorio local o el programa cliente SSH cierra la conexión.
Alternativamente, basta con cerrar el programa cliente SSH o Windows; SSH cerrará la conexión y no se hará ningún daño. Tenga
en cuenta, sin embargo, que cualquier programa actualmente en ejecución será cancelado al cerrar la sesión.
Si estás trabajando en una instancia Atmosphere o una máquina virtual similar alojada remotamente, es una buena idea suspender
también la instancia para que el tiempo dedicado a no funcionar no se cuente contra tu límite de uso.

Ejercicios
1. Practique iniciar sesión y volver a salir de la máquina remota a la que tiene acceso.
2. Cambia tu contraseña a algo seguro pero también fácil de recordar. La mayoría de los sistemas Linux/Unix no limitan la
longitud de una contraseña, y las contraseñas más largas compuestas por colecciones de palabras simples son más seguras que
las cadenas cortas de letras aleatorias. Por ejemplo, una contraseña como correcthorsebatterystaple es mucho
más segura que tr0UB4dor&3 . [4]
3. Un programa llamado telnet nos permite conectarnos a cualquier servidor en cualquier puerto e intentar comunicarnos
con él (lo que requiere que sepamos los mensajes correctos a enviar al servidor para el protocolo). Intente conectarse con

1.2.7 https://espanol.libretexts.org/@go/page/55207
telnet al puerto 80 de google.com usando el botón de radio “Telnet” en Putty si está en Windows, o ejecutando
telnet google.com 80 en el Terminal en OS X. Ejecute el comando GET http://www.google.com/ para
ver los datos sin procesar devueltos por el servidor.

1. Es probable que el estilo de tu sesión de terminal no se vea como lo que mostramos en este libro, pero puedes personalizar el
aspecto de tu terminal usando las preferencias. ¡Pasaremos mucho tiempo en este ambiente!
2. A veces, los programas de servidor se llaman “demonios”, terminología que evoca el infame “demonio” de Maxwell, una
entidad teórica imposible que trabaja en segundo plano para clasificar moléculas gaseosas.
3. La infraestructura de clave pública actualmente en uso por SSH solo es segura en la medida en que cualquier persona en el
ámbito académico sospecha: las matemáticas subyacentes al protocolo de intercambio de claves aún no se han demostrado
irrompibles. La mayoría de los matemáticos, sin embargo, sospechan que son irrompibles. Por otro lado, se sabe que los errores
ocurren en el propio software, aunque generalmente se corrigen puntualmente cuando se encuentran.
4. Estas contraseñas de ejemplo fueron extraídas de un webcomic sobre el tema, ubicado en http://xkcd.com/936/.

This page titled 1.2: Inicio de sesión is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil
(OSU Press) .

1.2.8 https://espanol.libretexts.org/@go/page/55207
1.3: La línea de comandos y el sistema de archivos
Los usuarios de computadoras están acostumbrados a interactuar con una “interfaz de usuario”. En muchas computadoras, esta
interfaz muestra el escritorio o la barra de tareas, iconos, vistas previas de archivos, etc. Toma la entrada del usuario en forma de
pulsaciones de teclas, movimientos del mouse y en algunos casos comandos de voz, y presenta los resultados de las acciones del
usuario. Quizás lo más importante es que la interfaz de usuario es en sí misma un programa (es un software que se ejecuta en una
computadora, después de todo) con el que interactuamos para ejecutar otros programas.
Lo mismo sucede cuando usamos SSH para iniciar sesión en una máquina remota, o abrir la aplicación Terminal en un escritorio
Linux o OS X. En este caso, sin embargo, en lugar de interactuar con una GUI (Graphical User Interface), interactuamos con una
CLI (Command-Line Interface), o shell, que hace el trabajo de mostrar el símbolo del sistema. El shell es el software con el que
interactuamos en la línea de comandos. En cierto sentido es la línea de comandos, ya que muestra el símbolo del sistema, acepta
entrada a través de texto mecanografiado, ejecuta otros programas en nuestro nombre y muestra los resultados textualmente. Un
símbolo del sistema es una línea de información de estado proporcionada en una interfaz basada en texto, que indica que los
comandos deben ingresarse y ejecutarse presionando Enter. Las solicitudes de comando a menudo incluyen información sobre qué
computadora o red está conectado, el nombre de usuario con el que se ha iniciado sesión y una indicación del “directorio de trabajo
actual” (discutido a continuación).
El primer comando que aprenderemos para la línea de comandos de Linux es echo , que imprime los parámetros que le damos.

oneils@atmosphere ~$ echo hello there


hello there

Desglosemos el símbolo del sistema y el programa que ejecutamos, que consistía en un nombre de programa y varios parámetros,
separados por espacios. En la siguiente figura, el símbolo del sistema consta de oneils @atmosphere ~$ .

El programa de eco puede parecer absurdamente simple, ya que simplemente imprime sus parámetros. Pero es bastante útil en
la práctica y como herramienta de aprendizaje. Por ejemplo, podemos usar echo para imprimir no solo cadenas simples, sino
también el contenido de una variable de entorno, que es un bit variable de información (generalmente conteniendo cadenas de
texto) que es accesible por el shell y otros programas que ejecuta el usuario. Acceder al contenido de una variable de entorno
requiere prefijarla con un $ .
El shell (y otros programas) comúnmente usa variables de entorno para almacenar información sobre su sesión de inicio de sesión,
al igual que cómo, en una interfaz GUI, una “variable” recuerda la imagen del fondo de pantalla para el escritorio. Las variables de
entorno controlan muchos aspectos del entorno de línea de comandos, por lo que son bastante importantes. Muchos de estos se
configuran automáticamente cuando iniciamos sesión. Por ejemplo, $USER .

oneils@atmosphere ~$ echo $USER


oneils

Configuración de variables de entorno, manejo de espacios


Establecer variables de entorno es algo que tendremos que saber hacer eventualmente, pero aprender ahora nos dará la oportunidad
de discutir algunos de los puntos más finos de cómo el shell interpreta nuestros comandos. En bash , el shell más utilizado,
establecer variables de entorno se realiza con el comando export , tomando como primer parámetro cuál debe ser el nombre de
la variable (sin el $ ) y en qué debe establecerse.
I.3_4_Unix_6_Export_Greeting_1

1.3.1 https://espanol.libretexts.org/@go/page/55200
Debido a que la exportación espera como su primer parámetro la descripción de la variable, obtendremos un resultado
impar si incluimos un espacio en nuestro saludo, ya que el shell usa espacios para separar parámetros.
I.3_5_Unix_7_Export_Greeting_2

En lo anterior, greeting=Hello fue tomado como primer parámetro, y el segundo, todos , fue ignorado por el comando
export . Hay al menos dos formas de sortear este problema. El primero es prefijar cualquier carácter que el shell considere
especial (como espacios) con una diagonal invertida, o \ , con lo que “escapa” de él para que sea ignorado por el shell como un
carácter especial. Alternativamente, podemos envolver una cadena entre comillas para que sea tratada literalmente por el
caparazón.

oneils@atmosphere ~$ export GREETING=hello\ everyone


oneils@atmosphere ~$ echo $GREETING
hello everyone

oneils@atmosphere ~$ export GREETING='hello everyone'


oneils@atmosphere ~$ echo $GREETING
hello everyone

La principal diferencia entre usar comillas simples y dobles es si las variables dentro de la cadena se expanden a su contenido o no.
I.3_7_Unix_7_Export_Greeting_4

Tenga en cuenta que al establecer una variable de entorno, no usamos el $ . Por convención, los nombres de variables de entorno
contienen sólo letras mayúsculas. [1] Además, esta expansión (de variables de entorno a sus contenidos) la realiza el shell; el
comando en sí se cambia de export greeting="Hello $USER” a export greeting="Hello oneils” .

Conchas Alternativas
Existe una variable de entorno especial, $0 , que generalmente contiene el nombre del programa actualmente en ejecución. En el
caso de nuestra interacción con la línea de comandos, este sería el nombre del programa de interfaz en sí, o shell.
I.3_8_Unix_10_echo_cero_bash

El comando anterior ilustra que estamos ejecutando bash , el shell más utilizado. [2]
Dependiendo del sistema en el que haya iniciado sesión, ejecutar echo $0 puede no reportar bash . La razón es (aunque
puede parecer extraño) que hay una variedad de shells disponibles, debido a la larga historia de Unix y Linux. Al principio, las
interfaces eran bastante simples, pero con el tiempo se desarrollaron mejores interfazes/shells que incluían nuevas características
(considere cómo ha cambiado el menú “Inicio” a lo largo de los años en las versiones de Microsoft Windows). Podemos ejecutar un
shell diferente, si está instalado, simplemente ejecutándolo como cualquier otro programa. El shell tcsh , en sí mismo una
consecuencia del shell csh , es a veces el valor predeterminado en lugar de bash . (Tanto csh como tcsh son mayores
que bash .)
I.3_9_Unix_12_TCSH

Al ejecutar tcsh , el comando setenv toma el lugar de la exportación , y la sintaxis es ligeramente diferente.
I.3_10_Unix_13_TCSH_Setenv

Aunque bash y shells similares como dash y zsh se encuentran más comúnmente (y se recomiendan), es posible que
necesite usar un shell como csh o su sucesor, tcsh . En este libro, la suposición es que estás usando bash , pero cuando
se necesitarían diferentes comandos para lograr lo mismo en el tcsh o csh más antiguo, una nota al pie de página te
explicará.
Para volver a bash desde tcsh , bastará con una simple salida .
I.3_11_Unix_14_Back_to_bash

1.3.2 https://espanol.libretexts.org/@go/page/55200
En general, puede ser difícil determinar qué shell se está ejecutando sobre la base del aspecto del símbolo del sistema; usar
echo $0 justo en la línea de comandos es la forma más confiable.

Archivos, directorios y rutas


Con algunos de los conceptos más difíciles del shell fuera del camino, volvamos a algo un poco más práctico: entender cómo se
organizan los directorios (también conocidos como carpetas) y los archivos.
La mayoría de los sistemas de archivos son jerárquicos, con archivos y directorios almacenados dentro de otros directorios. En los
sistemas operativos tipo Unix, el directorio de “nivel superior” en el que todo se puede encontrar se conoce como / (una diagonal
hacia adelante). Este directorio de nivel superior a veces se llama la raíz del sistema de archivos, como en la raíz del árbol del
sistema de archivos. Dentro del directorio raíz, comúnmente hay directorios con nombres como bin , etc , media y
home ; el último de estos suele ser donde los usuarios almacenarán sus propios datos individuales. [3]
jerarquía del sistema de archivos con la carpeta raíz (/) en la parte superior.

Cada archivo y directorio en el sistema de archivos se puede identificar de manera única por su ruta absoluta, un localizador único
para un archivo o directorio en el sistema de archivos, comenzando por la carpeta raíz / y enumerando cada directorio en el
camino hacia el archivo. En la figura anterior, la ruta absoluta al archivo todo_list.txt es
/home/oneils/documents/todo_list.txt .
Tenga en cuenta que una ruta absoluta debe comenzar con la diagonal delantera, lo que indica que la ruta comienza en la carpeta
raíz / , y contener una ruta válida de nombres de carpeta desde allí. (Si prefiere considerar / como un nombre de carpeta en sí
mismo, también se puede especificar una ruta absoluta como //home/oneils/documents/todo_list.txt , aunque
el uso de dos barras oblicuas se considera redundante).
Cada usuario normalmente tiene un directorio home, que sirve como un casillero de almacenamiento personal para archivos y
directorios. (A menudo, la cantidad de espacio disponible en esta ubicación no es tanto como los usuarios quisieran.) El shell y
otros programas pueden encontrar la ruta absoluta a su directorio home a través de la variable de entorno $HOME ; intente
ejecutar echo $HOME para ver la ruta absoluta a su propio directorio home.
¿Qué pasa con los dispositivos especiales como unidades de CD-ROM y unidades de red? En un sistema operativo Windows, estos
obtendrían su propio directorio “root” con nombres como D: y E: (con el disco duro principal generalmente teniendo el
nombre C: ). En los sistemas operativos basados en Unix, solo existe una jerarquía de sistemas de archivos, y el nivel superior
siempre es / . Dispositivos especiales como unidades de CD-ROM y unidades de red están montados en algún lugar del sistema
de archivos. Puede ser que el directorio /media permanezca vacío, por ejemplo, pero cuando se inserta un CD-ROM, puede
aparecer un nuevo directorio dentro de ese directorio, quizás con la ruta absoluta /media/cdrom0 , y los archivos del CD-
ROM aparecerán en esa ubicación.
Determinar cómo y dónde se montan dichos dispositivos es tarea del administrador del sistema. En las máquinas OS X, los
dispositivos insertados aparecen en /Volúmenes . Si ha iniciado sesión en una gran infraestructura computacional, es probable
que su directorio principal no esté ubicado en el disco duro interno de la computadora remota, sino que esté montado desde una
unidad de red presente en otra computadora remota. Esta configuración permite a los usuarios “ver” la misma jerarquía de sistemas
de archivos y archivos sin importar en qué equipo remoto se conecten, si hay más de uno disponible. (Por ejemplo, incluso
/home podría ser un soporte de red, por lo que todos los directorios de inicio de los usuarios podrían estar disponibles en varias
máquinas).

Moverse por el sistema de archivos


Es de vital importancia entender que, como estamos trabajando en el entorno de línea de comandos, siempre tenemos un “lugar”,
un directorio (o carpeta) en el que estamos trabajando llamado directorio de trabajo actual, o PWD. El shell realiza un seguimiento
del directorio de trabajo actual en una variable de entorno, $PWD .
Cuando inicias sesión por primera vez, tu directorio de trabajo actual se establece en tu directorio home; echo $PWD y
echo $HOME probablemente mostrarán el mismo resultado. También hay un programa dedicado para mostrar el directorio de
trabajo actual, llamado pwd .
I.3_13_Unix_15_Pwd

1.3.3 https://espanol.libretexts.org/@go/page/55200
Podemos enumerar los archivos y directorios que se almacenan en el directorio de trabajo actual usando el comando ls .
I.3_14_Unix_16_LS

Este comando revela que tengo una serie de directorios en mi directorio home ( /home/oneils ) con nombres como
Música e Imágenes (amablemente coloreado azul) y un archivo llamado todo_list.txt .
Podemos cambiar el directorio de trabajo actual, es decir, pasar a otro directorio, usando el comando cd , dándole la ruta a la que
nos gustaría movernos.
imagen

Observe que el símbolo del sistema ha cambiado para ilustrar el directorio de trabajo actual: ahora muestra
oneils @atmosphere /home $ , indicando que estoy en /home . Este es un recordatorio útil de dónde estoy en el
sistema de archivos mientras trabajo. Anteriormente, mostraba solo ~ , que en realidad es un atajo para $HOME , en sí mismo
un atajo para la ruta absoluta a mi directorio home. En consecuencia, hay varias formas de volver a mi directorio de inicio:
cd /home/oneils , o cd $HOME , o cd ~ , o incluso solo cd sin argumentos, que por defecto es pasar a $HOME .
I.3_16_Unix_18_CD_Inicio

Rutas relativas, archivos ocultos y directorios


No siempre es necesario especificar la ruta completa y absoluta para localizar un archivo o directorio; más bien, podemos usar una
ruta relativa. Una ruta relativa localiza un archivo o directorio relativo al directorio de trabajo actual. Si el directorio de trabajo
actual es /home/oneils , por ejemplo, un archivo ubicado en la ruta absoluta
/home/oneils/Pictures/profile.jpg tendría ruta relativa Pictures/profile.jpg . Para el archivo
/home/oneils/todo_list.txt , la ruta relativa es solo todo_list.txt . Si el directorio de trabajo actual fuera
/home , por otro lado, las rutas relativas serían oneils/Pictures/profile.jpg y oneils/todo_list.txt .
Debido a que las rutas relativas siempre son relativas al directorio de trabajo actual (en lugar del directorio raíz / ), no pueden
comenzar con una diagonal hacia adelante, mientras que las rutas absolutas deben comenzar con una diagonal delantera. Esta
distinción confunde ocasionalmente a los nuevos usuarios.
También podemos usar el comando cd con rutas relativas, y esto se hace más comúnmente para los directorios presentes en el
directorio de trabajo actual, según lo informado por ls . De hecho, se permiten rutas tanto relativas como absolutas donde sea
necesario especificar un archivo o directorio.
I.3_17_Unix_19_CD_Pictures

Algunas notas sobre cómo moverse por directorios: (1) Los sistemas operativos tipo UNIX casi siempre distinguen entre
mayúsculas y minúsculas, para comandos, parámetros y nombres de archivos y directorios. (2) Debido a la forma en que se tratan
los espacios en la línea de comandos, es poco común ver espacios en los nombres de archivos o directorios. El comando
cd mydocuments es mucho más fácil de escribir que cd my\ documents o cd 'mis documentos' . (3) Si
olvida especificar cd antes de especificar una ruta, se producirá un error críptico de permiso denegado , en lugar de un
error específico de la falta de comando cd .
Por defecto, al ejecutar el programa ls se listan los contenidos del directorio de trabajo actual, todos los archivos y directorios
que comiencen con a . están ocultos. Para ver todo incluyendo los archivos ocultos, podemos agregar la bandera -a a ls .
I.3_18_Unix_20_LS_A

¡Resulta que aquí hay bastantes archivos ocultos! Muchos de los que comienzan con un . , como .bash_login , son en
realidad archivos de configuración utilizados por varios programas. Pasaremos algún tiempo con los de capítulos posteriores.
En el tema de ls , hay muchos parámetros adicionales que podemos darle a ls : incluyen -l para mostrar información de
archivos “largos” incluyendo tamaños de archivo, y -h para que esos tamaños de archivo sean “legibles por humanos” (por
ejemplo, 4K frente a 4,196 bytes). Para ls , podemos especificar esta combinación de opciones como ls -l -a -h (donde
los parámetros se pueden dar en cualquier orden), o con un solo parámetro como ls -lah , aunque no todas las utilidades
proporcionan esta flexibilidad.
I.3_19_Unix_21_ls_lah

1.3.4 https://espanol.libretexts.org/@go/page/55200
Algunas de estas columnas de información están describiendo los permisos asociados a los diversos archivos y directorios; veremos
el esquema de permisos en detalle en capítulos posteriores.
I.3_20_punto_dotdot

Al usar ls -a , vemos dos directorios “especiales”: . y .. son sus nombres. Estos directorios realmente no existen per se,
sino que son atajos que hacen referencia a ubicaciones especiales relativas al directorio en el que están contenidos. El . directorio
es un atajo para el mismo directorio en el que está contenido. Coloquialmente, significa “aquí”. El .. directorio es un atajo para
el directorio encima del directorio que lo contiene. Coloquialmente, significa “arriba”.
Cada directorio tiene estos enlaces “virtuales”, y pueden ser utilizados como rutas relativas o en rutas relativas o absolutas. Aquí,
por ejemplo, podemos cd. para quedarnos donde estamos, o cd.. para subir un directorio:
I.3_21_Unix_22_cd_dot_dotdot

Si nos gusta, podemos subir dos directorios con cd../.. :


I.3_22_Unix_23_CD_dotdot_uptwo

Incluso podemos usar . y .. en rutas relativas o absolutas más largas (el número 2 en la siguiente figura ilustra la ruta
relativamente impar de /media/. /cdrom0 , que es idéntico a /media/cdrom0 ).
I.3_23_Filesystem_dots

Ejercicios
1. Practica moverte sobre el sistema de archivos con cd y enumerar los contenidos del directorio con ls , incluyendo navegar
por ruta relativa, ruta absoluta, . y .. . Use echo $PWD y pwd con frecuencia para enumerar su directorio de trabajo
actual. Practica navegar por casa con cd ~ , cd $HOME , y solo cd sin argumentos. Puede resultarle útil dibujar un
“mapa” del sistema de archivos, o al menos las porciones relevantes para usted, en una hoja de papel.
2. Sin escribir explícitamente tu nombre de usuario, crea otra variable de entorno llamada $CHECKCASH , de tal manera que
cuando se ejecuta echo $CHECKCASH , se imprime una cadena como oneils tiene $20 (excepto que
oneils sería tu nombre de usuario y debería establecerse leyendo desde $USUARIO ).
3. El directorio /etc es donde se almacenan muchos archivos de configuración. Intente navegar allí (usando cd ) usando
una ruta absoluta. A continuación, vuelve a casa con cd $HOME e intenta navegar allí usando una ruta relativa.
4. ¿Qué pasa si estás en el directorio de nivel superior / y ejecutas cd.. ?
5. Los directorios de inicio de los usuarios normalmente se encuentran en /home . ¿Este es el caso para ti? ¿Hay algún otro
directorio home perteneciente a otros usuarios que puedas encontrar, y si es así, puedes cd a ellos y ejecutar ls ?

1. Existe otro tipo de variable conocida como “variable shell”, que opera de manera similar a las variables de entorno. Hay
algunas diferencias: (1) por convención, estos tienen nombres en minúsculas; (2) se establecen de manera diferente, en bash
usando declare en lugar de exportar ; y (3) están disponibles solo para el shell, no otros programas que podrías
ejecutar. La distinción entre estos dos tipos puede ocasionar dolores de cabeza en ocasiones, pero no es lo suficientemente
crucial como para preocuparse ahora.
2. Debido a que $0 tiene el nombre del programa actualmente en ejecución, uno podría esperar que el eco $0 resulte en
que se reporta eco , pero este no es el caso. Como se mencionó anteriormente, el shell reemplaza las variables de entorno con
su contenido antes de que se ejecute el comando.
3. Los informáticos y matemáticos suelen dibujar árboles boca abajo. Una teoría es que los árboles son más fáciles de dibujar al
revés cuando se trabaja en una pizarra, el medio de exposición tradicional (y aún considerado por muchos el mejor) para esos
campos. Nuestro lenguaje refleja este pensamiento: cuando movemos un archivo, lo movemos “hacia abajo” a un subdirectorio,
o “arriba” a un directorio “por encima” del actual.

This page titled 1.3: La línea de comandos y el sistema de archivos is shared under a CC BY-NC-SA license and was authored, remixed, and/or
curated by Shawn T. O’Neil (OSU Press) .

1.3.5 https://espanol.libretexts.org/@go/page/55200
1.4: Trabajar con archivos y directorios
Ahora que sabemos cómo ubicar archivos y directorios en el sistema de archivos, aprendamos un puñado de herramientas
importantes para trabajar con ellos y el sistema en general.

Visualización del contenido de un archivo (de texto)


Si bien existen muchas herramientas para ver y editar archivos de texto, una de las más eficientes para visualizarlos se llama
less , que toma como parámetro una ruta al archivo a ver, que por supuesto puede ser solo un nombre de archivo en el
directorio de trabajo actual (que es un tipo de ruta relativa). [1]
I.4_1_UNIX_24_LES_A

La invocación de menos en el archivo p450s.fasta abre una “ventana interactiva” dentro de la ventana del terminal, en
donde podemos desplazarnos hacia arriba y hacia abajo (e izquierda y derecha) en el archivo con las teclas de flecha. (Como de
costumbre, el mouse no es muy útil en la línea de comandos.) También podemos buscar un patrón escribiendo / y luego
escribiendo el patrón antes de presionar Enter.
imagen

Cuando termine con menos , al presionar q saldrá y devolverá el control al shell o línea de comandos. Muchos de los
formatos de texto utilizados en biología computacional incluyen líneas largas; por defecto, menos envolverá estas líneas
alrededor del terminal para que puedan verse en su totalidad. El uso de menos -S se apagará este ajuste de línea, lo que nos
permite ver el archivo sin ningún reformateo. Así es como se ve el archivo anterior cuando se ve con
menos -S p450s.fasta :
imagen

Observe que la primera línea larga no ha sido envuelta, aunque todavía podemos usar las teclas de flecha para desplazarse hacia la
izquierda o hacia la derecha para ver el resto de esta línea.

Creación de nuevos directorios


El comando mkdir crea un nuevo directorio (a menos que ya exista un archivo o directorio del mismo nombre), y toma como
parámetro la ruta al directorio a crear. Este suele ser un nombre de archivo simple como ruta relativa dentro del directorio de
trabajo actual.
I.4_4_Unix_26_MKDIR

Mover o cambiar el nombre de un archivo o directorio


La utilidad mv sirve tanto para mover como renombrar archivos y directorios. El uso más simple funciona como mv
<source_path><destination_path>, donde <source_path>está la ruta (absoluta o relativa) del archivo/directorio a renombrar, y
<destination_path>es el nuevo nombre o ubicación para darle.
En este ejemplo, cambiaremos el nombre de p450s.fasta a p450s.fa , lo moveremos a la carpeta projects y
luego renombraremos la carpeta projects a projects_dir .
I.4_5_Unix_27_MV

Debido a que mv cumple un doble papel, es importante recordar la semántica:


Si <destination_path>no existe, se crea (siempre y cuando existan todas las carpetas contenedoras).
Si <destination_path>existe:
Si <destination_path>es un directorio, la fuente se mueve dentro de esa ubicación.
Si <destination_path>es un archivo, ese archivo se sobrescribe con la fuente.
Dicho de otra manera, mv intenta adivinar qué debe hacer, sobre la base de si el destino ya existe. Deshagamos rápidamente los
movimientos anteriores:
I.4_6_Unix_28_MV_Volver

1.4.1 https://espanol.libretexts.org/@go/page/55181
Algunas otras notas: Primero, especificar una ruta que es un directorio, el final / es opcional:
al
mv projects_dir/ projects es lo mismo que mv projects_dir projects si projects_dir es un
directorio (de manera similar, los proyectos podrían haberse especificado como proyectos/ ). Segundo, es posible
mover múltiples archivos al mismo directorio, por ejemplo, con proyectos mv p450s.fasta todo_list.txt .
Tercero, es bastante común ver . refiriéndose al presente directorio de trabajo como destino, como en mv.. /file.txt.
por ejemplo, que movería file.txt del directorio anterior al directorio de trabajo actual ( .. ) en el directorio de trabajo
actual ( . , o “aquí”).

Copiar un Archivo o Directorio


Copiar archivos y directorios es similar a moverlos, excepto que el original no se elimina como parte de la operación. El comando
para copiar es cp , y la sintaxis es cp <source_path><destination_path>. Hay una advertencia, sin embargo: cp no copiará
un directorio completo y todo su contenido a menos que agregue la bandera -r al comando para indicar que la operación debe
ser recursiva.
imagen

Olvidar el -r al intentar copiar un directorio da como resultado una advertencia de omisión del directorio .
Es posible copiar y mover simultáneamente (y eliminar, etc.) muchos archivos especificando múltiples fuentes. Por ejemplo, en
lugar de cp.. /todo_list.txt. , podríamos haber copiado tanto la lista de tareas pendientes como el archivo
p450s.fasta con el mismo comando:
imagen

Eliminar (Eliminar) un Archivo o Directorio


Los archivos se pueden eliminar con el comando rm , como en rm <target_file>. Si deseas eliminar un directorio completo y
todo lo que hay dentro, necesitas especificar el indicador -r para recursivo, como en rm -r <target_dir>. Dependiendo de la
configuración de su sistema, es posible que se le pregunte “¿está seguro?” para cada expediente, al que se puede responder con una
y . Para evitar esta comprobación, también puede especificar el indicador -f (force), como en rm -r -f <target_dir>o
rm -rf <target_dir>. Vamos a crear un directorio temporal junto con las copias de archivo desde arriba, dentro de la carpeta
projects, y luego eliminar el archivo p450s.fasta y el archivo todo_list.txt así como la carpeta temporal.
imagen

¡Cuidado! Los archivos borrados se han ido para siempre. No hay deshacer, y no hay papelera de reciclaje. Siempre que use el
comando rm , vuelva a verificar su sintaxis. Hay un mundo de diferencia entre rm -rf project_copy (que elimina la
carpeta project_copy ) y rm -rf project _copy (que elimina las carpetas project y _copy , si existen).

Comprobación del tamaño de un archivo o directorio


Aunque ls -lh puede mostrar los tamaños de los archivos, este comando no resumirá cuánto espacio en disco ocupa un
directorio y todo su contenido. Para conocer esta información, existe el comando du (uso de disco), que casi siempre se combina
con las opciones -s (resumen) y -h (mostrar tamaños en formato legible por humanos).
imagen

Como siempre, . es un objetivo útil, aquí ayudando a determinar el espacio de archivos utilizado por el directorio de trabajo
actual.

Edición de un archivo (de texto)


No faltan editores de texto de línea de comandos, y aunque algunos de ellos, como vi y emacs , son poderosos y pueden
mejorar la productividad a largo plazo, también tardan un tiempo razonable en familiarizarse con ellos. (Se han escrito libros
completos sobre cada uno de estos editores.)
Mientras tanto, un simple editor de texto disponible en la mayoría de los sistemas es nano ; para ejecutarlo, simplemente
especificamos un nombre de archivo para editar:
I.4_11_Unix_33_Nano_1

1.4.2 https://espanol.libretexts.org/@go/page/55181
Si el archivo no existe ya, se creará cuando se guarde por primera vez, o “escrito”. El nano editor abre una ventana interactiva
como mucho menos , pero el contenido del archivo se puede cambiar. Cuando termine, la secuencia de teclas Control-O
guardará las ediciones actuales en el archivo especificado (tendrá que presionar Enter para confirmar), y luego Control-X
saldrá y devolverá el control al símbolo del sistema. Esta información incluso se presenta en un pequeño menú de ayuda en la parte
inferior.
I.4_12_Unix_34_Nano_2

Aunque nano no es tan sofisticado como vi o emacs , sí admite una serie de características, incluyendo la edición de
múltiples archivos, cortar/copiar/pegar, buscar y reemplazar por patrón y resaltado de sintaxis de archivos de código.
Los archivos de código son los tipos de archivos que solemos querer editar con nano , en lugar de ensayos o cuentos cortos. Por
defecto, en la mayoría de los sistemas, nano automáticamente “envuelve” líneas largas (es decir, presiona automáticamente
Enter) si serían más largas que el ancho de la pantalla. Desafortunadamente, ¡esta característica causaría un error para la mayoría de
las líneas de código! Para desactivarlo, nano se puede iniciar con la bandera -w , como en nano -w todo_list.txt .

Eficiencia de la línea de comandos


Si bien el shell proporciona una interfaz poderosa para la computación, ciertamente es cierto que la gran dependencia de la
escritura puede ser tediosa y propensa a errores. Afortunadamente, la mayoría de los shells proporcionan una serie de
características que reducen drásticamente la cantidad de escritura necesaria.
Primero, caracteres comodín como * (que coincide con cualquier número de caracteres arbitrarios) y ? (que coincide con
cualquier carácter arbitrario) nos permiten referirnos a un grupo de archivos. Supongamos que queremos mover tres archivos que
terminan en .temp a un directorio temp . Podríamos ejecutar mv listando los archivos individualmente:
imagen

Alternativamente, podríamos usar el archivo mv *.temp ; el shell expandirá archivo*.temp a la lista de archivos
especificados anteriormente antes de pasar la lista expandida a mv . [2]
I.4_14_Unix_38_CP_Star2

Del mismo modo, podríamos mover solo Filea.temp y Fileb.temp (pero no FileAA.temp ) usando el
archivo mv?. tmp temp , porque el ? comodín sólo coincidirá con uno de cualquier carácter. Estos comodines se
pueden usar en cualquier lugar de una ruta absoluta o relativa, y se pueden usar más de uno en una sola ruta. Por ejemplo,
ls /home/ */*.txt inspeccionará todos los archivos que terminan en .txt en todos los directorios de inicio de los
usuarios (si son accesibles para su lectura).
Segundo, si desea volver a ejecutar un comando, o ejecutar un comando similar a un comando ejecutado anteriormente, puede
acceder al historial de comandos presionando la flecha hacia arriba. Una vez que hayas identificado qué comando quieres ejecutar o
modificar, puedes modificarlo usando las flechas izquierda y derecha, retroceder o eliminar, y luego escribir y presionar Enter
nuevamente cuando estés listo para ejecutar el comando modificado. (Ni siquiera necesitas tener el cursor al final de la línea para
presionar Enter y ejecutar el comando). Para una sesión de inicio de sesión determinada, puede ver parte de su historial de
comandos ejecutando el comando history .
Finalmente, una de las mejores formas de navegar por el shell y el sistema de archivos es mediante el uso de la terminación de
tabulación. Al escribir una ruta (ya sea absoluta o relativa), nombre de archivo o directorio, o incluso un nombre de programa,
puede presionar Tab, y el shell autocompletará la parte de la ruta o comando, hasta que el autocompletado se vuelva ambiguo.
Cuando las opciones son ambiguas, el shell te presentará las diversas opciones coincidentes para que puedas inspeccionarlas y
seguir escribiendo. (Si quieres ver todas las opciones aunque no hayas comenzado a escribir la siguiente parte de una ruta, puedes
presionar rápidamente Tab dos veces). Puedes presionar Tab tantas veces como quieras mientras ingresas un comando. ¡Los
usuarios expertos de la línea de comandos usan la tecla Tab muchas veces al minuto!

Cómo obtener ayuda en un comando o programa


Aunque hemos discutido algunas de las opciones (también conocidas como argumentos, o banderas) para programas como ls ,
cp , nano y otros, hay muchas más sobre las que podrías desear aprender. La mayoría de estos comandos básicos vienen con
“páginas man”, abreviatura de “páginas de manual”, a las que se puede acceder con el comando man .

1.4.3 https://espanol.libretexts.org/@go/page/55181
I.4_15_Unix_35_Man1

Este comando abre una página de ayuda para el comando en cuestión (generalmente en menos o un programa similar al
mismo), mostrando los diversos parámetros y banderas y lo que hacen, así como una variedad de otra información como comandos
y ejemplos relacionados. Para algunos comandos, también hay páginas de “info”; intenta ejecutar info ls para leer una
descripción más completa de ls . De cualquier manera, como en menos , al presionar q saldrá de la página de ayuda y
volverá al símbolo del sistema.

Visualización de los mejores programas en ejecución


La utilidad superior es invaluable para verificar qué programas están consumiendo recursos en una máquina; muestra en una
ventana interactiva los diversos procesos (programas en ejecución) ordenados por el porcentaje de tiempo de CPU que están
consumiendo, así como qué usuario los está ejecutando y cuánta RAM están consumiendo . Running Top produce una ventana
como esta:
I.4_16_Unix_36_top

Desde la perspectiva de los usuarios, la lista de procesos por debajo de la línea oscura es más útil. En este ejemplo, ningún proceso
está utilizando actualmente una cantidad significativa de CPU o memoria (y aquellos procesos que se están ejecutando son
propiedad de la raíz del administrador). Pero si algún usuario estuviera ejecutando procesos que requirieran más de un poquito
de CPU, probablemente se mostrarían. Para ordenar en su lugar por uso de RAM, use la secuencia de teclas Control-M .
Cuando termine, q saldrá de la parte superior y le devolverá al símbolo del sistema.
De particular importancia son las columnas %CPU y %MEM . El primero puede variar de 0 hasta 100 (por ciento) veces el
número de núcleos de CPU en el sistema; así un valor de 3200 indicaría un programa que utiliza 100% de 32 núcleos de CPU
(o quizás 50% de 64 núcleos). La columna %MEM oscila entre 0 y 100 (por ciento). Generalmente es algo malo para el sistema
cuando la memoria total utilizada por todos los procesos es cercana o superior al 100%, esto indica que el sistema no tiene
suficiente “memoria de trabajo” y puede estar intentando usar el disco duro mucho más lento como memoria de trabajo. Esta
situación se conoce como intercambio, y la computadora puede funcionar tan lentamente como para haberse estrellado
efectivamente.

Programas para matar a los pícaros


A veces sucede que los programas que deberían ejecutarse rápidamente, no lo hacen Quizás estén en un estado de error interno,
haciendo un ciclo para siempre, o quizás la tarea de análisis de datos que había estimado que tardaría uno o dos minutos está
tardando mucho más. Hasta que finalice el programa, el símbolo del sistema será inaccesible.
Hay dos formas de detener este tipo de programas en ejecución: la “suave” y la “dura”. La forma suave consiste en intentar ejecutar
la combinación de teclas Control-C , que envía una señal de parada al proceso en ejecución para que termine.
Pero si el programa pícaro se encuentra en un estado de error particularmente malo, no se detendrá ni siquiera con un
Control-C , y es necesaria una matanza “dura”. Para ello se requiere iniciar sesión en la misma máquina con otra ventana de
terminal para recuperar algún acceso a la línea de comandos. Ejecute la parte superior y anote el PID (ID de proceso)
del proceso infractor. Si no lo ves en la ventana superior , también puedes intentar ejecutar ps augx , que imprime una
tabla de todos los procesos en ejecución. Supongamos que el PID para que el proceso mate es 24516 ; matar este proceso se
puede hacer ejecutando kill -9 24156 . La opción -9 especifica que el sistema operativo debe detener el proceso en su
camino y limpiar inmediatamente los recursos utilizados por él. Los procesos que no se detienen a través de un kill -9 son
raros (aunque no se puede eliminar un proceso que ejecuta otro usuario) y probablemente requieran un reinicio de la
máquina o la intervención del administrador.

Ejercicios
1. Crea los siguientes directorios dentro de tu directorio home, si aún no los tienes: descargas , locales y
proyectos . Dentro de local , cree un directorio llamado bin . Estas carpetas son un conjunto común y útil para
tener en tu directorio de inicio; las usaremos en futuros capítulos para trabajar, descargar archivos e instalar software en.
2. Abra no una sino dos ventanas de inicio de sesión, e inicie sesión en una máquina remota en cada una. Esto le da dos directorios
de trabajo presentes, uno en cada ventana. Puedes usar uno para trabajar y otro para tomar notas, editar archivos o ver la salida
de top .

1.4.4 https://espanol.libretexts.org/@go/page/55181
Crea un directorio oculto dentro de tu directorio principal llamado .hidden . Dentro de este directorio, cree un archivo
llamado notas . Edita el archivo para que contenga trucos e información que temes que puedas olvidar. [3]
3. Dedique unos minutos a practicar la finalización de pestañas mientras se mueve por el sistema de archivos usando rutas
absolutas y relativas. Moverse de manera eficiente a través de la finalización de pestañas es una habilidad sorprendentemente
necesaria.
4. Deshojee la página de manual para ls , y pruebe algunas de las opciones enumeradas allí. Lee un poco de la página de
información para nano .

1. Hay un programa similar llamado más , originalmente diseñado para mostrar “más” de un archivo. El programa menos se
desarrolló como una alternativa más completa a más , y se llamó así porque “menos es más”.
2. Esto es importante tener en cuenta al combinar rm con comodines; ¡los comandos rm -rf *.temp y
rm -rf * .temp son muy diferentes! Este último eliminará todos los archivos del directorio actual, mientras que el
primero solo eliminará aquellos que terminen en .temp .
3. También podrías considerar llevar un cuaderno de papel, o algo en un Wiki u otro documento de texto. Si prefieres conservar
notas digitales, intenta usar un editor de texto simple como nano , TextEdit en OS X o Notepad en Windows. Los editores de
texto rico como Microsoft Word a menudo reemplazan automáticamente cosas como los caracteres de comillas simples con
comillas con serif, que no funcionan en la línea de comandos, lo que lleva a dolores de cabeza.

This page titled 1.4: Trabajar con archivos y directorios is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by
Shawn T. O’Neil (OSU Press) .

1.4.5 https://espanol.libretexts.org/@go/page/55181
1.5: Permisos y Ejecutables
Como se mencionó anteriormente, la raíz del administrador configura la mayoría de las cosas en el sistema, como dónde se
montan las unidades de red y cómo se otorga el acceso al sistema. [1] La mayoría de los archivos que son importantes para estas
configuraciones son “propiedad” de root, y por razones de seguridad, otros usuarios no pueden manipularlos. De hecho, todos los
usuarios del sistema suelen ser dueños de los archivos en su propio directorio home, y estos archivos no pueden ser manipulados
por otros usuarios (excepto el root , que tiene acceso a todo). Estas configuraciones de seguridad se controlan a través de
permisos asociados a cada archivo y directorio.
Root también puede unir a los usuarios en grupos, permitiendo a los usuarios del mismo grupo compartir archivos entre sí pero no
con extraños, si así lo desean. Los grupos pueden tener más de un usuario, y un solo usuario puede formar parte de más de un
grupo, como en el siguiente ejemplo que ilustra tres grupos y ocho usuarios.
I.5_1_Grupos_EX

El comando groups muestra los grupos a los que pertenece un usuario; en el ejemplo anterior, groups oneilst
reportaría emrichlab y hellmannlab . Para ver tus propios grupos, siempre puedes usar algo así como
grupos $USER (confiando en el shell para reemplazar $USER con tu nombre de usuario).
Desafortunadamente, no hay una manera segura o fácil de enumerar a todos los miembros de un grupo en particular, al menos no
sin un conocimiento profundo de cómo está configurado el sistema o alguna experiencia en programación. En algunos sistemas, un
comando como getent group <groupname>proporcionará la respuesta; los profesores del grupo getent
reportarían emrichs , schmidtj y hellmannj para el ejemplo anterior.
Si no estás seguro del nombre de usuario de una persona, el comando finger puede venir al rescate. Puede proporcionar un
dedo con un nombre o apellido para buscar (o incluso el nombre de usuario, si se conoce), y devolverá información, si es
ingresada por el administrador del sistema, sobre ese usuario.
I.5_2_Unix_39_dedo

Cada archivo y directorio está asociado con un usuario (el propietario) y un grupo; desafortunadamente, en los permisos normales
similares a Unix, uno y solo un grupo pueden estar asociados a un archivo o directorio. Cada archivo y directorio también tiene
asociados con él permisos que describen:
1. lo que puede hacer el propietario,
2. qué pueden hacer los miembros del grupo, y
3. lo que todos los demás (los demás) pueden hacer.
Esta información se muestra cuando se ejecuta ls -l , y se representa mediante una combinación de r (leer), w (escribir)
y x (ejecutar). Donde uno de estos tres está ausente, se sustituye por a - . Aquí hay un ejemplo, que muestra dos entradas
propiedad de oneils y en el grupo iplant-everyone ; una tiene permisos rwxrwxrwx (un conjunto de permisos
inseguro, que permite a cualquiera hacer cualquier cosa con el archivo), y la otra tiene rwxr-xr-x (un permiso mucho más
razonable conjunto).
I.5_3_Unix_40_LS_Permisos

Hay una entrada extra en la primera columna; el primer carácter describe el tipo de entrada, - para un archivo regular y d para
directorio. Vamos a desglosar estas primeras columnas para una de las entradas:
I.5_4_Permissions_description

Cada archivo o directorio puede tener alguna combinación de permisos r , w y x , aplicados al usuario, al grupo u otros en el
sistema. Para los archivos, los significados de estos permisos son bastante sencillos.

Código Significado para Archivos

r Puede leer el contenido del archivo

w Puede escribir en (editar) el archivo

x Puede (potencialmente) “ejecutar” el archivo

1.5.1 https://espanol.libretexts.org/@go/page/55195
Cubriremos lo que significa que un archivo sea ejecutable en un poco. Para los directorios, estos permisos toman diferentes
significados.

Código Significado para Directorios

r Puede ver el contenido del directorio (por ejemplo, ejecutar ls )


Puede modificar el contenido del directorio (crear o eliminar
w
archivos/directorios)

x Puede cd al directorio, y potencialmente acceder a subdirectorios

El directorio temp anterior le da al usuario todos los permisos ( rwx ), pero los miembros del grupo y otros solo pueden cd
al directorio y ver los archivos allí ( r-x ); no pueden agregar o eliminar archivos o directorios. (Es posible que puedan editar
archivos en temp , sin embargo, dependiendo de los permisos de esos archivos).
La utilidad chmod (modo de cambio) nos permite agregar o eliminar permisos. Hay dos tipos de sintaxis, la sintaxis de
“carácter” más simple y la sintaxis numérica “octal”. Describiremos la sintaxis más simple y dejaremos la discusión de la sintaxis
octal para aquellos lo suficientemente valientes como para leer la página del manual ( man chmod ).
I.5_5_CHMOD_Sintaxis

Para aclarar, aquí hay algunos ejemplos de modificación de permisos para el archivo p450s.fasta .
Comando Efecto

chmod go-w p450s.fasta Eliminar escritura para grupo y otros

chmod ugo+r p450s.fasta Agregar lectura para usuario, grupo y otros

chmod go-rwx p450s.fasta Eliminar leer, escribir y ejecutar para grupos y otros

chmod ugo+x p450s.fasta Agregar ejecutar para usuario, grupo y otros

chmod +x p450s.fasta Igual que chmod ugo+x p450s.fasta

Si deseas modificar un directorio y todo lo que hay dentro, puedes agregar el indicador -R (R mayúscula esta vez para recursivo)
a chmod . Para compartir un directorio de proyectos y todo lo que hay dentro para el acceso de lectura con los miembros
del grupo, por ejemplo, puedes usar proyectos chmod -R g+r .
Hay algunas pequeñas cosas a tener en cuenta sobre los permisos de archivos y directorios. El primero es que si bien es posible
cambiar el grupo de un archivo o directorio, solo puedes hacerlo con el comando chgrp si eres miembro de ese grupo.
I.5_6_Unix_41_CHGRP

Segundo, eres el dueño de los archivos que creas, pero generalmente solo el usuario root tiene acceso a la utilidad chown que
cambia el propietario de un archivo existente (no sería muy agradable “regalar” a otro usuario un programa nefasto).
Tercero, si bien es conveniente poder abrir un directorio para que lo lean los miembros del grupo, hacerlo solo es útil si todos los
directorios anteriores también son mínimamente accesibles. En particular, todos los directorios en la ruta a un directorio compartido
necesitan tener al menos x para el grupo si van a ser accedidos de alguna manera por los miembros del grupo.

Ejecutables y $PATH
¿Qué es un “programa”? En un sistema similar a Unix, es un archivo que tiene permisos ejecutables para algún usuario o usuarios.
(¡También ayuda si ese archivo contiene algunas instrucciones que tienen sentido para ejecutar!) A menudo, estos se codifican en
un formato “binario”, simplemente cadenas largas de 0 y 1 que representan código de máquina que la CPU puede interpretar,
pero en algunos contextos también pueden ser archivos de texto legibles por humanos. Muchos de los programas que hemos estado
usando, como echo y ls , son archivos ejecutables que viven en el directorio /bin junto con muchos otros, incluyendo
bash , nuestro programa shell.
I.5_7_Unix_42_slash_bin_ls

Si nos gusta, incluso podemos intentar echar un vistazo a uno de estos con menos . Por ejemplo, podemos intentar examinar los
contenidos del programa bash con menos /bin/bash ; aunque menos reporta una advertencia de que el archivo está

1.5.2 https://espanol.libretexts.org/@go/page/55195
codificado en binario, no va a doler intentarlo.
I.5_8_Unix_43_Bash_Less

Un archivo codificado en binario no se parece mucho cuando intentamos convertirlo a texto y verlo con menos . En cualquier
caso, aquí hay una “regla de ejecución” que vamos a romper casi de inmediato: para que el shell ejecute un programa (archivo
ejecutable), especificamos la ruta absoluta o relativa a él.
En este caso, nuestra regla de ejecución indicaría que para ejecutar el programa echo , especificaríamos la ruta absoluta
/bin/echo hola , ya que el programa echo vive en /bin , o ../../../../../../.. /bin/echo hola
para la ruta relativa (porque /bin está cuatro carpetas por encima de nuestro directorio de trabajo actual).
imagen

Ahora para la parte que rompe reglas: ya sabemos que solo podemos ejecutar echo sin especificar una ruta al programa. Esto
significa que cuando intentamos ejecutar un programa, el shell debe ser capaz de encontrar el archivo ejecutable de alguna manera.
¿Cómo se hace esto?
La respuesta, como ocurre con tantas preguntas que involucran el shell, es una variable de entorno llamada $PATH .
Comprobemos el contenido de esta variable: [2]
imagen

La variable de entorno $PATH contiene una cadena simple, que describe una lista de rutas absolutas separadas por :
caracteres. Cuando especificamos lo que le parece al shell como el nombre de un programa, busca en esta lista de rutas, en orden,
un archivo ejecutable de ese nombre. Cuando escribimos echo , intenta /usr/local/sbin/echo , luego
/usr/local/bin/echo , y así sucesivamente, hasta que lo encuentre en /bin/echo .
El primer archivo ejecutable coincidente que el shell encuentra en los directorios listados en $PATH es el que se ejecuta. Esto
podría llevar a alguna travesura: si un compañero de trabajo con una inclinación por los chistes prácticos pudiera modificar tu
variable $PATH , podría agregar su propio directorio home como primera entrada. A partir de ahí, podría crear un archivo
ejecutable llamado, digamos, ls que hizo lo que quisiera, ¡y sin saberlo estarías ejecutando eso! Es posible que cualquier
persona con acceso a tu cuenta modifique tu $PATH , por lo que es una buena idea no dejar abierta la ventana de tu terminal
alrededor de nadie con un cobarde sentido del humor.
Si hay varios archivos ejecutables con el mismo nombre en esta lista de rutas, ¿podemos descubrir cuál ejecutará el shell? Sí: en
bash podemos tomar esta determinación usando el comando which . [3]
I.5_11_Unix_46_WHI_Echo

¿Y un comando como cd ? Podemos intentar usar cuál para localizar un programa llamado cd , pero encontraremos que
no se reporta nada.
imagen

Esto se debe a que cd no es un programa (archivo ejecutable), sino más bien un “comando”, lo que significa que el shell nota
que es una palabra clave especial que debe manejar, en lugar de buscar un archivo ejecutable con ese nombre. Dicho de otra
manera, bash es realizar la acción, en lugar de llamar a un programa ejecutable externo. Conocer la diferencia entre los
comandos manejados por el shell y los programas que son archivos ejecutables es un punto menor, pero uno que podría resultar
confuso en casos como este.

Hacer archivos ejecutables


Hagamos algo decididamente raro, y luego volvamos y explicarlo. Primero, usaremos nano para crear un nuevo archivo
llamado myprog.sh , usando el indicador -w para nano para asegurar que las líneas largas no se envuelven
automáticamente ( nano -w myprog.sh ). En este archivo, haremos los dos primeros caracteres #! , seguida
inmediatamente por la ruta absoluta al archivo ejecutable bash . En líneas posteriores, pondremos algunos comandos que
podríamos ejecutar en bash , como dos llamadas de eco .
imagen

1.5.3 https://espanol.libretexts.org/@go/page/55195
A pesar de que parece el #! (pronunciado “shebang”, rimando con “the bang”) la línea comienza en la segunda línea, en realidad
es la primera línea del archivo. Esto es importante. Observe que nano se ha dado cuenta de que estamos escribiendo un archivo
que es un poco extraño, y que ha activado alguna coloración. Es posible que su nano no esté configurado para este resaltado de
sintaxis. Si no, no te preocupes: estamos creando un archivo de texto simple.
Después de guardar el archivo ( Control-o , luego Enter confirmar el nombre del archivo a escribir) y salir nano (
Control-X ), podemos agregar permisos de ejecución al archivo (para todos, quizás) con chmod +x myprog.sh .
I.5_14_Unix_49_Chmod_MyProg

Parecería que podríamos haber creado un programa, tenemos un archivo ejecutable, y es posible que hayas adivinado que la
sintaxis especial que hemos utilizado hace que el archivo sea ejecutable de manera significativa. Vamos a probarlo: de acuerdo con
nuestra regla de ejecución, podemos especificar la ruta absoluta hacia ella para ejecutarlo.
imagen

¡Corrió! Lo que hemos creado se conoce como guión para ser dirigido por un intérprete; en este caso, el intérprete es bash . Un
script es un archivo de texto con permisos de ejecución establecidos, que contiene comandos que pueden ser ejecutados por un
intérprete, generalmente especificados a través de la ruta absoluta en la parte superior del script con un #! línea. Un intérprete es
un programa que puede ejecutar comandos, a veces especificados en un archivo de script.
Lo que está sucediendo aquí es que el shell ha notado que el usuario está intentando ejecutar un archivo ejecutable, y pasa la
ejecución al sistema operativo. El sistema operativo, a su vez, nota los dos primeros bytes del archivo (el #! caracteres), y en
lugar de que la CPU ejecute el archivo como código binario de máquina, ejecuta el programa especificado en el #! línea,
pasando a ese programa el contenido del archivo como “código” para ser ejecutado por ese programa. Porque en este caso el
programa de interpretación es bash , podemos especificar cualquier comando que podamos enviar a nuestro shell, bash .
Posteriormente, veremos que podemos crear scripts que utilizan intérpretes mucho más sofisticados, como python , para
ejecutar código más sofisticado.
De acuerdo con nuestra regla de ejecución, también podemos ejecutar nuestro programa especificando una ruta relativa a él, como
. /myprog.sh (que especifica ejecutar el archivo myprog.sh que se encuentra en el directorio de trabajo actual).
I.5_16_Unix_50_MyProg_dotslash

Esta es la forma más común de ejecutar archivos y programas que existen en el presente directorio de trabajo.
Si cambiamos a otro directorio de trabajo presente, como nuestro directorio home, entonces para ejecutar el programa de acuerdo
con la regla de ejecución, tenemos que especificar nuevamente ya sea la ruta absoluta o relativa.
I.5_17_Unix_51_MiProg_FromHome

Este proceso es tedioso; nos gustaría poder especificar el nombre del programa, pero debido a que la ubicación de nuestro
programa no está especificada en un directorio listado en $PATH , obtendremos un error.
I.5_18_Unix_52_MyProg_NotFound

Instalación de un programa
Para agregar nuestros propios programas al sistema para que podamos ejecutarlos a voluntad desde cualquier ubicación,
necesitamos:
1. Obtener o escribir un programa ejecutable o script.
2. Colóquelo en un directorio.
3. Asegúrese de que la ruta absoluta a ese directorio se pueda encontrar en $PATH .
Tradicionalmente, la ubicación para almacenar los propios ejecutables personales se encuentra en el directorio home de uno, dentro
de un directorio llamado local , dentro de un directorio llamado bin . Vamos a crear estos directorios (crearlos también era
parte de un ejercicio anterior, por lo que puede que no sea necesario), y movemos nuestro archivo myprog.sh allí.
I.5_19_Unix_53_Create_Local_bin

Esto logra los pasos 1 y 2. Para el paso 3, necesitamos asegurarnos de que nuestro directorio local/bin se pueda encontrar en
$PATH . Debido a que $PATH es una variable de entorno, podemos establecerla con export , haciendo uso del hecho de

1.5.4 https://espanol.libretexts.org/@go/page/55195
que las variables de entorno dentro de comillas dobles (pero no comillas simples) se expanden a su contenido.
I.5_20_Unix_54_export_path

Debido a que el lado derecho del = se evalúa antes de que ocurra la asignación, la variable $PATH ahora contiene la ruta
completa al directorio local/bin , seguida del contenido anterior de $PATH . [4] Si escribimos un nombre de programa sin
especificar una ruta a él, ¡el shell buscará primero nuestra propia ubicación de instalación!
I.5_21_UNIX_55_MyProg_Installado

Solo hay un problema: cada vez que salimos y volvemos a iniciar sesión, se olvidan las modificaciones de las variables de entorno
que hemos realizado. Afortunadamente, bash busca dos archivos importantes cuando se inicia: [5] (1) los comandos en el
archivo.bash_login (en tu directorio home) se ejecutan cada vez que bash comienza como consecuencia de una
sesión de inicio de sesión (por ejemplo, al ingresar una contraseña causa bash para iniciar), y (2) comandos en el
archivo.bashrc (en su directorio home) se ejecutan cada vez que se inicia bash (por ejemplo, al iniciar sesión y cuando
bash se ejecuta a través de un #! guión).
Si quieres ver un saludo amistoso cada vez que inicies sesión, por ejemplo, podrías agregar la línea
echo “Hola $USER, ¡qué gusto volver a verte!” a su archivo.bash_login . Debido a que
queremos que nuestro $PATH sea modificado incluso si bash de alguna manera comienza sin nuestro inicio de sesión,
agregaremos el comando export al archivo.bashrc .
El archivo.bashrc puede tener información ya en él, lo que representa una configuración de shell predeterminada colocada
allí cuando el administrador creó la cuenta. Si bien podemos agregar nuestros propios comandos a este archivo, debemos hacerlo al
final, y debemos tener cuidado de no molestar a los otros comandos de configuración que probablemente estén ahí por una buena
razón. Además, los comandos de este archivo deben estar libres de errores y tipos: ¡algunos errores son lo suficientemente malos
como para evitar que inicies sesión! Usar el -w al editar el archivo con nano ayudará a asegurar que el editor no intente
autoenvolver comandos largos que no deben romperse en varias líneas.
I.5_22_Unix_56_Nano_bashrc1

En la parte inferior de este archivo, agregaremos la línea de exportación :


I.5_23_Unix_57_nano_bashrc2

Porque las líneas que comienzan con # son “comentarios” (no ejecutadas, aparte del #! line, por supuesto), podemos usar esta
función para recordarnos a nuestro yo futuro cómo llegó esa línea en el archivo. Debido a que los comandos en estos archivos solo
se ejecutan cuando se inicia el shell, para activar los cambios, basta con cerrar sesión y volver a iniciar sesión.

Ejercicios
1. Supongamos que un archivo tiene los siguientes permisos enumerados por ls -l : -rwxrw-r— . ¿Qué indica esta
cadena de permisos sobre el archivo?
2. ¿Cuál es la diferencia entre export path="$HOME/local/bin: $PATH” y
export PATH="$PATH: $INICIO/local/bin” ? ¿En qué situaciones podría preferirse la primera sobre la
segunda?
3. Agrega cuidadosamente la línea export path="$HOME/local/bin: $PATH” a tu .bashrc (suponiendo que
tienes un directorio local/bin en tu directorio home, y tu shell predeterminado es bash ). Asegúrese de no alterar
ninguna línea ya presente, o de crear errores tipográficos que puedan impedirle iniciar sesión.
4. Crea un script ejecutable en tu directorio local/bin llamado myinfo.sh , que ejecuta echo en las variables de
entorno $HOME , $PWD y $USER , y también ejecuta la utilidad date . Intenta ejecutarlo simplemente ejecutando
myinfo.sh desde tu directorio home (es posible que tengas que cerrar sesión y volver primero, para que el shell reconozca
el cambio en el contenido de las rutas listadas en $PATH si modificaste tu .bashrc ).
5. Scripts bash ejecutables que comienzan con #! /bin/bash funcionará bien, siempre que el programa bash viva
en el directorio /bin . En cualquier sistema donde este no sea el caso (probablemente una ocurrencia rara), no lo hará.
Intenta crear un script bash donde la primera línea es #! /usr/bin/env bash . El programa env utiliza la
variable $PATH para localizar el ejecutable bash , y pasa la interpretación del script al intérprete bash localizado.
Esto plantea el mismo problema: ¿y si env no se encuentra en /usr/bin ? Afortunadamente, esta ha sido una ubicación

1.5.5 https://espanol.libretexts.org/@go/page/55195
acordada para el programa env durante décadas, por lo que los guiones escritos de esta manera son portátiles en más
máquinas.

1. El administrador, o usuario root , a veces también se llama el “superusuario”. Esto se sabe que va a la cabeza de algunos
administradores.
2. Los shells tcsh y csh no utilizan la variable de entorno $PATH . En cambio, buscan en una variable shell llamada
$path .
3. En tcsh y csh , la aproximación más cercana a la cual es donde , aunque que también puede funcionar.
4. El comando correspondiente para establecer la variable tcsh o csh $path es:
set path = (“$HOME/local/bin” $path).
tcsh y csh son .login y .cshrc , respectivamente.
5. Los archivos correspondientes para los shells

This page titled 1.5: Permisos y Ejecutables is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

1.5.6 https://espanol.libretexts.org/@go/page/55195
1.6: Instalación de software (Bioinformática)
Idealmente, la infraestructura computacional a la que tiene acceso ya incluye una gran cantidad de paquetes de software
especializados necesarios para su trabajo, y las instalaciones de software se mantienen actualizadas a medida que los
desarrolladores realizan mejoras. Si este no es el caso, podrías considerar sobornar a tu administrador del sistema local con dulces y
cafeína. En su defecto, es probable que tenga que instalar el software que necesita usted mismo.
Instalar software más sofisticado que los simples scripts descritos en el capítulo 5, “Permisos y ejecutables”, seguirá el mismo
patrón básico: (1) obtener archivos ejecutables, (2) meterlos en $ Inicio/local/bin , y (3) asegurar que
$ Inicio/local/bin esté presente en el $ Variable de entorno PATH . El capítulo 5 cubrió el paso 3, que
solo debe hacerse una vez por nuestra cuenta. Los pasos 2 y 3, sin embargo, suelen ser bastante diferentes dependiendo de cómo se
distribuya el software.
En este capítulo, vamos a repasar un ejemplo de instalación y ejecución de una suite de bioinformática conocida como HMMER.
Este software busca coincidencias de secuencias proteicas (a partir de un conjunto de secuencias) basadas en un modelo
probabilístico oculto de Markov (HMM) de un conjunto de secuencias proteicas similares, como en proteínas ortólogas de
diferentes especies. La motivación para elegir este ejemplo no es para que podamos aprender sobre el modelado HMM o
específicamente esta suite de software, sino que es una tarea representativa que requiere que los usuarios descarguen archivos,
instalen software de diferentes maneras y obtengan datos de repositorios públicos.
El primer paso para instalar HMMER es encontrarlo en línea. Una simple búsqueda web nos lleva a la página principal:
imagen

Convenientemente, vemos un bonito botón grande de “Descargar”, pero el botón indica que la descarga está hecha para macOS
X/Intel, el sistema operativo que se ejecuta en mi portátil personal. Debido a que estamos conectados remotamente a una
computadora Linux, esta descarga no funcionará para nosotros. Al hacer clic en el enlace “Opciones de descarga alternativas” se
revelan opciones que podrían funcionar para el sistema Linux que estamos usando.
imagen

En esta captura de pantalla, vemos una serie de opciones de descarga interesantes, incluyendo una para “Fuente”, dos para
“binarios de Linux” y a continuación una sugerencia de alguna documentación, a la que volveremos más adelante.

¿Fuente o Binario?
Algún software bioinformático se crea como un simple script del tipo discutido en el capítulo 5: un archivo de texto con un #!
línea que hace referencia a un programa de interpretación (que ojalá esté instalado en el sistema) y se haga ejecutable con
chmod .
Pero resulta que tales programas interpretados son lentos debido a la capa extra de ejecución, y para algunas aplicaciones, la
conveniencia y relativa facilidad no valen la pena la pérdida de velocidad. En estos casos, el software puede escribirse en un
lenguaje compilado, lo que significa que el código del programa comienza como “código fuente” legible por humanos pero luego
se procesa en código binario legible por máquina. El truco es que el proceso de compilación necesita ser realizado de forma
independiente para cada tipo de CPU. Aunque hay menos tipos de CPU de uso común que en días pasados, las arquitecturas de
CPU x86 de 32 y 64 bits siguen siendo comunes, y el software compilado para una no funcionará en la otra. Si el desarrollador ha
puesto a disposición binarios compilados compatibles con nuestro sistema, entonces mucho mejor: podemos descargarlos,
asegurarnos de que sean ejecutables y colocarlos en $ HOME/local/bin . Alternativamente, es posible que necesitemos
descargar los archivos de código fuente y realizar la compilación nosotros mismos. En algunos casos, los desarrolladores
distribuyen binarios, pero ciertas características del programa se pueden personalizar en el proceso de compilación.
imagen

En aras de la integridad, haremos una instalación fuente de HMMER; más adelante, obtendremos algún otro software como
binarios. [1]

Desempaquetar y descargar
Vamos a descargar los archivos fuente para HMMER; primero, vamos a crear un nuevo directorio para almacenar descargas,
llamadas descargas , en nuestro directorio home (es posible que ya tengas dicho directorio).

1.6.1 https://espanol.libretexts.org/@go/page/55206
imagen

Si tuviéramos que hacer clic en el enlace de la página de descarga del HMMER, el navegador web intentaría descargar el archivo
ubicado en la URL correspondiente (http://eddylab.org/software/hmmer3/3.1b2/hmmer-3.1b2.tar.gz) al escritorio local. Debido a
que queremos que el archivo se descargue al sistema remoto, al hacer clic en el botón de descarga no funcionará. Lo que
necesitamos es una herramienta llamada wget , que pueda descargar archivos de Internet en la línea de comandos. [2] La utilidad
wget toma al menos un parámetro importante, la URL, para descargar. Por lo general, es una buena idea poner las URL entre
comillas, porque a menudo tienen caracteres que confunden el shell y necesitarían ser escapadas o citadas. Adicionalmente,
podemos especificar -O <filename>, donde <filename>es el nombre a usar al guardar el archivo. Aunque no se requiere en
esta instancia, puede ser útil para URL cuyos nombres de archivo final no son razonables (como
index.php? query=fasta&search=drosophila ).
imagen

En este punto, tenemos un archivo que termina en .tar.gz , conocido como “gzipped tarball”, que representa una colección de
archivos que primero se han combinado en un solo archivo (un tarball), y luego comprimido (con la utilidad gzip ).
imagen

Para sacar el contenido, tenemos


que revertir este proceso. Primero, descomprimiremos el archivo con
gzip -d hmmer-3.1b1.tar.gz , que reemplazará el archivo con el hmmer-3.1b1.tar sin comprimir. [3] A partir
de ahí, podemos des-tar el tarball con tar -xf hmmer-3.1b1.tar (el -x indica extracto, y el f indica que los datos
serán extraídos del nombre de archivo especificado).
I.6_7_Unix_60_GZIP_TAR

Parece que el tarball gzip contenía un directorio, llamado hmmer-3.1b1 .

Otros métodos de descarga y compresión


Antes de continuar trabajando con el código fuente descargado, hay un par de cosas a tener en cuenta con respecto a los archivos
comprimidos y la descarga. En primer lugar, aunque los tarballs gzip son el formato de compresión más utilizado para sistemas
similares a Unix, también se pueden encontrar otros tipos de compresión. Por lo general, se pueden identificar por la extensión del
archivo. Diferentes herramientas están disponibles para cada tipo, aunque también hay una utilidad genérica de
descomprimir que puede manejar los tipos más comunes.
Extensión Comando de descomprimir

archivo.bz2 bunzip2 archivo.bz2


file.zip descomprimir file.zip
archivo.tgz Igual que para .tar.gz

La sintaxis más común para crear un tarball gzip usa la utilidad tar , que puede hacer ambos trabajos de tarring y gzipping de
las entradas. Como ejemplo, el comando tar -cvzf hmmer_compress_copy.tar.gz hmmer-3.1b1 crearía ( c
), con salida detallada ( v ), un tarball gzipped ( z ) en un archivo ( f ) llamado hmmer_compress_copy.tar.gz
desde el directorio de entrada hmmer- 3.1b1 .
Tradicionalmente, los archivos comprimidos de código fuente eran la forma más común de distribuir software. Más recientemente,
los sistemas de control de versiones (utilizados por los desarrolladores para rastrear los cambios en su software a lo largo del
tiempo) se han habilitado para la web como una forma de distribuir software a los usuarios finales. Uno de esos sistemas es git ,
que permite a los usuarios descargar directorios completos de archivos usando una “URL de clon” a través de la web. GitHub es
una página igualmente popular para alojar estos proyectos. Aquí hay una captura de pantalla de la página de GitHub para Sickle,
una recortadora de calidad rápida para datos de secuencia de alto rendimiento.
imagen

Se muestra la “URL de clon HTTPS” de GitHub después de hacer clic en el enlace verde “Clonar o descargar”; para descargar la
fuente de Sickle en la línea de comandos, solo se necesita ejecutar un comando como
git clone https://github.com/najoshi/sickle.git , siempre que el programa git esté instalado. (El

1.6.2 https://espanol.libretexts.org/@go/page/55206
programa git también se puede utilizar en la línea de comandos para rastrear “instantáneas” de archivos en un directorio a lo
largo del tiempo, proporcionando un registro del trabajo realizado. Dichos directorios registrados se pueden sincronizar con
GitHub, lo que hace que sea relativamente fácil compartir proyectos con otros.)

Compilación de la Fuente
Habiendo descargado y desempaquetado el código fuente HMMER, el primer paso es verificar el contenido del directorio y buscar
cualquier archivo README o INSTALAR . Dichos archivos a menudo se incluyen y contienen información importante del
desarrollador de software.
I.6_9_Unix_61_HMMER_CONTENIDOS

Echando un vistazo al contenido del directorio hmmer-3.1b1 , hay un archivo INSTALL , que deberíamos leer con
menos . Aquí está la parte superior del archivo:
I.6_10_UNIX_62_HMMER_INSTAL_DOC

La documentación de instalación describe una serie de comandos, incluyendo muchos que ya hemos ejecutado (para extraer los
datos del tarball gzip). También hay cuatro comandos más enumerados: . /configure , haga , verifique y
haga la instalación . Tres de ellos comprenden el “proceso de instalación canónica” — make check es un paso
opcional para verificar el éxito del proceso a mitad de camino. Los tres pasos importantes son: (1) . /configure , (2)
make y (3) make install .
1. El contenido del directorio (arriba) incluye configure como un script ejecutable, y el comando . /configure
ejecuta el script desde el directorio de trabajo actual. Este script generalmente verifica que todas las bibliotecas y programas de
requisitos previos estén instalados en el sistema. Más importante aún, este paso puede establecer algunas variables de entorno o
crear un archivo llamado Makefile , dentro del cual habrá instrucciones detallando cómo debe proceder el proceso de
compilación e instalación, personalizado para el sistema.
2. En realidad, make es un programa de interpretación muy parecido a bash ( que make es probable que devuelva
/usr/bin/make —es un programa binario). Al ejecutar make , su comportamiento predeterminado es buscar un
archivo llamado Makefile en el directorio actual, y ejecutar un conjunto predeterminado de comandos especificados en el
Makefile en el orden especificado. En este caso, estos comandos predeterminados ejecutan los programas de compilación
que convierten el código fuente en binarios ejecutables.
3. El comando make install vuelve a ejecutar make , que busca el Makefile , pero esta vez estamos especificando
que el conjunto de comandos “install” en el Makefile debe ejecutarse. Este paso copia los archivos ejecutables binarios (y
otros archivos auxiliares, si es necesario) en la ubicación de instalación.
Este último paso, hacer instalar , puede llevarnos a preguntar: ¿cuál es la ubicación de la instalación? Por defecto, será
algo así como /usr/bin , una ubicación en todo el sistema que solo puede escribir el administrador. Entonces, a menos que
estemos conectados como root (el administrador), el paso final en el proceso fallará. Debemos especificar la ubicación de
instalación, y aunque la instalación en sí ocurre en el tercer paso, todo el proceso se configura en el primer paso. Puede haber
muchas opciones que podemos especificar en el . /configure paso, aunque la ubicación de instalación (conocida como
PREFIX ) es, con mucho, la más utilizada. Corriendo . /configure —help imprime mucha información; aquí está la
sección relevante:
I.6_11_UNIX_63_CONCENTRAR_AYUDA

La opción —prefix es la que usaremos para determinar dónde deben ubicarse los binarios. Aunque nuestros binarios
ejecutables eventualmente irán en $HOME/local/bin , para esta opción vamos a especificar $HOME/local , porque la
porción bin de la ruta está implícita (y otros directorios como lib y share también podrían crearse junto el directorio
bin ). Finalmente, nuestro proceso de instalación canónica modificado constará de tres pasos:
. /configure —prefix=$HOME/local , make , y make install .
I.6_12_UNIX_66_CONFIGURE_PREFIX
I.6_13_Unix_67_Make_Prefix
imagen

En este punto, si navegamos a nuestro directorio $HOME/local , veremos los directorios agregados y los archivos binarios.

1.6.3 https://espanol.libretexts.org/@go/page/55206
I.6_15_Unix_69_check_HMMER_bins

Debido a que estos archivos ejecutables existen en un directorio listado en la variable $PATH , podemos, como siempre, escribir
sus nombres en el símbolo del sistema cuando trabajamos en cualquier directorio para ejecutarlos. (Aunque, de nuevo, es posible
que necesitemos cerrar sesión y volver a entrar para obtener el shell para ver estos nuevos programas. [4])

Instalación desde binarios


Nuestro objetivo es ejecutar HMMER para buscar un perfil de conjunto de secuencias en una base de datos más grande de
secuencias. Para más detalles, la documentación de HMMER (disponible en el sitio web) es muy recomendable, particularmente la
sección “Tutorial”, que describe convertir una alineación múltiple de secuencias en un perfil (con hmmbuild ) y buscar ese
perfil contra el conjunto más grande (con hmmsearch ). También es útil leer la publicación revisada por pares que describe los
algoritmos implementados por HMMER o cualquier otro software bioinformático. Incluso si el material está fuera de su área de
especialización, revelará las fortalezas y debilidades del software.
imagen

Pronto llegaremos a descargar conjuntos de secuencias de consulta y destino, pero rápidamente nos daremos cuenta de que aunque
los programas de la suite HMMER pueden producir el perfil y buscarlo contra el conjunto objetivo, no pueden producir una
alineación múltiple a partir de un conjunto de secuencias que son similares pero no todas de la misma longitud. Aunque hay
muchas herramientas de alineación múltiple con diferentes características, descargaremos el músculo relativamente popular.
Esta vez, lo instalaremos desde binarios.
Vale la pena discutir cómo se va a descubrir estas secuencias de pasos, y qué herramientas usar. Las siguientes estrategias
generalmente funcionan bien, aunque la creatividad casi siempre es recompensada.
1. Lee las secciones de métodos de ponencias con objetivos similares.
2. Pregunte a sus compañeros.
3. Buscar en Internet.
4. Lee la documentación y los trabajos publicados para herramientas con las que ya estás familiarizado, así como aquellas
publicaciones que las citan.
5. No dejes que la aparente complejidad de un análisis te impida dar los primeros pasos. La mayoría de los tipos de análisis
emplean una serie de pasos y muchas herramientas, y es posible que no tenga una idea clara de cuál será el procedimiento final.
Experimenta con herramientas alternativas y busca ayuda cuando te quedes atascado. Asegúrese de documentar su trabajo, ya
que inevitablemente querrá volver sobre sus pasos.
Si visitamos la página de inicio muscular , veremos una variedad de opciones de descarga, incluyendo binarios para nuestro
sistema, Linux.
I.6_17_MUSCLE_DESCARGAR

Desafortunadamente, parece que hay dos opciones para los binarios de Linux: 32 bits y 64 bits. ¿Cómo sabemos cuál de estos
queremos? Podemos obtener una pista ejecutando el programa uname , junto con el parámetro -a para dar la mayor cantidad
de información posible.
I.6_18_Unix_70_UNAME

El programa uname da información sobre el sistema operativo, que en este caso parece ser GNU/Linux para una CPU x86 de 64
bits. Si es probable que alguno de los binarios funcione, será el conjunto “i86linux64”. Vamos a conseguir ese tarball gzip en
el directorio de descargas .
imagen

Tenga en cuenta que en este caso no hemos usado la opción -O para wget , porque el nombre de archivo descrito por la URL
( muscle3.8.31_i86linux64.tar.gz ) es lo que nos gustaría llamar al archivo cuando se descarga de todos modos.
Continuando para desempaquetarlo, encontramos que contiene solo un ejecutable que podemos intentar ejecutar.
I.6_20_Unix_72_Test_Muscle

Debido a que no reportó un error de ejecución, podemos instalarlo copiándolo a nuestro directorio $HOME/local/bin . Al
hacerlo, le daremos un nombre más sencillo, músculo .
I.6_21_UNIX_73_INSTAL_MUSCLE

1.6.4 https://espanol.libretexts.org/@go/page/55206
¡Ahora nuestro alineador múltiple, músculo , está instalado!

Ejercicios
1. Sigue los pasos anteriores para instalar la suite HMMER (de origen) así como muscle (de binarios) en tu directorio
$HOME/local/bin . Asegúrate de poder ejecutarlos desde cualquier lugar (incluso desde tu directorio de inicio)
ejecutando muscle —help y hmmsearch —help . Ambos comandos deben mostrar texto de ayuda en lugar de un
error. Además, comprueba que las versiones que encuentra el shell son de tu directorio home ejecutando qué hmmsearch
y qué músculo .
2. Determine si tiene instaladas las herramientas “NCBI Blast+” buscando el programa blastn . Si están instalados, ¿dónde
están ubicados? Si no están instalados, encuéntrelos e instálelos desde binarios.
3. Instala hoz desde el repo git en https://github.com/najoshi/sickle. Para instalarlo, deberá seguir las instrucciones
personalizadas dentro del archivo README.md . Si no tienes el programa git , está disponible para la instalación binaria
y fuente en http://git-scm.com.

Obtención de datos
Ahora que tenemos el software instalado para nuestro análisis de ejemplo, necesitaremos obtener algunos datos. Suponiendo que no
tenemos datos novedosos con los que trabajar, haremos la siguiente pregunta: ¿podemos identificar, usando HMMER y
músculo , homólogos de genes P450-1A1 en el conjunto de datos de proteínas Drosophila melanogaster? (Este es un ejemplo
inútil, ya que el genoma de D. melanogaster ya está bien anotado.)
El primer paso será descargar el conjunto de datos de D. melanogaster, que podemos encontrar en http://flybase.org, el repositorio
del genoma para genomas de Drosophila. Generalmente, los repositorios genómicos como FlyBase proporcionan conjuntos de
datos completos para su descarga, pero pueden ser difíciles de encontrar. Para comenzar, navegaremos a “Archivos”, luego a
“Releases (FTP)”.
I.6_22_FLYBASE_HOME

A partir de ahí, navegaremos a un lanzamiento reciente, como FB2014_05. Debido a que la información genómica a menudo se
actualiza a medida que se dispone de mejor información, las versiones más nuevas son indicadas por las nuevas versiones
I.6_23_FLYBASE_RECIENTE

A continuación veremos enlaces para especies específicas; trabajaremos con dmel_r6.02 . A menudo es una buena idea
anotar números de lanzamiento específicos o fechas de lanzamiento para los conjuntos de datos que descargues, para una
descripción eventual en las secciones de métodos de cualquier trabajo que escribas que se basen en esos datos.
I.6_24_Flybase_DMEL

El formato más común para la información de secuencia se conoce como FASTA y es el formato que queremos, por lo que el
siguiente paso es navegar al directorio fasta . Otras opciones potencialmente interesantes incluyen los directorios gff y
gtf , que contienen archivos de anotación basados en texto que describen la ubicación y función de los genes en el genoma.
(Estos formatos suelen ser útiles para el análisis de RNA-seq).
I.6_25_Flybase_Fasta

Por último, vemos una variedad de archivos gzip que podemos wget en la línea de comandos. Debido a que estamos interesados
en el conjunto completo de datos de proteínas para esta especie, usaremos la URL para
dmel-todo-traducción-r6.02.fasta.gz .
I.6_26_flybase_all_translation

Antes de ejecutar wget en el archivo, crearemos un directorio de proyectos en nuestro directorio home, y un directorio
p450s dentro de ahí para trabajar.
I.6_27_Unix_74_WGET_DMEL

Debido a que el archivo es gzip, podemos usar gzip -d para descomprimirlo, y luego usar menos -S para ver los
resultados sin las largas líneas envueltas en la ventana del terminal.
I.6_28_Unix_75_GZIP_DMEL

1.6.5 https://espanol.libretexts.org/@go/page/55206
El resultado ilustra el formato estándar para un archivo FASTA. Cada registro de secuencia comienza con una línea que comienza
con un carácter > , y la primera palabra que no contiene espacios en blanco siguiente que se considera el ID de secuencia.
Formato I.6_29_FASTA_

Esta línea podría entonces contener caracteres de espacio en blanco y metadatos. El espacio en blanco comprende una secuencia de
uno o más espacios, tabulaciones (representadas en Unix/Linux como un carácter especial a veces escrito como \ t ), o nuevas
líneas (representadas en Unix/Linux como un carácter especial a veces escrito como \ n ) en una fila.
Las líneas que siguen a la línea de cabecera contienen la información de secuencia, y no hay un formato específico para el número
de líneas sobre las que se puede romper la secuencia, o la longitud de esas líneas. Después de la última línea de secuencia para un
registro, puede comenzar un nuevo registro de secuencia.
Dependiendo de la fuente del archivo FASTA, los IDs o metadatos pueden representar múltiples piezas de datos; en este ejemplo,
los metadatos están separados por espacios y tienen un <label><value>formato =; que es específico de las secuencias de
proteínas de FlyBase.
Para nuestro siguiente truco, descargaremos algunas secuencias de proteínas P450-1A1 de UniProt.org. Uniprot.org es una
conocida base de datos de proteínas, y está compuesta por la base de datos “TremBL” y el subconjunto de TremBL, conocido como
“Swiss-Prot”. Si bien la primera contiene muchas secuencias con anotaciones, muchas de esas anotaciones han sido asignadas por
búsquedas de homología automatizadas y no han sido revisadas. Este último, Swiss-Prot, contiene únicamente secuencias cuyas
anotaciones han sido revisadas manualmente.
Para la descarga, ingresaremos “p450 1A1” en el campo de búsqueda, y filtraremos los resultados solo a aquellos en Swiss-Prot
haciendo clic en el enlace “Revisado”, resultando en 28 coincidencias. A continuación, podemos hacer clic en el botón “Descargar”
para descargar un archivo “FASTA (canónico)” (en lugar de con todas las isoformas incluidas).
I.6_30_Uniprot_Descargar

El sitio web de Uniprot se sometió recientemente a un rediseño, de tal manera que el archivo descargado se transfiere directamente
al navegador web, en lugar de presentarse como una URL a la que se podría acceder con wget . Esto no es un problema, ya que
nos da la oportunidad de discutir cómo transferir archivos entre el sistema remoto y el escritorio local a través de SFTP.
Al igual que SSH, SFTP es un protocolo cliente/servidor común. Siempre que el servidor se esté ejecutando en el equipo remoto
(de hecho, usa el mismo puerto que SSH, puerto 22, porque SSH proporciona la conexión segura), solo necesitamos instalar y
ejecutar un cliente SFTP en nuestro escritorio. Hay muchos clientes SFTP disponibles para Microsoft Windows (por ejemplo,
Core-FTP), OS X (por ejemplo, Cyberduck) y sistemas Linux (por ejemplo, sftp en la línea de comandos o el FileZilla
gráfica). El cliente discutido aquí se llama FireFTP, y está disponible como una extensión para el navegador web Mozilla Firefox
(en sí mismo disponible para Windows, OS X y Linux). Para obtenerlo requiere instalar y ejecutar Firefox, navegar a Herramientas
→ Addons y buscar “FireFTP”.
I.6_31_Fireftp_search

Una vez instalado el plugin (lo que requiere reiniciar Firefox), podremos acceder a él desde el submenú Herramientas →
Desarrollador. Conectar el cliente a un equipo remoto requiere que primero configuremos la conexión seleccionando “Crear una
cuenta”. La información básica requerida incluye un nombre de cuenta, el host al que conectarse (por ejemplo, una dirección IP
como 128.196.64.120 o un nombre de host como files.institution.edu ), así como nuestro nombre de
usuario y contraseña.
I.6_32_FireFTP_Accttab1

También necesitamos decirle al cliente con qué protocolo conectarse, que se realiza en la pestaña “Conexión”; queremos SFTP en
el puerto 22.
I.6_33_FireFTP_Accttab2

Con eso logrado, podemos transferir cualquier archivo de un lado a otro usando las flechas verdes en la interfaz, donde el sistema
de archivos remoto se muestra a la derecha y el sistema de archivos local se muestra a la izquierda. Aquí está el resultado después
de transferir nuestro archivo p450s.fasta .
I.6_34_Fireftp_Transfer

1.6.6 https://espanol.libretexts.org/@go/page/55206
DOS/Windows y Unix/Linux
En su mayor parte, la forma en que se codifica el texto en los sistemas operativos Microsoft (como DOS y Windows) y en sistemas
similares a Unix (como Linux y OS X) es similar. Pero hay una diferencia: cómo se representan los extremos de las líneas, o
“caracteres de nueva línea”. En sistemas tipo Unix, una nueva línea está representada por un solo byte de 8 bits (el carácter “Line
Feed” (NF)): 00001010 . En los sistemas Microsoft, están representados por un par de bytes de 8 bits (“Carriage Return” (CR)
seguido de NF): 0000110100001010 . Esto significa que los archivos de texto creados en archivos operativos de Microsoft
pueden no ser legibles en sistemas similares a Unix, y viceversa.
Afortunadamente, hay utilidades disponibles para convertir entre estos formatos. En la línea de comandos, las utilidades
dos2unix y unix2dos convierten a y desde un formato similar a Unix, respectivamente. Esto no suele ser un problema,
ya que la mayoría de los programas de transferencia de archivos (incluido FireFTP) realizan automáticamente la conversión
adecuada. El archivo de utilidad de línea de comandos también se puede utilizar para determinar el tipo de archivo, incluido
su tipo de nueva línea.

Poniéndolo todo junto


En este punto, hemos obtenido los datos que deseamos procesar, y hemos instalado con éxito el software que pretendemos ejecutar
en esos datos.
I.6_35_Unix_76_LS_P450s

El primer paso es correr músculo en el archivo p450s.fasta para producir una alineación múltiple. Una forma rápida de
identificar cómo se debe ejecutar un programa como el músculo (qué parámetros se necesita) es ejecutarlo sin ningún
parámetro. Alternativamente, podríamos probar las opciones más comunes para obtener ayuda: músculo -h ,
músculo —ayuda , músculo —h , o músculo -ayuda .
I.6_36_Unix_77_Muscle_help

La línea más importante de este texto de ayuda es la información de uso: muscle -in -out <inputfile><outputfile>; los
parámetros entre paréntesis angulares indican que el parámetro es requerido (los parámetros no requeridos suelen aparecer entre
corchetes rectos). El texto de ayuda adicional indica otros parámetros que podríamos optar por agregar. Presumiblemente, podrían
colocarse antes o después de los especificadores de entrada o salida. Entonces, ejecutaremos músculo en nuestro archivo
p450s.fasta , y produciremos un archivo cuyo nombre de archivo indique su pedigrí de alguna manera:
I.6_37_Unix_78_Muscle_CMD

Una vez que el comando


haya terminado de ejecutarse, podremos ver el archivo de alineación con
menos -S p450s.fasta.aln .
imagen

Con una mayor inspección, veríamos que las secuencias se han hecho de la misma longitud mediante la inserción de caracteres de
hueco. El siguiente paso es ejecutar hmmbuild para producir el perfil HMM. Nuevamente, ejecutaremos hmmbuild sin
ninguna opción para obtener información sobre qué parámetros necesita.
I.6_39_UNIX_80_HMMBuild_help

El resultado de ayuda para hmmbuild es más corto, aunque el comando también señala que podríamos ejecutar
hmmbuild -h para obtener información más detallada. La línea de uso, hmmbuild [-options] <hmmfile_out>
<msafile>, indica que se requieren los dos últimos parámetros, y son el nombre del archivo de salida (para el perfil HMM) y el
archivo de entrada de alineación de secuencias múltiples. Los corchetes indican que, antes de estos dos últimos parámetros, se
pueden dar una serie de parámetros opcionales, descritos más adelante en la salida de ayuda. En este caso, <hmmfile_out>y
<msafile>son posicionales: el argumento penúltimo debe especificar la salida, y el último debe especificar la entrada.
I.6_40_Unix_81_HMMBuild_CMD

Después de que termine esta operación, puede ser interesante echar un vistazo al archivo HMM resultante con
menos -S p450s.fasta.aln.hmm . Aquí hay un fragmento:
imagen

1.6.7 https://espanol.libretexts.org/@go/page/55206
Con cierta lectura de documentación, incluso podemos decodificar cómo se representa el perfil probabilístico en esta matriz de
letras y números. Como recordatorio, nuestro directorio de proyectos ahora contiene el archivo de secuencia original, un archivo de
alineación múltiple y el archivo de perfil HMM, así como el archivo de proteínas D. melanogaster en el que deseamos buscar el
perfil.
I.6_42_Unix_83_LS_despué_HMMBuild

En este punto, estamos listos para buscar el perfil en el conjunto de proteínas de D. melanogaster con hmmsearch . Como de
costumbre, primero inspeccionaremos el uso de hmmsearch .
I.6_43_UNIX_84_HMMBUSCAR_AYUDA

Este breve texto de ayuda indica que hmmsearch puede tomar una serie de parámetros opcionales (y tendríamos que ejecutar
hmmsearch -h para verlos), y los dos últimos parámetros son requeridos. Estos dos últimos parámetros constituyen el
archivo de perfil HMM que estamos buscando, así como el <seqdb>en el que buscar. No dice qué formato <seqdb>debe ser,
así que lo probaremos en nuestro archivo FASTA de D. melanogaster y esperamos lo mejor (si falla, tendremos que leer más texto
de ayuda o documentación).
I.6_44_Unix_85_HMMSearch_CMD

Tenga en cuenta que no había ninguna opción requerida para un archivo de salida. La ejecución de este comando hace que se
imprima bastante información en el terminal, incluyendo líneas como:
I.6_45_Unix_86_HMMSearch_out_snippet

Y, cuando ejecutamos ls , encontramos que no se ha creado ningún archivo de salida.


I.6_46_Unix_87_HMMSearch_out_LS

Parece que hmmsearch , por defecto, imprime toda su salida significativa en el terminal. En realidad, hmmsearch está
imprimiendo su salida al flujo de salida estándar. La salida estándar es el mecanismo de salida principal para los programas de
línea de comandos (que no sean escribir archivos directamente). Por defecto, la salida estándar, también conocida como “salida
estándar” o “stdout”, se imprime en el terminal.
Afortunadamente, es posible redirigir el flujo de salida estándar a un archivo indicando esto a nuestro shell con un operador >
redirect, y especificando un nombre de archivo o ruta. En este caso, redireccionaremos la salida de standard out a un archivo
llamado p450s_hmmsearch_dmel.txt .
I.6_47_Unix_88_HMMSearch_redirect

Cuando se ejecuta este comando, no se imprime nada, y en su lugar se crea nuestro archivo. Al usar la redirección > , el archivo
se sobrescribirá si ya existe. Si, en cambio, quisiéramos agregar a un archivo existente (o crear un nuevo archivo si no hay ningún
archivo al que agregar), podríamos haber utilizado la redirección >> .
Aquí están los contenidos de nuestro análisis final, un archivo de texto simple con bastante información, incluyendo algunos datos
de fila y columna muy bien formateados, como se muestra por menos -S p450s_hmmsearch_dmel.txt .
I.6_48_Unix_89_hmmbuscar_menos

Reproducibilidad con scripts


Es muy poco probable que un análisis de este tipo se realice sólo una vez. La mayoría de las veces, desearemos ajustar o
reemplazar los archivos de entrada, comparar con diferentes conjuntos de proteínas o ajustar los parámetros para los programas
ejecutados. Así tiene sentido capturar el análisis que acabamos de realizar como un script ejecutable, quizás llamado
runhmmer.sh .
I.6_49_Unix_90_Runhmmer_1

Tenga en cuenta en lo anterior que hemos roto la línea larga de hmmsearch en dos al terminarla a mitad de camino con una
barra invertida y continuarla en la siguiente línea. La barra invertida le permite a bash saber que se va a especificar más del
comando en líneas posteriores. (La barra invertida debe ser el último carácter de la línea, sin espacios ni tabulaciones siguientes).
Después de hacer este script ejecutable con chmod , podríamos volver a ejecutar el análisis navegando a este directorio y
ejecutándolo . /runhmmer.sh.

1.6.8 https://espanol.libretexts.org/@go/page/55206
¿Y si quisiéramos cambiar el archivo de entrada, digamos, a argonasa-1s.fasta en lugar de p450s.fasta ?
Podríamos crear un nuevo directorio de proyectos para trabajar, copiar este script allí, y luego cambiar todas las instancias de
p450s.fasta en el script a argonase 1s.fasta .
Alternativamente, podríamos usar el poder de las variables de entorno para diseñar nuestro script de tal manera que este proceso
sea más fácil.
I.6_50_Unix_92_Runhmmer_2

Ahora los nombres de archivo de interés se especifican solo una vez, cerca de la parte superior del script, y a partir de entonces el
script utiliza sus propios identificadores (como variables de entorno) para referirse a ellos. Reutilizar este script sería tan sencillo
como cambiar los nombres de archivo especificados en tres líneas.
Podemos ir un paso más allá. Resulta que los scripts de shell pueden tomar parámetros desde la línea de comandos. El primer
parámetro dado a un script en la línea de comandos se almacenará automáticamente en una variable accesible para el script llamada
$1 , el segundo parámetro se almacenará en $2 , y así sucesivamente. Así podemos generalizar aún más nuestro guión:
I.6_51_Unix_93_Runhmmer_3

Podríamos haber reemplazado todas las instancias de $query por $1 , pero esta organización hace que nuestro script sea más
fácil de leer en el futuro, una consideración importante a la hora de programar. Ahora podemos ejecutar un análisis completo
especificando los tres nombres de archivo relevantes en la línea de comandos, como en:
. /runhmmer.sh p450s.fasta dmel-todo-traducción-r6.02.fasta
p450s_hmmsearch_dmel.txt
.
Este runhmmer.sh es un buen candidato para su inclusión en nuestro $ Inicio/local/bin para que podamos
ejecutarlo desde cualquier lugar, aunque es posible que queramos agregar líneas inmediatamente después del #! line, para
proporcionar algún texto de ayuda para cualquier persona que intente ejecutar el script sin proporcionar las entradas correctas:
imagen

El “if block” anterior solo se ejecutará si el número de parámetros dados ( $# ) no es igual a 3. Aunque lenguajes como Python
proporcionan facilidades mucho más agradables para este tipo de ejecución basada en lógica, la capacidad de proporcionar
condicionalmente información de uso para scripts es importante. Como es habitual para bash , el intérprete ignora líneas que
comienzan con # .

Ejercicios
1. Crea una nueva carpeta en tu carpeta de proyectos llamada c_elegans . Localice el archivo FASTA para el genoma
de referencia de Caenorhabditis elegans de http://wormbase.org, y descárguelo a esta carpeta usando wget . El archivo que
estás buscando se llamará algo así como C_Elegans.prjna13758.ws244.genomic.fa.gz . Después de que se
descargue, descomprima y visualícelo con menos -S .
2. Instale un cliente SFTP en su escritorio, como FireFTP o CyberDuck, e intente conectarse a la misma máquina en la que inicie
sesión a través de SFTP. Descargue un archivo FASTA de algunas secuencias potencialmente homólogas de Uniprot a su
escritorio local y transfiéralo a su directorio remoto c_elegans .
3. Intenta ejecutar muscle y HMMER en las secuencias que descargaste de uniprot.org contra el genoma de C. elegans.
4. Si tiene acceso a más de una máquina basada en Unix (como una computadora de escritorio OS X y una computadora Linux
remota, o dos computadoras Linux remotas), lea la página man para scp con man scp , y también lea sobre ello en línea.
Intente transferir un archivo y un directorio entre máquinas usando scp en la línea de comandos.
5. Escribe un script bash ejecutable que automatice un proceso de algún tipo, e instálalo en tu $HOME/local/bin .
Pruebalo después de cerrar la sesión y volver a entrar.

1. Si tiene privilegios de administrador en la máquina, también están disponibles repositorios de software seleccionados con
muchos paquetes. Dependiendo del sistema, si inicia sesión como root , instalar HMMER puede ser tan simple como
ejecutar apt-get install hmmer o yum install hmmer .
2. Una herramienta similar llamada curl se puede utilizar para el mismo propósito. Los conjuntos de características son
ligeramente diferentes, por lo que en algunos casos se prefiere curl sobre wget y viceversa. Para las tareas simples de

1.6.9 https://espanol.libretexts.org/@go/page/55206
descarga en este libro, o bien bastará.
3. La utilidad gzip es uno de los pocos programas que se preocupan por las extensiones de archivo. Si bien la mayoría de los
programas funcionarán con un archivo de cualquier extensión, gzip requiere un archivo que termine en .gz . Si no está
seguro del tipo de un archivo, la utilidad de archivo puede ayudar; por ejemplo, file hmmer-3.1b1.tar.gz
informa que el archivo es gzip -compressed data, y lo haría incluso si el archivo no terminara en .gz .
4. No es estrictamente necesario volver a cerrar sesión y volver a entrar; cuando se trabaja en bash, ejecutar hash -r hará que
el shell actualice su lista de software que se encuentra en $PATH .

This page titled 1.6: Instalación de software (Bioinformática) is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated
by Shawn T. O’Neil (OSU Press) .

1.6.10 https://espanol.libretexts.org/@go/page/55206
1.7: Línea de comandos BLAST
Si bien los capítulos anteriores cubrieron la instalación y el uso de algunas herramientas bioinformáticas como ejemplos del
proceso, hay una herramienta casi ubicua: BLAST, o Herramienta de búsqueda de alineación local básica. [1]
Dadas una o más secuencias de consulta (generalmente en formato FASTA), BLAST busca regiones de secuencia coincidentes
entre ellas y un conjunto de sujetos.
I.7_1_BLAST_Ilustración

Una coincidencia suficientemente cercana entre subsecuencias (denotada por flechas en la figura anterior, aunque las coincidencias
suelen ser más largas de lo que se ilustra aquí) se denomina par de puntuación alta (HSP), mientras que se dice que una secuencia
de consulta golpea una secuencia objetivo si comparten una o más HSP. En ocasiones, sin embargo, el término “hit” se emplea de
manera floja, sin diferenciar entre ambos. Cada HSP está asociado con una “puntuación de bits” que se basa en la similitud de las
subsecuencias según lo determinado por un conjunto particular de reglas. Debido a que en conjuntos de sujetos más grandes es
probable que se encuentren algunas buenas coincidencias por casualidad, cada HSP también está asociado con un “valor E”, que
representa el número esperado de coincidencias que uno podría encontrar por casualidad en un conjunto de sujetos de ese tamaño
con esa puntuación o mejor. Por ejemplo, un valor E de 0.05 significa que podemos esperar una coincidencia por casualidad en 1 de
cada 20 búsquedas similares, mientras que un valor E de 2.0 significa que podemos esperar 2 coincidencias por casualidad para
cada búsqueda similar.
BLAST no es una sola herramienta, sino más bien un conjunto de herramientas (y la suite crece a lo largo de los años a medida que
se agregan más funciones y herramientas relacionadas). La versión más moderna del software, llamada BLAST+, es mantenida por
el Centro Nacional de Información Biotecnológica (NCBI) y puede descargarse en formato binario y fuente en
ftp://ftp.ncbi.nlm.nih.gov/blast/exe...blast+/LATEST/.
Este capítulo solo cubre brevemente la ejecución de BLAST en la línea de comandos de formas simples. Se recomienda leer la
información de ayuda (por ejemplo, con blastn —help ) y el Manual de usuario de aplicaciones de línea de comandos NCBI
BLAST en www.NCBI.nlm.nih.gov/books/nbk1763/. El manual del NCBI cubre bastantes características potentes y prácticas de
BLAST en la línea de comandos que este libro no hace.

Tipos BLAST
Los programas de la suite BLAST+ pueden buscar y contra secuencias en formato proteico (como hicimos para el ejemplo de
HMMER) y en formato de nucleótidos (A's, C's, T's y G's). Dependiendo de qué tipo sean los conjuntos de consulta y tema, se
utilizan diferentes programas BLAST.
I.7_2_BLAST_TYPES

Si bien dos secuencias de nucleótidos (comparaciones de N en la figura anterior) pueden compararse directamente (al igual que dos
secuencias de proteínas, representadas por P), cuando se desea comparar una secuencia de nucleótidos con una secuencia de
proteína, debemos considerar qué marco de lectura de la secuencia de nucleótidos corresponde a una proteína. Los programas
blastx y tblastn hacen esto convirtiendo secuencias de nucleótidos en secuencias de proteínas en los seis marcos de
lectura (tres en la cadena de ADN directo y tres en el reverso) y comparando con todos ellos. Generalmente tales programas dan
como resultado seis veces más trabajo por hacer. El programa tblastx compara consultas de nucleótidos contra sujetos
nucleotídicos, pero lo hace en el espacio proteico con las seis conversiones en comparación con las seis en ambos lados.
Otras herramientas BLAST más exóticas incluyen psiblast , que produce una búsqueda inicial y ajusta las reglas de
puntuación sobre la base de los resultados; estas reglas de puntuación ajustadas se utilizan en una segunda búsqueda, generalmente
encontrando aún más coincidencias. Este proceso se repite tantas veces como el usuario desee, revelándose más coincidencias
diferentes en iteraciones posteriores. El programa deltablast considera una base de datos precalculada de reglas de
puntuación para diferentes tipos de secuencias comúnmente encontradas (conservadas). Finalmente, rpsblast busca
coincidencias de secuencia contra conjuntos de perfiles, cada uno representando una colección de secuencias (como en HMMER,
aunque no basado en modelos ocultos de Markov).
Toda esta plática de reglas de puntuación indica que las reglas de puntuación específicas son importantes, especialmente cuando se
comparan dos secuencias de proteínas. Al comparar secuencias de proteínas de dos especies similares, por ejemplo, podríamos
desear dar una puntuación pobre a la coincidencia relativamente improbable de una valina no polar (V) con una tirosina polar (Y).

1.7.1 https://espanol.libretexts.org/@go/page/55187
Pero para especies disímiles separadas por un vasto tiempo evolutivo, tal desajuste podría no ser tan malo en relación con otras
posibilidades. Las matrices de puntuación que representan estos conjuntos de reglas con nombres como BLOSUM y PAM se han
desarrollado utilizando una variedad de métodos para capturar estas consideraciones. Una discusión de estos detalles se puede
encontrar en otras publicaciones.
Cada uno de los diversos programas de la suite BLAST acepta una gran cantidad de opciones; intente ejecutar blastn -help
para verlas para el programa blastn . Aquí hay un resumen de algunos parámetros que se utilizan más comúnmente para
blastn et al. :
-consulta <fasta file>
El nombre (o ruta) del archivo con formato FASTA que se va a buscar como secuencias de consulta.
-sujeto <fasta file>
El nombre (o ruta) del archivo con formato FASTA en el que se busca como secuencias de asunto.
-evalue <real number>
Solo deben reportarse los HSP con valores de E menores a este. Por ejemplo: -evalue 0.001 o -evalue 1e-6
.
-outfmt <integer>
Cómo formatear la salida. El valor predeterminado, 0 , proporciona un archivo de texto legible por humanos (pero no
programáticamente analizable). Los valores 6 y 7 producen filas y columnas separadas por tabuladores en un archivo
de texto, con 7 proporcionando líneas de comentario explicativas. De manera similar, un valor de 10 produce una
salida separada por comas; 11 produce un formato que luego se puede convertir rápidamente en cualquier otro con otro
programa llamado blast_formatter . Las opciones 6 , 7 y 10 se pueden configurar altamente en términos de
qué columnas se muestran.
-max_target_seqs <integer>
Cuando el formato de salida es 6 , 7 o 10 para cada secuencia de consulta, solo informa HSP para las mejores
<integer>secuencias de sujetos diferentes.
-max_hsps <integer>
Para cada par de consultas/objetivo, solo reportar los mejores <integer>HSP.
-out <output file>
Escriba la salida en <output file>oposición a la predeterminada de la salida estándar.

Bases de datos BLAST


Sin duda los lectores familiarizados con BLAST han sido curiosos: ¿no hay bases de datos de algún tipo involucradas en las
búsquedas BLAST? No necesariamente. Como hemos visto, los archivos FASTA simples serán suficientes tanto para el conjunto
de consulta como para el conjunto de temas. Resulta, sin embargo, que desde una perspectiva computacional, no se buscan
fácilmente archivos FASTA simples. Por lo tanto, BLAST+ proporciona una herramienta llamada makeblastdb que
convierte un archivo FASTA sujeto en una versión indexada y de búsqueda rápida (pero no legible por humanos) de la misma
información, almacenada en un conjunto de archivos con nombres similares (a menudo al menos tres que terminan en .pin
, .psq y .phr para las secuencias de proteínas, y .nin , .nsq y .nhr para las secuencias de nucleótidos). Este
conjunto de archivos representa la “base de datos”, y el nombre de la base de datos es el prefijo de nombre de archivo compartido
de estos archivos.
Base de datos I.7_3_BLAST_

Ejecutar makeblastdb en un archivo FASTA es bastante sencillo:


makeblastdb -in -out -dbtype -title -parse_seqids <fasta file><database name><type><title>, donde
<type>es uno de prot o nucl , y <title>es un human- título legible (encerrado entre comillas si es necesario). El
indicador -parse_seqids indica que los ID de secuencia del archivo FASTA deben incluirse en la base de datos para que
puedan ser utilizados en salidas así como por otras herramientas como blastdbcmd (discutido más adelante).
Una vez creada una base de datos BLAST, se pueden usar otras opciones con blastn et al. :

1.7.2 https://espanol.libretexts.org/@go/page/55187
-db <database name>
El nombre de la base de datos para buscar (a diferencia de usar -subject ).
-num_hilos <integer>
Utilice <integer>núcleos de CPU en un sistema multinúcleo, si están disponibles.
Al usar la opción -db , las herramientas BLAST buscarán los archivos de la base de datos en tres ubicaciones: (1) el directorio
de trabajo actual, (2) su directorio home y (3) las rutas especificadas en la variable de entorno $BLASTDB .
blastdbcmd se puede utilizar para obtener información sobre bases de datos BLAST, por ejemplo, con
La herramienta
blastdbcmd -db -info <database name>y puede mostrar las bases de datos en una ruta dada con
blastdbcmd -list <path>(así, blastdbcmd -list $BLASTDB mostrará el bases de datos que se encuentran en
las rutas de búsqueda predeterminadas). Esta herramienta también se puede utilizar para extraer secuencias o información sobre
ellas de bases de datos basadas en información como los IDs reportados en los archivos de salida. Como siempre, es muy
recomendable leer la ayuda y documentación para software como BLAST.

Ejecución de una autoexplosión


Para poner en práctica estas diversas herramientas y opciones, consideremos usar blastp para buscar proteínas que sean
similares en secuencia a otras proteínas en el exoma de levadura. Primero, necesitaremos usar wget para descargar el conjunto
de datos de proteínas (después de localizarlo en http://yeastgenome.org), y luego gzip -d para descomprimirlo, llamándolo
orf_trans.fasta .
imagen

Para encontrar secuencias que sean similares a otras, vamos a querer voltear este archivo contra sí mismo. Entonces,
comenzaremos por crear una base de datos de estas secuencias.
I.7_5_Unix_94_2_MakeblastDB

Ahora necesitamos determinar qué opciones usaremos para el blastp . En particular, ¿queremos limitar el número de HSP y
secuencias diana reportadas para cada consulta? Debido a que en su mayoría estamos interesados en determinar qué proteínas
coinciden con otras, probablemente solo necesitemos mantener un golpe. ¡Pero el mejor golpe de cada proteína probablemente será
para sí misma! Así que será mejor que mantengamos los dos primeros con -max_target_seqs 2 y solo el mejor HSP por
hit con -max_hsps 1 . También usaremos un -evalue 1e-6 , un corte de uso común. [2]
Para la salida, crearemos una salida separada por tabulaciones con líneas de comentario ( -outfmt 7 ), creando columnas para
el ID de secuencia de consulta, ID de secuencia de sujeto, longitud de alineación HSP, porcentaje de identidad de la alineación,
longitud de secuencia de sujeto, longitud de secuencia de consulta, posiciones de inicio y final en la consulta y sujeto, y el valor E.
(Los nombres codificados —qseqid , sseqid , length , etc.—se pueden encontrar ejecutando blastp -help .)
Finalmente, llamaremos al archivo de salida yeast_blastp_yeast_top2.txt y usaremos cuatro procesadores para
acelerar el cálculo (lo que realmente solo ayudará si la máquina en la que estamos registrados tiene al menos tantos).
I.7_6_Unix_94_3_Runblast

¡Es un comando largo, para estar seguro! Esta operación tarda varios minutos en terminar, incluso con -num_threads 4
especificado. Cuando termine, podemos ver con menos que el archivo de salida contiene las columnas que especificamos
intercaladas con las líneas de comentario proporcionadas por -outfmt 7 .
I.7_7_Unix_94_4_Blastout

En el fragmento de salida anterior, YAL0005C tiene un HSP consigo mismo (naturalmente), pero también uno con YLL024C.
Consideraremos análisis básicos que contienen este tipo de datos —filas y columnas almacenadas en archivos de texto, intercaladas
con líneas extrañas— en capítulos posteriores.

Ejercicios
1. Si aún no tiene instaladas las herramientas NCBI Blast+, instálelas. Una vez que lo haga, verifique el contenido de la variable
de entorno $BLASTDB . Si no está vacía, use blastdbcmd para determinar si tiene la base de datos “nr” disponible, y
cualquier información que pueda determinar al respecto (cuándo se descargó, cuántas secuencias tiene, etc.)

1.7.3 https://espanol.libretexts.org/@go/page/55187
2. Crea una nueva carpeta en tu carpeta de proyectos llamada blast . En este directorio, descargue el archivo
p450s.fasta y el exoma de levadura orf_trans.fasta de la página web del libro. Cree una base de datos
llamada orf_trans usando makeblastdb y use blastp para buscar el archivo p450s.fasta contra él. Al
hacer la búsqueda, use un corte de valor E de 1e-6 , mantenga las secuencias objetivo principales y produzca un archivo de
salida llamado p450s_blastp_yeast_top1.blast en formato de salida 11 .
3. Utilice la herramienta blast_formatter para convertir el archivo de formato de salida 11 anterior a un formato de
salida 6 llamado p450s_blastp_yeast_top1.txt , con columnas para: (1) ID de búsqueda de consulta, (2) ID de
secuencia de sujeto, (3) Longitud de secuencia de sujeto, (4) Porcentaje de coincidencias idénticas, (5) E Valor, (6) Cobertura de
Consulta por Asunto, y (7) Título de Materia. (Puede encontrar que navegar por el manual de NCBI BLAST+ y la salida de
blast_formatter -help es informativo.) La salida, cuando se ve con menos -S , debería verse algo así:
I.7_8_Unix_94_5_Blastout ¿Qué representan estas diversas columnas de salida?

4. El archivo yeast_selected_ids.txt contiene una columna de 25 IDs identificados como interesantes de alguna
manera. Use blastdbcmd para extraer solo esos registros de secuencia de la base de datos orf_trans como un
archivo FASTA llamado yeast_selected_ids.fasta . (Nuevamente, navegar por el manual BLAST+ y la salida de
blastdbcmd -help será útil.)

1. El artículo original de BLAST es de Stephen F. Altschul, Warren Gish, Webb Miller, Eugene W. Myers y David J. Lipman,
“Basic Local Alignment Search Tool”, Journal of Molecular Biology 215 (1990): 403 — 404. Estoy convencido de que
nombrar herramientas bioinformáticas emocionantes verbos como BLAST y HMMER resulta en un mayor uso.
2. Usar un límite de valor E de 1e-6 puede no ser la mejor opción; al igual que con todos los análisis, algunas evaluaciones o
orientación bibliográfica pueden ser necesarias, dependiendo de la tarea en cuestión.

This page titled 1.7: Línea de comandos BLAST is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

1.7.4 https://espanol.libretexts.org/@go/page/55187
1.8: Las corrientes estándar
En capítulos anteriores, aprendimos que los programas pueden producir salida no solo escribiendo en archivos, sino también
imprimiéndolos en el flujo de salida estándar. Para ilustrar mejor esta característica, hemos creado un programa simple llamado
fasta_stats que, dado un nombre de archivo FASTA como su primer parámetro, produce estadísticas sobre cada secuencia.
También veremos el archivo Pz_CDNAS.fasta , que contiene un conjunto de 471 secuencias de transcripción ensambladas de
novo de Papilio zelicaon, y pz_CDNAS_Sample.fasta , que contiene solo las dos primeras.
imagen

Podemos ejecutar fasta_stats


el programa (después de hacerlo ejecutable) con
. /fasta_stats pz_cdnas_muestra.fasta .
I.8_2_UNIX_96_FASTA_Stats_sample

Con base en la información impresa, parece que la secuencia PZ7180000031590 tiene un contenido de GC (porcentaje de la
secuencia compuesta por caracteres G o C) de 37.8%, tiene 486 pares de bases de largo, la secuencia de cinco pares de bases más
común es ACAAA (ocurriendo 5 veces), y la repetición perfecta más larga tiene una longitud de 10 pares de bases, causada por el
pentanucleótido ATTTA , ocurriendo dos veces.
Al igual que hmmsearch , este programa escribe su salida a la salida estándar. Si quisiéramos guardar los resultados, sabemos
que podemos redirigir la salida de standard out con el > redirect.
I.8_3_Unix_97_Fasta_Stats_Sample_redirect

Cuando ejecutamos este comando, sin embargo, vemos que aunque se haya creado el archivo de salida, ¡todavía se imprime texto
en la terminal! Si usamos menos -S para ver el archivo pz_sample_stats.txt , vemos que parte de la salida ha ido
al archivo.
I.8_4_Unix_98_Fasta_Stats_Sample_Stdout_less

Entonces, ¿qué está pasando? Resulta que los programas pueden producir salida (que no sea escribir en archivos) en dos flujos. Ya
estamos familiarizados con la primera salida estándar, que por defecto se imprime en el terminal pero se puede redirigir a un
archivo con > . El segundo, llamado error estándar, también se imprime por defecto en el terminal pero no se redirige con > .
Por defecto, al igual que la salida estándar, el error estándar (también conocido como “error estándar” o “stderr”) se imprime en el
terminal.
Debido a que el error estándar generalmente contiene información de diagnóstico, es posible que no nos interese capturarla en su
propio archivo. Aún así, si lo deseamos, bash puede redirigir el error estándar a un archivo usando la redirección 2> . [1]
I.8_5_Unix_99_Fasta_Stats_Sample_Stdout_Stderr

Podríamos representar gráficamente los programas y su producción como flujos de información alternativos:
I.8_6_Central_Dogma_1

Líneas de filtrado, entrada estándar


A menudo puede ser útil extraer líneas de un archivo basado en un patrón. Por ejemplo, el archivo pz_sample_stats.txt
contiene información sobre lo que describe cada columna, así como los datos en sí. Si queremos extraer todas las líneas que
coinciden con un patrón en particular, digamos, unit: , podemos usar la herramienta grep (para Búsqueda global de
Expresión Regular e Impresión), que imprime en líneas de salida estándar que coinciden con un patrón dado (o no coinciden con un
patrón dado, si se usa el -v bandera): grep '' <pattern><file>. Para ilustrar, primero ejecutaremos fasta_stats en
el archivo de entrada completo, redirigiendo la salida estándar a un archivo llamado pz_stats.txt .
I.8_7_Unix_100_Fasta_Stats_All_Out

Al mirar el archivo con menos -S pz_stats.txt , podemos ver que las líneas informativas así como las líneas que
contienen datos se almacenan en el archivo:
I.8_8_Unix_101_Fasta_Stats_all_less

1.8.1 https://espanol.libretexts.org/@go/page/55192
Para deshacernos de las líneas informativas, podemos usar grep para extraer las otras líneas buscando algún patrón que tengan
en común; en este caso, la unidad de patrón: servirá. Debido a que grep imprime sus resultados a la salida estándar,
necesitaremos redirigir la salida modificada a un archivo llamado, quizás, pz_stats.table para indicar su naturaleza
tabular.
I.8_9_Unix_102_Fasta_Stats_Table

Esta vez, menos -S pz_stats.table revela solo las líneas deseadas.


I.8_10_Unix_103_Fasta_Stats_Tabla_menos

En lugar de ver el archivo con menos , también podemos contar el número de líneas presentes en el archivo con la herramienta
wc , que cuenta el número de líneas, palabras y caracteres de entrada: wc <file>.
Trabajar con la tabla de datos limpiados revela que nuestro programa produjo 21,131 caracteres divididos en 3,297 palabras entre
471 líneas de salida de datos.
I.8_11_Unix_104_Fasta_Estados_Tabla_WC

Este tipo de análisis basado en la línea de comandos puede ser bastante potente, especialmente porque muchos de estos programas,
como less , grep y wc , pueden imprimir sus resultados en la salida estándar y leer la entrada desde una entrada estándar
en lugar de desde un archivo . La entrada estándar es el mecanismo de entrada secundario para programas de línea de comandos
(aparte de leer directamente desde archivos). Por defecto, la entrada estándar, o “stdin”, no se utiliza.
¿Cómo podemos obtener entrada a un programa en su entrada estándar? Resulta que la forma más fácil de hacerlo es redirigir la
salida estándar de otro programa a él usando la redirección | , también conocida como “pipe”, (que se encuentra encima de la
tecla Enter en la mayoría de los teclados). En este caso, los datos vienen en “a la izquierda”:
I.8_12_Central_Dogma_2

Para conducir esta casa, primero eliminaremos nuestro archivo pz_stats.table , y luego volveremos a ejecutar nuestro
grep for unit: en el archivo pz_stats.txt , pero en lugar de enviar el resultado de grep a un archivo con la
redirección > , lo dirigiremos directamente a la entrada estándar de wc con un | redirigir.
I.8_13_Unix_105_Fasta_Estados_GREP_WC

En este ejemplo, no hemos creado un nuevo archivo ni especificado un archivo para que wc lea; los datos se almacenan en un
búfer temporal que es manejado automáticamente por el shell y el sistema operativo. El programa menos también puede leer
desde la entrada estándar, así que si quisiéramos ver el contenido del grep sin crear un nuevo archivo, podríamos ejecutar
grep 'unit: 'pz_stats.txt | less -S .
Recordemos que el programa fasta_stats escribió su salida a standard out, y debido a que grep también puede leer
desde la entrada estándar, podemos procesar todo el archivo FASTA sin necesidad de crear ningún archivo nuevo mediante el uso
de múltiples búferes de este tipo:
I.8_14_Unix_106_Fasta_Stats_GREP_WC2

Cuando se ejecuta este comando, los resultados impresos por fasta_stats sobre error estándar seguirán siendo impresos en
el terminal (ya que ese es el valor por defecto y no redireccionamos error estándar), pero los resultados de salida estándar se
filtrarán a través de grep y luego se filtrarán a través de wc , produciendo la salida final de 471 líneas.
En este punto, la naturaleza larga de los comandos y el hecho de que nuestra ventana terminal sólo sea tan amplia están
dificultando la lectura de los comandos que estamos produciendo. Entonces, comenzaremos a romper los comandos sobre múltiples
líneas al terminar los comandos parciales con barras inversas. Al igual que en los scripts de shell que escribimos al final del
capítulo 6, “Instalar (Bioinformática) Software”, el uso de barras inversas permitirá al shell saber que no hemos terminado de
ingresar al comando. Sin embargo, el shell bash indica que un comando abarca múltiples líneas mostrándonos un > , lo que
no debe confundirse con el carácter de redireccionamiento que podríamos escribir nosotros mismos. En el siguiente ejemplo se
muestra exactamente el mismo comando en una forma más legible disgregada sobre varias líneas, pero los caracteres resaltados no
se han escrito.
imagen

1.8.2 https://espanol.libretexts.org/@go/page/55192
Una cadena de comandos como la anterior, separada por caracteres de tubería, a menudo se llama “tubería”. Sin embargo, de
manera más general, una canalización puede describir cualquier serie de pasos, desde los datos de entrada hasta los datos de salida
(como en la serie Muscle/HMMER cubierta en el capítulo 6).

Contando repeticiones AT simples


Ampliemos la pequeña tubería de arriba para inspeccionar solo las repeticiones AT “simples”, es decir, aquellas que son la cadena
“AT” repetida una o más veces. Podemos comenzar con lo que tenemos, pero en lugar de solo buscar unidad: , modificaremos
el patrón para encontrar unidad:AT , y veremos qué obtenemos:
I.8_16_Unix_108_Unidad_AT_1

El resultado resultante es cercano a lo que esperábamos, pero no del todo completo, ya que este patrón también coincide con cosas
como unit:ATT y Unit:ATG .
I.8_17_Unix_109_unidad_at_1_out

Probablemente queremos filtrar más la salida, pero ¿en base a qué patrón? En este caso, aquellas líneas que coinciden no sólo con
unidad:AT , sino también con el término dinucleótido . En lugar de intentar producir un solo patrón complicado que
haga este trabajo en un solo grep , podemos agregar otra llamada grep a la tubería.
I.8_18_Unix_110_Unidad_AT_2_CMD

Este comando da como resultado la salida que queremos:


I.8_19_UNIX_111_UNIT_AT_2_out

En lugar de ejecutar los resultados a través de menos -S , podríamos usar wc para contar las repeticiones AT simples
(dinucleótidos). Aquí hay un concepto importante en juego, el del desarrollo iterativo, la idea de que a medida que nos acercamos a
una solución, inspeccionamos los resultados y repetimos según sea necesario. El desarrollo iterativo es una buena estrategia para
muchas áreas de la vida, pero es esencial y omnipresente en la computación.
Una vez que hayamos decidido que nos gusta el pequeño proceso computacional que hemos creado, podríamos decidir
encapsularlo y hacerlo repetible como un script shell, quizás llamado count_ATs.sh .
I.8_20_Unix_112_unit_at_script

El script anterior deberá hacerse ejecutable y colocarse en una ubicación referenciada por $PATH , al igual que el programa
fasta_stats .

Ejercicios
1. Usa grep y wc para determinar cuántas secuencias hay en el archivo orf_trans.fasta sin crear ningún archivo
temporal.
2. ¿Cuántos encabezados de secuencia en el archivo orf_trans.fasta tienen el término “polimerasa”?
3. Algunos de los encabezados de secuencia en orf_trans.fasta tienen la frase “ORF verificado” para indicar que el
marco de lectura abierto ha sido verificado experimentalmente. Algunos también tienen el término “complemento inverso” para
indicar que el ORF (marco de lectura abierto) está presente en la secuencia inversa del complemento de la descripción del
genoma canónico. ¿Cuántas secuencias son ORF verificadas y no están en el complemento inverso?
4. Los encabezados de secuencia en orf_trans.fasta tienen información sobre el cromosoma del que se originan, como
Chr I o Chr II . ¿Cuántas secuencias hay presentes en el cromosoma I?

1. Desafortunadamente, los shells tcsh y csh no pueden redirigir de forma nativa por separado stdout y stderr a los
archivos. Una posible calzada se ve así:
(. /fasta_stats pz_cdnas_sample.fasta > pz_sample_stats.txt) > &
pz_sample_stats.err.txt
. Este comando ejecuta dos redirecciones independientes; el uso de paréntesis hace que la redirección de stdout ocurra primero,
luego la redirección adicional de stderr puede ocurrir a continuación. Cómo los shells compatibles con bash manejan la
salida estándar y el error estándar es una de las principales razones por las que se prefieren sobre los shells más antiguos
compatibles con csh .

1.8.3 https://espanol.libretexts.org/@go/page/55192
This page titled 1.8: Las corrientes estándar is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

1.8.4 https://espanol.libretexts.org/@go/page/55192
1.9: Clasificación, Primera y Última Líneas
Continuando con los ejemplos fasta_stats del capítulo 8, “The Standard Stream”, la séptima columna de la salida contiene
la longitud de la repetición perfecta más larga, en caracteres.
I.9_1_UNIX_113_UNIT_AT_Script

¿Qué secuencia contiene la repetición perfecta más larga? Para responder a esta pregunta, podríamos considerar ordenar las líneas
de acuerdo a esta séptima columna. (Primero, tendremos que eliminar las propias líneas de cabecera, lo que podemos lograr
filtrando líneas que coincidan con # usando la bandera -v de grep, o grepping para unit: , como en el capítulo 8.) Ingresa
sort , que s orta líneas de un archivo de texto (o de entrada estándar) por columnas especificadas: sort <file>or
... | sort .
Por defecto, ordena por todas las columnas, comparando las líneas completas en “diccionario” u orden lexicográfico. Para
ordenar por columnas específicas, necesitamos usar una sintaxis bastante sofisticada. Ilustraremos con una figura.
I.9_2_sort_sintaxis

La utilidad sort toma muchos parámetros potenciales, aunque los más importantes son los parámetros -k que especifican
las columnas por las cuales ordenar y cómo se debe hacer esa clasificación, y ocasionalmente el indicador -u . Los parámetros
-k (clave) se consideran en orden; lo anterior especifica que la clasificación debe hacerse en las columnas 2 a 4 (conglomeradas
en una sola “columna”), considerándolas en orden de diccionario, y ordenándolas a la inversa. En el caso de los vínculos, sólo la
primera columna se considera en orden normal de diccionario, y en el caso de vínculos adicionales, la quinta columna se considera
en orden numérico. [1] (La diferencia entre el orden n y g es que g puede manejar entradas en notación científica como
1e-6 , pero generalmente se prefiere n porque es más rápido y no está sujeto a pequeños errores de redondeo).
El indicador opcional -u (que puede especificarse antes o después de las claves, o incluso mezclarse) especifica que después de
que se consideren todas las claves, si todavía hay vínculos entre filas, entonces solo se debe emitir la primera fila. Solo genera
líneas “únicas” de acuerdo con el orden general de clasificación.
Por defecto, sort utiliza espacios en blanco como separador de columnas, aunque se puede cambiar (ejecutar man sort
para obtener más información). Para ver información sobre la repetición perfecta más larga, usaremos sort -k7,7nr ,
indicando que deseamos ordenar solo en la séptima columna, en orden numérico inverso.
I.9_3_UNIX_114_SORT_LARGEST_REPETIR

Las primeras líneas de salida indican que la repetición perfecta más larga es de 94 bases de largo y ocurre en la secuencia
PZ805359 (el contenido GC de esta secuencia es 0 , porque está compuesta completamente por una repetición AT larga).
I.9_4_UNIX_115_SORT_LONGEST_REPETIR_OUT

Los resultados también indican que hay una serie de lazos; varias secuencias contienen repeticiones perfectas de longitud 18 pares
de bases. Si solo quisiéramos reportar una secuencia por diferente longitud de repetición, podríamos intentar
ordenar -k7,7nr -u . Alternativamente, podríamos modificar nuestro tipo para ordenar secundariamente por contenido
GC (segunda columna), ordenar -k7,7nr -k2,2g .
Un truco útil es realizar dos tipos: uno que inicialmente ordena los datos según los criterios que se deseen, y otro que obtenga solo
la primera línea de un grupo en función de criterios secundarios. Es posible que deseemos reportar solo la secuencia de contenido
de GC más alta por diferente longitud de repetición, por ejemplo. Inicialmente podemos usar un sort -k7,7nr -k2,2gr
para ordenar por longitud de repetición y romper lazos por contenido de GC, dejando las secuencias de mayor contenido de GC en
la parte superior. A partir de ahí, podemos usar un sort -k7,7nr -u para reordenar los datos (¡aunque ya estén
ordenados!) por la séptima columna, manteniendo solo la línea superior por longitud de repetición.
I.9_5_Unix_116_Double_sort_1

Salida:
I.9_6_Unix_117_double_sort_1_out

Sin embargo, hay una pequeña preocupación: ¿cómo podemos estar seguros de que nuestro cuidadoso pedido por contenido de GC
no se deshizo en el segundo tipo? Después de todo, el segundo tipo sería técnicamente libre para reordenar los lazos de acuerdo con
la séptima columna, resultando en una salida incorrecta. Hay una bandera adicional para sort , la bandera -s , que indica que

1.9.1 https://espanol.libretexts.org/@go/page/55201
se debe usar la clasificación estable. La clasificación estable significa que, en el caso de las ataduras, los elementos se dejan en su
orden original. Entonces, para estar seguros, podríamos usar una especie secundaria de tipo -k7,7nr -u -s , aunque una
lectura cuidadosa de la documentación para sort indica que en la mayoría de los sistemas la bandera -u implica la bandera
-s .

Primera y Última Línea


A menudo deseamos extraer de un archivo (o de una entrada estándar) las primeras o últimas líneas. La cabeza y la cola
de las herramientas están diseñadas para hacer exactamente esto, y en combinación con otras herramientas son sorprendentemente
útiles. La herramienta cabezal extrae las primeras líneas de un archivo o entrada estándar: head -n <number><file>o
... | head -n <number>. La herramienta cola extrae las últimas líneas de un archivo o entrada estándar: tail -n
<number><file>o ... | tail -n <number>.
Las utilidades de cabeza y cola , como las otras cubiertas anteriormente, escriben su salida en la salida estándar, y así se
pueden usar dentro de tuberías. A menudo se emplean para inspeccionar el inicio o el final de un archivo (para verificar resultados
o formatear). También suelen extraer conjuntos de datos de prueba. Por ejemplo,
head -n 40000 input.fastq > test.fastq extraería los primeros 10,000 registros de secuencia de
input.fastq y produciría test.fastq (porque cada cuatro líneas de un archivo de secuencia FASTQ representa
información para una sola secuencia).
I.9_7_UNIX_118_FASTQ_Muestra

Lo anterior muestra las primeras 12 líneas de un archivo FASTQ generado en un Illumina HiSeq 2000. La primera línea de cada
conjunto de cuatro representa un identificador para la secuencia leída, la segunda línea contiene la secuencia misma, la tercera línea
a menudo no se usa (conteniendo solo un + , aunque puede ser seguida por el identificador y otros datos opcionales), y la cuarta
línea contiene la “calidad” de cada base en la secuencia codificada como un carácter. (La codificación ha variado en el pasado, pero
en los últimos años, las compañías de secuenciación han estandarizado la codificación utilizada por las máquinas de secuenciación
Sanger).
Con un poco de sintaxis modificada, tail también se puede usar para extraer todas las líneas de un archivo comenzando en una
línea dada. Como ejemplo, tail -n +5 input.fastq > test.fastq resultaría en test.fastq teniendo todo
menos el primer registro de secuencia de input.fastq (es decir, comenzando en la quinta línea). Esta característica es
especialmente útil para eliminar líneas de encabezado de salida o archivos antes de su posterior procesamiento, como en
. /fasta_stats pz_cdnas.fasta | tail -n +9 , en lugar de usar grep -v '#' arriba.

Ejercicios
1. Ejecutando fasta_stats en pz_cdnas.fasta , la séptima columna representa la longitud de la repetición perfecta
más larga que se encuentra en cada secuencia. Use solo grep , sort , wc , head y tail para determinar el valor
mediano en esta columna. (Es posible que deba ejecutar varios comandos o canalizaciones).
2. Ejecutando fasta_stats en pz_cdnas.fasta , ¿cuántas unidades diferentes de repetición perfecta (columna seis)
se encuentran?
3. El archivo pz_blastx_yeast_top10.txt es el resultado de ejecutar
blastx -query.. /fasta_stats/pz_cdnas.fasta -db orf_trans -evalue 1e-6 -
max_target_seqs 10 -max_hsps 1 -outfmt 7 -out pz_blastx_yeast_top1.txt
. Aparte de las líneas de “comentario” que comienzan con # , la primera columna es la ID de consulta, la segunda la ID de
destino (levadura), la tercera la identidad porcentual del HSP, la undécima el valor E y la duodécima la “puntuación de bits”.
¿Qué ID de consulta tuvo el bitscore más grande? ¿Cuántas secuencias de consulta diferentes (entradas en la primera columna)
tenían uno o más HSP contra la base de datos?
4. Extraiga de pz_blastx_yeast_top10.txt un archivo llamado pz_blastx_yeast_top1.txt que contenga
solo la línea HSP de valor E más pequeña por ID de consulta. (Puede eliminar las líneas de comentarios que comiencen por #
por completo.)

1. Una palabra de precaución: si una columna contiene una entrada que no puede interpretarse como un número entero o general,
se tratará como 0 en el orden de clasificación.

1.9.2 https://espanol.libretexts.org/@go/page/55201
This page titled 1.9: Clasificación, Primera y Última Líneas is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated
by Shawn T. O’Neil (OSU Press) .

1.9.3 https://espanol.libretexts.org/@go/page/55201
1.10: Filas y Columnas
Volvamos a la salida de las proteínas de levadura versus proteínas de levadura Autoexplosión que realizamos previamente, desde el
archivo yeast_blastp_yeast_top2.txt .
I.10_1_UNIX_119_Blast_cols

Preguntemos lo siguiente: ¿cuántas secuencias en este conjunto de datos tenían una coincidencia con alguna otra secuencia? Para
empezar, probablemente usaríamos un grep -v '#' para eliminar todas las líneas de comentarios, pero ¿entonces qué?
Podríamos intentar usar wc para contar las líneas, pero solo después de eliminar también los auto-hits, donde el ID en la primera
columna es igual al ID en la segunda columna. Ninguna de las utilidades que hemos visto hasta ahora —grep , sort ,
head o tail — puede realizar esta tarea. Necesitamos una nueva herramienta, awk , que es una herramienta de
procesamiento línea por línea y columna por columna para archivos de texto: awk ' <program><file>o ... | awk' '
<program>.
Escrita a finales de la década de 1970 y lleva el nombre de sus autores (Alfred Aho, Peter Weinberger y Brian Kernigan), awk
proporciona un sofisticado lenguaje de programación que facilita el análisis de datos tabulares como los resultados de BLAST
anteriores. La sintaxis para awk puede ser bastante compleja, pero gran parte de la complejidad se puede ignorar en el uso
regular.
Primero, respondamos a nuestra pregunta específica, de cuántas secuencias tenían coincidencias con otras secuencias, y luego
veremos alguna sintaxis awk de manera más general. El comando awk que queremos, imprimiendo solo aquellas líneas
donde las dos primeras columnas no son iguales, es awk '{if (¡$1! = $2) {print $0}} ' .
I.10_2_UNIX_120_AWK_BLAST

Desglosando el comando awk , el “programa” que se ejecuta está delimitado por las comillas simples (que cotejan su contenido
en un único parámetro de línea de comandos que se envía al programa awk ). El código dentro del par exterior de corchetes se
ejecuta para cada línea. Por cada línea, si el contenido de la primera columna (representado por $1 ) no es igual a ( ! =) la
segunda columna ( $2 ), luego se ejecuta el código en el siguiente par de llaves, y se imprime la línea ( $0 ) (a salida estándar).
I.10_3_UNIX_121_AWK_BLAST_OUT

En teoría, en este punto deberíamos poder sustituir al menos -S por un wc para contar las líneas, y así contar el número de
secuencias que tenían coincidencias con otras secuencias. Desafortunadamente, en este caso, la teoría no se alinea con la realidad:
inspeccionar la salida anterior revela que el ID YDR545W todavía está representado por dos líneas, por lo que esta secuencia se
contaría dos veces.
¿Por qué? En el comando BLAST, solicitamos los dos primeros HSP por consulta con -max_target_seqs 2 y
-max_hsps 1 , así que esperábamos que el mejor HSP sería para la secuencia misma, con el segundo mejor (si existiera) para
ser a un no-auto-hit. Pero en este caso, blastx decidió reportar dos no-auto-hits. De hecho, si tuviéramos que inspeccionar
YDR545W , YOR396W y YLR467W , encontraríamos que sus secuencias son idénticas, por lo que BLAST necesitaba elegir
dos HSP del empate de tres vías.
Para obtener el número correcto de secuencias que tenían coincidencias con otras, necesitamos eliminar los duplicados que aún se
puedan encontrar en la primera columna. Podemos hacer esto agregando una especie -k1,1d -u , para una respuesta final
de 2,884.
I.10_4_Unix_122_AWK_BLAST_CORRECT

Para cualquier conjunto de datos suficientemente complejo, es una buena idea verificar tantas suposiciones al respecto como sea
posible al realizar un análisis. En lo anterior, usar wc en las líneas para contar el número de secuencias que tenían aciertos a otras
implicaba que en la primera columna no se listaba ningún ID dos veces. En este caso, comparar los recuentos con y sin el
tipo -k1,1d -u serviría para verificar o rechazar esta suposición. En capítulos posteriores, aprenderemos más técnicas
para este tipo de “comprobación de la cordura”.

Sintaxis básica para awk


Aunque awk se puede utilizar como un lenguaje de programación con todas las funciones (completo con bucles, matrices, etc.),
para necesidades de programación sofisticadas, otros lenguajes como Python y R suelen ser más adecuados. Echemos un vistazo a

1.10.1 https://espanol.libretexts.org/@go/page/55180
un subconjunto práctico de su sintaxis.
imagen

Las sentencias en el bloque BEGIN se ejecutan antes de que se procese cualquier línea de la entrada, las sentencias en el bloque
medio sin adornos se ejecutan para cada línea y las sentencias en el bloque END se ejecutan después de procesar la última línea.
Cada uno de estos bloques es opcional, aunque el bloque medio “para cada línea” rara vez se omite en la práctica.
Al procesar una línea, hay varias variables disponibles para su uso. Aunque muchos de ellos comienzan con un $ , no son
variables de entorno (porque hemos colocado el “programa” dentro de comillas simples, se envían a awk como cadenas
inalteradas, signos $ intactos).
$0
Sostiene el contenido de toda la línea que se está procesando.
$1 , $2 , etc.
$1 contiene el contenido de la primera columna de la línea actual, $2 contiene el contenido de la segunda columna de
la línea actual, y así sucesivamente. Al igual que sort , awk por defecto asume que las columnas están separadas por
espacios en blanco.
NF
La variable especial NF contiene el número de columnas (también conocidas como campos) en la línea actual ( awk no
requiere que todas las líneas contengan el mismo número de columnas).
NR
NR contiene el número de líneas que se han procesado hasta el momento, incluida la línea actual. Así, en la línea
INICIO , NR tiene 0 ; durante el procesamiento de la primera línea, NR tiene 1; y en el bloque END , NR
contiene el número total de líneas en la entrada.
Tenga en cuenta que tanto el NF como el NR carecen de un prefijo $ . El texto colocado en los tres bloques puede constar de
una amplia variedad de cosas, incluyendo condicionales, declaraciones impresas, manipulaciones lógicas y matemáticas, y así
sucesivamente. Quizás una colección de ejemplos ayude a ilustrar la utilidad de awk . Todos estos ejemplos se basan en la salida
BLAST anterior, después de filtrar las líneas de comentario con grep -v '#' .
Este comando imprime solo las dos primeras columnas de la tabla, separadas por un espacio (el valor predeterminado cuando se
incluye una coma en una instrucción print ):
I.10_6_Unix_123_awk_blast_print_two_cols

En lugar de separar las dos columnas de salida por un espacio, podemos separarlas por una cadena como ::: , produciendo solo
una sola columna conglomerada de salida.
imagen

Si queremos agregar una nueva primera columna que simplemente contenga el número de línea, podemos usar la variable NR
junto con la variable $0 :
I.10_8_Unix_125_AWK_BLAST_PRINT_NR_LINE

Las sentencias IF permiten a awk ejecutar otras sentencias condicionalmente; la sintaxis es if () {} <logical expression>
<statements to execute>. Además, si una columna contiene valores numéricos, awk puede trabajar con ellos como tales, y
awk incluso entiende la notación científica. Aquí hay un ejemplo donde solo se imprimen líneas con valores HSP E (la décima
columna en nuestro ejemplo) de menos de 1e-10 .
I.10_9_Unix_126_AWK_BLAST_PRINT_IF1

Observe que la organización de los corchetes produce una estructura de bloques anidada; aunque para este simple caso se podría
omitir el conjunto de corchetes internos, suele ser la mejor práctica incluirlos, ya que ilustran exactamente qué sentencia es
controlada por el if anterior. [1]
Las declaraciones IF pueden controlar múltiples condiciones y, a veces, ayuda a romper los programas awk en varias líneas para
ayudar con su legibilidad, especialmente cuando se incluyen en scripts ejecutables. Esta sofisticada declaración agrega una nueva

1.10.2 https://espanol.libretexts.org/@go/page/55180
primera columna que categoriza cada HSP como “genial”, “bueno” o “ok”, dependiendo del valor E, imprimiendo solo los dos ID y
el valor E (columnas 1, 2 y 10):
I.10_10_Unix_127_AWK_BLAST_PRINT_IF2

Es bastante fácil determinar si una columna en particular es igual a una cadena dada, por ejemplo, para sacar todas las líneas donde
la primera columna es YAL054C :
imagen

Los cálculos matemáticos son una buena característica de awk . Por ejemplo, las columnas 4 y 5 contienen la longitud total de la
secuencia de consulta y la secuencia del sujeto, respectivamente, por lo que podríamos desear imprimir la relación de estas dos
como una columna adicional al final.
imagen

Luego podríamos canalizar el resultado a una especie -k3,3g | tail -n 5 para ver los cinco HSP con las
proporciones más grandes. Tenga en cuenta, sin embargo, que al realizar operaciones matemáticas o comparaciones con columnas,
cualquier contenido que no pueda ser analizado como un número ( 1.5 puede ser, como pueden 2 y 4e-4 , pero no i5 o
NA ) puede ser truncado (por ejemplo, 10x1 se trata como solo 10 ) o tratados como 0 . El uso de sort on column
con -g puede revelar tales problemas potenciales, ya que se usa el mismo método subyacente para analizar.
Hay una variedad de funciones matemáticas integradas en awk . Aquí hay una muestra:
registro ()
Devuelve el logaritmo natural de su argumento, como en la impresión $10 * log ($3 * $4) para imprimir el
log de la multiplicación de la tercera y cuarta columnas por la décima columna. [2]
longitud ()
La función length () devuelve el número de caracteres en su argumento, como en length ($1) para la
longitud de carácter de la primera columna, y length ($0) para la longitud de caracteres de toda la línea (espacios y
caracteres de tabulación incluidos).
**
Este operador devuelve el lado izquierdo elevado a la potencia del lado derecho, como en $1**2 para el cuadrado de la
primera columna.
%
El operador de módulo, devolviendo el resto después de dividir el lado izquierdo por el lado derecho. Por ejemplo, NR%4
será 1 en la primera línea, 2 en la segunda, 3 en la tercera, 0 en la cuarta, 1 en la quinta, y así sucesivamente.
exp ()
Esta función devuelve su argumento planteado al poder natural e. Por ejemplo, log (exp ($1)) devuelve el valor de
la primera columna.
int ()
Devuelve la parte entera de su argumento. Por ejemplo, int (6.8) devuelve 6 y int (-3.6) devuelve -3 .
rand ()
Cuando no se le dan argumentos, devuelve un número aleatorio entre 0 (inclusive) y 1 (exclusivo). Por defecto, cada vez que
se ejecuta awk , la serie de números aleatorios producidos por múltiples llamadas a rand () es la misma. Para
obtener una serie aleatoria “aleatoria”, ejecute srand () ( que “siembra” el generador de números aleatorios) en el
bloque BEGIN , como en BEGIN {srand ()} {print rand (), $0} .
Las expresiones lógicas se pueden combinar con operadores booleanos, incluyendo && para “y” y || para “or” (lo que
produce true si uno o ambos lados son verdaderos), y la agrupación se puede lograr con paréntesis. Por ejemplo, podríamos desear
imprimir solo aquellas líneas donde la primera columna no es igual a la segunda, y o bien la décima columna es menor que
1e-30 o la segunda columna es YAL044C .
I.10_13_AWK_BLAST_OR

1.10.3 https://espanol.libretexts.org/@go/page/55180
Hasta ahora, no hemos hecho mucho uso de los bloques BEGIN o END , que son especialmente útiles cuando definimos y
actualizamos nuestras propias variables. Podemos realizar esta tarea con una = asignación (no confundir con la comparación
== ). Este comando imprime los valores promedio de E en nuestro archivo de resultado BLAST de ejemplo.
I.10_14_Unix_131_AVG_EVALS1

Este comando funciona porque el lado derecho de una asignación a una variable con = se evalúa antes de que ocurra la
asignación. Así, en el bloque BEGIN , la variable sumeval se inicializa a 0 , luego para cada línea se suma el valor de
sumeval al contenido de la décima columna (el valor E de esa línea), y el resultado se almacena en sumeval . Finalmente,
en el bloque END , sumeval contiene la suma total de los valores E, y podemos dividir este resultado por el número de líneas
procesadas, NR .
Podemos ejecutar múltiples sentencias dentro de un solo bloque si las separamos con punto y coma. En el ejemplo anterior, el valor
promedio de E calculado incluye autoaciertos. Podemos filtrarlos con una sentencia if antes de modificar sumeval , pero luego
no vamos a querer dividir el resultado por NR , porque eso también incluirá los recuentos de autoaciertos. Para resolver este
problema, tendremos que mantener dos variables.
I.10_15_Unix_132_AVG_EVALS2

Como antes, algunos ID todavía están presentes más de una vez en la primera columna con esta solución, por lo que puede tener
más sentido filtrar primero las líneas deseadas usando la solución awk y sort -k1,1d -u desde arriba, y luego usar otro
awk para el cálculo promedio.

Ejercicios
1. En el archivo pz_blastx_yeast_top10.txt , ¿cuántos HSP (líneas) tienen un valor E que es menor que 1e-30 o
tienen un valor de identidad mayor al 50%? Use awk , wc y grep si es necesario para calcular la respuesta.
2. El archivo contig_stats.txt describe estadísticas para cóntigos de un ensamblaje de genoma de novo (un cóntig es
una “pieza” ensamblada del genoma). En la cuarta columna se describe el contenido de GC de los diversos cóntigos. Otros
análisis indican que la mayoría de los genes correctamente ensamblados tienen una cobertura promedio (segunda columna) de
entre 80.0 y 150.0. Use awk para determinar el contenido promedio de GC para cóntigos con cobertura entre 80.0 y 150.0.
Luego use otra invocación de awk para determinar el contenido promedio de GC para todos los demás cóntigos. (No cuente
la línea de cabecera en el archivo en sus cálculos).
3. El archivo pz.annot.txt es el resultado de un análisis de ontología génica (GO) para el conjunto completo de
secuencias de ADNc de Papilio zelicaon ensambladas. Debido a la naturaleza incompleta del proceso de anotación, no a todas
las secuencias se les asignaron términos GO. ¿Cuántos ID de secuencia diferentes se representan en este archivo?
4. Algunas versiones del programa de clasificación pueden ordenar líneas en orden “aleatorio” usando el indicador
-R . Sin embargo, en lugar de usar esta bandera, use grep , awk (con la característica rand () ) sort (sin el
indicador -R ) y diríjase para seleccionar cinco ID aleatorios de Pz_CDNAS.fasta . Un resultado de ejemplo
podría verse así:
i-10_16_fasta_ejercicio El mismo comando debería producir una lista diferente de cinco ID cada vez que se ejecuta.

1. Esta construcción anidada, un bloque controlado dentro de otro bloque que se ejecuta para cada elemento de un conjunto (en
este caso, para cada línea), es uno de nuestros primeros ejemplos de programación! Un indicio para reducir la confusión al
producir tales estructuras es rellenar su estructura desde el exterior hacia adentro, agregando pares de símbolos y luego
“retrocediendo” en ellos según sea necesario. En este ejemplo, podríamos haber comenzado con awk " , y luego agregamos
los corchetes para producir awk '{}' , siguiente awk '{if () {}}' , y finalmente rellenamos la lógica con
awk '{if ($10 < 1e-10) {print $0}}' .
2. Si le preocupa dónde se permiten espacios en las declaraciones awk , trate de no estarlo: en su mayor parte, están permitidos
en cualquier lugar, y puede sentirse libre de usarlos para mejorar la legibilidad de sus declaraciones. No están permitidos en
palabras clave y variables: i f ($1 > $2) {print N R} sería una expresión no válida porque i f , $1 y
N R tienen espacios erróneos.
This page titled 1.10: Filas y Columnas is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil
(OSU Press) .

1.10.4 https://espanol.libretexts.org/@go/page/55180
1.11: Patrones (Expresiones Regulares)
En capítulos anteriores, utilizamos un sencillo programa fasta_stats para realizar análisis básicos en un archivo FASTA
llamado Pz_CDNAS.FASTA , principalmente como excusa para aprender sobre los flujos estándar y herramientas como
grep y ordenar. Resulta que la información en el archivo Pz_CDNAS.fasta nos proporciona muchas preguntas
potenciales para reflexionar.
Las secuencias en este archivo son en realidad un subconjunto de transcripciones putativas, producidas a partir de un ensamblaje de
transcriptoma de novo para la mariposa Papilio zelicaon. Cada línea de cabecera de secuencia codifica una variedad de
información: la segunda y tercera columnas de las líneas de cabecera revelan el número de lecturas que contribuyen a cada
secuencia ensamblada y la cobertura promedio de la secuencia (definida como el número total de bases aportadas por lecturas,
dividido por la secuencia ensamblada longitud). Incluso los ID de secuencia codifican cierta información: todos comienzan con un
identificador pseudoaleatorios, pero algunos tienen un sufijo como _TY .
I.11_1_UNIX_133_PZ_CDNAS_Less

Los grupos de secuencias que comparten el mismo sufijo _ se identificaron previamente como que tenían coincidencias
compartidas usando un Self-blast. Los ID de secuencia sin dicho sufijo no tenían coincidencias. Podríamos preguntarnos: ¿cuántas
secuencias hay en tal grupo? Esto podría responderse fácilmente usando primero grep para extraer líneas que coincidan con
> (las líneas de encabezado), y luego usando otro grep para extraer aquellas con el patrón _ (las de un grupo) antes de
enviar el resultado a wc .
Una pregunta más compleja sería preguntar cuántos grupos diferentes están representados en el archivo. Si la información del
grupo se almacenaba en una columna separada (digamos, la segunda columna), esta pregunta podría responderse con el mismo
proceso anterior, seguida de un sort -k2,2d -u para eliminar identificadores de grupo duplicados. Pero, ¿cómo podemos
coaccionar la información del grupo en su propia columna? Esto podríamos hacer sustituyendo instancias de _ por espacios. La
herramienta sed (Stream Editor) nos puede ayudar. Aquí está el pipeline general que usaremos:
I.11_2_UNIX_134_SED_EX1

Y aquí está parte de la salida, donde solo se representan las secuencias en grupos y cada grupo se representa solo una vez
(reemplazando los menos -S con wc contaría así el número de grupos):
I.11_3_Unix_135_SED_EX1_out

La herramienta sed es un programa sofisticado para modificar la entrada (ya sea desde un archivo o entrada estándar) e
imprimir los resultados a la salida estándar: sed '' <program><file>o ... | sed '' <program>.
Al igual que awk , sed proviene de la década de 1970 y proporciona una gran variedad de potentes características y sintaxis,
solo una pequeña fracción de las cuales cubriremos aquí. En particular, nos centraremos en la operación s , o sustitución.
I.11_4_SED_Sintaxis

La opción -r que hemos usado le permite saber a sed que queremos que nuestro patrón sea especificado por la sintaxis
“expresión regular extendida POSIX”. [1] El patrón general del programa de sustitución es s///g <pattern><replacement>,
donde la g especifica que, para cada línea, cada instancia del patrón debe ser reemplazada. Alternativamente, podemos usar 1
en este spot para indicar que solo se debe reemplazar la primera instancia, 2 para indicar solo la segunda, y así sucesivamente. A
menudo, <pattern><replacement>se usa s/// , ya que tiene el mismo significado que s///1 <pattern><replacement>. [2]

Expresiones Regulares
El verdadero poder de sed no proviene de su capacidad de sustituir texto, sino de su utilidad para sustituir texto basado en
“patrones” o, más formalmente, expresiones regulares. Una expresión regular es una sintaxis para describir la coincidencia de
patrones en cadenas. Las expresiones regulares son descritas por los caracteres individuales que componen el patrón a buscar, y los
“meta-operadores” que modifican partes del patrón para mayor flexibilidad. En [ch] at , por ejemplo, los corchetes funcionan
como un meta-operador que significa “uno de estos personajes”, y este patrón coincide con gato y sombrero , pero no
chat . Las expresiones regulares a menudo se construyen encadenando expresiones más pequeñas, como en
[ch] en el [mh] at , gato a juego en el sombrero , gato en el tapete ,
sombrero en el sombrero y sombrero en el tapete .

1.11.1 https://espanol.libretexts.org/@go/page/55196
En el ejemplo anterior, todo el patrón fue especificado por _ , que no es un meta-operador de ningún tipo, por lo que cada
instancia de _ fue reemplazada por el reemplazo (un carácter de espacio). Los meta-operadores que son compatibles con
expresiones regulares son muchos y variados, pero aquí hay una lista básica junto con algunos ejemplos biológicamente inspirados:
caracteres o cadenas que no son meta-operadores
La mayoría de los personajes que no operan de una metamoda son simplemente emparejados. Por ejemplo, _ coincide
con _ , A coincide con A y ATG coincide con un codón de inicio. (De hecho, ATG son tres patrones individuales
especificados en una fila). En caso de duda, suele ser seguro escapar de un personaje (prefijándolo con una diagonal inversa)
para asegurarse de que se interprete literalmente. Por ejemplo, \[_\] coincide con la cadena literal [_] , en lugar de
hacer uso de los corchetes como meta-operadores.
.
Un periodo coincide con cualquier carácter individual. Por ejemplo, CC. coincide con cualquier codón P ( CCA , CCT ,
CCG , CCC ), pero también cadenas como CCX y CC% .
[] <charset>
Coincide con cualquier carácter individual especificado en <charset>. Por ejemplo, TA [CT] coincide con un codón Y
( TAC o TAT ).
[^] <charset>
Colocar un ^ como el primer carácter dentro de los corchetes del juego de caracteres niega el significado, de tal manera
que cualquier carácter único que no esté nombrado entre corchetes coincide. TA [^CT] coincide con TAT , TAG ,
TA% , y así sucesivamente, pero no TAC o TAT .
^ (fuera de [] )
Colocar un ^ fuera de los corchetes del juego de caracteres coincide con el inicio de la cadena o línea de entrada. Usando
sed -r 's/^atg/xxx/G' , por ejemplo, reemplaza todas las instancias de codones de inicio por XXX , pero solo
si existen al inicio de la línea.
$
Similar a ^ , pero $ coincide con el final de la cadena o línea. Entonces, sed -r 's/ATG$/xxx/G' reemplaza
a todos los codones de inicio que existen al final de sus respectivas líneas.
Hasta ahora nuestros patrones no son realmente tan flexibles, porque la mayoría de las piezas cubiertas hasta este punto coinciden
con un solo personaje. Los siguientes cinco metaoperadores resuelven esa limitación.
{x, y}
Modifica el patrón anterior para que coincida si ocurre entre x e y veces seguidas, inclusive. Por ejemplo,
[GC] {4,8} coincide con cualquier cadena de C y/o G que tenga una longitud de cuatro a ocho caracteres (disparando
para ocho personajes, si es posible). Entonces, sed -r/[GC] {4,8} /_x_/g' resultaría en las siguientes
sustituciones:
ATCCGTCT a ATCCGTCT (sin reemplazo)
ATCCGCGGCTC a AT_X_TC ATCGCGGCCCGTTCGGGCCT a AT_X_CCGTT_X_T
Usar {0,1} tiene el efecto de hacer que lo que sigue sea opcional en el patrón, y {x,} tiene el efecto de permitir que
el patrón coincida x o más veces sin límite superior.
*
Un asterisco modifica el patrón anterior para que coincida si ocurre cero o más veces; así es equivalente a {0,} .
El uso de * merece un ejemplo detallado. Considere el patrón ATG [ATGC] *TGA , donde ATG es el patrón para
un codón de inicio, [ATGC] * indica cero o más bases de ADN en una fila, y TGA es uno de los codones de parada
canónicos. Este patrón coincide con ATGTACCTTGA , y también coincide con ATGTGA (donde la parte media se ha
emparejado cero veces).
+

1.11.2 https://espanol.libretexts.org/@go/page/55196
El modificador de repetición más prominente, un signo más modifica el patrón anterior para que coincida una o más veces;
es equivalente a {1,} . En contraste con el ejemplo anterior, ATG [ATGC] +TGA coincide con ATGTACCTTGA
y ATGCTGA , pero no ATGTGA .
() <pattern>
Los paréntesis se pueden utilizar para agrupar una expresión o serie de expresiones en una sola unidad para que puedan
operarse juntas. Debido a que AT es el patrón A seguido de T , por ejemplo, AT+ coincide con AT , ATT
, ATTT, etc. Si quisiéramos hacer coincidir repeticiones AT , podríamos desear especificar un patrón como (AT) + ,
que coincida con AT , ATAT , ATAT , y así sucesivamente. Los paréntesis también “guardan” la cadena que coincidió
dentro de ellos para su uso posterior. Esto se conoce como retroreferenciación, se discute a continuación.
| <pattern x><pattern y>
Coincidir con el patrón <pattern x>o el patrón <pattern y>. Se pueden encadenar múltiples patrones u operaciones de
este tipo; por ejemplo, TAA|TAG|TGA coincide con cualquiera de los tres codones de parada canónicos. Sin embargo,
este ejemplo es un poco ambiguo: ¿este patrón lee “TA (A o T) A (G o T) GA” o “TAA o TAG o TGA”? Para hacerlo
concreto, probablemente nos gustaría especificarlo como ((TAA) | (TAG) | (TGA)) .
Usando estas piezas, podemos armar una expresión regular que sirve como un buscador simple (y no realmente útil en la práctica)
de lectura abierta. Para las secuencias procariotas (donde los intrones no son una consideración), las definimos como un codón de
inicio ATG , seguido de uno o más codones, seguido de uno de los tres codones de parada canónicos TAA , TAG o TGA . El
patrón para el inicio es ATG , y hemos visto cómo podemos codificar un stop arriba, con ((TAA) | (TAG) | (TGA)) .
¿Qué tal “uno o más codones”? Bueno, “uno o más” se encarna en el operador + , y un codón es cualquiera de tres A, T, C o G.
Entonces, “uno o más codones” se codifica como ([ACTG] {3,3}) + . Por lo tanto, la expresión regular de nuestro sencillo
buscador de marcos de lectura abierta es:
I.11_5_Regex_ORF

En realidad, las expresiones regulares no se utilizan a menudo en la búsqueda de regiones codificantes (aunque a veces se usan para
identificar motivos más pequeños). Parte de la razón es que las expresiones regulares son, por defecto, codiciosas: coinciden con el
primer patrón que ocurren que pueden, y buscan hacer coincidir tanto de la cadena como puedan. (La maquinaria celular que
procesa los marcos de lectura abiertos no es codiciosa de esta manera). Considera la siguiente secuencia, la cual tiene tres marcos
de lectura abiertos de acuerdo a nuestra definición simple y expresión regular anterior.
alt

Observe que la cadena TAG es tanto un tipo de codón en general ( [ACTG] {3,3} ) como una parada, por lo que
técnicamente ambas de las dos primeras opciones son válidas según la expresión regular. Por las reglas de la codicia, se emparejará
el primero, lo que podemos verificar con un simple eco y sed .
I.11_7_Unix_136_orf_comando_line_ex

La sintaxis de expresión regular utilizada por sed es similar a la sintaxis utilizada en lenguajes como Perl, Python y R. De
hecho, todos los ejemplos que hemos visto hasta ahora funcionarían igual en esos lenguajes (aunque son aplicados por sus propias
funciones específicas en lugar de una llamada a sed ). Una característica útil proporcionada por los motores de expresión regular
más modernos como estos es que los operadores como * y + pueden volverse no codiciosos (aunque prefiero el término más
claro “reacio”) siguiéndolos con un signo de interrogación. En Python, la expresión regular
ATG ([ACTG] {3,3}) +? ((TAA) | (TAG) | (TGA)) coincidiría con la segunda opción. (Cuando no sigue un
* , o + , hace que el anterior sea opcional; así TG (T)? CC es equivalente a TG (T) {0,1} CC .) Las características
más sofisticadas permiten al usuario acceder a todas las coincidencias de un patrón, aunque se superpongan, de manera que la más
satisfactoria pueda ser sacada por algunos criterios secundarios. Desafortunadamente, sed no admite coincidencias no
codiciosas y varias otras características avanzadas de expresión regular.

Clases de caracteres y expresiones regulares en otras herramientas


A menudo deseamos usar corchetes para que coincidan con cualquiera de una “clase” de caracteres; por ejemplo,
[0123456789] coincide con cualquier dígito individual. La mayoría de las sintaxis de expresión regular (incluida la utilizada
por sed ) permiten una versión abreviada [0-9] (si quisiéramos hacer coincidir solo un 0, 9 o -, podríamos usar [09-] ).
Del mismo modo, [a-z] coincide con cualquier letra minúscula y [A-Z] cualquier letra mayúscula. Estos incluso se

1.11.3 https://espanol.libretexts.org/@go/page/55196
pueden combinar: [A-za-Z0-9] coincide con cualquier dígito o letra. En la sintaxis extendida POSIX utilizada por sed ,
0-9 también se puede especificar como [:digit:] . Observe la falta de corchetes en el anterior, para que realmente
coincida con cualquier dígito, la expresión regular es [[:digit:]] (lo cual, sí, es molesto). Para que coincida con cualquier
no dígito, podemos negar el conjunto entre corchetes como [^ [:digit:]] .
Estas clases de caracteres POSIX son especialmente útiles cuando queremos hacer coincidir tipos de caracteres que son difíciles de
escribir o enumerar. En particular, [[:space:]] coincide con uno de cualquier carácter de espacio en blanco (espacios,
tabulaciones, nuevas líneas), y [[:punct:]] coincide con cualquier carácter de “puntuación”, de los cuales hay bastantes. La
clase de caracteres [[:space:]] es particularmente útil cuando está reformateando datos almacenados en filas y columnas
pero no está seguro de si los separadores de columnas son espacios, tabulaciones o alguna combinación.
En muchas sintaxis de expresiones regulares (incluidas las utilizadas por Perl, Python, R y algunas versiones de sed ), hay
disponibles atajos aún más cortos para clases de caracteres. En estos, \ d equivale a [[:digit:]] , \ D es equivalente a
[^ [:digit:]] , \ s para [[:space:]] , \ S para [^ [:space:]] , entre otros.
Resulta que las expresiones regulares pueden ser utilizadas tanto por grep como por awk . Al usar grep , podemos
especificar que el patrón debe tratarse como una expresión regular extendida agregando la bandera -E (a diferencia de la -r
utilizada para sed .) Así grep -E '[[:digit:]] +' extrae líneas que contienen un entero.
En awk , podemos usar el comparador ~ en lugar del comparador == en una sentencia if, como en
awk '{if ($1 ~ /PZ718 [[:digit:]] +/) {print $3}}' , que imprime la tercera columna de cada línea
donde la primera columna coincide con el patrón PZ718 [[:digit:]] + .

Back-Referenciación
De acuerdo con la definición anterior para las líneas de cabecera en el archivo Pz_CDNAS.fasta , los IDs deben
caracterizarse como un identificador pseudoaleatoria seguido de, opcionalmente, un guion bajo y un conjunto de letras mayúsculas
especificando el grupo. Usando grep '>' para extraer solo las líneas de cabecera, podemos inspeccionar esto visualmente:
I.11_8_Unix_137_pz_cdna_headers_snippet

Si enviamos estos resultados a través de wc , vemos que este archivo contiene 471 líneas de cabecera. ¿Cómo podemos verificar
que cada uno de ellos siga este patrón? Mediante el uso de una expresión regular con grep para el patrón, y comparando el
conteo con 471. Debido a que los ID deben comenzar inmediatamente después del símbolo > en un archivo FASTA, eso será
parte de nuestro patrón. Para la sección pseudoaleatoria, que puede o no comenzar con PZ pero al menos no debe incluir un
guion bajo o un espacio, podemos usar el patrón [^_ [:space:]] + para especificar uno o más caracteres no subrayados,
no espacios en blanco. Para el identificador de grupo opcional, el patrón sería (_ [A-Z] +) {0,1} (porque {0,1} hace
que el anterior sea opcional). Armando estos con grep -E y contando los partidos debería producir 471.
I.11_9_Unix_138_pz_cdna_headers_regex_check

Todos los encabezados coincidieron con el patrón que esperábamos. ¿Y si no lo hubieran hecho? Podríamos inspeccionar cuáles no
usando un grep -v -E para imprimir las líneas que no coincidieron con el patrón.
Ahora, hipotéticamente, supongamos que un colega (terco, y más senior) ha identificado una lista de identificaciones genéticas
importantes, y las ha enviado en un simple archivo de texto.
I.11_9_Unix_138_2_pz_cdna_headers_needs_fix

Desafortunadamente, parece que nuestro colega ha decidido utilizar un esquema de nomenclatura ligeramente alterado, añadiendo
-gen al final de cada identificador pseudoaleatoria, antes del _ , si está presente. Para continuar con colaboraciones pacíficas,
podría correspondernos modificar nuestro archivo secuencial para que corresponda a este esquema. Podemos hacer esto con sed
, pero será un reto, principalmente porque queremos realizar una inserción, más que una sustitución. En realidad, estaremos
realizando una sustitución, ¡pero estaremos sustituyendo partidos con contenidos de ellos mismos!
En cuanto a las referencias posteriores, en una expresión regular, las coincidencias o subcoincidencias que se agrupan y se
encierran entre paréntesis tienen sus cadenas coincidentes guardadas en variables \ 1 ,\ 2 , y así sucesivamente. El
contenido del primer par de paréntesis se guarda en \ 1 , el segundo en \ 2 (puede ser necesaria cierta experimentación para
identificar dónde se guardan las coincidencias entre paréntesis anidados). Toda la coincidencia de expresión se guarda en \ 0 .

1.11.4 https://espanol.libretexts.org/@go/page/55196
Para completar el ejemplo, modificaremos el patrón utilizado en el grep para capturar ambas partes relevantes del patrón,
reemplazándolas con \ 1-gen\ 2 .
I.11_10_SED_HEADER_FIX

Los contenidos de Pz_CDNAS.fasta , luego de ejecutarlo por el sed anterior, son los siguientes:
I.11_11_Unix_139_pz_cdna_headers_regex_fix

Las referencias posteriores también se pueden usar dentro del patrón en sí. Por ejemplo, un
sed -r/([A-za-Z] +)\ 1/\ 1/g' reemplazaría palabras “duplicadas” ([A-za-Z] +)\ 1 con una sola copia
de la palabra \ 1 , como en Me gusta mucho sed , resultando en que me gusta mucho sed . Pero ten cuidado
si estás pensando en usar la sustitución de este tipo como corrector gramatical, porque esta sintaxis no busca a través de los límites
de línea (aunque los programas sed más complejos pueden hacerlo). Este ejemplo no modificaría el siguiente par de líneas
(donde la palabra the aparece dos veces):

The quick sed regex modifies the


the lazy awk output.

Algunas notas finales sobre sed y expresiones regulares en general ayudarán a concluir este capítulo.
1. Las expresiones regulares, aunque poderosas, se prestan a errores. Trabajar de manera incremental y verificar regularmente los
resultados.
2. A menudo es más fácil usar múltiples invocaciones de expresiones regulares (por ejemplo, con múltiples comandos sed ) en
lugar de intentar crear una sola expresión compleja.
3. Use expresiones regulares en su caso, pero sepa que no siempre son apropiadas. Muchos problemas que pueden parecer un
ajuste natural para las expresiones regulares también se ajustan naturalmente a otras estrategias, que deben tomarse en
consideración dada la complejidad que las expresiones regulares pueden agregar a un comando o fragmento de código dado.
Algunas personas, cuando se enfrentan a un problema, piensan: “Lo sé, usaré expresiones regulares”. Ahora tienen dos problemas.
~Jamie Zawinski

Ejercicios
1. En el archivo de estadísticas de ensamblaje de novo contig_stats.txt , los ID de cóntigo se denominan NODE_1
, NODE_2 , etc. Preferiríamos que se llamaran contig1 , contig2 y similares. Producir un
contig_stats_renamed.txt con estos cambios realizados.
2. ¿Cuántas secuencias en el archivo Pz_CDNAS.fasta están compuestas por una sola lectura? Es probable que necesite
usar tanto awk como sed aquí, y asegúrese de verificar cuidadosamente los resultados de su tubería con menos .
3. Un colega particularmente odioso insiste en que en el archivo Pz_CDNAS.fasta , las secuencias que no forman parte de
ningún grupo (es decir, las que no tienen _ sufijo) deben tener el sufijo _nogroup . Apaciguar a este colega produciendo
un archivo a esta especificación llamado Pz_CDNAS_FIXED.FASTA .
4. Las líneas cabeceras en el conjunto de proteínas de levadura orf_trans.fasta se ven así cuando se ven con
menos -S después de grepping para >: I.11_12_Unix_139_2_ORF_Trans_Headers Notablemente, las líneas de encabezado
contienen información sobre las ubicaciones de los exones individuales; la secuencia YAL001C tiene dos exones en cromosoma
I de las ubicaciones 151006 a 147594 y 151166 a 151097 (los números van de grandes a pequeños porque las ubicaciones se
especifican en la cadena directa, pero el gen está en la cadena inversa del complemento). Por el contrario, la secuencia
YAL002W tiene solo un exón en la cadena directa.
¿Cuántas secuencias están compuestas por un solo exón? A continuación, produzca una lista de ID de secuencia en un archivo
llamado multi_exon_ids.txt que contenga todos esos ID de secuencia con más de un exón como una sola columna.
5. Como continuación de la pregunta 4, ¿qué secuencia tiene más exones? ¿Qué secuencia de un solo exón es la más larga, en
términos de la distancia desde la primera posición hasta la última posición señalada en la lista de exones?

1. POSIX, abreviatura de Interfaz de sistema operativo portátil, define un conjunto básico de estándares para programas y sistemas
operativos para que diferentes sistemas operativos similares a UNIX puedan interoperar.

1.11.5 https://espanol.libretexts.org/@go/page/55196
2. También debemos tener en cuenta que los / delimitadores son los más utilizados, pero la mayoría de los caracteres se pueden
usar en su lugar; por ejemplo, s| | |g <pattern><replacement>puede facilitar el reemplazo de / caracteres.

This page titled 1.11: Patrones (Expresiones Regulares) is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by
Shawn T. O’Neil (OSU Press) .

1.11.6 https://espanol.libretexts.org/@go/page/55196
1.12: Miscelánea
Herramientas como sort , head and tail , grep , awk y sed representan una poderosa caja de herramientas,
especialmente cuando se usan con las corrientes de entrada estándar y salida estándar. Hay muchas otras utilidades útiles de línea
de comandos, y cubriremos algunas más, pero no necesitamos pasar tanto tiempo con ellas como lo hemos hecho para awk y
sed .

Manipulación de saltos de línea


Todas las características de las herramientas que hemos cubierto hasta ahora asumen la línea como la unidad básica de
procesamiento; awk procesa columnas dentro de cada línea, sed coincide y reemplaza patrones en cada línea (pero no
fácilmente a través de líneas), y así sucesivamente. Desafortunadamente, a veces la forma en que los datos se rompen sobre las
líneas no es conveniente para estas herramientas. La herramienta tr traduce conjuntos de caracteres en su entrada a otro
conjunto de caracteres como se especifica: ... | tr '' '' <set1><set2>[1]
Como breve ejemplo, tr 'TA' 'AT' Pz_CDNAS.fasta traduciría todos los caracteres T a caracteres A , y viceversa
(esto va por cada T y A en el archivo, incluidos los de las líneas de encabezado, por lo que esta herramienta no sería muy útil
para manipular FASTA). En cierto modo, tr es como un simple sed . El mayor beneficio es que, a diferencia de sed ,
tr no rompe su entrada en una secuencia de líneas que se operan individualmente, sino que toda la entrada se trata como una
sola corriente. Así tr puede reemplazar los caracteres especiales de “nueva línea” que codifican el final de cada línea con algún
otro carácter.
En la línea de comandos, dichos caracteres de nueva línea pueden representarse como \ n , por lo que un archivo con las
siguientes tres líneas
i-12_0_tree_lineas

podría representarse alternativamente como línea 1\ nlínea 2\ nlínea 3\ n (la mayoría de los archivos terminan
con un carácter final de nueva línea). Suponiendo que este archivo se llamara lines.txt , podríamos reemplazar todas las
\ n nuevas líneas con # caracteres.
I.12_1_UNIX_140_TR_EX

Observe en lo anterior que incluso la nueva línea final ha sido reemplazada, y nuestro símbolo del sistema impreso en la misma
línea que la salida. Del mismo modo, tr (y sed ) pueden reemplazar caracteres con nuevas líneas, por lo que
tr '#' '\ n' desharía lo anterior.
Usar tr en combinación con otras utilidades puede ser útil, particularmente para formatos como FASTA, donde un solo
“registro” se divide en varias líneas. Supongamos que queremos extraer todas las secuencias de pz_CDNAS.fasta con
NREAD mayores a 5. La estrategia sería algo así como:
1. Identificar un carácter que no esté presente en el archivo, tal vez un carácter @ o tabulador \ t (y verifique con grep
para asegurarse de que no esté presente antes de continuar).
2. Usa tr para reemplazar todas las nuevas líneas con ese carácter, por ejemplo, tr '\ n'' @ ' .
3. Debido a que se utilizan > caracteres para indicar el inicio de cada registro en archivos FASTA, use sed para reemplazar
inicio de registro > caracteres con nuevas líneas seguidas de esos caracteres: sed -r 's/>/\ n>/g' .
En este punto, el flujo se vería así, donde cada línea representa un solo registro de secuencia (con @ caracteres extraños
insertados): i-12_2_unix_141_tr_ex2
4. Use grep , sed , awk , y así sucesivamente para seleccionar o modificar solo esas líneas de interés. (Si es necesario,
también podríamos usar sed para eliminar los caracteres @ insertados para que podamos procesar en la secuencia misma).
Para nuestro ejemplo, usa sed -r 's/=/ /1 '| awk' {if ($3 > 5) {print $0}} ' para imprimir solo
líneas donde el nReading sea mayor que 5.
5. Vuelva a formatear el archivo a FASTA reemplazando los caracteres @ por líneas nuevas, con tr o sed .
6. El flujo resultante tendrá líneas en blanco adicionales como resultado de las nuevas líneas adicionales insertadas antes de cada
carácter > . Estos se pueden quitar de diversas maneras, incluyendo awk '{if (NF > 0) print $0}' .

1.12.1 https://espanol.libretexts.org/@go/page/55191
Unir archivos en una columna común (y tareas relacionadas de fila/columna)
A menudo, la información con la que queremos trabajar se almacena en archivos separados que comparten una columna común.
Considere el resultado de usar blastx para identificar las HSP superiores contra el conjunto de marcos de lectura abiertos de
levadura, por ejemplo.
I.12_3_Unix_142_PZ_Blastx_levadura

El archivo resultante pz_blastx_yeast_top1.txt contiene la información estándar de BLAST:


I.12_4_Unix_143_pz_blastx_yeast_out

Del mismo modo, podemos guardar una tabla de información de secuencia del programa fasta_stats con las líneas de
comentario eliminadas como pz_stats.table .
I.12_5_UNIX_144_PZ_FASTA_STATS_Guardado

Visualización del archivo con menos -S :


I.12_6_Unix_145_PZ_Fasta_Stats_Saved_out

Dados estos datos, podríamos preguntar qué secuencias tuvieron un impacto en un marco de lectura abierto de levadura y un
contenido de GC de más del 50%. Podríamos averiguarlo fácilmente con awk , pero primero necesitamos invocar join , que
fusiona dos archivos de texto fila/columna basados en líneas con valores similares en una columna de “clave” especificada. Por
defecto, unir solo da salida a filas donde los datos están presentes en ambos archivos. Se requiere que ambos archivos de
entrada estén ordenados de manera similar (ya sea ascendente o descendente) en las columnas clave: join -1 -2 <key
column in file1><key column in file2><file1><file2>.
Como la mayoría de las herramientas, unir salidas su resultado a salida estándar, que puede ser redirigido a un archivo u otras
herramientas como less y awk . Idealmente, nos gustaría decir
join -1 1 -2 1 pz_stats.txt pz_blastx_yeast_top1.txt para indicar que deseamos unir estos archivos
por su primera columna común, pero hasta el momento los archivos no están ordenados de manera similar. Entonces, primero
crearemos versiones ordenadas.
I.12_7_Unix_146_Clasificación

Ahora podemos ejecutar nuestro


join -1 1 -2 1 pz_stats.sorted.txt pz_blastx_yeast_top1.sorted.txt , canalizando el resultado
en menos . La salida contiene todas las columnas del primer archivo, seguidas de todas las columnas del segundo archivo (sin la
columna clave), separadas por espacios únicos.
imagen

En lugar de ver la salida con menos , canalizarla en un awk '{if ($1 > 0.5) print $1}' identificaría
rápidamente esas secuencias con coincidencias BLAST y contenido GC superior al 50%.
Una dificultad con la salida anterior es que es bastante difícil de leer, al menos para nosotros los humanos. La misma queja podría
hacerse para la mayoría de los archivos que están separados por caracteres de tabulación; debido a la forma en que las pestañas se
formatean en herramientas menos y similares, las entradas de diferentes longitudes pueden hacer que las columnas se
desalineen (solo visualmente, por supuesto). La utilidad de columna ayuda en algunos casos. Reformatea la entrada de
fila/columna separada por espacios en blanco para que la salida sea legible por humanos, reemplazando uno o más espacios y
tabulaciones por un número apropiado de espacios para que las columnas estén alineadas visualmente: columna -t <file>o
... | columna -t .
la columna escriba su salida a la salida estándar. Aquí está el resultado de
A estas alturas, no debería sorprendernos que
join -1 1 -2 1 pz_stats.sorted.txt pz_blastx_yeast_top1.sorted.txt | column -t |
less -S
, que contiene los mismos datos que los anteriores, pero con espacios utilizados para rellenar las columnas apropiadamente.
I.12_9_Unix_148_Columna

Debido a que la mayoría de las herramientas como awk y sort utilizan cualquier número de caracteres de espacios en
blanco como delimitadores de columna, también se pueden usar en la columna de datos.

1.12.2 https://espanol.libretexts.org/@go/page/55191
Hay varias advertencias importantes al usar join . Primero, si se repite alguna entrada en las columnas clave, la salida contendrá
una fila por cada par de claves coincidentes.
I.12_10_Join_Duplicados

En segundo lugar, los archivos deben clasificarse de manera similar; si no lo están, unir producirá en el mejor de los casos una
advertencia difícil de ver. Un truco útil al usar shells compatibles con bash es hacer uso de las características para la
“sustitución de procesos”. Básicamente, cualquier comando que se imprima en la salida estándar puede envolverse en < ( y )
y usarse en lugar de un nombre de archivo: el shell creará automáticamente un archivo temporal con el contenido de la salida
estándar del comando y reemplazará la construcción con el nombre de archivo temporal. Este ejemplo une los dos archivos como
antes, sin pasos de ordenación separados:
join -1 1 -2 1 < (sort -k1,1d pz_stats.txt) < (sort -k1,1d
pz_blastx_yeast_top1.txt) < (sort -k1,1d)
. Porque el archivo pz_stats.txt fue el resultado de redirigir la salida estándar de
. /fasta_stats pz_cDNAs.txt a través de grep -v '#' , podríamos decir equivalentemente
join -1 1 -2 1 < (. /fasta_stats pz_cdnas.fasta | grep -v '#' | sort -k1,1d) <
(sort -k1,1d pz_blastx_yeast_top1.txt)
.
Por último, a menos que estemos dispuestos a suministrar un número desmesurado de argumentos, el valor por defecto para
unir es producir solo líneas donde la información clave se encuentre en ambos archivos. Más a menudo, podríamos desear que
se incluyan todas las claves, con valores “faltantes” (por ejemplo, NA ) asumidos para los datos presentes en un solo archivo. En
el lenguaje de bases de datos, estas operaciones se conocen como “unión interna” y “unión externa completa” (o simplemente
“unión externa”), respectivamente.
I.12_11_Join_types

Donde unir no produce fácilmente uniones externas, herramientas más sofisticadas pueden hacer esto y mucho más. Por
ejemplo, los lenguajes de programación Python y R (cubiertos en capítulos posteriores) sobresalen en la manipulación y fusión de
colecciones de datos tabulares. Otras herramientas utilizan un lenguaje especializado para la manipulación de bases de datos
conocido como Lenguaje de Consulta Estructurado o SQL. Dichas bases de datos a menudo se almacenan en formato binario, y
éstas se consultan con software como MySQL y Postgres (ambos requieren acceso de administrador para administrar), o motores
más simples como sqlite3 (que pueden ser instalados y administrados por usuarios normales). [2]

Recuento de líneas duplicadas


Vimos que ordenar con la bandera -u se puede usar para eliminar duplicados (definidos por las columnas clave utilizadas).
¿Qué hay de aislar duplicados, o de otra manera contarlos o identificarlos? Lamentablemente, ordenar no está a la altura de la
tarea, pero una herramienta llamada uniq puede ayudar. Se colapsa líneas consecutivas, idénticas. Si se usa el indicador -c ,
antepone cada línea con el número de líneas colapsadas: uniq <file>o ... | uniq .
Debido a que uniq considera líneas enteras en sus comparaciones, es algo más rígido que sort -u ; no hay forma de
especificar que solo se deben usar ciertas columnas en la comparación. [3] La utilidad uniq también solo colapsará líneas
idénticas si son consecutivas, lo que significa que la entrada ya debería estar ordenada (a menos que el objetivo realmente sea
fusionar solo líneas duplicadas ya consecutivas). Así, para identificar duplicados, la estrategia suele ser:
1. Extraer columnas de interés usando awk .
2. Ordenar el resultado usando sort .
3. Use uniq -c para contar duplicados en las líneas resultantes.
Consideremos nuevamente la salida de . /fasta_stats pz_cdnas.fasta , donde la columna 4 enumera los 5-meros
más comunes para cada secuencia. Usando este patrón de extracto/clasificación/uniq, podemos identificar rápidamente cuántas
veces se listó cada 5-mer.
I.12_12_UNIX_149_UNIQ_EX1

El resultado enumera los recuentos para cada 5-mer. Podríamos continuar clasificando la salida por la nueva primera columna para
identificar los 5-meros con los recuentos más grandes.

1.12.3 https://espanol.libretexts.org/@go/page/55191
I.12_13_UNIX_150_UNIQ_EX1_out

A menudo es útil ejecutar uniq -c en listas de recuentos producidos por uniq -c . Ejecutar el resultado anterior a través
de awk '{print $1}' | sort -k1,1n | uniq -c revela que 90 5-mers se enumeran una vez, 18 se enumeran
dos veces, y así sucesivamente.
I.12_14_UNIX_150_UNIQ_EX2_Out

Contar artículos con uniq -c es una técnica poderosa para los datos de “comprobación de la cordura”. Si queremos
comprobar que una columna o combinación de columnas dada no tiene entradas duplicadas, por ejemplo, podríamos aplicar la
estrategia extract/sort/uniq seguida de awk '{if ($1 > 1) print $0}' . Del mismo modo, si queremos asegurarnos
de que todas las filas de una tabla tengan el mismo número de columnas, podríamos ejecutar los datos a través de
awk '{print NF}' para imprimir el número de columnas en cada fila y luego aplicar extracto/sort/uniq, esperando que
todos los recuentos de columnas se colapsen en una sola entrada.

Trazado básico con gnuplot


Más ilustrando el poder de awk , sed , sort y uniq , podemos crear un “histograma” basado en texto de coberturas
para las secuencias en el archivo Pz_CDNAS.fasta , que se almacenan en la tercera columna de las líneas de encabezado (por
ejemplo, >PZ 7180000000004_TX nReads=26 cov=9.436 ). Comenzaremos aislando los números de cobertura con
grep (para seleccionar solo líneas de encabezado), sed (para reemplazar = caracteres con espacios) y awk (para
extraer la nueva columna de números de cobertura), mientras simultáneamente imprimimos solo la porción entera de la cobertura
columna.
I.12_15_UNIX_151_Coverage_EX

La salida es una columna simple de enteros, que representa coberturas redondeadas. A continuación, una
especie -k1,1n | uniq -c producirá los recuentos para cada bin de cobertura, revelando que la mayoría de las
secuencias (281) están en cobertura 1X, un puñado están en 2X y 3X, y las coberturas más altas son cada vez más raras.
I.12_16_UNIX_152_Coverage_ex_out

Aunque lenguajes como Python y R proporcionan paquetes de trazado de datos fáciles de usar, a veces puede ser útil trazar
rápidamente un conjunto de datos en la línea de comandos. El programa gnuplot puede producir no solo formatos de imagen
como PNG y PDF, sino también salida basada en texto directamente a salida estándar. Aquí está el comando para trazar el
histograma anterior (pero con puntos, en lugar de los cuadros tradicionales), junto con la salida.
I.12_17_Unix_153_Gnuplot_out

Es un poco difícil de ver, pero los puntos trazados están representados por caracteres A. Desglosando el comando
gnuplot , set term dumb instruye a gnuplot para producir salida basada en texto, plot “-” indica que
queremos trazar datos del flujo de entrada estándar, usar 2:1 indica que los valores X deben ser dibujados de la segunda
columna y los valores Y de la primera, y con puntos especifica que los puntos, a diferencia de líneas o cajas, deben
dibujarse. Debido a que los recuentos de coberturas caen tan rápidamente, es posible que queramos producir una gráfica de
registro/registro, y también podemos agregar un título:
gnuplot -e 'set term dumb; set logscale xy; plot “-” usando 2:1 con points' title
“Coverage Counts”
I.12_18_Unix_154_GNUPLOT_OUT2

Aunque solo hemos mostrado el uso más básico de gnuplot , en realidad es un paquete de trazado sofisticado: los diversos
comandos normalmente no se especifican en la línea de comandos sino que se colocan en un script ejecutable. Para una
demostración de sus capacidades, visite http://gnuplot.info.

For-Loops en bash
A veces queremos ejecutar el mismo comando o comandos similares como un conjunto. Por ejemplo, podemos tener un directorio
lleno de archivos que terminan en .tmp , pero deseábamos que terminaran en .txt .
I.12_19_Unix_155_for_TEMP_Files

1.12.4 https://espanol.libretexts.org/@go/page/55191
Debido a la forma en que funcionan los comodines de línea de comandos, no podemos usar un comando como
mv *.tmp *.txt ; el *.tmp se expandiría en una lista de todos los archivos, y *.txt se expandiría en nada (ya que
no coincide con ningún nombre de archivo existente).
Afortunadamente, bash proporciona una construcción de looping, donde los elementos reportados por comandos (como
ls *.tmp ) están asociados con una variable (como $i ), y otros comandos (como mv $i $i.txt ) se ejecutan para
cada elemento.
I.12_20_Unix_156_for_temp_files_out

Es más común ver tales bucles en scripts ejecutables, con la estructura de control rota en varias líneas.
I.12_21_Unix_157_for_temp_files_script

Esta solución funciona, aunque a menudo las técnicas de programación similares (como las sentencias if) en bash se vuelven
engorrosas, y usar un lenguaje más robusto como Python puede ser la mejor opción. Sin embargo, bash tiene un truco más
interesante bajo la manga: el shell bash puede leer datos en la entrada estándar, y al hacerlo intenta ejecutar cada línea.
Entonces, en lugar de usar un for-loop explícito, podemos usar herramientas como awk y sed para “construir” comandos
como líneas. Eliminemos el .tmp de la mitad de los archivos construyendo comandos mv sobre la base de una entrada inicial
de ls -1 *.tmp* (que enumera todos los archivos que coinciden con *.tmp* en una sola columna). Primero,
construiremos la estructura de los comandos.
I.12_22_Unix_158_Bash_Pipe

A esto agregaremos un sed -r s/\ .tmp//2 para reemplazar la segunda instancia de .tmp por nada (recordando
escapar el punto en la expresión regular), dando como resultado líneas como
I.12_23_Unix_158_2_Bash_Pipe_2

Después del sed , canalizaremos esta lista de comandos a bash , y nuestro objetivo se logra.
I.12_24_Unix_159_Bash_Pipe2

Control de versiones con git


En el capítulo 6, “Instalación de software (Bioinformática)”, trabajamos en un proyecto bastante sofisticado, que implica instalar
software (en nuestro directorio $HOME/local/bin ) así como descargar archivos de datos y escribir scripts ejecutables (lo
que hicimos en nuestro directorio $HOME/projects/P450s ). En particular, inicialmente creamos un script para
automatizar la ejecución de HMMER en algunos archivos de datos, llamado runhmmer.sh . Aquí están los contenidos del
directorio del proyecto cuando lo vimos por última vez:
I.12_25_Unix_159_3_Runhmmer_dir

Puede ser que a medida que continuemos trabajando en el proyecto, hagamos ajustes al script runhmmer.sh u otros archivos
de texto en este directorio. Idealmente, podríamos acceder a versiones anteriores de estos archivos, en caso de que necesitemos
referirnos por razones de procedencia o queremos deshacer ediciones posteriores. Una forma de lograr esta tarea sería crear
frecuentemente copias de seguridad de archivos importantes, tal vez con nombres de archivo incluyendo la fecha de la copia de
seguridad. Sin embargo, esto rápidamente se volvería difícil de manejar.
Una alternativa es usar el control de versiones, que es un sistema para administrar cambios en archivos (especialmente programas y
scripts) a lo largo del tiempo y entre diversos contribuyentes a un proyecto. Por lo tanto, un sistema de control de versiones permite
al usuario registrar cambios en archivos a lo largo del tiempo, e incluso permite que varios usuarios registren cambios,
proporcionando la capacidad de examinar las diferencias entre las distintas ediciones. Hay una serie de programas populares de
control de versiones, como svn (subversion) y cvs (sistema de versionado concurrente). Debido a que el trabajo de rastrear
los cambios en los archivos a lo largo del tiempo y los usuarios es bastante complejo (especialmente cuando varios usuarios pueden
estar realizando ediciones independientes simultáneamente), el uso del software de control de versiones puede ser un gran conjunto
de habilidades en sí mismo.
Uno de los sistemas de control de versiones desarrollados más recientemente es git , que ha ganado popularidad por varias
razones, incluido su uso en la gestión de proyectos de software populares como el kernel de Linux. [4] El sistema git (que es
administrado por un programa llamado git ) usa una serie de palabras de vocabulario que debemos definir primero.

1.12.5 https://espanol.libretexts.org/@go/page/55191
Repositorio
También conocido como “repositorio”, un repositorio git suele ser solo una carpeta/directorio.
Versión
Una versión es efectivamente una instantánea de un conjunto seleccionado de archivos o directorios en un repositorio. Por lo
tanto, puede haber múltiples versiones de un repositorio a través del tiempo (o incluso creadas independientemente por
diferentes usuarios).
Comprometer
Confirmar es la acción de almacenar un conjunto de archivos (en un repositorio) en una versión.
Diff
Dos versiones diferentes pueden ser “diffedadas”, lo que significa revelar los cambios entre ellas.
Etapa
No todos los archivos necesitan ser incluidos en una versión; la puesta en escena de un conjunto de archivos los marca para
su inclusión en la versión cuando ocurre la siguiente entrega.
El sistema git , como todos los sistemas de control de versiones, es bastante complejo y proporciona muchas características.
Los fundamentos, sin embargo, son: (1) hay una carpeta que contiene el proyecto de interés; (2) los cambios en algunos archivos se
hacen a lo largo del tiempo; (3) los archivos editados pueden ser “escenificados” periódicamente; y (4) un “commit” incluye una
instantánea de todos los archivos por etapas y almacena la información en una “versión”. (Para que conste, toda esta información se
almacena en un directorio oculto creado dentro del directorio del proyecto llamado .git , que es administrado por el propio
programa git ).
Para ilustrar, vamos a crear un repositorio para el proyecto p450s , editar el archivo de script runhmmer.sh así como crear
un archivo README.txt , y confirmar esos cambios. Primero, para convertir un directorio en un repositorio git ,
necesitamos ejecutar git init :
I.12_26_Unix_159_4_Git_init

Este paso crea el directorio oculto.git que contiene los archivos requeridos para su seguimiento por parte del sistema.
Normalmente no necesitamos trabajar directamente con este directorio, el software git lo hará por nosotros. A continuación,
crearemos nuestra primera versión poniendo en escena nuestros primeros archivos y ejecutando nuestro primer commit. Podríamos
mantener versiones rastreadas de todos los archivos de este directorio, pero ¿queremos? Los archivos de datos como
dmel-all-translation-r6.02.fasta son grandes y es poco probable que cambien, por lo que registrarlos sería
innecesario. Del mismo modo, debido a que el archivo de salida p450s_hmmsearch_dmel.txt se genera mediante
programación y siempre se puede regenerar (si tenemos una versión del programa que lo creó), tampoco haremos un seguimiento
de eso. Para “organizar” archivos para la siguiente confirmación, usamos git add ; para organizar todos los archivos en el
directorio del proyecto, usaríamos git add -A , pero aquí queremos poner en escena solo runhmmer.sh , así
ejecutaremos git add runhmmer.sh .
I.12_27_Unix_159_5_git_add_runhmmer

No se ha impreso ningún mensaje, pero en cualquier momento podemos ver el estado del proceso git ejecutando
git status .
I.12_28_Unix_159_6_Git_Status

La información de estado muestra que tenemos un nuevo archivo para rastrear, runhmmer.sh , y una serie de archivos sin
seguimiento (que hemos dejado sin seguimiento por una razón). Ahora podemos “comprometer” estos archivos por etapas a una
nueva versión, lo que hace que los archivos escalonados actualizados se almacenen para su posterior referencia. Al
comprometernos, necesitamos ingresar un mensaje de compromiso, lo que nos da la oportunidad de resumir los cambios que se
están cometiendo.
I.12_29_Unix_159_7_git_commit

En este punto, un estado git se limitaría a informarnos que todavía tenemos archivos sin seguimiento. Supongamos que
hacemos algunas ediciones a runhmmer.sh (agregando una nueva línea de comentarios, quizás), así como creamos un nuevo
archivo README.txt describiendo el proyecto.

1.12.6 https://espanol.libretexts.org/@go/page/55191
I.12_30_UNIX_159_8_GIT_NEWFILE

Al ejecutar git status en este punto se reportaría un nuevo archivo sin seguimiento, README.txt , así como una
lectura de línea modificada: runhmmer.sh para indicar que este archivo ha cambiado desde el último commit.
Podríamos seguir editando archivos y trabajando según sea necesario; cuando estemos listos para confirmar los cambios, solo
necesitamos organizar los archivos apropiados y ejecutar otro commit.
I.12_31_UNIX_159_9_GIT_EDIT_ESTAGE_COMMIT

Cada versión que cometemos se guarda, y podemos ver fácilmente un registro rápido del historial de un proyecto con git log
.
I.12_32_Unix_159_10_Git_log

Observe que a cada commit se le da un número de serie largo, como ec46950b36... . Para ver las diferencias entre dos
commits, podemos ejecutar git diff con solo los pocos caracteres de cada número de serie, como en
git diff 50c11fe ec4695 . El formato de salida no es notablemente legible por defecto.
I.12_33_Unix_159_11_GIT_diff

Muchas otras operaciones pueden ser realizadas por git , como ver el contenido de archivos de versiones anteriores y “revertir”
un proyecto a un estado anterior (al menos para aquellos archivos que son rastreados).
Hay otras dos características dignas de mención. Primero, no es raro tener muchos archivos que nos gustaría dejar sin seguimiento,
pero agregar todo el resto uno a la vez con git add es tedioso. Afortunadamente, git add -A mira el contenido del
archivo .gitignore (que puede ser necesario crear): cualquier archivo o directorio listado en .gitignore no será
escenificado por git add -A . (Y se puede rastrear el archivo.gitignore ).
Segundo, el sistema git hace que sea relativamente fácil compartir proyectos en línea con otros, al crear repositorios en sitios
como GitHub. Después de configurar una cuenta en http://github.com (o en un sitio similar, como http://bitbucket.com), puede
“empujar” el estado actual de su proyecto desde la línea de comandos a la web. (Los commits futuros también se pueden empujar a
medida que los creas). Otros usuarios pueden entonces “clonar” el proyecto desde el sitio web usando el comando git clone
discutido brevemente en el capítulo 6. GitHub y sitios web similares cuentan con excelentes tutoriales para interactuar sus
productos con sus propios repositorios de línea de comandos, si desea usarlos.

Ejercicios
1. En el archivo Pz_CDNAS.fasta , los ID de secuencia se agrupan de acuerdo con sufijos comunes como _TY
, _ACT y similares. ¿Qué grupo tiene el mayor número de secuencias y cuántas hay en ese grupo?
2. Usando las diversas herramientas de línea de comandos, extraiga todas las secuencias compuestas por una sola lectura (
nReads=1 ) de pz_CDNAS.fasta a un archivo formateado FASTA llamado Pz_CDNAS_Singles.fasta .
3. En el archivo de anotación Pz.annot.txt , cada ID de secuencia puede estar asociada con múltiples “números” de
ontología génica (GO) (columna 2) y una serie de “términos” diferentes (columna 3). Muchos ID están asociados con múltiples
números GO, y no hay nada que impida que un número o término en particular se asocie con múltiples ID.
I.12_34_UNIX_159_2_PZ_ANNOT_Sample¿Qué número GO está asociado con el mayor número de IDs únicos? ¿Con cuántos ID diferentes

está asociado? A continuación, responda las mismas preguntas usando el término GO en lugar del número GO. Para este último,
ten en cuenta que los separadores de columna en este archivo son caracteres de tabulación, \ t , pero awk por defecto usa
cualquier espacio en blanco, incluyendo los espacios que se encuentran en la columna de términos. En este archivo, sin
embargo, isocitrato no es un término, sino isocitrato deshidrogenasa (nad+) lo es.

1. A diferencia de otras herramientas, tr solo puede leer su entrada desde stdin.


2. Mientras que las bases de datos binarias como las utilizadas por sqlite3 y Postgres tienen su lugar (especialmente cuando
es necesario unir o buscar tablas grandes), almacenar datos en archivos de texto simples facilita el acceso y la manipulación.
Una discusión sobre la sintaxis SQL y las bases de datos relacionales está más allá del alcance de este libro; vea Jay Kreibich's
Using SQLite (Sebastopol, CA: O'Reilly Media, Inc., 2010) para una introducción amigable a sqlite3 y su sintaxis.
3. Esto no es del todo cierto: el <n>indicador -f para uniq elimina los primeros <n>campos antes de realizar la
comparación.

1.12.7 https://espanol.libretexts.org/@go/page/55191
4. Linus Torvalds, quien también inició el proyecto del kernel de Linux, desarrolló el sistema git . Al citar a Linus: “Soy un
bastardo egotista, y nombro a todos mis proyectos por mí mismo. Primero 'Linux, 'ahora' Git '”. (Aquí “git” se refiere a la jerga
británica para una persona “cabeza de cerdo y argumentativa”).

This page titled 1.12: Miscelánea is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil (OSU
Press) .

1.12.8 https://espanol.libretexts.org/@go/page/55191
CHAPTER OVERVIEW
2: Programación en Python
Python es tanto un idioma de primer nivel para el aprendizaje como una opción común en el desarrollo de software científico. Esta
parte cubre los conceptos básicos en programación (tipos de datos, sentencias if y bucles, funciones) a través de ejemplos de
análisis de secuencia de ADN. Esta parte también abarca temas más complejos en el desarrollo de software como objetos y clases,
módulos y APIs.
2.1: Hola, Mundo
2.2: Tipos de datos elementales
2.3: Colecciones y Looping- Listas y para
2.4: Entrada y salida de archivos
2.5: Flujo de control condicional
2.6: Funciones de Python
2.7: Interfaz de línea de comandos
2.8: Diccionarios
2.9: Knick-knacks bioinformáticos y expresiones regulares
2.10: Variables y Alcance
2.11: Objetos y Clases
2.12: Interfaces de Programación de Aplicaciones, Módulos, Paquetes, Azúcar Sintáctico
2.13: Algoritmos y estructuras de datos

This page titled 2: Programación en Python is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

1
2.1: Hola, Mundo
Antes de comenzar con la programación en Python, es útil considerar cómo encaja el lenguaje en el paisaje y la historia de
lenguajes similares. Inicialmente, la programación informática no estaba muy alejada del hardware en el que se estaba codificando.
Esto significaba escribir “bytecode” —o su equivalente legible por humanos, código ensamblado—que hacía referencia explícita a
ubicaciones de memoria (RAM) y copiaba datos hacia y desde el número relativamente pequeño de registros de CPU (las unidades
de almacenamiento directamente accesibles por la CPU). Desafortunadamente, esto significó que el código tuvo que ser reescrito
para cada uno de los muchos tipos de CPU. Posteriormente, se desarrollaron lenguajes más portátiles como el C. Estos lenguajes
aún funcionan cerca del hardware de muchas maneras; en particular, el programador debe decirle a la computadora cómo debe
asignar y desasignar la memoria de la RAM. Por otro lado, alguna abstracción proporciona construcciones de codificación de nivel
superior que no son específicas del tipo de CPU. Este código es luego compilado en bytecode por un programa de compilación para
cada CPU específica (como se discutió en capítulos anteriores, tuvimos que compilar algún software desde la fuente para
instalarlo). El resultado es un programa rápido y a menudo optimizado que libera al programador de tener que preocuparse por la
gran variedad de tipos de CPU.
Posteriormente, algunos programadores decidieron que no querían tener que preocuparse por especificar cómo debía asignarse y
desasignarse el espacio RAM. También querían más funciones integradas en sus idiomas para ayudarlos a diseñar rápidamente
programas complicados. Uno de los lenguajes destinados a lograr estos objetivos es Python, un proyecto iniciado en 1988 por el
matemático, informático y fan de Monty Python Guido van Rossum. [1] Los lenguajes de “alto nivel” como Python y R (cubiertos
en capítulos posteriores) proporcionan muchas características incorporadas al programador, y son incluso más abstractos que
lenguajes como C.
Desafortunadamente, debido a las abstracciones agregadas, lenguajes como Python no se pueden compilar fácilmente (como C
puede) para ejecutarse directamente en la CPU. [2] De hecho, estos lenguajes no se ejecutan de la misma manera que los programas
compilados o ensamblados son: son interpretados por otro programa que está escrito en un lenguaje compilado como C y se ejecuta
en la CPU. Entonces, un “programa” de Python es solo un archivo de texto de comandos que son interpretados por otro programa
que en realidad está interactuando con la CPU y la RAM.
II.1_1_compilado_VS_Interpretado

La facilidad y flexibilidad adicionales de los lenguajes interpretados generalmente tiene un costo: debido a la capa de ejecución
adicional, tienden a ser de 2 a 100 veces más lentos y usan de 2 a 20 veces más memoria que los programas C cuidadosamente
construidos, dependiendo de lo que se esté calculando. Sin embargo, estos lenguajes son significativamente más fáciles de usar y
podemos introducir nuestras ideas en el código mucho más rápidamente.
El trabajo en el lenguaje Python y los intérpretes para él ha progresado de manera constante desde la década de 1990, enfatizando
un enfoque de “una mejor manera”. En lugar de proporcionar múltiples versiones de comandos básicos que hacen lo mismo,
Python proporciona la menor cantidad de comandos posible mientras intenta no limitar al programador. Python también enfatiza la
legibilidad del código: la mayor parte de la sintaxis se compone de palabras similares al inglés, los atajos y los caracteres de
puntuación se mantienen al mínimo, y la estructura visual de los “bloques” de código se aplica con sangría.
Por estas razones, el uso de Python ha crecido significativamente en los últimos años, especialmente para la bioinformática y la
biología computacional. El énfasis en la legibilidad y “una mejor manera” facilita el aprendizaje, particularmente para aquellos que
son nuevos en la programación. [3] Lo más importante es que Python nos permite enfocarnos en los conceptos de programación sin
luchar a través de una abundancia de opciones y sintaxis confusa, y los nuevos programadores pueden leer y comprender con
frecuencia el código escrito por otros. Finalmente, Python incorpora una serie de paradigmas de programación modernos que lo
hacen apropiado tanto para tareas pequeñas como para proyectos de ingeniería de software más grandes: es un lenguaje oficial en
Google (junto con C++ y Java), y se enseña en cursos introductorios en la Universidad Johns Hopkins, la Universidad de Nueva
York, la Instituto Tecnológico de Massachusetts, y muchos otros.
Todo esto no quiere decir que cualquier lenguaje de programación esté desprovisto de peculiaridades y dificultades. Solo
cubriremos algo de lo que ofrece Python, las partes que son más básicas y que probablemente se encuentren en muchos idiomas.
Temas que son altamente “pitónicos” serán resaltados a medida que avancemos.

Versiones Python
En este libro estaremos trabajando con Python versión 2.7; es decir, vamos a suponer que el ejecutable de Python que se encuentra
en tu variable $PATH es la versión 2.7 (quizás 2.7.10, que es la última de la serie 2.7 a partir de 2015). Puede verificar esto

2.1.1 https://espanol.libretexts.org/@go/page/55133
ejecutando python —version en la línea de comandos. Si bien las versiones más nuevas están disponibles (hasta 3.4 y
superiores), aún no se usan universalmente. Estas versiones más recientes cambian algo de sintaxis de 2.7. Para muchos de los
conceptos introducidos en este libro, si te quedas con la sintaxis que se muestra, tu código también debería ser compatible con estas
versiones más nuevas, pero posiblemente no compatible con versiones anteriores como 2.5 o 2.6. Este es un artefacto
desafortunado de la filosofía “one best way” de Python: ¡en ocasiones, los diseñadores de Python cambian de opinión sobre cuál es
la mejor manera!
Para dar un ejemplo, la función print print (“hello there”) funciona en Python versiones 2.6, 2.7, 3.0, 3.1, y así
sucesivamente, mientras que la palabra clave version print “hello there” (note la falta de paréntesis) solo funcionaría
en las versiones 2.6 y 2.7. En algunos casos donde se producirían diferencias de comportamiento en versiones posteriores, las
anotaremos en notas al pie de página.

Hola, Mundo
Debido a que estamos trabajando en un lenguaje interpretado, para poder escribir un programa, necesitaremos crear un archivo de
código Python, y luego suministrarlo como entrada al programa de interpretación. Hay algunas formas de hacerlo: (1) usar un
entorno gráfico interactivo como el cuaderno Jupyter; (2) ejecutar el intérprete nosotros mismos en la línea de comandos, dando el
nombre del archivo que contiene nuestro código como argumento; o (3) haciendo del archivo de código un script ejecutable en el
entorno de línea de comandos usando #! sintaxis.

Cuaderno Jupyter
Para aquellos que deseen programar Python sin trabajar en la línea de comandos, hay una variedad de entornos gráficos
disponibles. Una instalación típica de Python de http://python.org incluye el editor de código “Idle”. Una de las mejores
alternativas a este default se conoce como Jupyter, que se ejecuta en un navegador web permite al programador intercalar secciones
de código y documentación.
La instalación de Jupyter requiere que Python ya esté instalado (desde http://python.org), y luego requiere usar el terminal de línea
de comandos en Linux, OS X o Windows; consulte http://jupyter.org/install para obtener más detalles. Una vez instalado, se puede
iniciar desde la línea de comandos ejecutando jupyter notebook :
imagen

La interfaz de Jupyter se abrirá en el navegador web de escritorio predeterminado, mostrando la lista de carpetas y archivos en
cualquier directorio desde el que se ejecutó el comando.
imagen

Al hacer clic en el botón “Nuevo”, seguido de “Python Notebook” se creará un nuevo documento de cuaderno compuesto por
“celdas”. Las celdas de un cuaderno pueden contener texto legible por humanos (como documentación) o líneas de código Python.
Si el texto de la celda se interpreta como código o texto depende de la elección que se haga en el menú “Celda”.
Cada celda puede ser “ejecutada” haciendo clic en el botón “Reproducir”; al hacerlo, las celdas de texto cambian a una salida muy
bien formateada y ejecuta líneas de código para celdas de código. Pero cuidado: la salida de una celda dada a menudo depende de
qué otras celdas se hayan ejecutado y en qué orden (vea la celda “La salida de celda depende del orden de ejecución” en la figura
siguiente).
imagen

Por esta razón, recomiendo encarecidamente hacer la suposición de que todas las celdas de código se ejecutarán en orden de arriba
a abajo, lo que se puede lograr seleccionando “Ejecutar todo” en el menú “Celda” cada vez que desee ejecutar cualquier código. Al
hacerlo, todas las celdas se vuelven a ejecutar cada vez que se ejecuta, pero tiene la ventaja de garantizar la corrección del cuaderno
general a medida que se realizan cambios en las celdas a lo largo del tiempo.

Intérprete especificado
Tan conveniente como es el cuaderno IPython, debido a que la parte anterior de este libro se centró en la línea de comandos, y
Python interactúa bastante bien con ella, los ejemplos aquí serán del entorno de línea de comandos. Debido a que los programas
Python son scripts interpretados, podemos especificar manualmente el intérprete en la línea de comandos cada vez que ejecutamos
dicho script.

2.1.2 https://espanol.libretexts.org/@go/page/55133
Para este método, primero tenemos que editar un archivo de código que llamaremos helloworld.py ( .py es la extensión
tradicional para programas Python). En la línea de comandos, editaremos archivos de código con nuestro editor de texto nano ,
pasando algunos parámetros adicionales:
II.1_5_py_1_nano_args

El -w le dice a nano que no envuelva automáticamente las líneas largas (estamos escribiendo código después de todo, no un
ensayo), -i dice que sanje automáticamente las nuevas líneas al nivel de sangría actual, -T 4 dice que las tab-stops deben
tener cuatro espacios de ancho, y -E dice que las pestañas deben convertirse en espacios (cuatro de ellas). Este uso de cuatro
espacios por nivel de sangría es ampliamente acordado por la comunidad Python como fácil a la vista. (“Una mejor manera”,
¿recuerdas?) Pondremos una simple llamada a la función print () en el archivo:
II.1_6_PY_2_Hola_Mundo

Como de costumbre, Control-o guarda el archivo (presione Enter si se le solicita sobre el nombre del archivo) y
Control-X sale nano . A continuación, para ejecutarlo, todo lo que necesitamos hacer es llamar al intérprete de Python en
el archivo:
II.1_7_py_hello_mundo_ex_output

¡Éxito! ¡Hemos escrito nuestro primer programa Python!

Hacer que el archivo sea ejecutable


Un método alternativo es hacer que el archivo de código sea un script ejecutable. Primero, tenemos que editar el archivo de código
para incluir una primera línea especial:
II.1_8_py_3_hola_mundo_chmod

Para que este método funcione, los dos primeros caracteres del archivo deben ser #! (de hecho, toda la línea necesita ser
replicada exactamente); aunque nano está mostrando lo que parece ser una línea en blanco por encima de nuestro #! línea,
realmente no hay una ahí.
En el capítulo 5, “Permisos y Ejecutables”, discutimos el #! línea como que contiene la ruta absoluta al intérprete, como en
#! /usr/bin/bash para scripts bash . En este caso, estamos especificando algo ligeramente diferente:
¡#! /usr/bin/env python . El programa env , entre otras cosas, busca la ubicación instalada del argumento dado y lo
ejecuta. A #! como esta provocará que un programa Python se ejecute con éxito, incluso si está instalado en una ubicación no
estándar. (Uno puede preguntar si env alguna vez se instala en una ubicación no estándar. Afortunadamente, es raro encontrar
env ubicado en otro lugar que no sea en /usr/bin .)
A continuación, necesitamos salir nano y hacer que el archivo sea ejecutable usando la utilidad chmod , y finalmente
podemos ejecutarlo con . /helloworld.py. Esto especifica que se debe ejecutar el programa helloworld.py y que existe en
el directorio actual ( . /).
II.1_9_py_4_hola_mundo_chmod_output

Configuración y uso de nano


Generalmente, no querrás escribir nano -w -i -E -T 4... cada vez que quieras editar un archivo de código Python.
Afortunadamente, nano se puede configurar para usar automáticamente estas opciones si se especifican correctamente en un
archivo llamado .nanorc en su directorio home. Pero esta puede no ser la mejor opción, ya sea: al editar archivos que no son
código Python, es probable que no quiera convertir todas sus entradas de tabulación en espacios. En su lugar, es posible que desee
definir un alias de shell llamado nanopy específicamente para editar código Python. Para tener este alias de shell preservado
para cada sesión de inicio de sesión, el código relevante tendría que ser agregado a su .bashrc (asumiendo que su shell es
bash ):
II.1_10_py_4_2_bash_login

Si vas a realizar lo anterior, vuelve a verificar que el comando sea exactamente como está escrito. Después de cerrar sesión y volver
a ingresar, puede editar un archivo de código Python con el alias usando nanopy helloworld.py .

2.1.3 https://espanol.libretexts.org/@go/page/55133
Como se desprende de la muestra de código anterior, nano también puede proporcionar resaltado de sintaxis (coloración del
código para legibilidad) si su $HOME/.nanorc y archivos relacionados están configurados correctamente, aunque no es
necesario para la programación.
No olvides que muchas veces es útil tener múltiples ventanas de terminales abiertas simultáneamente. Puedes usar uno para editar,
uno para ejecutar tu programa y probar, y tal vez un tercer running top , mostrando cuánta CPU y RAM está usando tu
programa.
Aunque no es tan potente como los editores de texto más sofisticados como emacs o vim , nano es fácil de usar e incluye
una serie de características como editar múltiples archivos, cortar y pegar dentro y entre archivos, búsqueda y búsqueda/reemplazo
basada en expresiones regulares, deletrear check, y más. Durante la edición, nano también puede llevarte directamente a un número
de línea ( Control — - ), que te será útil cuando necesites ir directamente a una línea que causó un error.
Sea cual sea el editor que elijas usar, leer alguna documentación te ayudará a ser mucho más productivo. Para el resto de nuestras
muestras de código, mostraremos capturas de pantalla desde dentro de vim , principalmente porque proporciona un resaltado de
sintaxis más bonito.

Ejercicios
1. Crear un archivo de código Python en la línea de comandos que contenga una impresión simple (“¡Hola!”)
declaración. Ejecútelo especificando el intérprete. Si está ejecutando iPython notebook, intente crear un cuaderno similarmente
simple y ejecute la celda.
2. Cree un archivo de código Python, conviértalo en un script ejecutable y ejecútelo. ¿Eso funciona? ¿Por qué o por qué no?
3. Determine qué versión de Python está ejecutando (tal vez ejecutando python —version ). Prueba para ver qué versiones
de impresión funcionan para ti: print (“¡Hola!” ) o imprimir “¡Hola! " . (El primero es muy preferido.)

1. La historia de la computación está llena de giros, giros y reinvención de ruedas. LISP, por ejemplo, es un lenguaje que incorpora
muchas de las mismas características de alto nivel que Python, ¡pero se desarrolló por primera vez en 1958!
2. Por otro lado, programadores ambiciosos están trabajando actualmente en proyectos como Cython para hacer exactamente esto.
3. Muchos creen que la mejor manera de aprender programación es de una manera orientada al hardware con ensamble o un
lenguaje como C. Esta es una filosofía legítima, pero para el público objetivo de este libro, nos quedaremos con los lenguajes de
nivel superior Python y R.

This page titled 2.1: Hola, Mundo is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil (OSU
Press) .

2.1.4 https://espanol.libretexts.org/@go/page/55133
2.2: Tipos de datos elementales
Las variables son vitales para casi todos los lenguajes de programación. En Python, las variables son “nombres que hacen
referencia a datos”. Los tipos de datos más básicos a los que se puede hacer referencia se definen por cómo funcionan las
computadoras contemporáneas y son compartidos por la mayoría de los idiomas.

Enteros, flotadores y booleanos


Considera el entero 10, y el número real 5.64. Resulta que estos dos están representados de manera diferente en el código binario
de la computadora, en parte por razones de eficiencia (por ejemplo, almacenar 10 vs. 10.0000000000 ). Python y la
mayoría de los otros lenguajes consideran que los enteros y los números reales son dos “tipos” diferentes: los números reales se
llaman floats (abreviatura de “números de punto flotante”), y los enteros se llaman ints. Asignamos datos como flotadores e ints a
variables usando el operador = .
II.2_1_PY_5_INT_Flotador

Mientras estamos en el tema de las variables, los nombres de variables en Python siempre deben comenzar con una letra minúscula
y contener solo letras, guiones bajos y números.
Tenga en cuenta que el intérprete ignora # caracteres y cualquier cosa después de ellos en la línea. [1] Esto nos permite poner
“comentarios” en nuestro código. Las líneas en blanco tampoco importan, permitiendo la inserción de líneas en blanco en el código
para facilitar la lectura.
Podemos convertir un tipo int en un tipo float usando la función float () , que toma un parámetro dentro de los paréntesis:
II.2_2_py_6_float_convert

Del mismo modo, podemos convertir floats a ints usando la función int () , que trunca el valor de coma flotante en el punto
decimal (así que 5.64 se truncará al int type 5, mientras que -4.67 estaría truncado al int type -4):
II.2_3_PY_7_INT_Convert

Esta información es útil debido a una advertencia particular cuando se trabaja con la mayoría de los lenguajes de programación,
Python incluyó: si realizamos operaciones matemáticas usando solo tipos int, el resultado siempre será un int. Así, si queremos que
nuestras operaciones devuelvan un valor de punto flotante, primero necesitamos convertir al menos una de las entradas del lado
derecho a un tipo flotante. Afortunadamente, podemos hacer esto en línea:
II.2_4_PY_8_float_int_math

En la última línea anterior, vemos que las expresiones matemáticas se pueden agrupar con paréntesis de la manera habitual (para
anular el orden estándar de las operaciones si es necesario), y si una subexpresión devuelve un float, entonces viajará por la cadena
para inducir matemáticas de punto flotante para el resto de la expresión. [2]
Otra propiedad de importancia es que el lado derecho de una tarea se evalúa antes de que ocurra la asignación.
II.2_5_PY_8_2_derecha_manos_lado_primero

Aparte de la suma y resta habituales, otros operadores matemáticos de nota incluyen ** para potencias exponenciales y % para
módulo (para indicar un resto después de la división entera, por ejemplo, 7% 3 es 1 , 8% 3 es 2 , 9% 3 es 0 , y así
sucesivamente).
II.2_6_py_9_more_math

Observe que hemos reutilizado las variables a , b y c ; esto es completamente permisible, pero debemos recordar que ahora
se refieren a diferentes datos. (Recordemos que una variable en Python es un nombre que hace referencia a, o hace referencia, a
algunos datos.) En general, la ejecución de líneas dentro del mismo archivo (o celda en un cuaderno IPython) ocurre de manera
ordenada de arriba a abajo, aunque más adelante veremos “estructuras de control” que alteran este flujo.
Los booleanos son tipos de datos simples que contienen el valor especial True o el valor especial False . Muchas funciones
devuelven Booleanos, al igual que las comparaciones:
II.2_7_PY_10_booleano

Por ahora, no vamos a usar mucho los valores booleanos, pero más adelante serán importantes para controlar el flujo de nuestros
programas.

2.2.1 https://espanol.libretexts.org/@go/page/55113
Cuerdas
Las cadenas, que contienen secuencias de letras, dígitos y otros caracteres, son los tipos de datos básicos más interesantes. [3]
Podemos especificar el contenido usando comillas simples o dobles, lo que puede ser útil si queremos que la cadena en sí contenga
una comilla. Alternativamente, podemos escapar de caracteres impares como comillas si confunden al intérprete mientras intenta
analizar el archivo.
II.2_8_PY_11_CotizoTipos

Las cadenas se pueden agregar junto con + para concatenerlas, lo que da como resultado que se devuelva una nueva cadena para
que pueda asignarse a una variable. La función print () , en su forma más simple, toma como parámetro un solo valor como
una cadena. Esta podría ser una variable que se refiera a un dato, o el resultado de un cálculo que devuelva uno:
II.2_9_PY_12_StringConcat

Sin embargo, no podemos concatenar cadenas con tipos de datos que no son cadenas.
II.2_10_PY_13_StringConcatError

Ejecutar el código anterior resultaría en un


TypeError: no se puede concatenar los objetos 'str' y 'float' , y se reportaría el número de
línea infractor. En general, el error real en tu código podría estar antes de la línea que informa el error. Este ejemplo de error en
particular no ocurriría si hubiéramos especificado height = “5.5" en la línea anterior, porque dos cadenas se pueden
concatenar con éxito.
Afortunadamente, la mayoría de los tipos de datos incorporados en Python se pueden convertir en una cadena (o una cadena que los
describe) usando la función str () , que devuelve una cadena.
II.2_11_PY_14_STRFUNC

Como lo ilustra lo anterior, podemos elegir en algunos casos almacenar el resultado de una expresión en una variable para su uso
posterior, o tal vez deseemos usar una expresión más compleja directamente. La elección es un equilibrio entre código verboso y
código menos verboso, cualquiera de los cuales puede mejorar la legibilidad. Deberías usar lo que tenga más sentido para ti, ¡la
persona con más probabilidades de tener que leer el código más tarde!
Python facilita la extracción de una cadena de un solo carácter de una cadena usando corchetes o sintaxis “index”. Recuerda que en
Python, la primera letra de una cadena ocurre en el índice 0 .
II.2_12_PY_15_STR_index1

El uso de corchetes también se conoce como sintaxis “slice”, porque podemos ellos extraer un slice (substring) de una cadena con
la siguiente sintaxis: string_var [begin_index:end_index] . Nuevamente, la indexación de una cadena comienza
en 0 . Porque las cosas no pueden ser demasiado fáciles, el índice inicial es inclusivo, mientras que el índice final es exclusivo.
II.2_13_PY_16_STR_index2

Una buena manera de recordar este poco confuso de sintaxis es pensar que los índices ocurren entre las letras.
II.2_14_entre_cartas_índices

Si una cadena parece que podría convertirse a un tipo int o float, probablemente pueda ser con las funciones de conversión
float () o int () . Sin embargo, si tal conversión no tiene sentido, Python se estrellará con un error. Por ejemplo, la
última línea de abajo producirá el error ValueError: could not convert string to float: XY_2.7Q .
II.2_15_py_17_nums_from_strs

Para obtener la longitud de una cadena, podemos usar la función len () , que devuelve un int. Podemos usar esto junto con la
sintaxis [] para obtener la última letra de una cadena, aunque no sepamos la longitud de la cadena antes de que se ejecute el
programa. Debemos recordar, sin embargo, que el índice del último carácter es uno menos que la longitud, basado en las reglas de
indexación.
II.2_16_PY_18_Last_Letter1

Del mismo modo, si queremos una subcadena desde la posición 2 hasta el final de la cadena, necesitamos recordar las
peculiaridades de la notación [] slice, que es inclusive:exclusive.

2.2.2 https://espanol.libretexts.org/@go/page/55113
imagen

Inmutabilidad
En algunos idiomas es posible cambiar el contenido de una cadena después de haber sido creada. En Python y algunos otros
lenguajes, este no es el caso, y se dice que las cadenas son inmutables. Se dice que los datos son inmutables si no pueden ser
alterados después de su creación inicial. La siguiente línea de código, por ejemplo, causaría un error como
TypeError: 'str' object does not support item assignment :
II.2_18_py_20_inmutable_string

Lenguajes como Python y Java hacen que las cadenas sean inmutables por una variedad de razones, incluida la eficiencia
computacional y como salvaguardia para evitar ciertas clases comunes de errores de programación. Para la biología computacional,
donde a menudo deseamos modificar cadenas que representan secuencias biológicas, esto es una molestia. Aprenderemos varias
estrategias para solucionar este problema en futuros capítulos.
En muchos casos, podemos hacer que parezca que estamos cambiando el contenido de algunos datos de cadena reutilizando el
nombre de la variable. En el siguiente código, estamos definiendo cadenas seqa y seqb , así como seqc como la
concatenación de estas, y luego seqd como una subcadena de seqc . Finalmente, reutilizamos el nombre de la variable
seqa para hacer referencia a diferentes datos (que se copian del original).
II.2_19_PY_21_Variable_reutilizar

Así es como podríamos representar estas variables y los datos almacenados en la memoria, tanto antes como después de la
reasignación de seqa .
imagen

Debido a que la cadena “ACTAG” es inmutable, redefinir seqa da como resultado que se cree una pieza de datos
completamente diferente. La cadena original “ACTAG” seguirá existiendo en la memoria (RAM) por poco tiempo, pero debido
a que no es accesible a través de ninguna variable, Python eventualmente la limpiará para hacer espacio en la memoria en un
proceso conocido como recolección de basura. [4] La recolección de basura es un proceso automático y periódico de desasignación
de memoria utilizada por datos que ya no son accesibles (y por lo tanto ya no son necesarios) por el programa.
Esta inmutabilidad de cadenas podría dar como resultado un código que tarde mucho más en ejecutarse de lo esperado, ya que la
concatenación de cadenas da como resultado la copia de datos (ver los resultados de seqc = seqa + seqb arriba). Por
ejemplo, si tuviéramos un comando que concatenara cadenas cromosómicas (millones de letras cada una) para crear una cadena
genómica, genoma = chr1 + chr2 + chr3 + chr4 , ¡el resultado sería una copia de los cuatro cromosomas que
se están creando en la memoria! Por otro lado, en muchos casos, Python puede utilizar la inmutabilidad de las cadenas a su favor.
Supongamos, por ejemplo, que queríamos obtener una subcadena grande de una cadena grande,
centromere_region = chr1 [0:1500000] . En este caso, Python no necesita hacer una copia de la subcadena en la
memoria. Debido a que la cadena original nunca puede cambiar, todo lo que necesita es algo de contabilidad detrás de escena para
recordar que la variable centromere_region está asociada con parte de las referencias de cadena chr1 . Es por ello que
seqd en la figura anterior no duplica datos de seqc .
Ninguna de esta discusión implica que en este momento deba preocuparse por la eficiencia computacional de estas operaciones.
Más bien, el concepto de inmutabilidad y la definición de una variable (en Python) como un “nombre que se refiere a algunos
datos” son lo suficientemente importantes como para garantizar una discusión formal.

Ejercicios
1. Cree y ejecute un programa Python que utilice enteros, flotantes y cadenas, y convierta entre estos tipos. Intente usar la función
bool () para convertir un entero, flotante o cadena a booleano e imprimir los resultados. ¿Qué tipos de enteros, flotantes y
cadenas se convierten en un False booleano y qué tipos se convierten a True ?
2. Sabemos que no podemos usar el operador + para concatenar un tipo de cadena y un tipo entero, pero ¿qué sucede cuando
multiplicamos una cadena por un entero? (Esta es una característica que es bastante específica de Python).
3. ¿Qué sucede cuando intentas usar un tipo flotante como índice en una cadena usando la sintaxis [] ? ¿Qué sucede cuando
usas un entero negativo?

2.2.3 https://espanol.libretexts.org/@go/page/55113
4. Supongamos que tiene una cadena de secuencia como variable, como seq = “ACTAGATGA” . Usando solo los conceptos
de este capítulo, escribe algún código que use la variable seq para crear dos nuevas variables,
primera_mitad y segunda_mitad que contengan la primera mitad (redondeada hacia abajo) y la segunda mitad
(redondeada hacia arriba) de seq . Al imprimirse, estos dos deben imprimir “ACTA” y “GATGA” , respectivamente.
II.2_21_PY_22_ejercicio_string_splitterEs importante destacar que tu código debería funcionar sin importar la cadena a la que se refiere

seq , sin cambiar ningún otro código, siempre y cuando la longitud de la cadena sea de al menos dos letras. Por ejemplo, si
seq = “TACTTG” , entonces el mismo código debería resultar en first_half refiriéndose a “TAC” y
second_half refiriéndose a “TTG” .

1. Los símbolos # se ignoran a menos que ocurran dentro de un par de comillas. Técnicamente, el intérprete también ignora el
#! línea, pero es necesario para ayudar al sistema a encontrar al intérprete en el proceso de ejecución.
2. Esto no es cierto en Python 3.0 y posteriores; por ejemplo, 10/3 devolverá el flotador 3.33333.
3. A diferencia de C y algunos otros lenguajes, Python no tiene un tipo de datos “char” específicamente para almacenar un solo
carácter.
4. La recolección de basura es una característica común de lenguajes de alto nivel como Python, aunque algunos lenguajes
compilados también lo admiten. C no: se requiere que los programadores se aseguren de que todos los datos no utilizados se
borran durante la ejecución del programa. No hacerlo se conoce como una “fuga de memoria” y causa muchos bloqueos de
software del mundo real.

This page titled 2.2: Tipos de datos elementales is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

2.2.4 https://espanol.libretexts.org/@go/page/55113
2.3: Colecciones y Looping- Listas y para
Una lista, como su nombre lo indica, es una lista de datos (enteros, flotantes, cadenas, booleanos, o incluso otras listas o tipos de
datos más complicados). Las listas de Python son similares a matrices o vectores en otros lenguajes. Al igual que las letras en
cadenas, los elementos de una lista se indexan a partir de 0 usando la sintaxis [] . También podemos usar corchetes para crear
una lista con algunos elementos de diferentes tipos, aunque en la práctica no lo haremos a menudo.
II.3_1_py_23_list1

Al igual que con las cadenas, podemos usar la notación [] para obtener una sublista “slice”, y podemos usar la función
len () para obtener la longitud de una lista.
II.3_2_PY_24_Sublista

Sin embargo, a diferencia de las cadenas, las listas son mutables, lo que significa que podemos modificarlas después de haber sido
creadas, por ejemplo, reemplazando un elemento por otro elemento. Como se mencionó anteriormente, ¡las listas pueden incluso
contener otras listas!
II.3_3_py_25_list_mutable

Normalmente queremos que nuestro código cree una lista vacía, y luego agregarle elementos de datos un elemento a la vez. Se
devuelve una lista vacía llamando a la función list () sin parámetros. Dada una variable que hace referencia a un objeto list,
podemos anexar un elemento al final usando el método.append () , dando al método el elemento que queremos anexar
como parámetro.
II.3_4_py_26_list_append

Esta sintaxis puede parecer un poco extraña en comparación con lo que hemos visto hasta ahora. Aquí
new_list.append (“G”) le está diciendo al objeto list a la variable new_list que se refiere para ejecutar su
método.append () , tomando como parámetro la cadena “G” . Exploraremos los conceptos de objetos y métodos de
manera más formal en capítulos posteriores. Por ahora, consideremos la lista no sólo una recopilación de datos, sino un objeto
“inteligente” con el que podemos interactuar usando . métodos.
Tenga en cuenta que el método.append () pide a la lista que se modifique a sí misma (lo que puede hacer, porque las listas
son mutables), pero esta operación no devuelve nada de uso. [1]
imagen

Este tipo de comando abre la posibilidad


de algunos errores insidiosos; por ejemplo, una línea como
new_list = new_list.append (“C”) parece bastante inocente y no causa ningún error inmediato, pero
probablemente no sea lo que pretendía el programador. El motivo es que la llamada new_list.append (“C”) solicita
satisfactoriamente a la lista que se modifique, pero luego se devuelve el valor None , que se asignaría a la variable
new_list con la asignación. Al final de la línea, new_list se referirá a Ninguno , y la lista en sí ya no será accesible.
(De hecho, será basura recolectada a su debido tiempo.) En resumen, use some_list.append (el) , no
some_list = some_list.append (el) .
A menudo queremos ordenar listas, lo que podemos hacer de dos maneras. Primero, podríamos usar la función sort () , que
toma una lista como parámetro y devuelve una nueva copia de la lista en orden ordenado, dejando solo el original.
Alternativamente, podríamos llamar a un método lists .sort () para pedirle a una lista que se ordene en su lugar.
II.3_6_PY_28_list_clasificación

Al igual que con el método .append () anterior, el método .sort () devuelve None , por lo que lo siguiente
seguramente habría resultado en un error: a_list = a_list.sort () .
En este punto, uno sería perdonado por pensar eso . siempre devuelven None y así la asignación basada en los resultados no
es útil. Pero antes de pasar de las listas, introduzcamos una forma sencilla de dividir una cadena en una lista de subcadenas, usando
el método .split () en un tipo de datos de cadena. Por ejemplo, dividamos una cadena donde quiera que ocurra la
subsecuencia “TA” .
II.3_7_py_29_string_split

2.3.1 https://espanol.libretexts.org/@go/page/55082
Si la secuencia fuera en cambio “CGCGTATACAGA” , la lista resultante habría contenido ["CGCG”, “”, “CAGA"] (es
decir, uno de los elementos sería una cadena vacía de longitud cero). Este ejemplo ilustra que las cadenas, al igual que las listas,
también son objetos “inteligentes” con los que podemos interactuar usando . métodos. (De hecho, también lo son los enteros, los
flotadores y todos los demás tipos de Python que cubriremos).

Tuplas (Listas inmutables)


Como se señaló anteriormente, las listas son mutables, lo que significa que pueden ser alteradas después de su creación. En algunos
casos especiales, es útil crear una versión inmutable de una lista, llamada “tupla” en Python. Al igual que las listas, las tuplas se
pueden crear de dos maneras: con la función tupla () (que devuelve una tupla vacía) o directamente.
II.3_8_PY_29_2_tupla

Las tuplas funcionan como listas; podemos llamar len () en ellas y extraer elementos o rebanadas con sintaxis [] . No
podemos cambiar, eliminar o insertar elementos. [2]

Bucle con para


Un for-loop en Python ejecuta un bloque de código, una vez por cada elemento de un tipo de datos iterable: uno al que se puede
acceder un elemento a la vez, en orden. Resulta que tanto cadenas como listas son tales tipos iterables en Python, aunque por ahora
exploraremos solo iterar sobre listas con bucles for.
Un bloque es un conjunto de líneas de código que se agrupan como una unidad; en muchos casos también se ejecutan como una
unidad, quizás más de una vez. Los bloques en Python se indican al estar sangrados un nivel adicional (generalmente con cuatro
espacios, recuerde que es consistente con esta práctica de sangría).
Cuando se usa un for-loop para iterar sobre una lista, necesitamos especificar un nombre de variable que haga referencia a cada
elemento de la lista a su vez.
II.3_9_py_30_for_loop1

En lo anterior, una línea tiene sangría y un nivel adicional justo debajo de la línea que define el bucle for. En el bucle for, la
variable gene_id se establece para hacer referencia a cada elemento de la lista gene_ids a su vez. Aquí está la salida del
bucle:
II.3_10_py_30_2_for_loop1_out

El uso de bucles for en Python a menudo confunde a los principiantes, porque se está asignando una variable (por ejemplo,
gen_id ) sin usar el operador de asignación standard = . Si ayuda, puedes pensar en el primer bucle a través del bloque como
ejecutar gene_id = gene_ids [0] , la próxima vez como ejecutar gene_id = gene_ids [1] , y así
sucesivamente, hasta que se hayan utilizado todos los elementos de gene_ids .
Los bloques pueden contener varias líneas (incluidas las líneas en blanco) para que varias líneas de código puedan funcionar juntas.
Aquí hay un bucle modificado que mantiene una variable de contador , incrementándola en una cada vez.
II.3_11_py_30_3_for_loopcounter

La salida de este bucle sería la misma que la salida anterior, con una línea adicional de impresión 3 (el contenido del
contador después de que termine el bucle).
Algunos errores comunes al usar estructuras de bloques en Python incluyen los siguientes, muchos de los cuales resultarán en un
IndentationError .
1. No usar el mismo número de espacios para cada nivel de sangría, o mezclar sangría de pestaña con sangría de múltiples
espacios. (La mayoría de los programadores de Python prefieren usar cuatro espacios por nivel).
2. Olvidar el colon : eso termina la línea antes del bloque.
3. Usar algo así como una línea for-loop que requiere un bloque, pero no sangrar la siguiente línea.
4. Innecesariamente sangría (creación de un bloque) sin una línea de definición de bucle correspondiente.
A menudo queremos recorrer un rango de enteros. Convenientemente, la función range () devuelve una lista de números. [3]
Comúnmente toma dos parámetros: (1) el entero inicial (inclusive) y (2) el entero final (exclusivo). Así podríamos programar
nuestro for-loop de manera ligeramente diferente generando una lista de enteros para usar como índices, e iterando sobre eso:

2.3.2 https://espanol.libretexts.org/@go/page/55082
II.3_12_PY_31_por_Loop2

La salida de uno de los bucles anteriores:


II.3_13_py_31_2_for_loop2_out

El segundo ejemplo anterior ilustra la justificación detrás de la naturaleza inclusivo/exclusiva de la función range () : debido
a que los índices comienzan en cero y van a uno menos que la longitud de la lista, podemos usar range (0, len (ids))
(en lugar de necesitar modificar el índice final) para correctamente iterar sobre los índices de ids sin conocer primero la
longitud de la lista. Los programadores experimentados generalmente encuentran esto intuitivo, pero aquellos que no están
acostumbrados a contar desde cero pueden necesitar algo de práctica. Debes estudiar estos ejemplos de bucle cuidadosamente, y
probarlos. Estos conceptos suelen ser más difíciles para los principiantes, pero son importantes de aprender.
Los bucles y los bloques que controlan pueden anidarse, con un efecto poderoso:
II.3_14_Nested_loop_Illustrated

En lo anterior, el bucle for externo controla un bloque de cinco líneas; contenido dentro está el bucle for interno que controla un
bloque de solo dos líneas. El bloque exterior se refiere principalmente a la variable i , mientras que el bloque interno se refiere
principalmente a la variable j . Vemos que ambos bloques también hacen uso de variables definidas fuera de ellos; el bloque
interno hace uso de sum , i , y j , mientras que las líneas específicas del bloque externo hacen uso de sum e i (pero no
j ). Este es un patrón común que veremos con más frecuencia. ¿Se puede determinar el valor del total al final sin ejecutar el
código?

Comprensiones de listas
Python y algunos otros lenguajes incluyen sintaxis taquigráfica especializada para crear listas a partir de otras listas conocidas
como comprensiones de listas. Efectivamente, esta taquigrafía combina una sintaxis for-loop y una sintaxis de creación de listas en
una sola línea.
Aquí hay un ejemplo rápido: comenzando con una lista de números [1, 2, 3, 4, 5, 6] , generamos una lista de
cuadrados ( [1, 4, 9, 16, 25, 36] ):
II.3_15_py_33_2_list_comp_1

Aquí estamos usando una convención de nomenclatura de num en nums , pero como un for-loop, la variable loop puede ser
nombrada casi cualquier cosa; por ejemplo, squares = [x ** 2 for x in nums] lograría la misma tarea.
Las comprensiones de listas pueden ser bastante flexibles y usarse de manera creativa. Dada una lista de secuencias, podemos
generar fácilmente una lista de longitudes.
II.3_16_py_33_3_list_comp_2

Estas estructuras también admiten la “inclusión condicional”, aunque aún no hemos cubierto operadores como == :
II.3_17_py_33_4_list_comp_3

El siguiente ejemplo genera una lista de 1 s por cada elemento donde la primera base es “T” , y luego usa la función
sum () para resumir la lista, dando como resultado un recuento de secuencias que comienzan con “T” .
II.3_18_py_33_5_list_comp_4

Aunque muchos programadores de Python suelen usar comprensiones de listas, no las usaremos mucho en este libro. En parte, esto
se debe a que son una característica que muchos lenguajes de programación no tienen, pero también porque pueden llegar a ser
difíciles de leer y entender debido a su compacidad. Como ejemplo, ¿qué opinas que hace la siguiente comprensión?
[x para x en rango (2, n) si x no en [j para i en rango (2, sqrtn) para j en rango
(i*2, n, i)]]
(Supongamos que n = 100 y sqrtn = 10 . Este ejemplo también hace uso del hecho de que range () puede tomar
un argumento step, como en range (start, stop, step) .)

Ejercicios
1. ¿Cuál es el valor del total al final de cada bucle establecido a continuación? Primero, vea si puede calcular las respuestas a
mano, y luego escribir y ejecutar el código con algunas declaraciones print () agregadas para verificar sus respuestas.

2.3.3 https://espanol.libretexts.org/@go/page/55082
II.3_19_py_33_sum_anidad_ex

2. Supongamos que decimos que el primer bloque for-loop anterior tiene “profundidad” 1 y “ancho” 4, y el segundo tiene
profundidad 2 y ancho 4, y el tercero tiene profundidad 3 y ancho 4. ¿Se puede definir una ecuación que indique cuál sería el
total para un bloque de bucle anidado con profundidad d y ancho w? ¿Cómo se relaciona esta ecuación con el número de
veces que tiene el intérprete para ejecutar la línea total = total + 1 ?
3. Determinar una ecuación que relacione el valor final del total por debajo con el valor de x . II.3_20_py_34_sum_anidad_ex2
4. Dada una declaración de una cadena de secuencia, como seq = “ATGATAGAGGGATACGGATAG” , y una subsecuencia
de interés, como subseq = “GATA” , escribir algún código que imprima todas las ubicaciones de esa subcadena en la
secuencia, una por línea, usando solo los conceptos de Python que hemos cubierto hasta ahora ( como len () , for-loops y
.split () ). Para el ejemplo anterior, la salida debe ser 3 , 11 y 18 .
Tu código aún debería funcionar si la subcadena ocurre al inicio o al final de la secuencia, o si la subsecuencia ocurre espalda
con espalda (por ejemplo, en “GATACCGATATAGATA” , “GATA” ocurre en las posiciones 1, 7 y 11). Como indicio,
puede asumir que la subsecuencia no se superpone (por ejemplo, no necesita preocuparse por ubicar “GAGA” en
“GAGAGAGAGA” , lo que ocurriría en las posiciones 1, 3, 5 y 7).
5. Supongamos que tenemos una matriz representada como una lista de columnas:
cols = [[10, 20, 30, 40], [5, 6, 7, 8], [0.9, 0.10, 0.11, 0.12]] . Debido a que cada
columna es una lista interna, se dice que esta disposición está en “orden de columna mayor”. Escribe algún código que
produzca los mismos datos en “orden fila-mayor”; por ejemplo, las filas deben contener
[[10, 5, 0.9], [20, 6, 0.10], [30, 7, 0.11], [40, 8, 0.12]] . Se puede suponer que todas las
columnas tienen el mismo número de elementos y que la matriz es de al menos 2 por 2.
Este problema es un poco complicado, pero te ayudará a organizar tus pensamientos alrededor de bucles y listas. Puede
comenzar determinando primero el número de filas en los datos y luego construyendo la “estructura” de filas como una
lista de listas vacías.

1. Devuelve un tipo de datos especial conocido como Ninguno , que permite que exista una variable pero no haga referencia a
ningún dato. (Técnicamente, Ninguno es un tipo de datos, aunque muy simple). Ninguno se puede usar como un tipo
de marcador de posición, y así en algunas situaciones no es del todo inútil.
2. Las tuplas son causa de una de las partes más confusas de Python, porque se crean encerrando una lista de elementos dentro de
paréntesis, pero las llamadas a funciones también toman parámetros listados dentro de paréntesis, ¡y las expresiones
matemáticas también se agrupan por paréntesis! Considera la expresión (4 + 3) * 2 . ¿Es (4 + 3) un número
entero o una tupla de un solo elemento? En realidad, es un entero. Por defecto, Python busca una coma para determinar si se
debe crear una tupla, por lo que (4 + 3) es un entero, mientras que (4 + 3, 8) es una tupla de dos elementos y
(4 + 3,) es una tupla de un solo elemento. Use paréntesis deliberadamente en Python: ya sea para agrupar expresiones
matemáticas, crear tuplas o llamar a funciones, donde el nombre de la función y el paréntesis de apertura son vecinos, como en
print (a) en lugar de print (a) . Agregar innecesariamente paréntesis (y por lo tanto crear tuplas accidentalmente)
ha sido la causa de algunos errores difíciles de encontrar.
3. Una alternativa a range () en Python 2.7 es xrange () , que produce un tipo iterable que funciona como una lista de
números pero que es más eficiente en la memoria. En versiones más recientes de Python (3.0 y superiores) la función
range () funciona como xrange () y xrange () ha sido eliminada. Los programadores que usan Python 2.7
pueden desear usar xrange () para obtener eficiencia, pero nos quedaremos con range () para que nuestro código
funcione con la más amplia variedad de versiones de Python, incluso si sacrifica la eficiencia en algunos casos. Hay una
diferencia importante entre range () y xrange () en Python 2.7: range () devuelve una lista, mientras que
xrange () devuelve un tipo iterable que carece de algunas características de listas verdaderas. Por ejemplo,
nums = range (0, 4) seguido de nums [3] = 1000 daría como resultado nums referenciando
[0, 1, 2, 1000] , mientras que nums = xrange (0, 4) seguido de nums [3] = 1000 produciría un
error.

This page titled 2.3: Colecciones y Looping- Listas y para is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by
Shawn T. O’Neil (OSU Press) .

2.3.4 https://espanol.libretexts.org/@go/page/55082
2.4: Entrada y salida de archivos
Hasta ahora, todos los datos con los que hemos estado trabajando han sido “codificados” en nuestros programas. En la vida real, sin
embargo, estaremos buscando datos de archivos externos.
Podemos acceder a datos que existen en un archivo externo (un archivo de texto, generalmente) usando un manejador de archivo,
un tipo especial de tipo de datos al que podemos conectarnos usando una fuente externa. En su forma más simple, un manejador de
archivo para leer datos funciona como una tubería: el sistema operativo pone datos (generalmente cadenas) en un extremo, y
extraemos datos del otro extremo, generalmente una línea a la vez. Los manejadores de archivos para escribir en archivos mueven
los datos en la dirección opuesta.
imagen

Para usar un manejador de archivo en Python, tenemos que decirle al intérprete que queremos utilizar algunas funciones de
entrada/salida poniendo primero el comando especial import io cerca de la parte superior de nuestro programa. Este
comando importa el módulo io , poniendo a disposición funciones adicionales que existen dentro de él. Estas funciones también
utilizan una sintaxis de punto para especificar el módulo en el que existen, que es confusamente similar a la sintaxis de puntos
discutida anteriormente para los métodos. Para asociar un gestor de archivo llamado fhandle con el archivo “
filename.txt" , por ejemplo, el comando es fhandle = io.open (” filename.txt “, “rU”) , donde
open () es una función en el módulo io importado. La r indica que estamos creando un manejador de archivo de solo
lectura, y la U le permite saber al intérprete que queremos una interpretación “universal” para nuevas líneas, para permitir la
lectura de archivos creados en sistemas Microsoft Windows o sistemas similares a Unix. [1] El tipo de datos de manejador de
archivo tiene un método llamado .readline () , que devuelve la siguiente línea disponible de la tubería como una cadena;
así, line = fhandle.readline () extrae una cadena (línea) del manejador de archivo y la asigna a line .
Vamos a crear un archivo pequeño con tres líneas, cada una de las cuales contiene un ID de gen, una pestaña y un valor de
contenido de GC correspondiente.
II.4_2_PY_35_IDS_List

Ahora vamos a crear un programa que lea e imprima los datos. Cuando terminemos de leer datos de un archivo, necesitamos
ejecutar el método .close () del identificador de archivo. El uso de .close () hace que el identificador del archivo
avise al sistema operativo de que hemos terminado de leer los datos del identificador. En este punto el sistema operativo es libre de
eliminar la “tubería” de back-end y cualquier dato que pueda haber en él.
II.4_3_PY_36_LEER_LINES

Si el archivo no existe o no es legiblepor el programa, obtendrá un IOError que indica


No dicho archivo o directorio o Permiso denegado con el nombre del archivo que intentó abrir. [2]
Debido a que nuestro archivo existe, podemos ejecutar con éxito el código e inspeccionar los resultados:
II.4_4_PY_36_2_LEER_LINES_OUT

Aquí hay algunas cosas a tener en cuenta. Primero, cada vez que llamamos a fhandle.readline () , el identificador del
archivo devuelve una cadena diferente: la siguiente cadena esperando ser sacada de la tubería. Segundo, vemos que la salida
contiene nuestras tres líneas, pero separadas por líneas en blanco: esto se debe a que ya hay caracteres de “nueva línea” en el
archivo. No vemos estos caracteres de nueva línea, pero podemos representarlos nosotros mismos si queremos, en una cadena con
el código de control \ n . Del mismo modo, los caracteres de tabulación tienen un código de control como \ t . El archivo es
en realidad una sola cadena serial, y el método.readline () pide al identificador del archivo que devuelva todo hasta e
incluyendo el siguiente \ n .
II.4_5_Newlines_en_str

Al final, la razón de las líneas en blanco en nuestra salida es que la función print () , para nuestra conveniencia, agrega un
\ n al final de cualquier cadena que imprimimos (de lo contrario, la mayor parte de nuestra salida estaría en la misma línea).
Así, cada línea leída del archivo se está imprimiendo con dos \ n caracteres. Aprenderemos a imprimir sin una nueva línea
adicional más adelante, cuando aprendamos a escribir datos en archivos. Por ahora, resolveremos el problema eliminando espacios
en blanco iniciales y finales (espacios, tabulaciones y nuevas líneas) pidiéndole a una cadena que ejecute su
método.strip () .

2.4.1 https://espanol.libretexts.org/@go/page/55090
II.4_6_py_37_string_strip

Aunque estamos llamando de nuevo a import io , es solo por claridad: un programa solo necesita importar un módulo una
vez (y generalmente estos se recopilan cerca de la parte superior del programa). Aquí está la salida modificada:
II.4_7_py_37_2_string_strip_out

Si te sientes aventurero (¡y deberías!) , puede probar el encadenamiento de métodos, donde la sintaxis de punto para los métodos se
puede agregar siempre que el método anterior devuelva el tipo correcto.
II.4_8_PY_38_método_encadenamiento

Para vincular esto a conceptos anteriores, después de haber extraído cada línea y despojado del espacio en blanco final, podemos
usar .split (“\ t”) para dividir cada línea en una lista de cadenas. A partir de ahí, podemos usar float () para
convertir el segundo elemento de cada uno en un tipo float, y calcular el contenido medio de GC.
II.4_9_PY_38_2_MEAN_GC

Lo anterior imprime el contenido promedio de GC de las tres líneas como 0.523333333 . (Debido a que .strip ()
también devuelve una cadena, podríamos tener más métodos encadenados, como en
line1_list = fhandle.readline () .strip () .split (“\ t”) .)
Debido a que los manejadores de archivos funcionan como una tubería, no permiten el “acceso aleatorio”; podemos obtener el
siguiente bit de datos del extremo de la tubería, pero eso es todo. [3] Uno podría pensar que un comando como
line5 = fhandle [4] funcionaría, pero en su lugar produciría un error como
TypeError: '_io.bufferedReader' el objeto no tiene atributo '__getitem__' .
Por otro lado, al igual que las listas, los manejadores de archivos son iterables, lo que significa que podemos usar un for-loop para
acceder a cada línea en orden. Un programa simple para leer líneas de un archivo e imprimirlas una a la vez (sin líneas en blanco
adicionales) podría verse así:
II.4_10_py_39_lines_loop

Al igual que .readline () , usando un for-loop extrae líneas de la tubería. Entonces, si llama a .readline () dos
veces en un controlador de archivo adjunto a un archivo con 10 líneas, y luego ejecuta un for-loop en ese controlador de archivo, el
for-loop iterará sobre las 8 líneas restantes. Esta llamada podría ser útil si desea eliminar una línea de encabezado de una tabla de
texto antes de procesar las líneas restantes con un bucle, por ejemplo.

Redacción de datos
Escribir datos en un archivo funciona de la misma manera que leer datos: abrimos un controlador de archivo (que de nuevo
funciona como una tubería), y llamamos a un método en el controlador llamado .write () para escribirle cadenas. En este
caso, en lugar de usar el parámetro “ru” al llamar a io.open () , usaremos “w” para indicar que queremos escribir en
el archivo. Tenga en cuenta: cuando abre un identificador de archivo para escribir de esta manera, sobrescribe cualquier contenido
existente del archivo. Si prefieres agregar al archivo, puedes usar “a” . [4]
A diferencia de la función print () , el método .write () de un identificador de archivo no incluye automáticamente
un carácter de nueva línea adicional “\ n” . Así, si deseas escribir varias líneas en un manejador de archivo, tienes que
agregarlas tú mismo. Aquí hay un ejemplo que imprime los números del 0 al 9 en un archivo, uno por línea.
imagen

Como se mencionó anteriormente, un identificador de archivo abierto para escritura funciona como una tubería que es establecida
por el sistema operativo. Al escribir, sin embargo, colocamos datos en la tubería, y el sistema operativo extrae los datos del otro
lado, escribiéndolos en el archivo en el disco.
imagen

Debido a que el sistema operativo está ocupado manejando solicitudes similares para muchos programas en el sistema, es posible
que no llegue a extraer los datos de la tubería y escribir en el archivo en el disco de inmediato. Entonces, es importante que
recordemos llamar al método .close () en el controlador de archivo cuando terminemos de escribir datos; esto le dice al
sistema operativo que cualquier información que quede en la tubería debe ser vaciada y guardada en el disco, y que la estructura de

2.4.2 https://espanol.libretexts.org/@go/page/55090
la tubería se puede limpiar. Si nuestro programa se estrellara (o se matara) antes de que el mango se cerrara correctamente, es
posible que los datos en la tubería nunca se escriban.

Computación de una media


Pongamos en práctica muchas de las habilidades que hemos aprendido hasta ahora para calcular la media de los valores E a partir
de la salida de BLAST, que se formatea como una tabla de texto simple con pestañas que separan las columnas. Así es como se ve
el archivo, pz_blastx_yeast_top1.txt , en menos -S . Aunque el lector tendrá que tomar nuestra palabra para ello,
la undécima columna de este archivo contiene valores E como 5e-14 , 2e-112 y 1e-18 .
II.4_13_PY_41_BLAST_LESS

A la hora de resolver un problema como este, suele ser una buena idea escribir primero, en inglés simple (o en el lenguaje de no
programación que prefieras), la estrategia que pretendes utilizar para resolver el problema. La estrategia aquí será la siguiente: la
media de un conjunto de números se define como su suma, dividida por el recuento de los mismos. Tendremos que mantener al
menos dos variables importantes, eval_sum y counter . Después de abrir el archivo con io.open () , podemos
recorrer las líneas y extraer cada valor E. (Esto requerirá limpiar la línea con .strip () , dividirla en pedazos usando
.split (“\ t”) , y finalmente convertir el valor E en un flotador en lugar de usar la cadena.) Por cada línea que veamos,
podemos agregar el valor E extraído a la variable eval_sum , y agregaremos 1 a la variable de contador también. Al
final, podemos simplemente reportar eval_sum/counter .
A menudo ayuda convertir esta estrategia en algo a medio camino entre el lenguaje natural y el código, llamado pseudocódigo, que
puede ayudar enormemente a organizar tus pensamientos, particularmente para programas complejos:

import io
open fhandle

counter = 0
sum_eval = 0.0

for each line in fhandle


linestripped = line.strip()
break into a list of strings with line_list = linestripped.split("\t")
eval as a string is in line_list at index 10 (11th column)
add float(eval) to sum_eval and save in sum_eval
add 1 to count and save in count

mean = sum_eval divided by counter


print("mean is " + mean)

Con el pseudocódigo esbozado, podemos escribir el código real para nuestro programa. Cuando se ejecuta, imprime de manera
confiable La media es: 1.37212611293e-08 .
II.4_14_PY_42_BLAST_Ejemplo

Tenga en cuenta que el código Python real (en blast_mean.py ) terminó pareciéndose bastante al pseudocódigo, este es uno
de los puntos de venta frecuentemente citados para Python. (Por esta razón, también saltaremos el paso de pseudocódigo para la
mayoría de los ejemplos en este libro, aunque todavía puede ser una técnica valiosa a la hora de programar en cualquier lenguaje).
Esto puede parecer una buena cantidad de trabajo para calcular una media simple, pero es una consecuencia de escribir software
“desde cero”, ¡y tenemos que empezar por alguna parte! Adicionalmente, el beneficio de aprender estas técnicas dará sus frutos a
largo plazo a la hora de resolver problemas novedosos.

2.4.3 https://espanol.libretexts.org/@go/page/55090
El Proceso de Programación
Aunque el proceso de diseñar una estrategia (y pseudocódigo) parece tedioso, es muy recomendable. A medida que avanzas en tus
habilidades, tus estrategias y pseudocódigo se volverán más concisos y de nivel superior, pero nunca debes saltarte los pasos de
planeación antes de codificar. (Por otro lado, también existe el peligro de una planificación excesiva, pero esto afecta más a
menudo a equipos de codificadores que trabajan en proyectos grandes).
Hay otra cosa que vale la pena señalar en este punto: programas como el anterior casi nunca se escriben de arriba a abajo en su
totalidad, ¡al menos no sin requerir una depuración significativa! Puede olvidar que el resultado de dividir una cadena da como
resultado una lista de cadenas, lo que resulta en un error en una línea como eval_sum = eval_sum + eval_str
porque eval_sum es un float, mientras eval_str es una cadena. Después de encontrar y corregir este error, puede
encontrar otro error más si intenta imprimir con una línea como print (“La media es:" + media) , porque
nuevamente los tipos no coinciden y no se pueden concatenar. Después de todo esto, quizás encuentres que la media resultante es
inesperadamente un número grande, como 131.18 , solo para enterarte que fue porque usaste accidentalmente
eval_str = line_list [11] , olvidando que los índices de lista comienzan en 0 .
Hay dos estrategias para evitar largas cadenas de errores molestos como este, y errores más difíciles de encontrar (y por lo tanto
más peligrosos) que dan como resultado una salida incorrecta que obviamente no es incorrecta. La primera estrategia es escribir
solo unas pocas líneas a la vez, y probar las líneas recién agregadas con declaraciones print () que revelan si están haciendo
lo que se supone que deben hacer. En el ejemplo anterior, puede escribir algunas líneas y hacer algunas impresiones para asegurarse
de que pueda abrir con éxito el manejador del archivo y leerlo (si el archivo es grande, cree una versión más pequeña para probar).
Luego escribe un bucle for-loop simple y haz algo de impresión para asegurarte de que puedes recorrer con éxito las líneas del
archivo y dividirlas en listas de cadenas. Continúe rellenando parte del código en el for-loop, nuevamente imprimiendo y probando
para asegurarse de que el código esté haciendo lo que cree que es. Y así sucesivamente a través del desarrollo iterativo.
La segunda estrategia, para evitar los errores más peligrosos que no son inmediatamente obvios, es probar y desarrollar tus
programas usando pequeños archivos para los que puedas calcular la respuesta a mano (o algún otro proceso confiable). Cuando
sabe por pruebas que su programa o incluso solo partes de su programa produce la salida prevista, es mucho menos probable que
los errores ocultos se escapen por las grietas. Algunas personas producen varios insumos pequeños que representan la variedad de
diferentes situaciones de entrada, ejecutan las pruebas y comparan los resultados con los resultados esperados de manera
automatizada. Esto se conoce como “pruebas unitarias”, una práctica común en el desarrollo de software profesional.
Por último, ¡recuerda que cada programador, sin importar su nivel de experiencia, se frustra de vez en cuando! Esto es
completamente normal, y una buena estrategia cuando esto sucede es dar un breve paseo, o incluso tomar un descanso para el día,
recogiendo de nuevo al día siguiente. Algunos encuentran que programan eficientemente por la mañana, otros a altas horas de la
noche. Hagas lo que hagas, evita la tentación de programar por prueba y error, ajustando sin pensar el código con la esperanza de
que produzca la salida que deseas. Incluso si tienes éxito de esta manera (lo que rara vez sucede), es probable que produzca código
con errores insidiosos, y bien puede ser ilegible incluso para ti mismo dentro de unos días.

Ejercicios
1. Escriba un programa Python para calcular la desviación estándar de muestra de los valores E en el archivo
pz_blastx_yeast_top1.txt . Como recordatorio, la desviación estándar de la muestra se define como la raíz
cuadrada de la suma de las diferencias cuadradas de la media, dividida por el número de valores menos 1:
<span translate=\ [stdev =\ sqrt {\ frac {\ suma_ {eval_ {i}\ en evals} (eval_ {i} - media) ^ {2}} {n-1}}\]” title="Rendido por

Quicklatex.com” src=” https://bio.libretexts.org/@api/deki...1dc65f8_l3.png "/>


Para lograr esto, necesitarás hacer dos pasadas sobre los datos: una como en el ejemplo para calcular la media, y otra para
calcular la suma de las diferencias cuadradas. Esto significa que necesitarás acceder a los valores de E dos veces. En lugar de
cerrar y volver a abrir el archivo de datos, debe crear una lista inicialmente vacía a la que pueda agregar cada valor E (en su
primera pasada sobre los datos) para su uso posterior.
Para calcular la raíz cuadrada de un flotador, deberá importar el módulo matemático llamando a import math
cerca de la parte superior de su programa. Entonces la función math.sqrt () devolverá la raíz cuadrada de un float; por
ejemplo, math.sqrt (3.0) devolverá el float 1.7320508 .

2.4.4 https://espanol.libretexts.org/@go/page/55090
2. Si a_list es una lista, entonces b_list = reverse (a_list) crea un “ listreverseiterator ” que
permite a uno recorrer los elementos con un bucle for en orden inverso. Usando esta información, escriba un programa llamado
reverse_blast.py que lea el contenido de pz_blastx_yeast_top1.txt y escriba las líneas en orden
inverso a un archivo llamado pz_blastx_yeast_top1_reversed.txt .
3. Un quine (después del lógico y filósofo W. V. Quine) es un programa no vacío que imprime exactamente su propio código
fuente. Utilice el módulo io y los manejadores de archivo para escribir una quine llamada quine.py . (Técnicamente, a
los programas Quine no se les permite abrir archivos. ¿Se puede escribir un programa que imprima su propio código fuente sin
utilizar el módulo io ?)

1. Python incluye una función simple open () que funciona en gran medida de la misma manera que la io.open ()
que se muestra aquí. En las versiones más recientes de Python (Python 3.0 y superiores), open () es en realidad un atajo a
io.open () , mientras que en versiones anteriores las dos operan de manera ligeramente diferente. Para mayor
consistencia, sigamos con un io.open () explícito, que funciona igual en todas las versiones recientes (Python 2.7 y
superiores). Con respecto al parámetro “U” , los sistemas basados en Microsoft y Unix utilizan diferentes codificaciones para
nuevas líneas. El parámetro “U” maneja esto de manera transparente, de manera que todo tipo de nuevas líneas pueden
considerarse como basadas en Unix, \ n , incluso si el archivo en cuestión fue creado en un sistema Microsoft.
2. En muchos idiomas, es una buena práctica probar si un identificador de archivo se abrió con éxito y, si no, salir del programa
con un error. También podríamos hacer tales pruebas en Python, pero debido a que Python por defecto se cierra con un error
informativo en esta situación de todos modos, no nos preocuparemos tanto por verificar los errores aquí. Python también
incluye un especial con palabra clave que maneja tanto la apertura como el cierre del archivo: La documentación de
II.4_15_PY_42_B_con Python recomienda usar con de esta manera, ya que es un poco más seguro en casos de bloqueos del

programa que ocurren cuando un controlador de archivo está abierto. Aún así, dejamos esta idea a una nota al pie de página, ya
que lo abierto y el cierre son explícitos y directos.
3. En el uso más sofisticado de los mangos de archivo, nuestra analogía de tuberías se descompone. Es posible “rebobinar” o
“adelantar” la posición actual del sistema operativo en el archivo que se está leyendo, si sabemos hasta dónde debe moverse la
“cabeza de lectura” para llegar a los datos que queremos. Quizás una mejor analogía para los mangos de archivos sería una cinta
magnética de cassette anticuada, tal vez una analogía adecuada dado que los archivos alguna vez se almacenaban en tales cintas.
4. También es posible abrir un manejador de archivo para lectura y escritura simultáneas. No lo cubriremos aquí, ya que esa
funcionalidad es menos necesaria.

This page titled 2.4: Entrada y salida de archivos is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

2.4.5 https://espanol.libretexts.org/@go/page/55090
2.5: Flujo de control condicional
La frase “flujo de control” se refiere al hecho de que construcciones como bucles for cambian el flujo de ejecución del programa
lejos del orden simple de arriba a abajo. Hay varios otros tipos de flujo de control que cubriremos, dos de los cuales son de
naturaleza “condicional”.

Uso de sentencias If
Las sentencias IF nos permiten ejecutar condicionalmente un bloque de código, dependiendo de una variable que haga referencia a
un Boolean True o False , o más comúnmente una condición que devuelve un Booleano True o False . La sintaxis
es bastante simple, se describe aquí con un ejemplo.
II.5_1_py_43_if_ejemplo

Todas las líneas desde el if inicial hasta la última línea en un bloque elif: o bien: son parte de la misma construcción
lógica. Tal construcción debe tener exactamente uno si es un bloque condicional, puede tener uno o más bloques elif (son
opcionales), y puede tener exactamente un bloque catchall else al final (también opcional). Cada condicional se evalúa en
orden: se ejecutará el primero que evalúe a True , y el resto se omitirá. Si hay un bloque else presente, se ejecutará si
ninguno de los bloques if o elif anteriores lo hizo como “último recurso”.
Al igual que con los bucles for, las sentencias if se pueden anidar dentro de otros bloques, y otros bloques pueden ocurrir dentro de
los bloques de instrucciones if. También al igual que for-loops, Python usa indentación (la práctica estándar es de cuatro espacios
por nivel de sangría) para indicar la estructura de bloques, por lo que obtendrá un error si aplica sangría innecesariamente (sin una
línea de flujo de control correspondiente como for , if , elif , or else ) u olvidarse de sangría cuando se espera una
sangría. [1]
II.5_2_PY_44_IF_Ejemplo2

El código anterior imprimiría Número corto: 2 número largo: 2 .

Uso de While-Loops
Los loops while se utilizan con menos frecuencia (dependiendo de la naturaleza de la programación que se esté realizando), pero
pueden ser invaluables en ciertas situaciones y son una parte básica de la mayoría de los lenguajes de programación. Un bucle
while ejecuta un bloque de código siempre que una condición permanezca True . Tenga en cuenta que si la condición nunca se
convierte en False , el bloque se ejecutará una y otra vez en un “bucle infinito”. Sin embargo, si la condición es False para
empezar, el bloque se omite por completo.
II.5_3_PY_44_2_While_Pre_Ejemplo

Lo anterior imprimirá Contador es ahora: 0 , seguido de Contador es ahora: 1 ,


Contador es ahora: 2 , Contador es ahora: 3 , y finalmente
Hecho. El mostrador termina con: 4 . Al igual que con el uso de un bucle for sobre un rango de enteros, también
podemos usar un bucle while para acceder a índices específicos dentro de una cadena o lista.
II.5_4_py_45_mientras_ejemplo1

El código anterior se imprimirá base es: A , entonces base es: C , y así sucesivamente, terminando con
base es: T antes de finalmente imprimir Hecho . Por lo tanto, los bucles while se pueden usar como un tipo de bucle for
de grano fino, para iterar sobre elementos de una cadena (o lista), a su vez usando índices enteros simples y sintaxis [] . Si bien
el ejemplo anterior agrega 1 a base_index en cada iteración, podría agregar fácilmente algún otro número. Agregar 3
provocaría que se imprima cada tercera base, por ejemplo.

Operadores Booleanos y Conectivos


Ya hemos visto un tipo de comparación booleana, < , que devuelve si el valor de su lado izquierdo es menor que el valor de su
lado derecho. Hay una serie de otros:

Operador Significado Ejemplo (con a = 7 , b = 3 )


< menos de? a < b # Falso

2.5.1 https://espanol.libretexts.org/@go/page/55122
> mayor que? a > b # Verdadero
<= menor o igual a? a <= b # Falso
>= mayor o igual a? a >= b # Verdadero
! = no es igual a? ¡a! = b # Verdadero
== igual a? a == b # Falso

Estas comparaciones funcionan para flotadores, enteros e incluso cadenas y listas. La clasificación en cadenas y listas se realiza en
orden lexicográfico: un orden en el que el elemento A es menor que el elemento B si el primer elemento de A es menor que el
primer elemento de B; en el caso de un empate, se considera el segundo elemento, y así sucesivamente. Si en este proceso nos
quedamos sin elementos para la comparación, ese más corto es más pequeño. Cuando se aplica a cadenas, el orden lexicográfico
corresponde al orden alfabético familiar.
Imprimimos la versión ordenada de una lista de cadenas de Python, que hace su clasificación usando las comparaciones anteriores.
Tenga en cuenta que los dígitos numéricos se consideran caracteres alfabéticos “menores que”, y las letras mayúsculas vienen antes
que las letras minúsculas.
II.5_5_py_46_sort_lexicográfico

Los conectivos booleanos nos permiten combinar condicionales que devuelven True o False en declaraciones más
complejas que también devuelven tipos booleanos.

Conectivo Significado Ejemplo (con a = 7 , b = 3 )


y Verdadero si ambos son True a < 8 y b == 3 # Verdadero
o Verdadero si uno o ambos son True a < 8 o b == 9 # Verdadero
no True si lo siguiente es False no a < 3 # Verdadero

Estos pueden agruparse con paréntesis, y por lo general deben ser para evitar confusiones, sobre todo cuando más de una prueba
siguen un no . [2]
imagen

Por último, tenga en cuenta que generalmente cada lado de un y o o debe resultar en solo Verdadero o Falso . La
expresión a == 3 o a == 7 tiene la forma correcta, mientras que a == 3 o 7 no. (De hecho, 7 en este último
contexto se tomará como Verdadero , y así un == 3 o 7 siempre resultará en Verdadero .)

Peligros lógicos
Observe la similitud entre = y == , y sin embargo tienen significados dramáticamente diferentes: el primero es el operador de
asignación de variables, mientras que el segundo es una prueba de igualdad. Usar accidentalmente uno donde se entiende el otro es
una manera fácil de producir código erróneo. Aquí count == 1 no inicializará count a 1 ; más bien, devolverá si ya es
1 (o resultará en un error si count no existe como variable en ese punto). El error inverso es más difícil de cometer, ya que
Python no permite la asignación de variables en definiciones de sentencia if y while loop.
II.5_7_PY_47_IF_Seguridad

En lo anterior, la intención es determinar si la longitud de seq es un múltiplo de 3 (según lo determinado por el resultado de
len (seq) %3 usando el operador de módulo), pero la sentencia if en este caso debería ser realmente
si resto == 0: . En muchos idiomas, lo anterior sería un error difícil de encontrar ( el resto se asignaría a 0 , ¡y el
resultado sería True de todos modos!). En Python, el resultado es un error: SyntaxError: sintaxis no válida .
Aún así, una cierta clase de comparación peligrosa es común a casi todos los idiomas, incluido Python: la comparación de dos tipos
flotantes para igualdad o desigualdad.
Aunque los enteros se pueden representar exactamente en aritmética binaria (por ejemplo, 751 en binario se representa
exactamente como 1011101111 ), los números de punto flotante solo se pueden representar aproximadamente. Este no debería
ser un concepto completamente desconocido; por ejemplo, podríamos decidir redondear fracciones a cuatro decimales al hacer

2.5.2 https://espanol.libretexts.org/@go/page/55122
cálculos en lápiz y papel, trabajando con 1/3 como 0.3333. El problema es que estos errores de redondeo pueden agravarse de
maneras difíciles de predecir. Si decidimos computar (1/3) * (1/3)/(1/3) como 0.3333*0.3333/0.3333, trabajando de izquierda a
derecha comenzaríamos con 0.3333*0.3333 redondeado a cuatro dígitos como 0.1110. Esto se divide luego por 0.3333 y se
redondea nuevamente para producir una respuesta de 0.3330. Entonces, aunque sabemos que (1/3) * (1/3)/(1/3) == 1/3, nuestro
proceso de cálculo los llamaría desiguales porque finalmente prueba 0.3330 contra 0.3333!
Las computadoras modernas tienen muchos más dígitos de precisión (alrededor de 15 dígitos decimales como mínimo, en la
mayoría de los casos), pero el problema sigue siendo el mismo. Peor aún, los números que no necesitan redondeo en nuestro
sistema aritmético Base-10 sí requieren redondear en el sistema Base-2 de la computadora. Considere 0.2, que en binario es
0.001100110011, y así sucesivamente. En efecto, 0.2 * 0.2/0.2 == 0.2 resultados en ¡ Falso !
Si bien la comparación de flotadores con < , > , <= y >= suele ser segura (dentro de márgenes de error extremadamente
pequeños), la comparación de flotadores con == y ! = suele indicar un malentendido de cómo funcionan los números de punto
flotante. [3] En la práctica, determinaríamos si dos valores de punto flotante son suficientemente similares, dentro de algún margen
de error definido.
II.5_8_PY_48_Epsilon_Compare

Codones de parada de conteo


Como ejemplo del uso de flujo de control condicional, consideraremos el archivo seq.txt , que contiene una sola cadena de
ADN en la primera línea. Deseamos contar el número de codones de parada potenciales “TAG” , “TAA” o “TGA” que
ocurren en la secuencia (solo en la cadena directa, para este ejemplo).
Nuestra estrategia será la siguiente: Primero, tendremos que abrir el archivo y leer la secuencia desde la primera línea. Tendremos
que mantener un contador del número de codones de parada que veamos; este contador comenzará en cero y le agregaremos uno
por cada subsecuencia de “TAG” , “TAA” o “TGA” que veamos. Para encontrar estas tres posibilidades, podemos usar un
for-loop y string slicing para inspeccionar cada subsecuencia de 3bp de la secuencia; la secuencia de 3bp en el índice 0 de
seq ocurre en seq [0:3] , la de la posición 1 ocurre en seq [1:4] , y así sucesivamente.
Debemos tener cuidado de no intentar leer una subsecuencia que no ocurra en la secuencia. Si seq = “AGAGAT” , solo hay
cuatro secuencias posibles de 3bp, e intentar seleccionar la que comienza en el índice 4, seq [4:7] , resultaría en un error.
Para empeorar las cosas, la indexación de cadenas comienza en 0 , y también están las peculiaridades de la naturaleza
inclusivo/exclusiva de [] slicing y la función range () !
Para ayudar, dibujemos una imagen de una secuencia de ejemplo, con varios índices y subsecuencias de 3bp que nos gustaría ver
anotadas.
II.5_9_SEQ_STO_Windows

Dado un índice de inicio, la subsecuencia de 3bp se define como seq [index:index + 3] . Para la secuencia
anterior, len (seq) devolvería 15 . El primer índice de inicio que nos interesa es 0 , mientras que el último índice de
inicio que queremos incluir es 12 , o len (seq) - 3 . Si tuviéramos que usar la función range () para devolver una
lista de secuencias de inicio que nos interesan, usaríamos range (0, len (seq) - 3 + 1) , donde el + 1 da cuenta
del hecho de que range () incluye el primer índice, pero es exclusivo en el último índice. [4]
También debemos recordar ejecutar .strip () en la secuencia de lectura, ya que no queremos que la inclusión de ningún
\ n caracteres de nueva línea ¡estropeando el cálculo correcto de la longitud de la secuencia!
Observe en el siguiente código (que se puede encontrar en el archivo stop_count_seq.py ) la línea comentada
#print (codon) .
II.5_10_py_49_stop_count_ex

Mientras codificamos, se utilizó esta línea para imprimir cada codón para estar seguros de que las subsecuencias de 3bp estaban
siendo consideradas de manera confiable, especialmente la primera y la última en seq1.txt ( ATA y AAT ). Esta es una
parte importante del proceso de depuración porque es fácil hacer pequeños errores “off-by-one” con este tipo de código. Cuando
estamos satisfechos con la solución, simplemente comentamos la declaración impresa.
Para tareas de ventana como esta, ocasionalmente puede ser más fácil acceder a los índices con un bucle while.

2.5.3 https://espanol.libretexts.org/@go/page/55122
II.5_11_py_49_2_stop_count_ex_while

Si quisiéramos acceder
a codones no superpuestos, podríamos usar index = index + 3 en lugar de
index = index + 1 sin ningún otro cambio en el código. Del mismo modo, si quisiéramos inspeccionar ventanas de 5bp,
podríamos reemplazar instancias de 3 por 5 (o usar una variable windowsize ).

Ejercicios
1. El peso molecular de una cadena de ADN monocatenario (en g/mol) es (recuento de “A” ) *313.21 + (recuento de “T” )
*304.2 + (recuento de “C” ) *289.18 + (recuento de “G” ) *329.21 — 61.96 (para dar cuenta de la eliminación de un
fosfato y la adición de un hidroxilo en el sencillo hebra).
Escriba código que imprima el peso molecular total de la secuencia en el archivo seq.txt . El resultado debe ser
21483.8 . Llama a tu programa mol_weight_seq.py .
2. El archivo seqs.txt contiene una serie de secuencias, una secuencia por línea. Escribe un nuevo programa Python que
imprima el peso molecular de cada uno en una nueva línea. Por ejemplo: Es posible
ii-5_11b_py_out_ex que desee utilizar partes sustanciales de la respuesta para la pregunta 1 dentro de un bucle de algún tipo. Llama a

tu programa mol_weight_seqs.py .
3. El archivo ids_seqs.txt contiene las mismas secuencias que seqs.txt ; sin embargo, este archivo también
contiene ID de secuencia, con un ID por línea seguido de un carácter tabulador ( \ t ) seguido de la secuencia. Modifique su
programa de la pregunta 2 para imprimir la misma salida, en un formato similar: un ID por línea, seguido de un carácter de
tabulación, seguido del peso molecular. El formato de salida debería así verse así (pero los números serán diferentes, para evitar
regalar la respuesta):
II.5_12_py_49_2_mol_peso_fuera_ex Llama a tu programa mol_weight_ids_seqs.py .

Debido a que los caracteres de tabulación hacen que la salida se alinee de manera diferente dependiendo de la longitud de la
cadena ID, es posible que desee ejecutar la salida a través de la columna de herramienta de línea de comandos con una
opción -t , que formatea automáticamente la entrada separada por tabulaciones
II.5_13_PY_50_mol_peso3_salida

4. Crear una versión modificada del programa en la pregunta 3 del capítulo 15, “Colecciones y Looping, Parte 1: Listas y
para”, para que también identifique las ubicaciones de las subsecuencias que son autosuperpuestas. Por ejemplo,
“GAGA " ocurre en “GAGAGAGAGAGATATGAGA " en las posiciones 1, 3, 5, 7 y 14.

1. Python es uno de los únicos lenguajes que requieren que los bloques sean delineados por sangría. La mayoría de los otros
lenguajes utilizan pares de llaves para delinear bloques. Muchos programadores sienten que esto elimina demasiada creatividad
en el formateo del código Python, mientras que otros así impone un método común para distinguir visualmente los bloques.
2. En ausencia de paréntesis para agrupar, y tiene prioridad sobre o , al igual que la multiplicación tiene prioridad sobre la
suma en álgebra numérica. La lógica booleana, por cierto, lleva el nombre del matemático y filósofo del siglo XIX George
Boole, quien primero describió formalmente el “álgebra lógica” de combinar los valores de la verdad con conectivos como “y”,
“o” y “no”.
3. Puedes ver esto por ti mismo: print (0.2*0.2/0.2 == 0.2) impresiones Falso ! Algunos lenguajes
matemáticamente orientados son capaces de trabajar completamente simbólicamente con tales ecuaciones, evitando en absoluto
la necesidad de trabajar con números. Esto requiere un motor de análisis sofisticado pero permite que dichos lenguajes evalúen
incluso expresiones genéricas como x*x/x == x como true.
4. Sí, este tipo de pensamiento lógico detallado puede ser tedioso, pero se vuelve más fácil con la práctica. Dibujar imágenes y
considerar pequeños ejemplos también es invaluable cuando se trabaja en problemas de programación, así que ten a mano un
lápiz y un trozo de papel.

This page titled 2.5: Flujo de control condicional is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

2.5.4 https://espanol.libretexts.org/@go/page/55122
2.6: Funciones de Python
El perfil psicológico [de un programador] es principalmente la capacidad de cambiar los niveles de abstracción, de nivel bajo a
nivel alto. Para ver algo en lo pequeño y ver algo en lo grande.
~ Donald Knuth
Las funciones (a veces llamadas “subrutinas”) son posiblemente el concepto más importante en la programación. Ya hemos visto su
uso en muchos contextos, por ejemplo, al usar funciones como len () y float () . Aquí hay un poco de código que
calcula el contenido de GC de una secuencia de ADN en una variable llamada seq :
II.6_1_PY_50_2_GC_Loop

¿Y si quisiéramos calcular el contenido de GC para múltiples variables diferentes en nuestro código? ¿Deberíamos reescribir
nuestro for-loop de GC-Computing para cada variable diferente? ¡De ninguna manera! Primero, estaríamos en un riesgo mucho
mayor de errores (es un hecho probabilístico que más mecanografía lleva a más errores tipográficos). Segundo, el objetivo de la
programación es que la computadora haga todo el trabajo, no nosotros.
Idealmente, nos gustaría encapsular la funcionalidad de calcular el contenido de GC, así como la funcionalidad de obtener la
longitud de una secuencia está encapsulada por la función len () . Sólo queremos poder decir
gc = gc_content (seq) . Las funciones nos permiten hacer exactamente esto: encapsular un bloque de código para
reutilizarlo siempre que lo necesitemos. Hay tres partes importantes de una función:
1. La entrada (parámetros dados a la función).
2. El bloque de código que se va a ejecutar usando esos parámetros. Como es habitual, un nivel adicional de sangría definirá el
bloque.
3. La salida de la función, llamada el valor de retorno. Esto puede ser opcional, si la función “hace algo” (como print ())
en lugar de “devuelve algo” (como len () ).
Ignorando el punto 2, las funciones pueden representar realmente un ideal matemático: relacionan entradas con salidas. Incluso
tienen dominios (el conjunto de todas las entradas válidas) y rangos (el conjunto de todas las salidas potenciales).
Definimos funciones en Python usando la palabra clave def , y en Python las funciones deben definirse antes de que puedan ser
ejecutadas. Aquí hay una función de ejemplo que calcula una “composición base” (recuento de un carácter en una cadena dada)
dado dos parámetros: (1) la secuencia (una cadena) y (2) la base/carácter a buscar (también una cadena).

2.6.1 https://espanol.libretexts.org/@go/page/55081
Las dos últimas líneas anteriores llaman a la función con diferentes parámetros; tenga en cuenta que los nombres de las variables de
parámetros (en este caso seq y query_base ) no necesitan relacionarse con los nombres de las variables de los datos fuera
de la función. Este es un punto importante al que volveremos. Cuando el intérprete lee la línea def y el bloque correspondiente,
se define la función (disponible para su uso), pero las líneas no se ejecutan, ni se llaman, hasta que la función se usa en las dos
últimas líneas.
Una de las mejores cosas de las funciones es que pueden llamar a otras funciones, siempre que ya se hayan definido en el momento
en que se llaman.
II.6_3_PY_52_base_composición_función_2

Debido a que las funciones solo necesitan ser definidas antes de ser llamadas, es común ver colecciones de funciones primero en un
programa. Además, el orden de definición no necesita corresponder a su orden de ejecución: ya sea la definición de función
gc_content () o base_composition () podrían ocurrir primero en este archivo y el cálculo seguiría
funcionando.
La idea de encapsular ideas pequeñas en funciones de esta manera es poderosa, y desde este punto en adelante se debe intentar
pensar principalmente en términos de “qué función estoy escribiendo/necesito”, en lugar de “¿qué programa estoy escribiendo?”

Notas importantes sobre las funciones


En un esfuerzo por producir código limpio, legible y reutilizable, debe esforzarse por seguir estas reglas al escribir funciones.
1. Los bloques de función solo deben utilizar variables que se crean dentro del bloque de función (por ejemplo, g_cont y
c_cont dentro de la función gc_content () anterior), o se pasan como parámetros (por ejemplo, seq ). Las
funciones deben ser “mundos en sí mismos” que sólo interactúan con el mundo exterior a través de sus parámetros y
declaración de retorno. La siguiente redefinición de la función gc_content () funcionaría, pero se consideraría de mala
forma porque se usa seq4 pero no es uno de los dos tipos enumerados. imagenObserve la diferencia: la función no toma
parámetros y por lo tanto debe usar los mismos nombres de variables definidos fuera de la función. Esto hace que la función
esté fuertemente ligada al contexto en el que se le llama. Debido a que las definiciones de funciones pueden ser cientos o miles
de líneas eliminadas de donde se les llama (o incluso están presentes en un archivo diferente), esto hace que la tarea de codificar
sea mucho más difícil.
2. Documentar el uso de cada función con comentarios. ¿Qué parámetros se toman y qué tipos deberían ser? ¿Es necesario que los
parámetros se ajusten a alguna especificación? Por ejemplo: “esta función sólo funciona para cadenas de ADN, no cadenas de
ARN”. ¿Qué se devuelve? Aunque anteriormente hemos comentado nuestras funciones mediante el uso de líneas de
comentarios simples, Python le permite especificar un tipo especial de comentario de función llamado “docstring”. Estas
cadenas de comillas triples deben aparecer en la parte superior del bloque de función y pueden abarcar varias líneas.
II.6_5_py_54_función_docstringMás tarde, cuando discutamos la documentación para el código Python, aprenderemos cómo el intérprete

de Python puede recopilar automáticamente estos comentarios en documentos agradables y legibles por humanos para otros que
quieran llamar a sus funciones. De lo contrario, usaremos comentarios regulares para la documentación de funciones.
3. Las funciones no deberían ser “demasiado largas”. Esto es subjetivo y depende del contexto, pero la mayoría de los
programadores se sienten incómodos con funciones que tienen más de una página de largo en su ventana de editor. [1] La idea es
que una función encapsula una idea única, pequeña y reutilizable. Si te encuentras escribiendo una función que es difícil de leer
o entender, considera dividirla en dos funciones que necesitan ser llamadas en secuencia, o en una función corta que llama a
otra función corta.
4. ¡Escribe muchas funciones! Incluso si una sección de código sólo va a ser llamada una vez, es completamente aceptable hacer
una función fuera de ella si encapsula alguna idea o bloque bien separable. Después de todo, nunca se sabe si podría necesitar
volver a usarlo, y solo el acto de encapsular el código te ayuda a asegurar su corrección, permitiéndote olvidarte de él cuando
trabajas en el resto de tu programa.

Contenido de GC sobre secuencias en un archivo


En el capítulo 17, “Flujo de control condicional”, uno de los ejercicios consistió en calcular el peso molecular para cada secuencia
en un archivo separado por tabuladores de IDs y secuencias, ids_seqs.txt . Aquí están las líneas de este archivo visto con
menos -S :
II.6_6_py_55_short_seqs_ids_less

2.6.2 https://espanol.libretexts.org/@go/page/55081
Si completaste el ejercicio de peso molecular en el capítulo 17, es posible que te haya resultado algo desafiante. Sin usar funciones,
es probable que necesite usar un bucle for para iterar sobre cada línea del archivo, y dentro de allí otro bucle for para iterar sobre
cada base de la secuencia actual, y dentro de eso una sentencia if para determinar el peso molecular de cada base.
Aquí escribiremos un programa similar, excepto que esta vez haremos uso de una función que devuelve el contenido GC de su
parámetro. Toda la solución es en realidad bastante corta y legible.
II.6_7_PY_56_GC_Lines_EX

En el código anterior ( ids_seqs_gcs.py ), hay una clara “separación de preocupaciones” que facilita el diseño de una
solución. Una función maneja el conteo de bases en una secuencia de ADN y otra el cálculo del contenido de GC. Con esos fuera
del camino, podemos enfocarnos en analizar el archivo de entrada e imprimir la salida.
Para simplificar la exposición, la mayoría de las pequeñas muestras de código en los próximos capítulos no implican la creación de
nuevas funciones. Sin embargo, la mayoría de los ejercicios sugerirán funciones de escritura como un medio para descomponer un
problema en sus partes componentes.

Ejercicios
1. Ahora hemos cubierto cuatro de las estructuras básicas de flujo de control comunes a la mayoría de los lenguajes de
programación. ¿Qué son y qué hacen?
2. A menudo queremos mirar algún tipo de ventanas de una secuencia; tal vez queremos mirar los codones de una secuencia como
“ACTTAGAGC” (“ACT” , “TAG” y “ AGC” ), que podemos pensar como un “tamaño de ventana” de 3 pb y un
“tamaño de paso” de 3 pb. O tal vez queremos considerar ventanas superpuestas de 6-mers (como “ACTTAG” ,
“CTTAGA” , “TTAGAG” , y “TAGAGC” , tamaño de ventana 6, tamaño de paso 1).
Escribe un programa con una función llamada get_windows () que tome tres parámetros: la secuencia para extraer
ventanas de (string), el tamaño de la ventana (int) y el tamaño del paso (int). La función debería devolver una lista de cadenas,
una por ventana, sin “ventanas parciales” si la última ventana se corría fuera del final de la secuencia. II.6_8_py_56_2_get_windows_exercise
(Puede encontrar un bucle de tiempo para ser más útil que un bucle for para este ejercicio). Usa este código para probar tu
función: II.6_9_py_56_3_get_windows_ex_use Este código debería generar lo siguiente: II.6_10_py_56_4_get_windows_ex_use_out
3. Aunque la función get_windows () está diseñada para ejecutarse en cadenas, la indexación en cadenas y en listas
funciona igual en Python. ¿Qué pasa si ejecutas get_windows ([1, 2, 3, 4, 5, 6, 7, 8], 3, 2) ?
4. Rehacer el ejercicio 1 del capítulo 17, pero esta vez escribe y usa una función molecular_weight () que toma una
cadena de ADN como parámetro llamado seq .

1. Con el advenimiento de los monitores grandes, ¡quizás esta ya no sea una buena regla general! Personalmente, encuentro que
pocas de mis funciones necesitan superar las 30 líneas de longitud, dependiendo sólo algo del idioma en el que esté escribiendo.

This page titled 2.6: Funciones de Python is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

2.6.3 https://espanol.libretexts.org/@go/page/55081
2.7: Interfaz de línea de comandos
Si bien Python es un gran lenguaje para la programación independiente, también es un lenguaje de primera clase para interactuar
con la línea de comandos Unix/Linux. Este capítulo explora algunos de esos conceptos, permitiéndonos usar Python como
“pegamento” para conectar otros poderosos conceptos de línea de comandos.

La entrada estándar es un manejador de archivo


En capítulos anteriores, vimos que las utilidades simples de Unix/Linux como grep y sort se pueden encadenar para una
variedad de tareas de manipulación de datos.
II.7_1_py_57_sort_blast_stdin

El comando anterior utiliza grep para hacer coincidir las líneas del archivo pz_stats.table con el patrón _L , que se
imprimen a la salida estándar. Usando la redirección | , enviamos esta salida para ordenar en su entrada estándar y ordenar
las líneas de la segunda columna en orden numérico general, antes de canalizar esta salida a menos -S .
II.7_2_PY_58_Sort_blast_stdin_out

Nuevamente, sort y less no están leyendo su entrada de un archivo, sino de flujos de entrada estándar. Del mismo modo,
no están escribiendo su salida en un archivo, sino simplemente imprimiendo a las secuencias de salida estándar. A menudo es útil
diseñar nuestras propias pequeñas utilidades como programas Python que operan de la misma manera.
La analogía de “file handle as a pipe” que hemos estado usando nos servirá bien a la hora de escribir programas que leen entrada de
entrada estándar. El manejador de archivo asociado con la entrada estándar es fácil de usar en Python. Solo tenemos que importar el
módulo sys , y luego sys.stdin es una variable que hace referencia al manejador de archivo de solo lectura que funciona
igual que cualquier otra. Ni siquiera tenemos que usar io.open () . Aquí hay un programa que imprime cada línea que pasa a
él en la entrada estándar.
II.7_3_py_59_stdin_ex

En este caso, estamos usando sys.stdin de manera muy parecida a los manejadores de archivos de solo lectura que vimos en
ejemplos anteriores. Podemos probar nuestro programa en un archivo simple que contiene algunos ID de genes:
II.7_4_py_60_stdin_ex_out

Pero si intentamos ejecutar nuestro programa sin darle ninguna entrada en el flujo de entrada estándar, se sentará y esperará datos
que nunca llegarán.
II.7_5_py_61_stdin_ex_esperando

Para matar el programa, podemos usar el Control-C habitual. La razón de este comportamiento es que el flujo de entrada
estándar se puede utilizar para la entrada desde el estándar de otro programa (como deseamos hacer), o puede usarse para construir
programas interactivos. Para ver esto, podemos ejecutar el mismo programa, excepto que esta vez vamos a escribir alguna entrada
usando el teclado, y cuando hayamos terminado enviaremos el código de control Control-D , que es una forma de decirle a
Python que hemos terminado de enviar entrada.
II.7_6_py_62_stdin_ex_waiting2

Esto sugiere algunas posibilidades interesantes para programas interactivos, [1] pero en este caso probablemente sería confuso para
alguien que quiera utilizar nuestro programa canalizando datos a él. Afortunadamente, sys.stdin tiene un método llamado
.isatty () que devuelve True si no hay datos presentes en el flujo de entrada cuando se llama. (TTY es la abreviatura
de “TeleTypeWriter”, un nombre común para un dispositivo de entrada conectado al teclado de décadas pasadas). Entonces,
podemos modificar nuestro programa con bastante facilidad para que salga con algún texto de uso útil si detecta que no hay datos
presentes en la entrada estándar.
II.7_7_py_63_stdin_ex_waiting3

Casi siempre es una buena idea que un programa verifique si se está llamando correctamente, y que dé alguna información útil de
uso si no. También es común incluir, en comentarios cercanos a la parte superior del programa, información de autoría como fecha
de última actualización e información de contacto. También puede necesitar o querer incluir algún texto de derechos de autor y
licencia, ya sea de su elección o el de su institución.

2.7.1 https://espanol.libretexts.org/@go/page/55098
Tenga en cuenta que no hemos llamado a .close () en el controlador de archivo de entrada estándar. Aunque Python no se
quejará si lo haces, no es obligatorio, ya que la entrada estándar es un tipo especial de manejador de archivos que el sistema
operativo maneja automáticamente.

Salida estándar como manejador de archivo


Cualquier cosa impresa por un programa Python se imprime en el flujo de salida estándar, por lo que esta salida también se puede
enviar a otros programas para su procesamiento.
II.7_8_py_64_stdout_sort

Sin embargo, el módulo sys también proporciona acceso al controlador de archivo de salida estándar, sys.stdout . Al
igual que otros manejadores de archivos orientados a la salida, podemos llamar a .write () en este manejador de archivo
para imprimir. La principal diferencia entre este método y llamar a print () es que mientras print () por defecto
agrega un carácter de nueva línea “\ n” sys.stdout.write () no lo hace. [2] Esta característica puede ser
particularmente útil cuando deseamos imprimir una tabla de manera elemento por elemento porque los datos no se almacenan en
cadenas que representan filas individuales.
II.7_9_py_65_stdout_table

Salida de lo anterior:
II.7_10_py_66_stdout_table_out

Si bien print () es una forma sencilla de imprimir una línea de texto, sys.stdout.write () le brinda más control
sobre el formato de la salida impresa. Al igual que con sys.stdin , no es necesario llamar a .close () en el controlador
de archivo. A diferencia de otros manejadores de archivos orientados a la salida, los datos se descargan regularmente de la tubería
(ya sea para imprimir o para la entrada estándar de otro programa).

Parámetros de Línea de Comandos


Hasta ahora, a pesar de que hemos estado ejecutando nuestros programas desde la línea de comandos, ninguno de nuestros
programas Python ha aceptado parámetros de entrada. Considere el ejemplo del capítulo 18, “Funciones de Python”, donde
calculamos el contenido de GC para cada secuencia en un archivo. En lugar de codificar el nombre del archivo en la llamada
io.open () , sería preferible proporcionar el nombre del archivo con el que trabajar en la línea de comandos, como en
. /ids_seqs_gcs.py ids_seqs.txt .
El módulo sys vuelve a venir al rescate. Después de importar sys , la variable sys.argv hace referencia a una lista de
cadenas que contienen, comenzando en el índice 0, el nombre del script en sí, luego cada parámetro. Debido a que sys.argv
es siempre una lista de cadenas, si queremos introducir un argumento float o integer, necesitaremos convertir el parámetro
apropiado usando int () o float () antes de usarlo.
II.7_11_PY_67_Params_ex

Este código también determina si el número esperado de parámetros ha sido dado por el usuario mirando len (sys.argv) ,
saliendo si este no es el caso.
II.7_12_PY_68_Params_ex_out1

Al igual que con otros programas ejecutados en la línea de comandos, si queremos enviar un solo parámetro que contenga espacios,
necesitamos envolverlo en comillas simples o dobles.
II.7_13_PY_69_PARAMS_EX_OUT2

Aunque no lo cubriremos aquí, el módulo argparse hace que escribir scripts que requieran parámetros de entrada de
diferentes tipos sea relativamente fácil. El módulo argparse también automatiza la impresión y formateo de la información de
ayuda a la que pueden acceder los usuarios que ejecutan el programa con un indicador -h o —help . Una variedad de
tutoriales para argparse se pueden encontrar en línea. (También hay un optparse de módulo más simple, pero menos
funcional).

2.7.2 https://espanol.libretexts.org/@go/page/55098
Ejecución de Comandos de Shell dentro de Python
A veces es útil ejecutar otros programas desde dentro de nuestros propios programas. Esto podría deberse a que queremos ejecutar
una serie de comandos generados algorítmicamente (por ejemplo, tal vez queremos ejecutar un programa de ensamblaje de genoma
en una variedad de archivos en secuencia), o tal vez queremos que la salida de un programa se ejecute en la línea de comandos.
Considere la tarea de línea de comandos de enumerar todos los archivos en el directorio actual que coincidan con el
cluster de patrón*.fasta . Para ello podemos usar ls -1 cluster*.fasta , donde -1 le dice a ls
que imprima su salida como una sola columna de nombres de archivo, y cluster*.fasta coincide con todos los nombres
de archivo que coincidan con el patrón deseado (como cluster_ab.fasta , Cluster_ac.fasta ,
cluster_ag.fasta y cluster_d.fasta .).
II.7_14_py_70_ls_fasta

Hay algunas formas de obtener esta información en un programa Python, pero una de las más fáciles es ejecutar el comando
ls -1 cluster*.fasta desde dentro de nuestro programa, capturando el resultado como una cadena. [3] El módulo de
subproceso nos permite hacer exactamente esto mediante el uso de la función subprocess.check_output () .
Esta función toma una serie de parámetros potenciales, pero la ejecutaremos con solo dos: (1) el comando que deseamos ejecutar
(como una cadena) y (2) shell = True , lo que indica que el intérprete de Python debería ejecutar el comando como si lo
hubiéramos ejecutado en la línea de comandos.
El resultado de la llamada a la función es una sola cadena que contiene cualquiera que sea el comando impreso a la salida estándar.
Debido a que es una sola cadena, si queremos que se represente como una lista de líneas, primero necesitamos .strip () de
cualquier carácter de nueva línea al final, y luego dividirlo en los \ n caracteres que separan las líneas.
II.7_15_PY_71_Subprocess_LS

Al ejecutar el programa, vemos que el resultado es una lista de cadenas, una por archivo:
II.7_16_PY_72_subprocess_list_out

Con esta lista de nombres de archivo en la mano, podríamos desear ejecutar una serie de ensamblajes con el programa
RunAssembly (producido por 454 Life Sciences para ensamblar secuencias de transcripción). Este programa requiere un
parámetro -o para especificar dónde deben escribirse las salidas así como una entrada de nombre de archivo; las generamos con
operaciones de cadena para “construir” un comando a ejecutar. Una vez que estemos satisfechos con los comandos, podemos
descomentar la llamada real a subprocess.check_output () .
II.7_17_PY_72_2_Subprocess_Run_Asambleas

Esta versión del programa informa de los comandos que se ejecutarán. [4]
II.7_18_PY_72_3_RunAssembly_out

Mientras un comando se ejecuta a través de la función subprocess.check_output () de esta manera, el programa


esperará antes de continuar a la siguiente línea (aunque probablemente solo notará si el comando tarda mucho en ejecutarse). Si el
comando se bloquea o se mata, el programa Python también se bloqueará con un llamado ProcessError (¡generalmente
es algo bueno!).
Otras funciones del módulo de subprocesos permiten ejecutar comandos en segundo plano, por lo que el script no espera a
que se completen antes de continuar en su propia ejecución, y trabajar con múltiples procesos de este tipo mientras se comunica
con ellos. Otros módulos de Python incluso permiten un trabajo avanzado con ejecución paralela y subprocesos, aunque estos
temas están más allá del alcance de este capítulo.
¡No olvides que eres el responsable de la ejecución de tus programas! Si escribes un programa que ejecuta muchos otros
programas, puedes utilizar rápidamente más recursos computacionales de los que crees. Si estás trabajando en un sistema con el
que no te importa tomar algunos riesgos, podrías intentar ejecutar el siguiente programa, que ejecuta una copia de sí mismo (que a
su vez ejecuta una copia de sí mismo, que a su vez ejecuta una copia de sí mismo, y una y otra vez):
II.7_19_py_73_subprocess_calls_itself

2.7.3 https://espanol.libretexts.org/@go/page/55098
Ejercicios
1. En el ejemplo de impresión de tablas se utilizó sys.stdout para imprimir muy bien una mesa en orden de columna
mayor. Escribe una función llamada print_row_major () que imprima una tabla almacenada en orden fila-major, por
lo que una llamada como print_row_major ([["A”, “B"], ["C”, “D"], ["E”, “F"]]) da como
resultado la salida con el siguiente aspecto:
alt

stdin_eval_mean.py que lee un archivo en el formato


2. Escribe un programa llamado
pz_blastx_yeast_top1.txt en sys.stdin , calcula la media de la columna E -value (columna 11, con valores
como 1e-32 ), e imprime la media a salida estándar. Si no hay datos en la entrada estándar, el programa debe producir un
mensaje de “uso”.
A continuación, intente ejecutar el programa con cat pz_blastx_yeast_top1.txt |. /stdin_eval_mean.py.
Además, intente prefiltrar las líneas antes del cálculo con algo como
cat pz_blastx_yeast_top1.txt | grep '_L' |. /stdin_eval_mean.py.
Ahora copia este programa a uno llamado stdin_eval_sd.py que lee en pz_blastx_yeast_top1.txt en
sys.stdin y calcula e imprime la desviación estándar de los valores E. Nuevamente, intente ejecutar el programa con y
sin prefiltrado con grep . (En este punto deberías sentirte cómodo escribiendo las funciones mean () y sd () que
toman listas de flotadores y devuelven respuestas. Su función sd () bien podría llamar a la función mean () .)
3. Modifique el programa stdin_eval_mean.py anterior (llame a la nueva versión
stdin_eval_mean_threshold.py ) para aceptar un umbral superior de valor E como primer parámetro. Así
cat pz_blastx_yeast_top1.txt |. /stdin_eval_mean.py 1e-6 debe calcular e imprimir la media de
la columna E -value, pero sólo para aquellas líneas cuyo valor E sea menor que 1e-6. (Deberá usar sys.argv y asegurarse
de convertir la entrada en ella en un flotador).
Su programa debe imprimir información de uso y salir si no hay datos proporcionados en la entrada estándar, o si no hay ningún
argumento de umbral.

1. Intenta usar una línea de código como value = input (“¿Cuál es tu nombre? “) seguido de
print (valor) . Tenga en cuenta que cuando ejecute el programa y se le solicite, deberá incluir comillas al escribir su
nombre (por ejemplo, “Shawn” ).
2. En Python 3.0 y superiores, la función print () también puede tomar algunos parámetros para especificar si el “\ n”
debe imprimirse o no, o incluso reemplazarse por algún otro carácter.
3. Un método alternativo para obtener una lista de archivos es usar el módulo os y la función os.listdir () , que
devolverá una lista de nombres de archivo. Para enumerar y trabajar con nombres de archivo y rutas, se prefieren las funciones
en el módulo os , ya que funcionarán ya sea que el programa se ejecute en un sistema Windows o en un sistema similar a
UNIX. Para ejecutar comandos shell de manera más general, el módulo de subprocesos es útil. Aquí estamos ilustrando
que en la línea de comandos Unix/Linux, puede hacer ambos trabajos llamando a utilidades del sistema.
4. Aquí estamos dividiendo los nombres de archivo en una lista de [nombre, extensión] con
nombrearchivo.split (“.”) . Este método no funcionará si hay múltiples . s en el expediente. Una alternativa
más robusta se encuentra en el módulo os : os.path.splitext (“this.file.txt”) devolvería
["this.file”, “txt"] .

This page titled 2.7: Interfaz de línea de comandos is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn
T. O’Neil (OSU Press) .

2.7.4 https://espanol.libretexts.org/@go/page/55098
2.8: Diccionarios
Los diccionarios (a menudo llamados “tablas hash” en otros idiomas) son una forma eficiente e increíblemente útil de asociar datos
con otros datos. Considere una lista, que asocia cada elemento de datos de la lista con un entero que comienza en cero:
II.8_1_dict_list_indices_valores

Un diccionario funciona de la misma manera, excepto que en lugar de índices, los diccionarios usan “claves”, que pueden ser
enteros o cadenas. [1] Normalmente dibujaremos diccionarios a lo largo del tiempo, e incluiremos las claves dentro de los corchetes
ilustrativos, ya que son tanto una parte de la estructura del diccionario como los valores:
II.8_2_dict_keys_values

Vamos a crear un diccionario que contenga estas claves y valores con algún código, llamándolo ids_to_gcs . Tenga en cuenta
que este nombre codifica tanto lo que representan las claves como los valores, lo que puede ser útil a la hora de realizar un
seguimiento del contenido de estas estructuras. Para crear un diccionario vacío, llamamos a la función dict () sin
parámetros, que devuelve el diccionario vacío. Entonces, podemos asignar y recuperar valores como lo hacemos con las listas,
excepto (1) estaremos usando cadenas como claves en lugar de índices enteros, y (2) podemos asignar valores a claves aunque la
clave no esté ya presente.
II.8_3_PY_74_dict_ex

Entonces podemos acceder a valores individuales al igual que con una lista:
alt

Sin embargo, no podemos acceder a un valor para una clave que no existe. Esto dará como resultado un KeyError , y el
programa se detendrá.
II.8_5_PY_76_dict_ex_error

Los diccionarios van en una sola dirección: dada la clave, podemos buscar el valor, pero dado un valor, no podemos encontrar
fácilmente la clave correspondiente. Además, el nombre “diccionario” es un poco engañoso, ya que aunque los diccionarios reales
están ordenados en orden alfabético, los diccionarios Python no tienen un orden intrínseco. A diferencia de las listas, que están
ordenadas y tienen un primer y último elemento, Python no garantiza cómo se almacenan internamente los pares clave/valor.
Además, cada clave única solo puede estar presente una vez en el diccionario y asociada a un valor, aunque ese valor podría ser
algo complejo como una lista, o incluso otro diccionario. Quizás una mejor analogía sería un conjunto de estantes etiquetados,
donde cada etiqueta solo se puede usar una vez.
imagen alt

Hay una variedad de funciones y métodos que podemos usar para operar en diccionarios en Python. Por ejemplo, la función
len () devolverá el número de pares clave/valor en un diccionario. Para el diccionario anterior, len (ids_to_gcs)
devolverá 3 . Si queremos, podemos obtener una lista de todas las claves de un diccionario usando su método.keys () ,
aunque esta lista puede estar en un orden aleatorio porque los diccionarios están desordenados. Siempre podríamos ordenar la lista,
y pasar por encima de eso:
II.8_7_py_77_dict_keys_loop

Del mismo modo, podemos obtener una lista de todos los valores usando .values () , nuevamente sin ningún orden en
particular. Entonces, ids_to_gcs.values () devolverá una lista de tres flotadores. [2]
Si tratamos de obtener un valor para una clave que no está presente en el diccionario, obtendremos un KeyError . Entonces,
por lo general vamos a querer probar si una clave está presente antes de intentar leer su valor. Podemos hacer esto con el método
.has_key () del diccionario, que devuelve True si la clave está presente en el diccionario. [3]
II.8_8_PY_78_dict_has_key

Contando Términos de Ontología Génica


Para ilustrar el uso de un diccionario en la práctica, considere el archivo Pz.ANNOT.txt , el resultado de anotar un conjunto
de transcripciones ensambladas con términos y números de ontología génica (GO). Cada línea separada por tabuladores da una ID
de gen, una anotación con un número GO y un término legible por humanos correspondiente asociado con ese número.

2.8.1 https://espanol.libretexts.org/@go/page/55106
II.8_9_PY_78_2_PZ_ANNOT_TXT_Sample

En este archivo, cada gen puede estar asociado con múltiples números GO, y cada número GO puede estar asociado con múltiples
genes. Además, cada término GO puede estar asociado con múltiples números GO diferentes. ¿Cuántas veces se encuentra cada ID
en este archivo? Idealmente, nos gustaría producir una salida separada por tabuladores que se vea así:
II.8_10_py_78_3_pz_annot_txt_sample_out

Nuestra estrategia: Para practicar algunos de los conceptos de interacción de línea de comandos, haremos que este programa lea el
archivo en la entrada estándar y escriba su salida en la salida estándar (como se discutió en el capítulo 19, “Interfaz de línea de
comandos”). Tendremos que mantener un diccionario, donde las claves son los ID de genes y los valores son los recuentos. Un for-
loop servirá para leer en cada línea, quitando la nueva línea final y dividiendo el resultado en una lista en el carácter de tabulación,
\ t . Si el ID está en el diccionario, agregaremos uno al valor. Debido a que el diccionario comenzará vacío, frecuentemente nos
encontraremos con IDs que no están ya presentes en el diccionario; en estos casos podemos establecer el valor en 1 . Una vez que
hayamos procesado toda la entrada, podemos recorrer el diccionario imprimiendo cada conteo e ID.
En el siguiente código ( go_id_count.py ), cuando el diccionario tiene la clave seqid , ambos estamos leyendo del
diccionario (en el lado derecho) y escribiendo al valor (en el lado izquierdo).
II.8_11_py_79_go_id_count_ex

Pero cuando la clave no está presente, simplemente estamos escribiendo al valor. En el bucle que imprime el contenido del
diccionario, no estamos comprobando la presencia de cada id antes de leerlo para imprimirlo, porque se garantiza que la
lista de ids_list contendrá exactamente esas claves que están en el diccionario, ya que es el resultado de
ids_to_counts.keys () .
II.8_12_py_80_go_id_count_ex_call1

¿Cuál es la ventaja de organizar nuestro programa Python para leer filas y columnas en la entrada estándar y escribir filas y
columnas en la salida estándar? Bueno, si conocemos suficientemente bien las herramientas de línea de comandos incorporadas,
podemos utilizarlas junto con nuestro programa para otros análisis. Por ejemplo, primero podemos filtrar los datos con grep
para seleccionar aquellas líneas que coincidan con el término transcriptasa :
II.8_13_PY_81_GO_ID_GREP_Transcriptasa

El resultado son solo líneas que contienen la palabra “transcriptasa”:


II.8_14_py_81_2_go_id_grep_transcriptase_out

Si entonces alimentamos esos resultados a través de nuestro programa (


cat pz.annot.txt | grep 'transcriptase' |. /go_id_count.py), solo vemos recuentos para IDs entre esas
líneas.
II.8_15_py_81_3_go_id_grep_transcriptase_out_through_python

Finalmente, podríamos canalizar los resultados a través de wc para contar estas líneas y determinar cuántos ID se anotaron al
menos una vez con ese término (21). Si quisiéramos ver qué ocho genes tenían más anotaciones que coincidieran con la
“transcriptasa”, también podríamos hacer eso clasificando los recuentos y usando la cabeza para imprimir las ocho líneas
superiores (aquí estamos rompiendo el comando largo con barras inversas, lo que nos permite seguir escribiendo en la siguiente
línea en la terminal). [4]
II.8_16_py_82_go_id_count_ex_call3

Parece que el gen PZ32722_B ha sido anotado como transcriptasa siete veces. Este ejemplo ilustra que, a medida que
trabajamos y construimos herramientas, si consideramos cómo podrían interactuar con otras herramientas (incluso otras piezas de
código, como funciones), podemos aumentar nuestra eficiencia notablemente.

Extracción de todas las líneas que coinciden con un conjunto de ID


Otra propiedad útil de los diccionarios es que el método.has_key () es muy eficiente. Supongamos que teníamos una lista
desordenada de cadenas, y queríamos determinar si una cadena en particular se produjo en la lista. Esto se puede hacer, pero
requeriría mirar cada elemento (en un for-loop, quizás) para ver si igualaba al que estamos buscando. Si en cambio almacenamos
las cadenas como claves en un diccionario (almacenando “present” , o el número 1 , o cualquier otra cosa en el valor),

2.8.2 https://espanol.libretexts.org/@go/page/55106
podríamos usar el método .has_key () , que toma un solo paso de tiempo (efectivamente, en promedio) sin importar cuántas
claves haya en el diccionario. [5]
Volviendo a la lista GO/ID del último ejemplo, supongamos que tuvimos el siguiente problema: deseamos primero identificar todos
aquellos genes (filas en la tabla) que fueron etiquetados con GO:0001539 (que podemos hacer fácilmente con grep en la
línea de comandos), y luego deseamos extraer todas las filas de la tabla que coincide con esos ID para tener una idea de qué otras
anotaciones podrían tener esos genes.
En esencia, queremos imprimir todas las entradas de un archivo:
II.8_17_PY_82_2_GO_TERMINOS

Donde la primera columna coincide con cualquier ID en la primera columna de otra entrada:
II.8_18_py_82_3_go_id_grep_go

Resulta que el problema anterior es común en el análisis de datos (subestableciendo líneas sobre la base de un conjunto de
“consulta” de entrada), por lo que tendremos cuidado de diseñar un programa que no sea específico de este conjunto de datos,
excepto que los ID en cuestión se encuentran en la primera columna. [6]
Escribiremos un programa llamado match_1st_cols.py que toma dos entradas: en la entrada estándar, leerá una serie de
líneas que tienen los ID de consulta que deseamos extraer, y también tomará un parámetro que especifica el archivo desde el que se
deben imprimir las líneas coincidentes. Para esta instancia, nos gustaría poder ejecutar nuestro programa de la siguiente manera:
alt

En términos de código, el programa puede leer primero la entrada de entrada estándar y crear un diccionario que tenga claves
correspondientes a cada ID que deseamos extraer (los valores pueden ser cualquier cosa). A continuación, el programa recorrerá las
líneas del archivo de entrada (especificado en sys.argv [1] ), y por cada ID lo verificará con .has_key () contra el
diccionario creado previamente; si se encuentra, se imprime la línea.
II.8_20_PY_84_GO_EXTRACT_CODIGO

Hacer ejecutable el programa ( match_1st_cols.py ) y ejecutarlo revela todas las anotaciones para esos ID que están
anotados con GO:0001539 .
II.8_21_py_85_go_extract_code_run

Como antes, podemos usar esta estrategia para extraer fácilmente todas las líneas que coincidan con una variedad de criterios,
simplemente modificando una o ambas entradas. Dada cualquier lista de identificaciones de genes de interés de un colaborador, por
ejemplo, podríamos usar eso en la entrada estándar y extraer las líneas correspondientes del archivo GO.

Ejercicios
1. Los diccionarios se utilizan a menudo para búsquedas simples. Por ejemplo, un diccionario podría tener claves para las tres
secuencias de ADN de pares de bases ( “TGG” , “GCC ”, “TAG ”, etc.) cuyos valores corresponden a códigos de
aminoácidos (correspondientemente, “W” , “A ”, “*” para “detener”, etc.). La tabla completa se puede encontrar en
la web buscando “tabla de codones de aminoácidos”.
Escribe una función llamada codon_to_aa () que tome una sola cadena de tres pares de bases y devuelva una cadena de
un carácter con el código de aminoácido correspondiente. Es posible que tengas que definir las 64 posibilidades, ¡así que ten
cuidado de no cometer errores tipográficos! Si la entrada no es una cadena de ADN válida de tres pares de bases, la función
debería devolver “X” para significar “desconocido”. Pruebe su función con algunas llamadas como
print (codon_to_aa (“TGG”)) , print (codon_to_aa (“TAA”)) e
print (codon_to_aa (“BOB”)) .
2. Combine el resultado de la función codon_to_aa () anterior con la función get_windows () de los ejercicios
del capítulo 18, “Funciones de Python”, para producir una función dna_to_aa () . Dada una cadena como
“AAACTGTCTCTA” , la función debe devolver su traducción como “KLSL” .
3. Usa la función get_windows () para escribir una función count_kmers () ; debería tomar dos parámetros (una
secuencia de ADN y un entero) y devolver un diccionario de k-mers para contar para esos k-mers. Por ejemplo,
count_kmers (“AAACTGTCTCTA”, 3) debería devolver un diccionario con las claves “AAA” , “AAC” ,

2.8.3 https://espanol.libretexts.org/@go/page/55106
“ACT” , “CTG” , “TGT” , “GTC” , “TCT” , “CTC” , “CTA” y los valores correspondientes 1 , 1 , 1 ,
1 , 1 , 1 , 2 , 1 , 1 . (El conteo de K-mer es un paso importante en muchos algoritmos bioinformáticos, incluido el
ensamblaje del genoma).
4. Crear una función union_dictionaries () que toma dos diccionarios como parámetros devuelve su “unión” como
diccionario; cuando se encuentra una clave en ambos, el valor mayor debe usarse en la salida. Si diccionario dict_a mapea
“A " , “B " , “C " a 3 , 2 , 6 y dict_b mapea “B " , “C " , “D ” " a 7 , 4 , 1 , por
ejemplo, la salida debe mapear “A " , “B " , “C " , “D " a 3 , 7 , 6 , 1 .

1. Las claves de diccionario pueden ser de cualquier tipo inmutable, que incluye enteros y cadenas, pero también incluyen otros
tipos más exóticos, como tuplas (listas inmutables). Si bien los flotadores también son inmutables, debido a que las claves se
buscan en base a la igualdad y los errores de redondeo pueden agrandarse, generalmente no se recomienda utilizarlas como
claves de diccionario.
2. En Python 3.0 y posteriores, lo que devuelven .keys () y .values () no es técnicamente una lista sino una “vista”,
que opera de manera similar a una lista sin usar ninguna memoria adicional. El código que se muestra sigue funcionando, pero
ids.sort () (la versión sort-in-place) no lo haría, ya que las vistas no tienen un método .sort () .
3. También hay un método más específico de Python para consultar una clave en un diccionario:
“TL34_X” en ids_to_gcs es equivalente a ids_to_gcs.has_keys (“TL34_X”) . Curiosamente, la
palabra clave in también funciona para listas: si ids = ["CYP6B”, “CATB”, “AGP4"] , entonces
“TL34_X” en ids devolverá False . Sin embargo, en este caso cada entrada en ids debe compararse con
“TL34_X” para hacer esta determinación, y así in es mucho más lento para las listas que para los diccionarios. El uso de
la palabra clave in para diccionarios es generalmente preferido por la comunidad Python, y de hecho .has_key () se
ha eliminado en Python 3.0 y posteriores. Para los fines de este libro, usaremos .has_key () al trabajar con diccionarios
para que quede claro exactamente lo que está sucediendo.
4. Para problemas simples como este, si conocemos suficientemente bien las herramientas de línea de comandos, ni siquiera
necesitamos usar Python. Esta versión del problema se puede resolver con una tubería como
cat Pz.Anot.txt | grep 'transcriptasa' | awk '{print $1}' | sort | uniq -c |
sort -k1,1nr | head
.
5. En términos de informática, decimos que la búsqueda de una lista desordenada se ejecuta en el tiempo , o “orden” ,
donde se toma para que sea el tamaño de la lista. Se ejecuta el método.has_key () , es decir, que el tiempo
empleado es independiente del tamaño del diccionario. Si necesitamos hacer esa búsqueda muchas veces, estas diferencias
pueden sumar significativamente. Más información sobre consideraciones de tiempo de ejecución se trata en el capítulo 25,
“Algoritmos y estructuras de datos”.
6. La utilidad grep puede realizar una operación similar; grep -f query_patterns.txt subject_file.txt
imprimirá todas las líneas en subject_file.txt que coincidan con cualquiera de los patrones en
query_patterns.txt . Pero esto requiere que todos los patrones se comparen con todas las líneas (incluso si los
patrones son simples), y así nuestra solución personalizada de Python es mucho más rápida cuando el número de consultas es
grande (porque el método .has_key () es muy rápido).

This page titled 2.8: Diccionarios is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil (OSU
Press) .

2.8.4 https://espanol.libretexts.org/@go/page/55106
2.9: Knick-knacks bioinformáticos y expresiones regulares
El capítulo 14, “Tipos de datos elementales”, mencionó que las cadenas en Python son inmutables, lo que significa que no se
pueden modificar después de haber sido creadas. Para la biología computacional, esta característica suele ser un obstáculo. Para
sortear esta limitación, frecuentemente convertimos cadenas en listas de cadenas de un solo carácter, modificamos las listas según
sea necesario y convertimos las listas de caracteres en cadenas cuando sea necesario. Python viene incorporado con una serie de
funciones que operan en listas y cadenas útiles para la biología computacional.

Convertir una cadena en una lista de cadenas de un solo carácter


Recuerda que debido a que las cadenas de Python son inmutables, no podemos ejecutar algo así como
II.9_1_py_88_inmutable_string_2

Para convertir una cadena en una lista de cadenas de un solo carácter, podemos pasar la cadena como parámetro a la función
list () (que, cuando se llama sin parámetros, crea una lista vacía):
II.9_2_PY_89_LISTA_BASE_LISTA

Convertir una lista de cadenas en una cadena


Si por casualidad tenemos una lista de cadenas (como la lista de cadenas de un solo carácter anterior), podemos unirlas en una sola
cadena usando el método .join () de cualquier cadena que queramos usar como separador para la cadena de salida.
II.9_3_py_90_base_list_join

La sintaxis parece un poco hacia atrás, “separator” .join (list_variable) , pero tiene sentido a la hora de
considerar cuál es el método . -sintaxis dice: pide a la cadena “ separator” que produzca una cadena dada la lista de
entrada list_variable . Debido a que la salida es de tipo string, tiene sentido que le pidamos a una cadena que haga esta
producción.

Revertir una lista


A veces es posible que queramos invertir el orden de una lista. Podemos hacer esto con el método .reverse () de la lista
que pide a la lista que se revierta en su lugar (pero devuelve None ).
alt

Al igual que con los métodos .sort () y .append () del capítulo 15, “Collections and Looping, Part 1: Lists and
for ”, .reverse () devuelve None , por lo que una línea como base_list = base_list.reverse ()
casi seguramente sería un error.

Invertir una cadena


Hay dos formas de revertir una cadena en Python: (1) primero, convertirla en una lista de cadenas de un solo carácter, luego revertir
la lista, luego unirla de nuevo en una cadena, o (2) usar “sintaxis de rebanada extendida”. La primera solución utiliza los métodos
descritos anteriormente, mientras que el segundo método extiende la sintaxis de [] slice para que los corchetes puedan contener
valores para [start:end:step] , donde start y end pueden estar vacíos (para indicar el primer y último índice de
la cadena), y el paso puede ser -1 (para indicar un paso atrás en la cadena).
alt

Aunque la sintaxis de slice extendida es bastante más rápida y requiere menos escritura, es un poco más difícil de leer. Una
búsqueda en línea de “python string reverse” probablemente revelaría primero esta solución.

Búsqueda y reemplazo simple en una cadena


Ya hemos visto cómo dividir una cadena en una lista de cadenas con el método.split () . Un método similar,
.replace () , nos permite reemplazar todas las subcadenas coincidentes de una cadena por otra cadena. Debido a que las
cadenas son inmutables, este método devuelve una copia modificada de la cadena original.
II.9_6_py_93_string_replace

2.9.1 https://espanol.libretexts.org/@go/page/55125
También debido a que las cadenas son inmutables, los datos originales se dejan sin cambios. Debido a que una variable es
simplemente un nombre que usamos para referirnos a algunos datos, sin embargo, podemos hacer que parezca que hemos
modificado la cadena original reasignando la variable seq a lo que sea devuelto por el método.
II.9_7_py_94_string_replace_reref

Comandos versus consultas


¿Por qué algunas operaciones (como el método .sort () de una lista) cambian los datos en su lugar pero devuelven None ,
mientras que otras (como el método .split () de una cadena o la función len () ) devuelven algo pero dejan solos los
datos originales? ¿Por qué es tan raro ver operaciones que hagan ambas cosas? El motivo es que los diseñadores de Python
(generalmente) intentan seguir lo que se conoce como el principio de separación comando-consulta, un principio filosófico en el
diseño de lenguajes de programación que establece que las operaciones individuales deben o bien modificar datos o devolver
respuestas a las consultas, pero no ambas.
La idea detrás de este principio es que al leer código, debería quedar inmediatamente obvio lo que hace, característica que se logra
fácilmente si cada operación solo tiene una cosa que puede hacer. Cuando las operaciones cambian los datos y devuelven una
respuesta, existe la tentación de “codificar por efecto secundario”, es decir, hacer uso de efectos simultáneos para minimizar la
escritura a costa de la claridad. Comparado con muchos otros lenguajes, Python hace un intento más fuerte de seguir este principio.

Expresiones Regulares
Las expresiones regulares, comunes a muchos lenguajes de programación e incluso herramientas de línea de comandos como
sed , son sintaxis para hacer coincidir patrones sofisticados en cadenas. Los patrones más simples son solo cadenas simples; por
ejemplo, “ATG” es el patrón para un codón de inicio. Debido a que Python trata las barras inversas como especiales en cadenas
(por ejemplo, “\ t” no es en realidad “\” seguido de una “t” , sino más bien un carácter de tabulación), los patrones
para expresiones regulares en Python generalmente se expresan como “cadenas sin procesar”, lo que se indica prefijándolas con
una r . Entonces, R"atg” es también el patrón para un codón de inicio, pero r "\ t” es la expresión regular para
“\” y “t” en lugar del carácter tabulador.
II.9_8_PY_94_2_Raw_VS_Tab_len

La funcionalidad de expresión regular de Python se importa con el módulo re mediante el uso de import re cerca de la
parte superior del script. Hay muchas funciones en el módulo re para trabajar con cadenas y patrones de expresión regular, pero
cubriremos solo las tres más importantes: (1) buscar un patrón en una cadena, (2) reemplazar un patrón en una cadena, y (3) dividir
una cadena en una lista de cadenas basada en un patrón.

Búsqueda de un patrón en una cadena


La búsqueda de un patrón se realiza con la función re.search () . La mayoría de las funciones del módulo re devuelven
un tipo de datos SRE_match especial, o una lista de ellos (con su propio conjunto de métodos), o Ninguno si no se
encontró ninguna coincidencia. La sentencia if- (y los bucles de tiempo) en Python trata a None como falso, por lo que podemos
usar fácilmente re.search () en una sentencia if. El resultado de una coincidencia exitosa puede indicarnos la ubicación de
la coincidencia en la consulta utilizando el método .start () :
II.9_9_PY_96_RE_Match

Reemplazar un patrón en una cadena


La función re.subn () se puede utilizar para buscar y reemplazar un patrón dentro de una cadena. Se necesitan al menos
cuatro argumentos importantes (y algunos opcionales que no discutiremos aquí): (1) el patrón para buscar, (2) la cadena de
reemplazo con la que reemplazar las coincidencias, (3) la cadena para buscar y (4) el número máximo de reemplazos para hacer (
0 para reemplazar todas las coincidencias). Esta función devuelve una lista [1] que contiene dos elementos: en el índice 0 , la
copia modificada de la cadena, y en el índice 1 , el número de reemplazos realizados.
II.9_10_py_97_re_subn

2.9.2 https://espanol.libretexts.org/@go/page/55125
Dividir una cadena en una lista de cadenas basada en un patrón
Ya hemos visto que podemos dividir una cadena basada en cadenas simples con .split () . Ser capaz de dividirse en
expresiones regulares complejas a menudo también es útil, y la función re.split () proporciona esta funcionalidad.
II.9_11_PY_98_RE_Split

Lo dejaremos como un ejercicio para que el lector determine qué sería la salida para una secuencia donde las coincidencias están
espalda con espalda, o donde las coincidencias se superponen, como en re.split (r"ATA”, “GCATATAGG”) .

El lenguaje de las expresiones regulares, en Python


En el capítulo 11, “Patrones (expresiones regulares)”, cubrimos las expresiones regulares con cierto detalle al discutir el motor de
expresión regular de línea de comandos sed . Las expresiones regulares en Python funcionan de manera similar: cadenas simples
como R"atg” coinciden con sus expresiones, los puntos coinciden con cualquier carácter único ( R"cc.” coincide con
cualquier codón P ), y los corchetes se pueden usar para emparejar uno de un conjunto de caracteres ( r "[ACTG]” coincide
con una de una secuencia de ADN, y r "[a-Za-Z0-9_]” es la abreviatura de “cualquier carácter alfanumérico y el guión
bajo”). Los paréntesis agrupan patrones, y el signo más modifica el grupo anterior (o grupo implícito) para que coincida una o más
veces ( * para cero o más), y las tuberías verticales | funcionan como un “o”, como en
r "([ACTG]) + (TAG|TAA|TGA)” , que coincidirá con cualquier secuencia de ADN terminada por una parada codón (
TAG , TAA o TGA ). Completando la discusión de capítulos anteriores, las expresiones regulares de Python también
admiten corchetes (por ejemplo, r "(AT) {10,100}” coincide con un “AT” repetido de 10 a 100 veces) y notación
estándar para el inicio y el final de la cadena. (Tenga en cuenta que r"^ ([ACTG]) +$” coincide con una cadena de ADN y
solo ADN. Para ejemplos más detallados de estos constructos de expresión regular, consulte el capítulo 11.)
La sintaxis de expresión regular de Python, sin embargo, difiere de la sintaxis extendida POSIX que discutimos para sed , y de
hecho proporciona una sintaxis extra extendida conocida como expresiones regulares “estilo Perl”. Estos admiten una serie de
características sofisticadas, dos de las cuales se detallan aquí.
Primero, los operadores que especifican que se debe repetir una coincidencia, como signos más, llaves y asteriscos, son por defecto
“codiciosos”. Lo mismo es cierto en la sintaxis POSIX extendida utilizada por sed . En Python, sin embargo, tenemos la opción
de hacer que el operador no sea codicioso, o más exactamente, reacio. Considera el patrón
r "([ACTG]) + (TAG|TAA|TGA)” , que coincide con una secuencia de ADN terminada por un codón de parada. La
parte codiciosa, ([ACTG]) + , consumirá todo menos el último codón de parada, dejando lo menos posible de la cadena
restante para hacer coincidir el resto del patrón.
II.9_12_Regex_Greedy_ex

En Python, si queremos hacer que el signo más sea reacio, podemos seguirlo con un signo de interrogación, lo que hace que el
partido deje lo más posible para partes posteriores del patrón.
II.9_13_Regex_renuentes

El operador de reluctancia también puede seguir el asterisco y las llaves, pero tenga cuidado: cuando se usa sin seguir a uno de
estos tres operadores de repetición, el operador de signo de interrogación funciona como un operador “opcional” (por ejemplo,
R"c (ATG)? C” coincide con “CC” y “CATGC” ).
La segunda diferencia principal entre las expresiones regulares Python (estilo Perl) y las expresiones regulares extendidas por
POSIX está en cómo especifican las clases de caracteres. Como se mencionó anteriormente, un patrón como
r "[a-Za-z0-9_]” es la abreviatura de “cualquier alfanumérico y el guión bajo”, por lo que una serie de alfanuméricos se
puede emparejar con r "[a-Za-Z0-9_] +” . En las expresiones regulares POSIX, la clase de caracteres A-zA-z0-9_
se puede especificar con [:alnum:] , y entonces el patrón tendría que usarse como [[:alnum:]] + en sed .
La sintaxis de expresión regular de estilo PERL utilizada por Python introdujo una serie de códigos abreviados para las clases de
caracteres. Por ejemplo, \ w es la abreviatura de “cualquier alfanumérico y el guión bajo”, así que r "\ w+” es el patrón
para una serie de estos y es equivalente a r "[a-za-Z0-9_] +” . El patrón \ W coincide con cualquier carácter que no
sea alfanumérico o el guión bajo, por lo que r "\ W+” coincide con una serie de estos (equivalente a
r "[^a-Za-z0-9_] +” ). Quizás la clase taquigráfica más importante es \ s , que coincide con un solo carácter de
espacio en blanco: un carácter de tabulación, espacio o nueva línea ( \ S coincide con cualquier carácter que no sea de espacio

2.9.3 https://espanol.libretexts.org/@go/page/55125
en blanco). Esto es más útil cuando se analiza la entrada, donde no se puede garantizar que las palabras estén separadas por
tabulaciones, espacios o alguna combinación de los mismos.
II.9_14_py_99_re_split_whitespace

En el código anterior, hemos reemplazado la división en “\ t” a la que estamos acostumbrados por un re.split () en
r "\ s+” , lo que asegurará que analizamos correctamente las piezas de la línea aunque estén separadas con múltiples
tabulaciones, espacios, o alguna combinación de los mismos.

Contando Elementos Promotor


Considere el archivo grape_promoters.txt , que contiene en cada línea 1000bp aguas arriba de las regiones génicas en el
genoma de Vitis vinifera:
II.9_15_PY_99_2_Grape_Promotores

Estas columnas no están separadas por un carácter tabulador, sino por un número variable de espacios.
Los motivos promotores son pequeños patrones de ADN cercanos a las secuencias génicas a las que se une la maquinaria celular
para ayudar a iniciar el proceso de transcripción génica. Por ejemplo, la proteína ABF se une al patrón de ADN “CACGTGGC”
si está cerca de un gen en algunas plantas. Algunos motivos son flexibles y pueden describirse mediante expresiones regulares; la
proteína GATA se une a cualquier secuencia de ADN corta que coincida con el patrón “[AT] GATA [GA]” . Se desea
analizar las regiones aguas arriba de V. vinifera y contar para cada una el número de ocurrencias del motivo GATA. Nuestra salida
debería verse así:
II.9_16_py_99_3_ex_out

Después de importar los módulos io y re , primero escribiremos una función count_motif () que toma dos
parámetros: primero una secuencia en la que contar coincidencias de motivos, y segundo un motivo para buscar. Hay una variedad
de formas en las que podríamos codificar esta función, pero una solución simple será usar re.split () para dividir la
secuencia en la expresión regular del motivo; el número de motivos será entonces la longitud del resultado menos uno (porque si no
se encuentran motivos, no se hará división; si se encuentra uno, se hará una división, y así sucesivamente).
Con esta función escrita y probada, podemos abrir el archivo usando io.open () y bucle sobre cada línea, llamando a la
función count_motif () en cada secuencia con el motivo r "[AT] GATA [GA]” . Debido a que las columnas están
separadas con un número variable de espacios en lugar de una sola tabulación o caracteres de espacio, usaremos
re.split () para dividir cada línea en pedazos.
Primero, escribiremos y probaremos la función que cuenta motivos y ofrece un ejemplo de uso, donde el número de coincidencias
debe ser dos.
alt

A continuación podemos terminar el programa, usando re.split () para procesar cada línea.
alt

Cuando se ejecuta, este sencillo programa ( grape_count_gata.py ) produce la salida deseada anteriormente. Debido a que
la salida se envía a la salida estándar, podemos filtrar aún más los resultados a través de las utilidades de línea de comandos
sort y head para identificar qué promotores tienen más probabilidades de ser encontrados por GATA:
. /grape_count_gata.py | ordenar -k2,2nr | cabeza -n 10 .

Ejercicios
1. Escribe una función llamada reverse_complements () que tome un parámetro de secuencia de ADN y devuelva su
complemento inverso (es decir, invierte la cadena, cambia A y T, y cambia G y C).
2. Las secuencias de ADN que están destinadas a convertirse en proteínas son “leídas” por la maquinaria celular en uno de los seis
“marcos de lectura” con longitudes que son múltiplos de tres. Los tres primeros se derivan de la propia secuencia, comenzando
en el índice 0 , índice 1 e índice 2 ; los tres primeros marcos de lectura de “ACTAGACG” son “ACTAGACG”
, “CTAGAC” y “TAGACG” .

2.9.4 https://espanol.libretexts.org/@go/page/55125
Para derivar los marcos de lectura tres, cuatro y cinco, primero complementamos de forma inversa la secuencia (
“CGTCTAGT” ) y luego producimos marcos a partir de los índices 0 , 1 y 2 , dando como resultado “CGTCTA” ,
“GTCTAG” y “TCTAGT” .
Usando la función reverse_complements () desde arriba (y potencialmente la función get_windows () del
capítulo 18, “Funciones de Python”), escriba una función seq_to_six_frames () que tome una secuencia de ADN
como parámetro y devuelva una lista de los seis marcos de lectura (como cadenas).
3. Escribe una función llamada longest_non_stop () que tome una secuencia de ADN como parámetro y devuelva la
secuencia de aminoácidos más larga que se pueda producir a partir de ella. Esto se hace generando los seis marcos de lectura a
partir de la secuencia, convirtiendo cada uno en una secuencia de aminoácidos (probablemente usando la función
dna_to_aa () del capítulo 20, “Diccionarios”), y luego recortando las secuencias resultantes al primer codón de
“parada” ( “*” ) si hay alguno.
En la secuencia de ADN seq = “AGCTATAGGAAGATATAGACGATTAGAC” , por ejemplo, las seis traducciones son
SY*EDRRLD (Frame 1), ATRKIDD* (Frame 2), LLGR*TIR (Frame 3), V*SSIFLVA (Frame 4),
SNRLSS** (Frame 5), y LIVYLPSS (Frame 6). En orden de longitudes, las posibilidades son así “V” , “SY” ,
“LLGR” , “SNRLSS” , “ATRKIDD” y “LIVYLPSS” . Como resultado, longest_non_stop (seq)
debería devolver “LIVYLPSS” .
4. Modifique el programa grape_count_gata.py , llamándolo motifs_count.py para que pueda leer el nombre
del archivo y los motivos a procesar (este último como una lista separada por comas) en sys.argv . Cuando se ejecuta
como
. /motifs_count.py grape_promoters.txt [AT] GATA [GA], [CGT] ACGTG [GT] [AC],
TTGAC
, la salida debe tener el siguiente aspecto: II.9_19_py_99_6_motifs_ex_out
5. El genotipado por secuenciación (GBS) es un método para identificar variantes genéticas en una muestra de ADN dividiendo
primero los cromosomas en ubicaciones pseudoaleatorias mediante la aplicación de enzimas de restricción y luego
secuenciando lecturas cortas de los extremos de los fragmentos resultantes: alt En lo anterior, líneas negras representan la
secuencia de ADN de entrada, las líneas rojas son sitios de corte y las líneas verdes representan la salida de la máquina de
secuenciación. (Se han hecho algunas simplificaciones a la figura anterior en aras de la claridad.) El resultado es una
profundidad mucho mayor de secuenciación en menos ubicaciones, lo que puede ser útil en una variedad de contextos,
incluyendo la búsqueda de variantes genéticas entre diferentes versiones del mismo cromosoma (lo que requiere muchas
lecturas de la misma ubicación para confirmar estadísticamente esas variantes).
Las enzimas de restricción cortan en base a patrones; por ejemplo, la enzima aPEK1 corta moléculas de ADN en el patrón
“GC [AT] GC” . Con base en los patrones reconocidos, algunas enzimas son “cortadoras frecuentes” y otras no; los
cortadores más frecuentes muestran más ubicaciones en un genoma pero sacrifican la profundidad de secuenciación. Por esta
razón, los investigadores suelen querer conocer, dado un patrón enzimático y una secuencia genómica, la distribución de las
longitudes de los fragmentos que resultarán.
Escribe una función gbs_cut () que tome una secuencia de ADN, un patrón de expresión regular y un “tamaño de bin”.
Debe devolver las longitudes de secuencia de mapeo de un diccionario, redondeadas hacia abajo al tamaño de bin más cercano,
al número de fragmentos producidos por el cortador en ese bin. Como ejemplo,
“AAAAGCAGCAAAAAAAAGCTGCAAGCAGCAAAAA " cuando se procesa con “GC [AT] GC " produce tamaños
de fragmento de 4, 6, 2 y 5. Si se agrupa en un tamaño de bin de 3, el diccionario tendría las claves 0 , 3 y 6 , y los
valores 1 , 1 y 2 .

1. En realidad, devuelve una tupla, que funciona de manera muy parecida a una lista pero es inmutable. Las tuplas se tratan
brevemente en el capítulo 15.

This page titled 2.9: Knick-knacks bioinformáticos y expresiones regulares is shared under a CC BY-NC-SA license and was authored, remixed,
and/or curated by Shawn T. O’Neil (OSU Press) .

2.9.5 https://espanol.libretexts.org/@go/page/55125
2.10: Variables y Alcance
En el capítulo 18, “Funciones de Python”, aprendimos que una función debe usar solo variables que hayan sido definidas dentro del
bloque de la función, o pasadas explícitamente como parámetros (Regla 1). Esta regla trabaja en estrecha colaboración con nuestra
definición de una variable en Python, como un nombre que hace referencia a algunos datos.
Este es un concepto potente, que nos permite pasar parámetros a funciones y obtener valores devueltos. En Python, una sola pieza
de datos puede ser referenciada por múltiples variables. Las variables de parámetro son nuevos nombres para los mismos datos.
Podemos explorar este concepto con un ejemplo sencillo.
II.10_1_py_99_7_vars_refs_ex

En esta muestra de código, hemos creado una sola lista, pero estamos asociando con ella dos nombres de variables, nums y
numsb . Cuando modificamos la lista a través de un nombre, el cambio se refleja a través del otro (solo podemos cambiar los
datos porque las listas son mutables, pero incluso los datos inmutables pueden ser referidos por múltiples nombres).
Lo mismo sucede cuando usamos un parámetro en una función. Volvamos a visitar la función gc_content () , que hace uso
de una función base_composition () que escribimos (pero dejar fuera aquí para mayor claridad). En este listado, todos
los nombres de variables que pertenecen a la función están resaltados en rojo, mientras que otros nombres de variables “globales”
se resaltan en púrpura y subrayados con una línea discontinua. De acuerdo con la Regla 1, no deberíamos ver ninguna variable
morada/subrayada en la definición de la función.
alt
alt

Fuera de la función, los datos de cadena “ACCCTAGACTG” se asignaron a la variable seq3 , pero dentro de la función, se
asignó a la variable seq . Dentro de la función, el valor de datos 0.54 fue referenciado por la variable gc , y afuera fue
referenciado por seq3_gc . [1]
Sabemos que podríamos acceder a la variable seq3 desde dentro de la función, aunque no debiéramos Pero, ¿y al revés?
¿Podríamos acceder a la variable gc , que se definió dentro de la función, fuera de la función?
alt

De hecho, no podemos. La línea anterior print (gc) , incluso después del cálculo de seq3_gc , produciría un
nameError: name 'gc' no está definido . Este error se produce porque las variables tienen alcance, y el alcance
de la variable gc es limitado, desde el momento en que se define hasta el final del bloque de función en el que se define (todas
las variables resaltadas en rojo comparten esta característica). Las variables con alcance limitado se denominan variables locales.
El alcance de una variable es el contexto en el que se puede usar. Define “cuánto tiempo vive la variable” o “dónde existe la
variable”. Una variable con alcance limitado, como en la ejecución de una función, se denomina variable local.
Después de que termine la llamada a la función, la imagen se vería más así:
alt

Eventualmente, el recolector de basura notará que ninguna variable se refiere a los datos 2 , 4 y 11 , y los limpiará de la
RAM para dejar espacio para nuevos datos.
Debido a que scope habla de variables, poco tiene que ver con los datos a los que hace referencia una variable, excepto decir que
los datos son basura recolectados cuando no hay variables que se refieran a esos datos en ningún ámbito actual. Sin embargo,
debido a que la variable gc no está en el alcance después de que termina la función, no podemos usarla fuera del bloque de
función.
Una de las características particularmente interesantes y poderosas de las variables locales es que existen independientemente de
cualquier otra variable que pueda existir en ese momento con el mismo nombre (a veces se dice que “sombrean” otras variables con
el mismo nombre). Considera el siguiente código:
alt

¿Qué opinas que imprimirá la última línea, print (gc) ? Si adivinaste


“Medida de bases G y C en una secuencia” , ¡tienes razón! Aunque la función gc_content () define
su propia variable gc , debido a que esta variable es local, no “pisa” la variable gc externa. En resumen:

2.10.1 https://espanol.libretexts.org/@go/page/55115
Las variables definidas dentro de una función (y los parámetros de una función) son locales a la llamada a la función.
Estas variables locales “sombrean” cualquier otra variable que pueda existir durante la ejecución de la función.
Cuando termina la llamada a la función, se olvidan las variables locales.
Una de las lecciones importantes del alcance es saber cuándo podría generar problemas. Esto es más fácil de ver si fuéramos a
comparar cómo funciona el scoping con otro lenguaje, C++ en este caso. En C++, las variables se declaran con sus tipos, y las
variables declaradas dentro de un bloque de sentencia if (o cualquier otro bloque) también son locales a ese bloque. Esto significa
que se van fuera de alcance cuando termina el bloque. Si tratáramos de utilizarlos fuera del bloque en el que fueron declarados, se
produciría un error.
En Python, por otro lado, las variables declaradas en sentencias if, bloques for-loop y bloques while loop no son variables locales, y
permanecen en el alcance fuera del bloque. Así decimos que C++ tiene alcance de “nivel de bloque”, mientras que Python usa solo
el alcance de “nivel de función”. Los corchetes en la siguiente figura ilustran el alcance de algunas variables en código equivalente
de C++ y Python. Debido a que C++ usa alcance a nivel de bloque, y x e y se declaran dentro de bloques, no podemos
acceder a su contenido fuera de esos bloques.
II.10_7_amboth_scope

Esto puede parecer una ventaja para Python, pero en este caso pensamos que ese C++ puede tenerlo bien.
Después de todo, si necesitábamos que las variables y y z fueran accesibles
después de los if-blocks en el ejemplo de C++, podríamos haberlas declarado junto
con x .
Incluso podríamos haberles dado un valor predeterminado para mantener usando declaraciones como int y = -1 e
int z = -1 ; si estas variables aún mantienen -1 después de ejecutar los if-blocks, entonces sabemos que esas líneas
internas no se ejecutaron. Esto sería especialmente útil si el valor de x no se fijara en cinco como está aquí, sino que depende de
los datos de entrada.
En Python, por otro lado, código como este podría causar un problema. Si la variable x depende de los datos de entrada,
entonces es posible que las variables y y z nunca se establezcan en absoluto, lo que lleva a un eventual NameError
cuando posteriormente se accede a ellas. Ciertamente no deberíamos considerar que ningún programa que a veces produzca un
error, dependiendo de los datos de entrada, ¡es muy bueno!
Probablemente la mejor manera de evitar este tipo de problema es pretender que Python usa alcance a nivel de bloque, declarando
variables en el bloque/nivel más amplio en el que se usarán. Ninguno es un buen valor predeterminado que luego podemos
verificar si queremos ejecutar condicionalmente código posterior sobre la base de si las variables realmente se establecieron en algo
útil:
II.10_8_PY_103_Ámbito Robusto

Este código no se bloqueará, sin importar el valor de x . Establecer los valores iniciales para estas variables en Ninguno
antes de los if-blocks también proporciona un recordatorio y una pista visual de que estas variables están destinadas a ser utilizadas
fuera de los bloques if anidados, y que debemos verificar su contenido antes de usarlas más tarde.
C++ y otros lenguajes de ámbito de nivel de bloque fomentan así las variables de “corta duración”, lo cual es un buen mantra de
programación. Definir variables solo cuando sea necesario y usarlas por el menor tiempo posible ayuda a producir código más
claro, más modular e incluso más eficiente (porque las variables de corta duración permiten que el recolector de basura limpie más
datos). En cierto modo, romper la Regla 1 de funciones es un tipo similar de abuso de alcance variable.
II.10_9_Espaguetis

A los programadores principiantes a menudo les resulta más fácil evitar estas convenciones estableciendo una gran cantidad de
variables cerca del inicio de un programa y luego accediendo y configurándolas en varios puntos a lo largo del programa. Esto a
veces se llama coloquialmente “código de espagueti”, porque las conexiones de larga distancia creadas entre las diferentes áreas de
código se asemejan a espaguetis Sin embargo, rara vez esta estrategia da sus frutos.
Como se discute en el capítulo 25, “Algoritmos y estructuras de datos”, las variables locales y conceptos similares nos permiten
resolver problemas complejos de maneras fascinantes y elegantes.

2.10.2 https://espanol.libretexts.org/@go/page/55115
Ejercicios
1. Cree un diagrama RAM/variable que ilustre el siguiente segmento de código, y utilícelo para explicar la salida impresa.
II.10_10_py_103_2_ram_diagram_ex1

2. Cree un diagrama RAM/variable que ilustre el siguiente segmento de código, y utilícelo para explicar la salida impresa.
II.10_11_py_103_3_ram_diagram_ex2

3. Paso a través del siguiente código, y crear un diagrama RAM/variable para cada vez que se alcanza un “#point”. (Debido a que
#point 1 se alcanza dos veces, eso significará cuatro diagramas.) Se puede suponer que los datos nunca se recolectan basura,
sino que las variables locales sí desaparecen. II.10_12_py_103_4_ram_diagram_ex3
4. ¿Qué devuelve una función en Python si no se especifica ninguna instrucción return?

1. Muchos lenguajes funcionan como Python y permiten que diferentes variables se refieran a los mismos datos, pero en algunos
lenguajes esto es imposible o menos común. En algunos idiomas, todos los datos son inmutables, lo que hace que el punto sea
discutible. R es un ejemplo donde la mayoría de las variables terminan refiriéndose a datos únicos; vea el recuadro en el
capítulo 29, “Funciones R”, para una comparación más detallada.

This page titled 2.10: Variables y Alcance is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

2.10.3 https://espanol.libretexts.org/@go/page/55115
2.11: Objetos y Clases
Este libro, al presentar inicialmente Python, mencionó algunas de las características del lenguaje, como su énfasis en “una mejor
manera” y código legible. Python también proporciona bastante funcionalidad incorporada a través de módulos importables como
sys , re y subprocess .
Python proporciona otra ventaja: está naturalmente “orientada a objetos”, aunque aún no hayamos discutido este punto. Aunque
existen paradigmas en competencia para la mejor manera de diseñar programas, el paradigma orientado a objetos se usa
comúnmente para la ingeniería de software y la gestión de grandes proyectos. [1] Sin embargo, incluso para programas pequeños,
los conceptos básicos de orientación a objetos pueden facilitar la tarea de programación.
Un objeto, prácticamente hablando, es un segmento de memoria (RAM) que hace referencia tanto a datos (referidos por variables
de instancia) de diversos tipos como a funciones asociadas que puede operar con los datos. [2] Las funciones que pertenecen a
objetos se denominan métodos. Dicho de otra manera, un método es una función que está asociada a un objeto, y una variable de
instancia es una variable que pertenece a un objeto.
En Python, los objetos son los datos que hemos estado asociando con variables. Cuáles son los métodos, cómo funcionan y cuáles
son los datos (por ejemplo, una lista de números, diccionario de cadenas, etc.) son definidos por una clase: la colección de código
que sirve como “blueprint” para objetos de ese tipo y cómo funcionan.
II.11_1_object_ilustración

Así, la clase (al igual que el blueprint para una casa) define la estructura de los objetos, pero las variables de instancia de cada
objeto pueden referirse a diferentes elementos de datos siempre que se ajusten a la estructura definida (al igual que cómo pueden
vivir diferentes familias en casas construidas a partir del mismo blueprint). En Python, cada dato que encontramos de forma
rutinaria constituye un objeto. Cada tipo de datos que hemos tratado hasta ahora (listas, cadenas, diccionarios, etc.) tiene una
definición de clase, un plano, que lo define. Por ejemplo, las listas tienen datos (números, cadenas o cualquier otro tipo) y métodos
como .sort () y .append () .
alt

En cierto sentido, llamar a métodos de objeto hace una solicitud del objeto: nums_list.sort () podría interpretarse como
“objeto al que hace referencia nums_list , por favor ejecute su método sort () ”. Al recibir este mensaje, el objeto
reordenará sus datos. [3]

Creación de nuevas clases


Las definiciones para las clases de Python son solo bloques de código, indicados por un nivel adicional de sangría (como bloques
de función, bloques de instrucción if y bloques de bucle). Cada definición de clase requiere tres cosas, dos de las cuales ya estamos
familiarizados con:
1. Métodos (funciones) que pertenecen a objetos de la clase.
2. Variables de instancia referidas a datos.
3. Un método especial llamado constructor. Este método se llamará automáticamente cada vez que se cree un nuevo objeto de la
clase, y debe tener el nombre __init__ .
Una peculiaridad de Python es que cada método de un objeto debe tomar como primer argumento un parámetro llamado self ,
[4]
que usamos para acceder a las variables de instancia. Comencemos definiendo una clase, Gene (los nombres de clase
tradicionalmente comienzan con una letra mayúscula): cada objeto Gene tendrá (1) un id (cadena) y (2) una
secuencia (también una cadena). Al crear un objeto Gene , debemos definir su id y secuencia pasándolos como
parámetros al método __init__ .
Fuera del bloque que define la clase, podemos hacer uso de ella para crear e interactuar con objetos Gene .
II.11_3_PY_104_GENE_CLASS_1

(Normalmente no incluimos llamadas print () en el constructor; lo estamos haciendo aquí solo para aclarar el proceso de
creación del objeto.) Ejecutándose lo anterior:
II.11_4_py_104_w_gene_class_1_out

2.11.1 https://espanol.libretexts.org/@go/page/55091
Tenga en cuenta que aunque cada método (incluido el constructor) toma como su primer parámetro self , no especificamos este
parámetro al llamar a métodos para los objetos. (Por ejemplo, .print_id () toma un parámetro self que no
especificamos al llamarlo.) Es bastante común olvidar incluir este autoparámetro “implícito”; si lo haces, obtendrás un
error como TypeError: print_id () no toma argumentos (1 dado) , porque el número de parámetros
tomados por el método no coincide con el número dado cuando se llama. Además, cualquier parámetro enviado a la función de
creación (Gene (“AY342", “CATTGAC”) ) se pasa al constructor (
__init__ (self, creationid, creationseq) ).
¿Qué es el auto ? El parámetro self es una variable que se le da al método para que el objeto pueda referirse a “sí mismo”.
Al igual que otras personas podrían referirse a ti por tu nombre, podrías referirte a ti mismo como “yo”, como en “yo: recuerda
volver a enviar ese manuscrito mañana”.
Curiosamente, en cierto sentido, los métodos definidos para las clases están rompiendo la primera regla de funciones: ¡están
accediendo a variables que no se pasan como parámetros! Esto en realidad está bien. Todo el punto de los objetos es que contienen
funciones y datos a los que siempre se puede suponer que las funciones tienen acceso directo.
Continuemos nuestro ejemplo agregando un método que calcula el contenido GC de la variable de instancia self.sequence .
Este método necesita ser incluido en el bloque que define la clase; observe que un método que pertenece a un objeto puede llamar a
otro método que pertenece a sí mismo, por lo que podemos calcular el contenido de GC como un par de métodos, al igual que lo
hicimos con funciones simples:
II.11_5_PY_105_GENE_CLASS_GC

Dando como resultado la salida:


II.11_6_PY_105_2_GENE_CLASS_GC_Out

También puede ser útil escribir métodos que nos permitan obtener y establecer las variables de instancia de un objeto. Podríamos
agregar a nuestros métodos de definición de clase para obtener y establecer la secuencia, por ejemplo, haciendo que los métodos se
refieran a la variable de instancia self.seq .
II.11_7_py_106_gene_class_getter_setter

Podríamos hacer uso de esta funcionalidad añadida más adelante en nuestro código con una línea como
print (“la secuencia del gen A es" + genea.get_seq ()) o
genea.set_seq (“ACTAGGGG”) .
Aunque los métodos pueden devolver valores (como con .base_composition () y .gc_content () ) y realizar
alguna acción que modifique el objeto (como con .set_seq ()) , el principio de separación comando-consulta establece que
no deben hacer ambas cosas a menos que sea absolutamente necesario.
¿Es posible que modifiquemos las variables de instancia de un objeto directamente? Tiene sentido que podamos; porque el nombre
del objeto génico por sí mismo es self y establece su secuencia vía self.sequence , deberíamos poder establecer la
secuencia del objeto génico usando nuestro nombre para ello, GeneA . De hecho, Genea.Sequence = “ACTAGGGG”
tendría el mismo resultado que llamar a Genea.set_seq (“ACTAGGGG”) , como se definió anteriormente.
Entonces, ¿por qué podríamos querer usar los métodos “getter” y “setter” en lugar de modificar o leer directamente las variables de
instancia de un objeto? La diferencia está un poco relacionada con la cortesía, si no con el objeto en sí, entonces con quien escribió
el código para la clase. Mediante el uso de métodos, estamos solicitando que el objeto cambie sus datos de secuencia, mientras que
establecer directamente las variables de instancia solo llega y lo cambia, ¡lo cual es un poco como realizar una cirugía a corazón
abierto sin el permiso del paciente!
Esta es una distinción sutil, pero es considerado un negocio serio para muchos programadores. Para ver por qué, supongamos que
hay muchos métodos que no funcionarán en absoluto en las secuencias de ARN, por lo que debemos asegurarnos de que la variable
de instancia de secuencia nunca tenga ningún carácter U en ella. En este caso, podríamos hacer que el método
.set_seq () decida si acepta o no la secuencia:
II.11_8_py_107_gene_class_getter_setter_check

Python tiene una declaración assert para este tipo de comprobación de errores. Al igual que una función, toma dos
parámetros, pero a diferencia de una función, no se permiten paréntesis.

2.11.2 https://espanol.libretexts.org/@go/page/55091
II.11_9_py_108_gene_class_getter_setter_assert

Al usar un assert, si la comprobación no se evalúa como True , entonces el programa se detendrá y reportará el error
especificado. El código completo para este ejemplo se puede encontrar en el archivo gene_class.py .
Usar métodos cuando se trabaja con objetos se trata de encapsulación y dejar que los objetos hagan el mayor trabajo posible. De
esa manera, pueden asegurar resultados correctos para que tú (o con quien estés compartiendo código, que podría ser “tú futuro”)
no tengas que hacerlo. Los objetos pueden tener cualquier número de variables de instancia, y los métodos pueden acceder y
modificarlas, pero es una buena idea asegurarse de que todas las variables de instancia se dejen en un estado coherente para un
objeto dado. Por ejemplo, si un objeto Gene tiene una variable de instancia para la secuencia, y otro que contiene su contenido
de GC, entonces el contenido de GC debe actualizarse siempre que la secuencia sea. Aún mejor es computar tales cantidades según
sea necesario, como hicimos anteriormente. [5]
Los pasos para escribir una definición de clase son los siguientes:
1. Decidir qué concepto o entidad representarán los objetos de esa clase, así como qué datos (variables de instancia) y métodos
(funciones) tendrán.
2. Crea un método constructor y haz que inicialice todas las variables de instancia, ya sea con parámetros pasados al constructor, o
como vacío (por ejemplo, algo como self.id = “” o self.go_terms = list () ). Aunque las variables de
instancia pueden ser creadas por cualquier método, tenerlas todas inicializadas en el constructor proporciona una referencia
visual rápida para referirse a la hora de codificar.
3. Escriba métodos que establezcan u obtengan las variables de instancia, calcule cálculos, llame a otros métodos o funciones, y
así sucesivamente. ¡No olvides el parámetro de auto !

Ejercicios
1. Crear un programa objects_test.py que defina y utilice una clase. La clase puede ser lo que quieras, pero debe tener
al menos dos métodos (distintos del constructor) y al menos dos variables de instancia. Uno de los métodos debe ser una
“acción” que se le puede pedir a un objeto de esa clase que realice, y el otro debe devolver una “respuesta”.
Crea una instancia de tu clase en al menos dos objetos y prueba tus métodos en ellos.
2. Una vez definidas, las clases (y los objetos de esos tipos) se pueden usar en cualquier lugar, incluyendo otras definiciones de
clase. Escribe dos definiciones de clase, una de las cuales contiene múltiples instancias de la otra. Por ejemplo, las variables de
instancia en un objeto House podrían referirse a varios objetos Room diferentes. (Para un ejemplo más biológicamente
inspirado, un objeto Gene podría tener un self.exons que contiene una lista de objetos Exon .)
El siguiente ejemplo ilustra esto más a fondo, pero tener algo de práctica primero será beneficioso.
3. Si las clases implementan algunos métodos especiales, entonces podemos comparar objetos de esos tipos con == , < , y los
otros operadores de comparación.
Al comparar dos objetos Gene , por ejemplo, podríamos decir que son iguales si sus secuencias son iguales, y GeneA es
menor que GeneB si Genea.Seq < GeneB.seq . Así podemos agregar un método especial __eq__ () , el cual,
dado el yo habitual y una referencia a otro objeto del mismo tipo llamado otro , devuelve True si consideraríamos los
dos iguales y False de otra manera: También II.11_10_py_107_2_comparitor_métodos_1 podemos implementar un __lt__ ()
método para “menos que”: II.11_11_py_107_3_comparitor_métodos_2 Con estos, Python puede averiguar cómo comparar los objetos Gene
con < y == . Las otras comparaciones se pueden habilitar definiendo __le__ () (for <= ), __gt__ () (for
> ), __ge__ () (for >= ) y __ne__ () (for ! =).
Finalmente, si tenemos una lista de objetos
imagen Gene genes_list que definen estos comparadores, entonces Python
puede ordenar de acuerdo a nuestros criterios de comparación con genes_list.sort () y
ordenado (genes_list) .
Explore estos conceptos definiendo su propio tipo de datos ordenados, implementando __eq__ () , __lt__ () y los
otros métodos de comparación. Compara dos objetos de esos tipos con los operadores de comparación estándar y ordena una
lista de ellos. También puede intentar implementar un método __repr__ () , que debería devolver una cadena que
represente el objeto, habilitando print () (como en print (GeneA) ).

2.11.3 https://espanol.libretexts.org/@go/page/55091
Contando SNPs
Resulta que se pueden definir múltiples clases que interactúan entre sí: las variables de instancia de una clase personalizada pueden
referirse a tipos de objetos personalizados. Considere el archivo trio.subset.vcf , un archivo VCF (formato de llamada
variante) para describir polimorfismos de un solo nucleótido (SNP, pronunciado “snips”) a través de individuos en un grupo o
población. En este caso, el archivo representa un muestreo aleatorio de SNP de tres personas, una madre, un padre y su hija, en
comparación con el genoma humano de referencia. [6]
II.11_13_PY_108_2_TRIO_Archivo

Este archivo contiene una variedad de información, incluyendo líneas de encabezado que comienzan con # que describen parte
de la codificación que se encuentra en el archivo. Las columnas 1, 2, 3, 4 y 5 representan el número de cromosomas del SNP, la
posición del SNP en el cromosoma, la ID del SNP (si se ha descrito previamente en poblaciones humanas), la base presente en la
referencia en esa posición y una base alternativa encontrada en uno de los tres miembros de la familia, respectivamente. Otras
columnas describen diversa información; este archivo sigue el formato “VCF 4.0”, que se describe con más detalle en
http://www.1000genomes.org/node/101. Algunas columnas contienen un . , que indica que la información no está presente; en el
caso de la columna ID, éstas representan nuevos polimorfismos identificados en este trío.
Para este ejemplo, nos interesan las cinco primeras columnas, y las preguntas principales son:
¿Cuántas transiciones (A vs. G o C vs. T) hay dentro de los datos para cada cromosoma?
¿Cuántas transversiones (cualquier otra cosa) hay dentro de los datos para cada cromosoma?
Es posible que en el futuro tengamos otras preguntas sobre transiciones y transversiones por cromosoma. Para responder a las
preguntas anteriores, y para prepararnos para las futuras, comenzaremos definiendo algunas clases para representar a estas diversas
entidades. Este ejemplo demostrará ser un poco más largo que otros que hemos estudiado, en parte porque nos permite ilustrar
respondiendo múltiples preguntas usando la misma base de código si hacemos algún trabajo extra por adelantado, pero también
porque los diseños orientados a objetos tienden a resultar en significativamente más código (una crítica común de usar clases y
objetos).

Clase SNP
Un objeto SNP contendrá información relevante sobre una sola línea sin encabezado en el archivo VCF. Las variables de instancia
incluirían el alelo de referencia (una cadena de un carácter, por ejemplo, “A” ), el alelo alternativo (una cadena de un carácter,
por ejemplo, “G” ), el nombre del cromosoma en el que existe (una cadena, por ejemplo, “1" ), la posición de referencia (un
número entero, por ejemplo, 799739 ), y el ID del SNP (por ejemplo, “rs57181708" o “.” ). Debido a que vamos a
analizar líneas una a la vez, toda esta información se puede proporcionar en el constructor.
Los objetos SNP deberían poder responder preguntas: .is_transition () debería devolver True si el SNP es una
transición y False si no, mirando las dos variables de instancia de alelo. Del mismo modo, .is_transversion ()
debería devolver True si el SNP es una transversión y False en caso contrario.

Clase Cromosómica
Un objeto cromosómico contendrá datos para un cromosoma individual, incluido el nombre del cromosoma (una cadena, por
ejemplo, “1" ) y todos los objetos SNP que se encuentran en ese cromosoma. Podríamos almacenar los objetos SNP en una lista,
pero también podríamos considerar almacenarlos en un diccionario, que asigna ubicaciones SNP (enteros) a los objetos SNP.
Entonces no solo podemos obtener acceso a la lista de SNPs (usando el método .values () del diccionario) o la lista de
ubicaciones (usando el método .keys () del diccionario), sino que también, dada cualquier ubicación, podemos obtener
acceso al SNP en esa ubicación. (Incluso podemos usar .has_key () para determinar si existe un SNP en una ubicación
determinada.)
alt

El constructor de cromosomas inicializará el nombre del cromosoma como self.chrname , pero el diccionario snps
comenzará como vacío.
Un objeto Cromosoma también debería ser capaz de responder preguntas: .count_transition () debería decirnos
el número de SNP de transición, y .count_transversion () debería devolver el número de SNP de transversión.
También vamos a necesitar alguna manera de agregar un objeto SNP al diccionario SNP de un cromosoma porque comienza vacío.

2.11.4 https://espanol.libretexts.org/@go/page/55091
Lo lograremos con un método.add_snp () , que tomará toda la información de un SNP, creará el nuevo objeto SNP y lo
agregará al diccionario. Si ya existe un SNP en esa ubicación, debería ocurrir un error, porque nuestro programa no debería aceptar
archivos VCF que tengan múltiples filas con la misma posición para el mismo cromosoma.
Para la estrategia general, una vez que tengamos nuestras clases definidas (y depuradas), la parte “ejecutable” de nuestro programa
será bastante simple: necesitaremos mantener una colección de objetos Cromosoma con los que podamos interactuar para
agregar SNP, y al final simplemente recorreremos estos y preguntaremos a cada uno cuántos transiciones y transversiones que
tiene. También tiene sentido mantener estos objetos cromosómicos en un diccionario, siendo las claves los nombres de los
cromosomas (cadenas) y los valores son los objetos Cromosómicos . Llamaremos a este diccionario
chrnames_to_chrs .
II.11_15_Chrnames_to_chrs_fig

A medida que recorremos cada línea de entrada (leyendo un nombre de archivo dado en sys.argv [1] ), lo dividiremos y
verificaremos si el nombre del cromosoma está en el diccionario con .has_key () . Si es así, le pediremos al objeto en esa
ranura que agregue un SNP con .add_snp () . Si no, entonces primero tendremos que crear un nuevo objeto Cromosoma
, pedirle a .add_snp () , y finalmente agregarlo al diccionario. Por supuesto, todo esto debería suceder solo para líneas que
no sean de cabecera.
Empezaremos con la clase SNP, y luego la clase Cromosoma . Aunque es difícil de mostrar aquí, es una buena idea trabajar y
depurar cada método a su vez (con declaraciones ocasionales print () ), comenzando por los constructores. Debido a que un
SNP es solo un SNP si el alelo de referencia y alternativo difieren, afirmaremos esta condición en el constructor para que se
produzca un error si alguna vez intentamos crear un SNP no polimórfico (que en realidad no sería un SNP en absoluto).
II.11_16_PY_109_SNP_EX1

Observe el atajo que tomamos en el código anterior para el método .is_transversion () , que llama al método
.is_transition () y devuelve la respuesta opuesta. Este tipo de codificación “atajo” tiene sus beneficios y desventajas.
Un beneficio es que podemos reutilizar métodos en lugar de copiar y pegar para producir muchos fragmentos similares de código,
lo que reduce el área de superficie potencial para que ocurran errores. Una desventaja es que tenemos que tener más cuidado, en
este caso, hemos tenido que asegurarnos de que los alelos difieran (a través de la afirmación en el constructor), por lo que
un SNP debe ser una transición o una transversión. (¿Es esto realmente cierto? ¿Y si alguien intentara crear un objeto SNP con
caracteres que no sean ADN? Siempre es prudente considerar formas en que el código podría ser inadvertidamente mal utilizado.)
Lo anterior muestra el inicio del script y la clase SNP; código como este podría probarse con solo unas pocas líneas:
II.11_17_PY_110_SNP_EX2

Aunque en última instancia no dejaremos estas líneas de prueba adentro, proporcionan una buena verificación de cordura para el
código. Si estas comprobaciones estuvieran envueltas en una función que pudiera llamarse cada vez que hagamos cambios en el
código, tendríamos lo que se conoce como prueba unitaria, o una colección de código (a menudo una o más funciones), con el
propósito específico de probar la funcionalidad de otro código para su corrección. [7] Estos pueden ser especialmente útiles ya que
el código cambia a lo largo del tiempo.
Sigamos con la clase Cromosoma . Tenga en cuenta que el método.add_snp () contiene afirmaciones de que la
ubicación del SNP no es un duplicado y que el nombre del cromosoma para el nuevo SNP coincide con el self.chrname del
cromosoma.
II.11_18_PY_111_SNP_EX3

Ahora podemos escribir los métodos para .count_transiciones () y .count_transversion () . Debido a


que nos hemos asegurado de que cada objeto SNP sea una transición o una transversión, y no se dupliquen ubicaciones dentro de
un cromosoma, el método .count_transversion () puede hacer uso directo del método
.count_transition () y el número total de SNP almacenados a través de
len (self. locations_to_snps) . (Alternativamente, podríamos hacer un count_transversion () que
opere de manera similar a count_transition () haciendo un bucle sobre todos los objetos SNP.)
II.11_19_PY_111_2_SNP_EX3

2.11.5 https://espanol.libretexts.org/@go/page/55091
El código de prueba correspondiente se encuentra a continuación. Aquí estamos usando declaraciones assert , pero también
podríamos usar líneas como print (chr1.count_transition ()) y asegurarnos de que la salida sea la esperada.
II.11_20_PY_112_SNP_EX4

Con las definiciones de clase creadas y depuradas, podemos escribir la parte “ejecutable” del programa, preocupada por analizar el
archivo de entrada (a partir de un nombre de archivo dado en sys.argv [1] ) e imprimir los resultados. Primero, la porción
de código que verifica si el usuario ha dado un nombre de archivo (y produce algún texto de ayuda si no) y lee los datos en.
Nuevamente, estamos almacenando una colección de objetos cromosómicos en un diccionario chrnames_to_chrs .
Para cada línea VCF, determinamos si ya existe un Cromosoma con ese nombre: si es así, le pedimos a ese objeto a
.add_snp () . Si no, creamos un nuevo objeto Cromosoma , se lo pedimos a .add_snp () , y lo agregamos al
diccionario.
II.11_21_PY_113_SNP_EX5

En la línea chr_obj = chrnames_to_chrs [chrname] anterior, estamos definiendo una variable que hace
referencia al objeto Cromosoma en el diccionario, y después de eso estamos pidiendo a ese objeto que agregue el SNP con
.add_snp () . Podríamos haber combinado estos dos con sintaxis como
chrnames_to_chrs [chrname] .add_snp () .
Finalmente, un pequeño bloque de código imprime los resultados haciendo un bucle sobre las claves del diccionario, accediendo a
cada objeto Cromosoma y preguntándole el número de transiciones y transversiones:
II.11_22_PY_114_SNP_EX6

Tendremos que eliminar o comentar el código de prueba (particularmente las pruebas que esperábamos que fallaran) para ver los
resultados. Pero una vez que hagamos eso, podemos ejecutar el programa (llamado snps_ex.py ).
II.11_23_PY_114_2_SNP_EX_Run

Lo que hemos creado aquí no es poca cosa, ¡con casi 150 líneas de código! Y sin embargo, cada pieza de código está encapsulada
de alguna manera; incluso el long for-loop representa el código para analizar el archivo de entrada y rellenar el diccionario
chrnames_to_chrs . Al nombrar claramente nuestras variables, métodos y clases podemos ver rápidamente lo que hace
cada entidad. Podemos razonar sobre este programa sin demasiada dificultad al más alto nivel de abstracción pero también
profundizar para entender cada pieza individualmente. Como beneficio, podemos reutilizar o adaptar fácilmente este código de una
manera poderosa agregando o modificando métodos.

Una extensión: Búsqueda de regiones densas en SNP


El recuento de transiciones y transversiones por cromosoma para este archivo VCF podría haberse logrado sin definir clases y
objetos. Pero una de las ventajas de dedicar algún tiempo a organizar el código por adelantado es que podemos responder más
fácilmente preguntas relacionadas sobre los mismos datos.
Supongamos que, habiendo determinado el número de transiciones y transversiones por cromosoma, ahora estamos interesados en
determinar la región más densa de SNP de cada cromosoma. Hay varias formas en las que podríamos definir la densidad de SNP,
pero elegiremos una fácil: dada una región desde las posiciones l a m, la densidad es el número de SNP que ocurren dentro de l y m
dividido por el tamaño de la región, m — l + 1, veces 1,000 (para SNP por 1,000 pares de bases).
II.11_24_SNP_densidad_cálculo

Para que un objeto Cromosoma pueda decirnos la región de mayor densidad, necesitará poder calcular la densidad para
cualquier región dada contando los SNP en esa región. Podemos comenzar agregando a la clase cromosómica un método que
compute la densidad de SNP entre dos posiciones l y m.
II.11_25_PY_114_3_SNP_Densidad_Región

Después de depurar este método y asegurar que funcione, podemos escribir un método que encuentre la región de mayor densidad.
Pero, ¿cómo debemos definir nuestras regiones? Digamos que queremos considerar regiones de 100 mil bases. Entonces podríamos
considerar las bases 1 a 100,000 como una región, 100,001 a 200,000 como una región, y así sucesivamente, hasta que el inicio de
la región considerada es más allá de la última ubicación del SNP. Podemos lograr esto con un bucle de tiempo. La estrategia será
mantener información sobre la región más densa encontrada hasta el momento (incluyendo su densidad así como la ubicación de
inicio y finalización), y actualizar esta respuesta según sea necesario en el bucle. [8]

2.11.6 https://espanol.libretexts.org/@go/page/55091
II.11_26_PY_114_4_SNP_densidad_bucle

En lo anterior, necesitábamos acceder a la posición del último SNP en el cromosoma (para que el código pudiera dejar de
considerar regiones más allá del último SNP). En lugar de escribir ese código directamente en el método, decidimos que debería ser
su propio método, y lo marcamos con un comentario “todo”. Entonces, también necesitamos agregar este método:
II.11_27_PY_114_5_SNP_Last_SNP_POS

En el código que imprime los resultados, podemos agregar la nueva llamada a .max_density (100000) para cada
cromosoma, e imprimir la información relevante.
II.11_28_PY_114_6_SNP_densidad_uso

Llamemos a nuestro nuevo snps_ex_density.py (canalizando el resultado a través de la columna -t para ver más
fácilmente el diseño de la columna separada por tabuladores):
II.11_29_py_114_7_snp_densidad_out

Nuevamente, ninguno de los métodos individuales o secciones de código son particularmente largos o complejos, pero juntos
representan un programa de análisis bastante sofisticado.

Resumen
Quizás encuentres que estos ejemplos usando clases y objetos para la resolución de problemas sean elegantes, o quizás no. Algunos
programadores piensan que este tipo de organización da como resultado un código demasiado detallado y complejo. Sin duda es
fácil llegar a ser demasiado ambicioso con la idea de clases y objetos. Crear clases personalizadas para cada pequeña cosa corre el
riesgo de confusión y molestias innecesarias. Al final, depende de cada programador decidir qué nivel de encapsulación es el
adecuado para el proyecto; para la mayoría de las personas, una buena separación de conceptos mediante el uso de clases es una
forma de arte que requiere práctica.
¿Cuándo deberías considerar crear una clase?
Cuando tienes muchos tipos diferentes de datos relacionados con el mismo concepto, y te gustaría mantenerlos organizados en
objetos individuales como variables de instancia.
Cuando tienes muchas funciones diferentes relacionadas con el mismo concepto, y te gustaría mantenerlas organizadas en
objetos individuales como métodos.
Cuando tienes un concepto que ahora es simple, pero sospechas que podría aumentar en complejidad en el futuro a medida que
le añades. Al igual que las funciones, las clases permiten que el código se reutilice, y es fácil agregar nuevos métodos y
variables de instancia a las clases cuando sea necesario.

Herencia y Polimorfismo
A pesar de esta discusión sobre los objetos, existen algunas características únicas del paradigma orientado a objetos que no hemos
cubierto pero que a veces se consideran integrales al tema. En particular, la mayoría de los lenguajes orientados a objetos (incluido
Python) admiten herencia y polimorfismo para objetos y clases.
La herencia es la idea de que algunos tipos de clases pueden representarse como casos especiales de otros tipos de clases.
Considere una clase que define una Secuencia , que podría tener variables de instancia para self.seq y self.id .
Las secuencias podrían ser capaces de reportar su longitud y, por lo tanto, podrían tener un método .length bp () ,
devolviendo len (self.seq) . También puede haber muchas otras operaciones que una Secuencia genérica podría
soportar, como .get_id () . Ahora, supongamos que queríamos implementar una clase openReadingFrame ; también
debería tener un self.id y un self.seq y poder reportar su .length bp () . Debido a que un objeto de este tipo
representaría un marco de lectura abierto, probablemente también debería tener un método .get_translation () que
devuelva la traducción de aminoácidos de su self.seq . Al usar la herencia, podemos definir la clase
OpenReadingFrame como un tipo de clase Sequence , ahorrándonos de tener que volver a implementar
.longitud_bp () —solo necesitaríamos implementar el método .get_translation () específico de la clase y
cualquier otro método sería heredado automáticamente de la clase Sequence .
imagen

El polimorfismo es la idea de que heredar tipos de clases no tiene que aceptar los métodos predeterminados heredados, y son libres
de volver a implementar (o “anular”) métodos específicos incluso si sus clases “padre” o “hermano” ya los definen. Por ejemplo,

2.11.7 https://espanol.libretexts.org/@go/page/55091
podríamos considerar otra clase llamada AminoacidSequence que hereda de Sequence , así que también tendrá un
.get_id () y .longitud_bp () ; en este caso, sin embargo, el heredado .longitud_bp () estaría mal,
porque len (self.seq ) sería tres veces demasiado corto. Entonces, una AminoacidSequence podría anular el
método .length bp () para devolver 3*len (self.seq) . La característica interesante del polimorfismo es que
dado un objeto como Gene_a , ni siquiera necesitamos saber qué “tipo” de objeto Sequence es: ejecutar
Gene_a.Length_BP () devolverá la respuesta correcta si es alguno de estos tres tipos de secuencia.
II.11_31_py_114_9_polimorfismo

Estas ideas son consideradas por muchos como los puntos definitorios del “diseño orientado a objetos”, y permiten a los
programadores estructurar su código de manera jerárquica (vía herencia) al tiempo que permiten patrones interesantes de
flexibilidad (vía polimorfismo). No los hemos cubierto en detalle aquí, ya que hacer un buen uso de ellos requiere una buena
cantidad de práctica. Además, ¡la simple idea de encapsular datos y funciones en objetos proporciona bastantes beneficios en sí
misma!

Ejercicios
1. Modifique el script snps_ex_density.py para generar, por cada región de 100.000pb de cada cromosoma, el
porcentaje de SNP que son transiciones y el número de SNP en cada ventana. La salida debe ser un formato que se vea así:
II.11_32_PY_114_10_SNP_DENS_EX_Salida En la sección sobre programación R (capítulo 37, “Ploting Data y ggplot2 ”), descubriremos

formas fáciles de visualizar este tipo de salida.


2. El módulo random (usado con import random ) nos permite hacer elecciones aleatorias; por ejemplo,
random.random () devuelve un float aleatorio entre 0.0 y 1.0 .
La función random.randint (a, b) devuelve un entero aleatorio entre a y b
(inclusive); por ejemplo, random.randint (1, 4) podría devolver 1 , 2 , 3 o
4 .
También hay una función random.choice () ; dada una lista, devuelve un solo elemento (al azar) de ella. Entonces, si
bases = ["A”, “C”, “T”, “G"] , entonces random.choice (bases) devolverá una sola cadena, ya sea
“A” , “C” , “T” o “G” .
Crea un programa llamado pop_sim.py . En este programa escribe una clase Bug ; un objeto “bug” representará un
organismo individual con un genoma, a partir del cual se puede calcular una aptitud. Por ejemplo, si a = Bug () , tal vez a
tendrá un autogenoma como una lista de 100 bases de ADN aleatorias (por ejemplo,
["G”, “T”, “A”, “G”,... ; estos deben crearse en el constructor). Debe implementar un método
.get_fitness () que devuelve un float calculado de alguna manera a partir de self.genome , por ejemplo el
número de bases G o C, más 5 si el genoma contiene tres caracteres “A” en una fila. Los objetos bug también deben tener un
método.mutate_random_base () , que hace que un elemento aleatorio de self.genome se establezca en un
elemento aleatorio de ["A”, “C”, “G”, “T"] . Finalmente, implementar un método .set_base () , que
establece un índice específico en el genoma a una base específica: a.set_base (3, “T”) debería establecer
self.genome [3] en “T” .
Pon a prueba tu programa creando una lista de 10 objetos Bug , y en un for-loop, haz que cada uno ejecute su
método.mutate_random_base () e imprima su nueva aptitud.
3. A continuación, crear una clase de Población . Los objetos de población tendrán una lista de objetos Bug (digamos, 50)
llamados self.bug_list .
Esta clase Population debe tener un método .create_descendencia () , que: 1) creará una lista new_pop
, 2) por cada elemento oldbug de self.bug_list : a) crear un nuevo objeto Bug newbug , b) y establecer el
genoma de newbug (una base a la vez) para que sea el mismo que el de oldbug , c) llame a
newbug.mutate_random_base () , y d) agregue oldbug y newbug a new_pop . Por último, este método
debería 3) establecer self.bug_pop en new_pop .
La clase Population también tendrá un método .cull () ; esto debería reducir self.bug_pop al 50%
superior de los objetos bug por aptitud física. (Podría encontrar el ejercicio anterior discutiendo . __lt__ () y métodos
similares útiles, ya que te permitirán ordenar self.bug_pop por fitness si se implementa correctamente.)

2.11.8 https://espanol.libretexts.org/@go/page/55091
Finalmente, implementar un método .get_mean_fitness () , que debería devolver la aptitud promedio de
self.bug_pop .
Para probar tu código, crea una instancia de un objeto p = Population () , y en un for-loop: 1) ejecuta
p.create_descendencia () , 2) ejecuta p.cull () y 3) imprime p.get_mean_fitness () ,
permitiéndote ver el progreso evolutivo de tu simulación.
4. Modificar el programa de simulación anterior para explorar su dinámica. Podría considerar agregar un
método.get_best_individual () a la clase Population , por ejemplo, o implementar un esquema de
“apareamiento” mediante el cual los genomas de descendencia son mezclas de dos genomas parentales. También podrías
intentar ajustar el método.get_fitness () . Este tipo de simulación se conoce como algoritmo genético,
especialmente cuando los individuos evolucionados representan soluciones potenciales a un problema computacional. [9]

1. Otra metodología destacada para la programación es el paradigma “funcional”, donde las funciones son el foco principal y los
datos suelen ser inmutables. Si bien Python también es compatible con la programación funcional, no nos centraremos en este
tema. Por otro lado, el lenguaje de programación R enfatiza la programación funcional, por lo que exploraremos este paradigma
con más detalle en capítulos posteriores.
2. Esta definición no es precisa y, de hecho, tergiversa intencionalmente cómo se almacenan los objetos en la RAM. (En realidad,
todos los objetos del mismo tipo comparten un solo conjunto de funciones/métodos). Pero esta definición nos va a servir bien
conceptualmente.
3. Aunque algunos han criticado la antropomorfización de objetos de esta manera, está perfectamente bien, siempre y cuando
siempre digamos “¡por favor!”
4. Este primer parámetro técnicamente no necesita ser nombrado self , pero es un estándar ampliamente aceptado.
5. Uno podría preguntarse si un objeto Gene alguna vez necesitaría permitir que su secuencia cambiara en absoluto. Una
posible razón sería si simuláramos mutaciones a lo largo del tiempo; un método como .mutar (0.05) podría pedirle a
un gen que cambie aleatoriamente el 5% de sus bases.
6. Este archivo se obtuvo del Proyecto 1000 Genomas Humanos en ftp://ftp.1000genomes.ebi.ac.uk/vol1..._07/trio/snps/, en el
archivo CEU.Trio.2010_03.genotypes.vcf.gz . Después de descomprimir, muestreamos el 5% de los SNP al
azar con
awk '{if ($1 ~ “##” || rand () < 0.05) {print $0}}'
CEU.trio.2010_03.genotypes.vcf > trio.sample.vcf
; ver capítulo 10, “Filas y Columnas” para más detalles.
7. Las pruebas unitarias suelen ser automatizadas; de hecho, Python incluye un módulo para construir pruebas unitarias llamado
unittest .
8. Esta estrategia no es la más rápida que podríamos haber ideado. Para determinar la región de mayor densidad, tenemos que
considerar cada región, y el cálculo para cada región implica el bucle sobre todas las posiciones del SNP (la gran mayoría de las
cuales se encuentran fuera de la región). Existen algoritmos más sofisticados que correrían mucho más rápido, pero están fuera
del alcance de este libro. Sin embargo, ¡una solución lenta es mejor que ninguna solución!
9. La idea de simular poblaciones “in silico” no sólo es bastante divertida, sino que también ha producido interesantes
conocimientos sobre la dinámica poblacional. Para un ejemplo, véase Hinton y Nowlan, “Cómo el aprendizaje puede guiar la
evolución”, Complex Systems 1 (1987): 495—502. La simulación de sistemas complejos mediante muestreo aleatorio se
conoce comúnmente como método de Monte Carlo. Para un tratamiento más imaginario de simulaciones de sistemas naturales,
véase Daniel Shiffman, La naturaleza del código (autor, 2012). Los ejemplos allí utilizan el lenguaje gráfico disponible en
http://processing.org, aunque una versión de Python también está disponible en http://py.processing.org.

This page titled 2.11: Objetos y Clases is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil
(OSU Press) .

2.11.9 https://espanol.libretexts.org/@go/page/55091
2.12: Interfaces de Programación de Aplicaciones, Módulos, Paquetes, Azúcar
Sintáctico
Sabemos que los objetos como listas tienen métodos como .sort () y .append () . ¿Tienen otros? Lo hacen, y una
forma de encontrarlos es ejecutar Python en el modo interactivo en la línea de comandos. [1]
Para ello, ejecutamos el intérprete python sin especificar un archivo a interpretar. El resultado es un prompt de Python, >>>
, donde podemos escribir líneas individuales de código Python y ver los resultados.
II.12_1_py_115_interactive_ex

El modo interactivo, además de proporcionar una interfaz para ejecutar pruebas rápidas de la funcionalidad de Python, ¡incluye un
sistema de ayuda! Simplemente ejecute la función help () en un nombre de clase (como help (list)) o en una
instancia de la clase.
II.12_2_py_116_interactive_help_list1

Este comando abre un visor interactivo en el que podemos desplazarnos para ver todos los métodos que proporciona un objeto de
ese tipo. Aquí hay una muestra de la página de ayuda:
II.12_3_py_117_interactive_help_list2

Navegar por esta documentación revela que las listas de Python tienen muchos métodos, incluyendo .append () ,
.count () y otros. A veces la documentación no es tan clara como cabría esperar. En estos casos, puede requerirse alguna
experimentación. Entre la descripción anterior y algunas pruebas de código, por ejemplo, ¿puedes determinar qué hace el método
.count () de una lista y cómo usarlo de manera efectiva?
El conjunto de métodos y variables de instancia que pertenecen a un objeto o clase se conoce como su API, o Interfaz de
Programación de Aplicaciones. La API es el conjunto de funciones, métodos o variables proporcionadas por una colección
encapsulada de código. Las APIs son una parte importante de la programación porque representan la “interfaz de usuario” para los
constructos de programación que otros han escrito.
En ocasiones puede ser útil determinar qué clase o “tipo” a la que se refiere una variable, especialmente para buscar en la API
objetos de ese tipo. La función type () es útil cuando se combina con una llamada print () : imprime el tipo (la clase)
de datos referenciados por una variable. El resultado puede entonces ser investigado con help () .
II.12_4_py_117_2_type_api

Módulos
Considere este trozo de código del ejemplo del capítulo 23, “Objetos y clases”, que hace uso de las definiciones de las clases
Cromosoma y SNP :
imagen

Este segmento de código toma un nombre de archivo y produce un diccionario con contenidos bien organizados del archivo.
Debido a que esta funcionalidad está definida de manera sucinta, tiene sentido convertirla en una función que tome el nombre del
archivo como parámetro y devuelva el diccionario. El código es casi exactamente el mismo, excepto por estar envuelto en una
definición de función.
imagen

Posteriormente, podemos llamar a esta función para hacer todo el trabajo de analizar un nombre de archivo dado:
imagen

Ahora bien, ¿y si esta función y las dos definiciones de clase fueran cosas que queríamos usar en otros proyectos? Es posible que
deseemos hacer un módulo con ellos, un archivo que contenga código Python (generalmente relacionado con un solo tema, como
un conjunto de funciones o definiciones de clase relacionadas con un tipo particular de datos o procesamiento).
Ya hemos visto varios módulos, incluyendo io , re y sys . Para usar un módulo, solo necesitamos ejecutar
import modulename . Resulta que los módulos son simplemente archivos de código Python que terminan en ¡ .py ! Así,
cuando usamos import modulename , el intérprete busca un modulename.py que contenga las distintas definiciones

2.12.1 https://espanol.libretexts.org/@go/page/55099
de función y clase. Los archivos de módulo pueden existir en una ubicación de todo el sistema, o estar presentes en el directorio de
trabajo del programa importándolos. [2]
Importar el módulo de esta manera crea un espacio de nombres para el módulo. Este espacio de nombres categoriza todas las
funciones y clases contenidas dentro de ese módulo para que diferentes módulos puedan tener funciones, clases y variables con el
mismo nombre sin conflicto.
II.12_8_Two_Módulos

Si tenemos los dos archivos anteriores en el mismo directorio que nuestro programa, el código fuente como el siguiente imprimiría
4 , 64 , “TAC” y 2.71828 .
imagen

Desafortunadamente, Python usa el . operador de múltiples maneras similares, como indicar un método que pertenece a un objeto
(como en a_list.append (“ID3")) y para indicar una función, variable o definición de clase perteneciente a un espacio
de nombres (como arriba).
Así, como un “contenedor” para los nombres, el espacio de nombres permite a diferentes módulos nombrar funciones, clases y
variables de la misma manera sin entrar en conflicto. Así como una clase puede ser documentada por su API, también lo pueden
hacer los módulos: la API de un módulo describe las funciones, clases y variables que define. También se puede acceder a ellos a
través del menú de ayuda interactiva (y en línea). Intente ejecutar import re seguido de help (re) para ver todas las
funciones proporcionadas por el módulo re .
imagen

Hay una gran cantidad de módulos (y paquetes) disponibles para descargar en línea, y Python también viene con una amplia
variedad de módulos útiles por defecto. Aparte de re , sys e io , discutidos anteriormente, algunos de los módulos
potencialmente más interesantes incluyen:
string : operaciones de cadena comunes (cambio de mayúsculas y minúsculas, formateo, etc.)
hora , fechahora y calendario : operaciones en fechas y horas
random : generar y trabajar con números aleatorios
argparse: análisis fácil de usar de argumentos complejos en la línea de comandos
Tkinter : creación de interfaces gráficas de usuario (con botones, barras de desplazamiento, etc.)
unittest : automatizar la creación y ejecución de pruebas unitarias
turtle : una interfaz simple para mostrar gráficos basados en líneas [3]
Los tutoriales para estos módulos específicos se pueden encontrar en línea, así como listas de muchos otros paquetes y módulos
que vienen instalados con Python o que están disponibles para su descarga.

Creación de un módulo
Vamos a crear un módulo llamado MyVCFModule.py para contener las dos definiciones de clase y la función de análisis del
último ejemplo. Mientras lo hacemos, también convertiremos los comentarios que habíamos creado para nuestras clases, métodos y
funciones en “docstrings”. Las cadenas de documentos son cadenas de comillas triples que proporcionan una funcionalidad similar
a los comentarios, pero se pueden analizar posteriormente para producir la API del módulo. Pueden abarcar varias líneas, pero
deben ocurrir inmediatamente dentro de la definición de módulo, clase o función correspondiente. Aquí dejamos fuera el contenido
de los métodos y funciones (que son los mismos que en el capítulo anterior) para ahorrar espacio:
alt

El programa que hace uso de este módulo, que existe en un archivo separado en el mismo directorio (quizás llamado
snps_ex2.py ), es corto y dulce. [4]
imagen

Si otros proyectos necesitan analizar archivos VCF, este módulo puede ser de nuevo de utilidad. Por cierto, debido a que
documentamos nuestro módulo con docstrings, su API es fácilmente accesible a través de la interfaz de ayuda interactiva.
imagen

Parte de la página de ayuda de la API:

2.12.2 https://espanol.libretexts.org/@go/page/55099
imagen

Paquetes
Como si Python no tuviera ya suficientes “cajas” para la encapsulación —funciones, objetos, módulos, etc.— también hay
paquetes. En Python, un “paquete” es un directorio que contiene algunos archivos de módulo. [5]
Un directorio de paquetes también debe contener un archivo especial llamado __init__.py , que le permite a Python saber
que el directorio debe tratarse como un paquete desde el que se pueden importar módulos. (Se podría poner código en este archivo
que se ejecutaría cuando se ejecute la sentencia import , pero no exploraremos esta característica aquí).
alt

Como ejemplo, supongamos que junto con nuestro MyVCFModule.py , también habíamos creado un módulo para analizar
archivos de ontología génica llamado GOParseModule.py . Podríamos juntarlos en un paquete (directorio) llamado
MyBioParsers .
Para usar un módulo contenido en un paquete, la sintaxis es de packagename import modulename . [6] Nuestro
programa Python podría vivir en el mismo directorio en el que se encontró el directorio MyBioParsers , y podría comenzar
así:
imagen

Posteriormente, el propio módulo se puede utilizar igual que antes.


imagen

Analizar un archivo FASTA


Hasta este punto, nos hemos saltado algo que es ampliamente considerado un “básico” en biología computacional: leer un archivo
FASTA. En su mayor parte, los ejemplos anteriores que necesitan datos de secuencia leen esos datos de archivos simples con
formato de fila/columna.
imagen

La mayoría de los datos de secuencia, sin embargo, aparecen en el llamado formato FASTA, donde cada secuencia tiene una línea
de cabecera que comienza con > y un ID, y la secuencia se divide en cualquier número de líneas siguientes antes de que aparezca
la siguiente línea de cabecera.
imagen

Analizar datos en formato FASTA no es demasiado difícil, pero requiere un paso de preprocesamiento adicional (generalmente
implica un bucle y una variable que realiza un seguimiento del ID “actual”; siempre que las líneas posteriores no coincidan con el
carácter > , deben ser líneas de secuencia y pueden agregarse a una lista de cadenas para luego unirse para esa identificación). Si
bien reinventar la rueda de análisis FASTA es una buena práctica para los codificadores novatos, el paquete BioPython proporciona
funciones para analizar estos y muchos otros formatos. [7]
BioPython es un paquete grande con muchos módulos y APIs, sobre los que puedes leer más en http://biopython.org. Para la
aplicación específica de análisis de un archivo FASTA, un buen lugar para comenzar es una simple búsqueda web de “biopython
fasta”, que nos lleva a una página wiki que describe el módulo SeqIO, completa con un ejemplo.
II.12_20_Biopython_wiki_shot

Esta captura de pantalla muestra algunas diferencias con la forma en que hemos estado escribiendo código, por ejemplo, el uso de
open () en contraposición a io.open () , y la llamada a print record.id en lugar de nuestra habitual
print (record.id) (la primera de las cuales funcionará solo en versiones anteriores de Python, mientras que esta última
funcionará en todas las versiones razonablemente recientes de Python). Sin embargo, dado nuestro conocimiento de paquetes,
módulos y objetos, podemos deducir lo siguiente de este sencillo ejemplo de código:
1. El módulo SeqIO se puede importar desde el paquete Bio .
2. Se abre un manejador de archivo.
3. Se llama a la función seqio.parse () , y toma dos parámetros: el identificador del archivo desde el que leer los datos, y
una cadena de especificación que describe el formato del archivo.

2.12.3 https://espanol.libretexts.org/@go/page/55099
4. Esta función devuelve algo iterable (similar a una lista, o un identificador de archivo normal), por lo que se puede enrollar con
un bucle for.
5. El bucle accede a una serie de objetos de algún tipo, y cada uno se asocia con el registro de nombre de variable.
6. Se imprime el registro.id , que es (aparentemente) una variable de instancia que pertenece al objeto record .
7. El mango del archivo está cerrado (¡siempre buena práctica!).
Si hacemos un poco más de lectura en la página wiki de SeqIO , encontraríamos que los objetos de registro realmente
tienen la clase SeqRecord , y también tienen una variable de instancia seq que proporciona acceso a la secuencia completa.
Al juntar todo esto, aquí hay un programa corto que convierte un archivo FASTA al formato de fila/columna con el que hemos
estado tratando.
imagen

Azúcar Sintáctico
En Python, casi todo es un objeto, ¡incluso enteros simples! Al mirar la API de un entero, por ejemplo, descubrimos que
proporcionan un método llamado .bit_length () .
imagen

Aquí hay una parte de la vista de API:


imagen

Podemos probarlo así, para descubrir que el entero 7 se puede representar con tres bits binarios (como 111 ):
imagen

Si intentaras ver la API para un entero como lo hemos hecho, verías una serie de métodos de aspecto impar, como
. __add__ () y . __abs__ () :
imagen

Esto parece indicar que podemos obtener el valor absoluto de un entero mediante el uso de una llamada a un método o mediante el
uso de la sintaxis de llamada a la función estándar. Del mismo modo, aparentemente podemos agregar un entero a otro mediante el
uso de la llamada al método o el operador estándar + . Estos son realmente ciertos, y podemos usar las funciones y operadores
estándar o sus versiones de método:
imagen

Las operaciones como la adición parecen operaciones básicas y fundamentales, y Python realiza tales operaciones a través de
llamadas a métodos en objetos. [8] Una declaración como a = b + c se convierte en a = b.__add__ (c) detrás de
escena. Aunque podemos ejecutar tales operaciones orientadas a métodos nosotros mismos, la incómoda sintaxis de doble
subrayado es cómo los diseñadores nos hacen saber que esos métodos son para uso interno, y debemos seguir con la sintaxis
estándar. Esta conversión automática de sintaxis se conoce como azúcar sintáctica, y está presente en muchos lenguajes de
programación para que puedan ser internamente consistentes pero más agradables (“más dulces”) para que los humanos trabajen
con ellos.

Ejercicios
1. Cree un módulo llamado mymodule.py que incluya al menos una definición de clase y definición de función, así como
documente el módulo, clase, métodos y funciones con docstrings. Crear un myprogram.py que importe este módulo y
haga uso de la funcionalidad en él. También intente ver la API para su módulo en el intérprete interactivo de Python con
import mymodule y help (mymodule) .
2. Navegue a través de la API en línea en http://docs.python.org/2/library para obtener la “Biblioteca estándar de Python” y
especialmente cualquier cosa relacionada con cadenas. Además, revise la documentación de la API para los módulos io ,
sys , re , math y random .
3. Para este ejercicio, vamos a instalar un módulo desde la web y hacer uso de él para analizar un archivo VCF
trio.sample.vcf . El módulo de lectura VCF que vamos a instalar se encuentra en GitHub (un lugar cada vez más
popular para alojar software): https://github.com/jamescasbon/PyVCF.

2.12.4 https://espanol.libretexts.org/@go/page/55099
Lo primero que hay que hacer es clonar este repositorio desde el enlace .git (que requiere que tengamos instalada la
herramienta git ). Esta clonación debería descargar una carpeta PyVCF ; cd hasta allí y usar ls para ver el
contenido: imagen A continuación, necesitamos instalarlo, esto configurará el módulo y sus archivos necesarios, y los colocará en
una carpeta oculta llamada .local dentro de su directorio principal ( Python también busca allí módulos y paquetes por
defecto). Para introducirlo en .local en tu propio directorio home en lugar de usar una instalación en todo el sistema (que
quizás no tengas permiso para hacer), tenemos que agregar —user al comando que ejecuta el script setup.py : imagen
Una vez hecho esto, vuelve a tu directorio original, y entonces puedes activar el intérprete interactivo de Python, cargar el
módulo y ver su API: imagen (Lamentablemente, la documentación de la API en el paquete en sí no es tan buena, la versión en
línea en http://pyvcf.rtfd.org es mejor, específicamente en la introducción).
4. Escribe un programa llamado vcf_module_use.py que importe este módulo vcf y lo utilice para contar e imprimir
cuántas líneas en trio.sample.vcf tienen un alelo de referencia de “A ” . (Tenga en cuenta que debe importar y
usar el módulo, en lugar de analizar cada línea usted mismo).

1. Gran parte de esta información también está disponible en línea en http://docs.python.org.


2. Los módulos y paquetes de Python no necesitan estar instalados en todo el sistema o estar presentes en el mismo directorio que
el programa que los usa. La variable de entorno $PYTHONPATH enumera los directorios que se buscan por módulos y
paquetes, a los que cada usuario es libre de agregar sus propias rutas de búsqueda.
3. El módulo de gráficos tortuga es una forma divertida de entender y visualizar procesos computacionales (y paquetes
similares están disponibles para muchos lenguajes de programación). Si bien este libro no es el lugar para tales exploraciones, el
lector puede aprender más sobre ellas en el excelente libro de Jeffrey Elkner, Allen Downey y Chris Meyers, How to Think Like
a Computer Scientist (Green Tea Press, 2002). Al momento de escribir este artículo, una versión del libro está disponible en
línea en http://interactivepython.org/runestone/static/thinkcspy/index.html.
4. En algunos scripts de Python, puede encontrar una línea si __name__ == '__main__': . El código dentro de esta
sentencia if se ejecutará, pero solo si el archivo se ejecuta como un script. Si se importa como módulo, el bloque no se
ejecutará. Esto permite a los desarrolladores escribir archivos que pueden servir para ambos propósitos, y muchos
desarrolladores lo incluyen por defecto para que las funciones de su script y las definiciones de clase puedan ser utilizadas en el
futuro importando el archivo como un módulo.
5. Los paquetes también pueden contener otros archivos, como archivos que contienen conjuntos de datos específicos o incluso
código escrito en otros idiomas como C. Por lo tanto, algunos módulos pueden necesitar ser instalados por procesos más
complicados que simplemente descomprimirlos en la ubicación correcta.
6. Es posible que ocasionalmente vea una línea como de modulename import * . Esto permite usar las funciones y otras
definiciones dentro de modulename sin prefijarlas con modulename. . Por ejemplo, podemos
importar matemáticas y luego usar print (math.sqrt (3.0)) , o podemos
desde math import * y luego print (sqrt (3.0)) . Esto último generalmente se evita, porque si múltiples
módulos contienen la misma función u otros nombres, entonces no es posible desambiguarlos.
7. Dependiendo del sistema en el que estés trabajando, es posible que ya tengas el paquete BioPython. Si no, puedes usar la
utilidad pip install para instalarlo: pip install BioPython , o si no tienes privilegios de administrador,
pip install —user BioPython . Algunas versiones anteriores de Python no vienen con la utilidad pip , pero
esta se puede instalar con una herramienta más antigua: easy_install pip o easy_install —user pip .
8. Es seguro decir que Python no está “meramente” orientado a objetos, está muy orientado a objetos.

This page titled 2.12: Interfaces de Programación de Aplicaciones, Módulos, Paquetes, Azúcar Sintáctico is shared under a CC BY-NC-SA license
and was authored, remixed, and/or curated by Shawn T. O’Neil (OSU Press) .

2.12.5 https://espanol.libretexts.org/@go/page/55099
2.13: Algoritmos y estructuras de datos
Habiendo aprendido tantos conceptos de programación —variables y los datos a los que se refieren, funciones, bucles, etc.— sería
una pena no hablar de algunos de los métodos sorprendentes y elegantes que habilitan. Los estudiantes de informática pasan años
aprendiendo sobre estos temas, y los temas de este capítulo subyacen a gran parte de la bioinformática.
Comenzaremos con algoritmos, que según un libro clásico sobre el tema —Introducción a los algoritmos de Thomas H. Cormen,
Charles E. Leiserson, Ronald L. Rivest y Clifford Stein— son:
cualquier procedimiento computacional bien definido que tome algún valor, o conjunto de valores, como entrada y produzca algún
valor, o conjunto de valores, como salida.
Con una definición tan amplia, toda la codificación Python que hemos hecho podría clasificarse con precisión como práctica en
algoritmos. La palabra “algoritmo”, por cierto, deriva del nombre del matemático persa del siglo VIII al-Khwārizmī, quien
desarrolló enfoques sistemáticos para resolver ecuaciones (entre otros logros). Aunque la mayor parte del código funcional puede
caracterizarse como un algoritmo, los algoritmos generalmente implican algo más que recopilar datos y se emparejan con
problemas bien definidos, proporcionando una especificación de qué son las entradas válidas y cuáles deben ser las salidas
correspondientes. Algunos ejemplos de problemas incluyen:
1. Dada una lista de números en orden arbitrario, devuélvala o una copia de la misma en orden ordenado. (El problema de
la “clasificación”.)
2. Dada una lista de números en orden ordenado y un número de consulta q , devuelve True si q está presente en la
lista y False si no lo está. (El problema de la “búsqueda”.)
3. Dadas dos cadenas de longitud y , alinearlas una contra la otra (insertando guiones donde sea necesario para
hacerlas de la misma longitud) para maximizar una partitura basada en las coincidencias de caracteres. (El problema de
“alineación de cadenas”).
II.13_1_string_alineamiento

Claramente, algunos problemas, como el problema de alineación de cuerdas, son de especial interés para los científicos de la vida.
Otros, como clasificar y buscar, son ubicuos. No importa a quién le importe el problema, un buen algoritmo para resolverlo debería
hacerlo de manera eficiente. Por lo general, “eficientemente” significa “en poco tiempo”, aunque con los grandes conjuntos de
datos producidos por las modernas tecnologías de secuenciación de ADN, a menudo también significa “usar una pequeña cantidad
de memoria”.
La mayoría de los problemas bioinformáticos como la alineación de cuerdas requieren métodos bastante sofisticados construidos a
partir de conceptos más simples, por lo que comenzaremos con el tema introductorio habitual para el diseño y análisis de
algoritmos: la clasificación. Después de todo, alguien tuvo que escribir la función sort () de Python para listas, y forma una
buena base para el estudio.
Considera una pequeña lista de cinco números, en orden sin clasificar.
II.13_2_Clasificación_Pre

¿Qué código podríamos escribir para ordenar estos números? Sea lo que sea, podría tener sentido ponerlo en una función que tome
una lista sin clasificar y devuelva una copia de la misma en orden ordenado (para mantenerse consistente con el uso de Python del
principio de separación de comando-consulta). Para comenzar, haremos una copia de la lista, y luego tendremos la función
simplemente “arreglar” algunos de los números fuera de orden en esta copia mirando una ventana deslizante de tamaño 2, pares de
números adyacentes, y cambiando su ubicación si están fuera de servicio.
II.13_3_PY_138_Bubblesort_a

El simple hecho de arreglar un puñado de pares de esta manera no dará como resultado que se ordene la lista, pero debería ser
obvio que debido a que los pares de arreglos se superponen, el número más grande habrá encontrado su camino hasta la última
posición. En cierto sentido, “burbujeó” hasta la cima. Si tuviéramos que repetir este proceso, el número de segundo a mayor
también debe encontrar su camino hacia el lugar correcto. De hecho, si repetimos este proceso tantas veces como haya números, la
lista estará completamente ordenada.
II.13_4_PY_139_Bubblesort_b
II.13_5_py_139_2_bubblesort_b_test

2.13.1 https://espanol.libretexts.org/@go/page/55105
Este algoritmo de clasificación se conoce cariñosamente como “bubblesort”. En un sentido rudo, ¿cuántas operaciones realizará
este algoritmo, en el peor de los casos, para una lista de tamaño ? Supongamos que la sentencia if siempre encuentra
True , y los números necesitan ser intercambiados. En este caso, cada operación del bucle interno requiere cinco o así “pasos de
tiempo” (cuatro asignaciones y una verificación if ). El bucle interno corre tiempos (si hay números), que
a su vez se repiten veces en el bucle externo. El paso de copia para producir la nueva lista c_nums también usa
pasos, por lo que el número total de operaciones básicas es, más o menos,
<span translate=\ [n (n-1) 5 + n = 5n^ {\ textcolor {blanco} {\ sombrero {\ textcolor {negro} {2}}} - 4n\.\]” title="Rendido por

Quicklatex.com” src=” https://bio.libretexts.org/@api/deki...a1f374d_l3.png "/>


Sin embargo, al analizar el tiempo de ejecución de un algoritmo, solo nos importan las “cosas grandes”.

Notación de orden
Al calcular el tiempo de ejecución de un algoritmo, solo nos importan los términos de orden superior, y tendemos a ignorar
constantes que no están relacionadas con el “tamaño” de la entrada. Decimos que el tiempo de ejecución es orden del término de
orden más alto (sin constantes), que denotamos con mayúscula : is
Leemos esto como “cinco al cuadrado menos cuatro es orden al cuadrado” o “cinco al cuadrado menos
cuatro es grande oh cuadrado”. Debido a que este tiempo de ejecución habla de un algoritmo en particular, también
podríamos decir “bubblesort es orden al cuadrado”. La notación Big-O implica que, aproximadamente, un algoritmo se
ejecutará en un tiempo menor o igual que la ecuación interpretada como una función del tamaño de entrada, en el peor de los casos
(ignorando constantes y términos pequeños).
El análisis en el peor de los casos supone que tenemos mala suerte con cada entrada, pero en muchos casos el algoritmo podría
funcionar más rápido en la práctica. Entonces, ¿por qué hacemos análisis en el peor de los casos? Primero, le da al usuario de un
algoritmo una garantía: nadie quiere escuchar que el software podría usar cierta cantidad de tiempo. Segundo, si bien a veces es
posible hacer análisis de casos promedio, requiere conocimiento de la distribución de los datos de entrada en la práctica (lo cual es
raro) o técnicas de análisis más sofisticadas.
¿Por qué usamos la notación de orden para caracterizar el tiempo de ejecución del algoritmo, dejando pequeños términos y
constantes? En primer lugar, aunque antes dijimos que cada operación básica es un “paso de computadora”, eso no es realmente
cierto: la línea c_nums [index + 1] = leftnum requiere tanto una adición como una asignación. Pero nadie quiere
contar cada ciclo de CPU individualmente, especialmente si el resultado no va a cambiar la forma en que comparamos dos
algoritmos para grandes conjuntos de datos, que es el propósito principal de la notación de orden. Dejar términos constantes y
términos de orden inferior tiene buen sentido matemático en estos casos. Para entrada lo suficientemente grande (es decir, todos
mayores que algún tamaño ), incluso términos que parecen que harían una gran diferencia resultan no importar, como
demuestra la siguiente comparación hipotética.
<span translate=\ [\ begin {alineado} 0.01n^ {\ textcolor {blanco} {\ sombrero {\ textcolor {negro} {2}}} - 100n &> 200n^ {\ textcolor

{blanco} {\ sombrero {\ textcolor {negro} {1.5}}} + 20n,\ texto {para} n >\ texto {algunos} c,\\ O (n^ {2} &) >O (n^ {1.5}),\\\
text {Tiempo Alg A} &>\ texto {Tiempo Alg B (para entradas grandes).} \ end {aligned}\]” title="Renderizado por
Quicklatex.com” src=” https://bio.libretexts.org/@api/deki...0c800bc_l3.png "/>
(Técnicamente en lo anterior O (n^ {\ textcolor {blanco} {\ sombrero {\ textcolor {negro} {2}}}) O (n^ {\ textcolor {blanco} {\ sombrero {\ textcolor
{negro} {1.5}}}})" title="Procesado por Quicklatex.com" height="24" width="256" style="vertical-align: -4px;"
src="https://bio.libretexts.org/@api/deki...67bc088_l3.png"> hay un abuso de notación; deberíamos decir que se
basa en la definición de la notación.) En este caso, es mayor que cuando
es mayor a 400,024,000 (que es el de este ejemplo ).
Entonces, aunque la notación de orden parece una forma difusa de ver la eficiencia de un algoritmo, es una forma rigurosamente
definida y razonable de hacerlo. [1] Debido a que algunos lenguajes de programación son más rápidos que otros, pero solo por un
factor constante (quizás el código C compilado es 100 veces más rápido que Python), ¡un buen algoritmo implementado en un
lenguaje lento a menudo supera a un algoritmo mediocre implementado en un lenguaje rápido!
Ahora bien, un tiempo de ejecución de no es muy bueno: el tiempo que se tarda en ordenar una lista de números
crecerá cuadráticamente con el tamaño de la lista.
II.13_8_N_Squared_plot

2.13.2 https://espanol.libretexts.org/@go/page/55105
no es genial, pero para el problema de clasificación podemos hacerlo mejor, y volveremos al estudio de algoritmos y
clasificación un poco más tarde. Primero, desviemos y hablemos de formas interesantes en las que podemos organizar los datos,
utilizando variables y los objetos a los que se refieren.

Estructuras de datos
Una estructura de datos es una organización para una recopilación de datos que idealmente permite un acceso rápido y ciertas
operaciones sobre los datos. Ya hemos visto un par de estructuras de datos en Python: listas y diccionarios. Dada tal estructura,
podríamos hacer preguntas como:
¿La estructura de datos mantiene sus elementos en un orden específico (como el orden ordenado)? Si usamos .append ()
para agregar elementos a las listas, las listas se mantienen en el orden en que se agregan los elementos. Si quisiéramos que la
lista se mantuviera ordenada, tendríamos que asegurarnos de que los nuevos elementos se inserten en el lugar correcto.
¿Cuánto tiempo se tarda en agregar un nuevo elemento? Para las listas de Python, el método .append () opera en el
tiempo , es decir, se puede agregar un solo elemento al final, y el tiempo empleado no depende de qué tan grande sea la
lista. Desafortunadamente, esto no será cierto si necesitamos mantener la lista ordenada, porque insertar en el medio requiere
reorganizar la forma en que se almacenan los datos.
¿Cuánto tiempo se tarda en encontrar el elemento más pequeño? Porque usando .append () agrega elementos al final de
la lista, a menos que mantengamos la lista ordenada, necesitamos buscar en toda la lista el elemento más pequeño. Esta
operación lleva tiempo , donde está la longitud de la lista. Sin embargo, si la lista está ordenada, solo toma
tiempo mirar al frente de la misma.
Hay compensaciones que podemos hacer al pensar en cómo usar las estructuras de datos. Supongamos, por ejemplo, que queríamos
encontrar rápidamente el elemento más pequeño de una lista, incluso cuando se le agreguen elementos. Una posibilidad sería
ordenar la lista después de cada adición; si lo hiciéramos, entonces el elemento más pequeño estaría siempre en el índice 0 para
una rápida recuperación. Pero esto es mucho trabajo, ¡y el tiempo total dedicado a clasificar comenzaría a superar los beneficios de
mantener la lista ordenada en primer lugar! Aún mejor, podríamos escribir una función que (1) inserte el nuevo elemento hasta el
final y luego (2) lo burbujee hacia atrás a su ubicación correcta; solo se necesitaría uno de esos burbujeos para cada inserción, pero
cada uno es una operación (a menos que de alguna manera podamos garantizar que solo se agreguen elementos grandes).
II.13_9_List_Keep_Sorted

Encontrar el artículo más pequeño es fácil, ya que sabemos que siempre está presente en el índice 0 .
Estructura Insertar un artículo Obtener el más pequeño

Lista Simple Ordenada

En cambio, hagamos algo mucho más creativo y construyamos nuestra propia estructura de datos desde cero. Si bien esta estructura
de datos es un gran problema de crear por solo un pequeño beneficio, tiene sus usos y sirve como un bloque de construcción para
muchas otras estructuras sofisticadas.

Listas Enlazadas Ordenadas


Nuestra estructura de datos, llamada “lista enlazada ordenada”, mantendrá una colección de artículos en orden ordenado, y hará el
trabajo de insertar nuevos elementos en la ubicación correcta en ese orden. Estos elementos podrían ser enteros, flotantes o
cadenas, cualquier cosa que pueda compararse con el operador < (que, recordar, compara cadenas en orden lexicográfico; como
se discutió en capítulos anteriores, incluso podemos definir nuestros propios tipos de objetos que son comparables con >
implementando . __lt__ () , . __eq__ () y métodos similares).
Para lograr esta tarea, vamos a hacer un uso intensivo de clases y objetos, y el hecho de que una variable (incluso una variable de
instancia) es un nombre que hace referencia a un objeto.
La estrategia será tener dos tipos de objetos: el primero, al que llamaremos LinkedList , será el tipo principal de objeto con el
que interactuarán nuestros programas (al igual que interactuamos con objetos Cromosoma en capítulos anteriores, mientras que
los objetos SNP fueron tratados por los objetos Cromosómicos ). El objeto LinkedList tendrá métodos como
.insert_item () y .get_smaller () . Los otros objetos serán de tipo Node , y cada uno de estos tendrá una

2.13.3 https://espanol.libretexts.org/@go/page/55105
variable de instancia self.item que hará referencia al ítem almacenado por un Nodo individual. Entonces, dibujando solo
los objetos en la RAM, nuestra lista ordenada enlazada de tres elementos ( 4 , 7 y 9 ) podría verse así:
II.13_10_LL_PRE_LINKS

(Es poco probable que los objetos estén bien organizados de esta manera en RAM; los estamos mostrando en una línea solo para
ilustración). Las flechas en esta figura indican que después de crear un nuevo objeto LinkedList llamado itemlist ,
esta variable es un nombre que hace referencia a un objeto, y cada objeto Node tiene una variable de instancia self.item
que se refiere a un objeto “data” de algún tipo, como un entero o cadena. [2]
Ahora bien, si esta colección de objetos existiera como se muestra, no sería tan útil. Nuestro programa solo sería capaz de
interactuar con el objeto itemlist , y de hecho no hay variables que se refieran a los objetos Node individuales, por lo que
se eliminarían a través de la recolección de basura.
Aquí está el truco: si una variable de instancia es solo una variable (y por lo tanto una referencia a un objeto), podemos darle a cada
objeto Node una variable de instancia self.next_n que hará referencia al siguiente nodo en la línea. Del mismo modo, el
objeto LinkedList tendrá una variable de instancia self.first_n que hará referencia a la primera.
II.13_11_LL_POST_LINKS

El último objeto Node self.next_n se refiere a None , un marcador de posición que podemos usar para permitir que
una variable haga referencia a “nada aquí”. En realidad, None será el valor inicial para self.next_n cuando se cree un
nuevo nodo, y tendremos que agregar métodos para get_next_n () y set_next_n () que nos permitan obtener o
cambiar la variable next_n de Node a voluntad. La variable first_n de LinkedLists se inicializará de manera similar
como None en el constructor.
Supongamos que tenemos esta estructura de datos en su lugar, y queremos agregar el número 2 ; este sería el nuevo elemento
“más pequeño”. Para ello necesitaríamos ejecutar itemlist.insert_item (2) , y este método consideraría las
siguientes preguntas para manejar todos los casos posibles para insertar un ítem (usando if-statements):
1. ¿Es self.first_n igual a None ? Si es así, entonces el nuevo elemento es el único elemento, así que cree un nuevo
Nodo que contenga el nuevo elemento y establezca self.first_n en ese nodo.
2. Si self.first_n no es igual a None :
1. ¿El nuevo artículo es más pequeño que el artículo de self.first_n ? Si es así, entonces (1) crea un nuevo Nodo
que contenga el nuevo elemento, (2) establezca su next_n en self.first_n , y luego (3) establezca
self.first_n en el nuevo nodo. Aquí hay una ilustración para este caso: II.13_12_LL_2a
2. De lo contrario, el nuevo nodo no va entre el objeto LinkedList y el primer objeto Node . En este caso, podríamos
tratar el objeto self.first_n como si fuera él mismo un LinkedList , si tan solo tuviera un método
.insert_item () .
Este caso (b) es realmente el corazón de la estrategia de lista enlazada: cada objeto Node también tendrá un método
.insert_item () . Además, el insert_item () de cada nodo seguirá la misma lógica que la anterior: si
self.next_n es None , el nuevo nodo va tras ese nodo. Si no, el nodo necesita determinar si el nuevo nodo debe ir entre él
y el siguiente nodo, o si debe “pasar el dólar” al nodo siguiente en la línea.
Ahora podemos pasar al código. Primero, aquí está el código para la clase LinkedList .
II.13_13_PY_140_LISTA_LINQUED_LISTA

Las líneas resaltadas anteriormente son las ilustradas en el paso 2a y son cruciales; en particular, el orden en que se establecen las
diversas variables marca la diferencia. ¿Qué pasaría si self.first_n = newnode fuera llamado antes
newnode.set_next (self.first_n) ? Perderíamos todas las referencias al resto de la lista:
itemlist.first_n se referiría a newnode , .next_n del nuevo nodo se referiría a None, y ninguna
variable se referiría al nodo que contiene 3 —se perdería en RAM y eventualmente basura colectado.
Como se mencionó anteriormente, la clase para un Nodo es bastante similar. De hecho, es posible construir listas enlazadas con
un solo tipo de objeto, pero esto requeriría un nodo “ficticio” para representar una lista vacía de todos modos (tal vez almacenando
None en su self.item ).

2.13.4 https://espanol.libretexts.org/@go/page/55105
II.13_14_PY_141_Nodo

Nuestra nueva estructura de datos es relativamente fácil de usar y se mantiene ordenada muy bien:
II.13_15_PY_142_LL_Uso

Métodos de lista enlazada


Esta idea de “pasar el dólar” entre nodos es bastante inteligente, y nos permite escribir consultas sofisticadas sobre nuestra
estructura de datos con facilidad. Supongamos que quisiéramos preguntar si un ítem dado ya está presente en la lista.
Para resolver un problema como este, podemos pensar en cada nodo como implementar un procedimiento de decisión (usando un
método, como .is_item_present (query) ). El objeto de interfaz LinkedList devolvería False si su
self.first_n es None (para indicar que la lista está vacía, por lo que el elemento de consulta no puede estar presente).
Si su self.first_n no es None , llama a self.first_n.is_item_present (query) , esperando que ese
nodo devuelva True o False .
II.13_16_PY_143_LL_CONTROLER_IS_ITEM_PRESENTE

Para un nodo, el procedimiento de decisión es solo un poco más complejo:


1. Compruebe si self.item es igual a la consulta . Si es así, un True puede ser devuelto de manera segura.
2. De lo contrario:
1. Si self.next_n es None , entonces se puede devolver False , porque si el buck se pasó al final de la lista,
ningún nodo ha coincidido con la consulta .
2. Si sí existe self.next_n , por otro lado, solo pasa el dólar por la línea, y confía en la respuesta para volver, que se
puede devolver.
II.13_17_py_144_ll_node_is_item_present

Aquí hay una demostración rápida del uso (todo el script se puede encontrar en el archivo linkedlist.py ):
II.13_18_py_145_ll_usage_is_item_present

Observe la similitud en todos estos métodos: cada nodo determina primero si puede responder al problema; si es así, calcula y
devuelve la respuesta. Si no es así, comprueba si hay un nodo al que pasar el problema, y si existe, se pasa el dólar. Observe que en
cada pase de dólar el método que se llama es el mismo, solo se le llama para un objeto diferente. Y cada vez que el “problema”
general se hace más pequeño a medida que disminuye el número de nodos que quedan para pasar el dólar a.
¿Cuánto tiempo se tarda en insertar un ítem en una lista que ya es de longitud ? Debido a que el nuevo elemento podría tener
que ir al final de la lista, es posible que el dólar deba pasar tiempos, lo que significa que una inserción es . ¿Y qué hay
de conseguir el elemento más pequeño? En el método .get_smaller () de LinkedList , solo necesita determinar si
self.first_n es None , y si no, devuelve el elemento almacenado en ese nodo. Porque no hay dólar que pasa, el tiempo
lo es .

Estructura Insertar un artículo Obtener el más pequeño

Lista Simple Ordenada

Lista Enlazada Ordenada

La creación de la estructura de listas enlazadas ordenadas no nos consiguió superar mucho una lista más sencilla mantenida en
orden ordenada a través de burbujeo, pero las ideas implementadas aquí allanan el camino para soluciones mucho más sofisticadas.

Ejercicios
1. ¿Cuánto tiempo tomaría insertar secuencias en una lista de Python y luego al final ordenarlas con bubblesort en el peor
de los casos (usando notación de orden)?
2. ¿Cuánto tiempo tardaría en insertar elementos en una lista enlazada ordenada que comience vacía, en el peor de los casos
(usando notación de orden)? (Tenga en cuenta que la primera inserción es rápida, pero el segundo elemento puede tomar dos
pasadas de compensación, el tercero puede tomar tres, y así sucesivamente).
3. Agregue los métodos “pass the buck” a las clases LinkedList y Node que dan como resultado que cada elemento se
imprima en orden.

2.13.5 https://espanol.libretexts.org/@go/page/55105
4. Escriba métodos de “pasar el dólar” que provoquen que se imprima la lista de artículos, pero en orden inverso.
5. Agregue métodos a las clases LinkedList y Node para que la lista enlazada pueda convertirse en una lista normal (en
cualquier orden, aunque el orden inverso es lo más natural). Por ejemplo,
print (itemlist.collect_to_list ()) debería imprimir algo como ['9', '3', '7'] .

Divide y conquista
Hasta ahora, tanto el algoritmo (bubblesort) como la estructura de datos (lista enlazada ordenada) que hemos estudiado han sido de
naturaleza lineal. Aquí, veremos cómo estas ideas se pueden extender de manera “bifurcada”.
Consideremos la lista enlazada ordenada de la última sección, la cual fue definida por una clase “controladora” (la
LinkedList ) y un número de Nodos casi idénticos, cada uno con una referencia a un objeto Node “next” en la línea.
Siempre y cuando se siguieran ciertas reglas (por ejemplo, que la lista se mantuviera en orden ordenado), esto permitió que cada
nodo tomara decisiones locales que dieron como resultado respuestas globales a las preguntas.
¿Y si le damos a cada nodo un poco más de potencia? En lugar de una sola variable de instancia self.next_n , ¿y si hubiera
dos: una self.left_n y una self.right_n ? Necesitaremos una regla correspondiente para mantener las cosas
organizadas: los artículos más pequeños van hacia la izquierda y los artículos más grandes (o de igual tamaño) van hacia la
derecha. Esta estructura de datos es el árbol binario bien conocido.
II.13_19_Binary_Tree

La ilustración anterior se ve bastante más complicada. Pero si inspeccionamos esta estructura de cerca, es bastante similar a la lista
enlazada: [3] hay una clase controladora de BinaryTree , y en lugar de una variable de instancia self.first_n , tiene
una variable de instancia llamada self.root_n , porque el nodo al que hace referencia es la “raíz” del árbol. Antes de que se
haya insertado algún elemento, self.root_n será Ninguno . Al insertar un ítem, si self.root_n es None , el
nuevo ítem va allí; de lo contrario, el buck se pasa necesariamente a self.root_n . Veremos por qué en un momento.
II.13_20_py_147_tree_constructor_insert

Ahora, para la clase Node , necesitaremos un constructor, así como métodos “get” y “set” tanto para left_n como
right_n , que inicialmente se establecen en None .
II.13_21_py_148_tree_node_getters_setters

¿Qué pasa con el método .insert_item () de un nodo? ¿Qué tipo de proceso de toma de decisiones debe suceder? El
proceso es incluso más simple que para una lista enlazada ordenada. Si cada nodo siempre sigue la regla de que los elementos más
pequeños se pueden encontrar a la izquierda y los elementos más grandes o iguales siempre se pueden encontrar a la derecha,
entonces los nuevos elementos siempre se pueden insertar en la parte inferior del árbol. En el árbol de arriba, por ejemplo, un nodo
que contiene 8 se colocaría a la derecha de (y “debajo”) del nodo que contiene 7 . El proceso de decisión para un nodo es así
como sigue:
1. ¿El nuevo artículo para insertar es menor que nuestro self.item ? Si es así, el nuevo artículo va a la izquierda:
1. ¿Es self.left_n igual a None ? Si es así, entonces necesitamos crear un nuevo nodo que contenga el nuevo
elemento, y establecer self.left_n en ese nodo.
2. Si no, podemos pasar el dólar a self.left_n .
2. De lo contrario, el artículo debe ser mayor o igual a self.item , por lo que debe ir a la derecha:
1. ¿Es self.right_n igual a None ? Si es así, entonces necesitamos crear un nuevo nodo que contenga el nuevo
elemento, y establecer self.right_n en ese nodo.
2. Si no, podemos pasarle el dólar a self.right_n .
En la figura anterior de un árbol, 8 iría a la derecha de 7 , 6 iría a la izquierda de 7 , 18 iría a la derecha de 11 , y así
sucesivamente. La lógica para insertar un nuevo elemento es, por lo tanto, bastante simple, desde el punto de vista de un nodo:
II.13_22_py_149_tree_node_insert

El método restante a considerar es el .get_smaller () del árbol. En el caso de una lista enlazada, el ítem más pequeño (si
estaba presente) era el primer nodo de la lista, y así se podía acceder sin pasar buck. Pero en el caso de un árbol binario, esto no es

2.13.6 https://espanol.libretexts.org/@go/page/55105
cierto: el elemento más pequeño se puede encontrar hasta la izquierda. El código para .get_smaller () en la clase
Tree y la clase Node correspondiente refleja esto.
II.13_23_py_150_árbol_get_más pequeño
II.13_24_PY_151_Nodo_Get_Squiest

En el caso de un nodo, si self.left_n es None , entonces el ítem de ese nodo debe ser el más pequeño, porque puede
suponer que el mensaje solo se pasó alguna vez hacia él a la izquierda. Código de uso similar al de la lista enlazada demuestra que
esta maravillosa estructura ( binarytree.py ) realmente funciona:
II.13_25_py_152_árbol_uso

La pregunta más interesante es: ¿cuánto tiempo se tarda en insertar un nuevo elemento en un árbol que ya contiene
elementos? La respuesta depende de la forma del árbol. Supongamos que el árbol es bonito y “tupido”, lo que significa que todos
los nodos excepto los de la parte inferior tienen nodos a su izquierda y derecha.
II.13_26_Binary_Tree_Bushy

El tiempo que se tarda en insertar un artículo es el número de veces que se necesita pasar el dólar para llegar al fondo del árbol. En
este caso, en cada nodo, el número total de nodos en consideración se reduce a la mitad; primero , luego , luego , y así
sucesivamente, hasta que haya un solo lugar al que el nuevo ítem podría ir. ¿Cuántas veces se puede dividir un número por
la mitad hasta alcanzar un valor de 1 (o menor)? La fórmula es . Se necesita la misma cantidad de tiempo para encontrar el
objeto más pequeño para un árbol tupido, porque la longitud hacia abajo por el lado izquierdo es la misma que cualquier otro
camino hacia una “hoja” en el árbol.

Estructura Insertar un artículo Obtener el más pequeño

Lista Simple Ordenada

Lista Enlazada Ordenada

Árbol binario “tupida”

En general, el logaritmo de es mucho más pequeño que él mismo, por lo que un árbol binario cambia algo de
velocidad en la búsqueda del elemento más pequeño para la velocidad en la inserción.
Obsérvese, sin embargo, que la forma de un árbol depende del orden en que se inserten los elementos; por ejemplo, si se inserta
10 en un árbol vacío, seguido de 9 , el 9 irá a la izquierda. Más insertando 8 lo pondrá todo el camino a la izquierda
de 9 . Así, es posible que un árbol no sea de hecho tupido, sino muy desequilibrado. Para un ejemplo extremo, si los números de
se insertaran en ese orden, el árbol se vería así:
II.13_27_Binary_Tree_Unbushy

En este caso, el árbol se ha degenerado en una lista enlazada de orden inverso. Entonces, el tiempo de inserción (para 1 , por
ejemplo) es , y encontrar el elemento más pequeño también lo es, porque el árbol está muy desequilibrado en dirección hacia la
izquierda. Desafortunadamente, en la práctica, no podemos garantizar el orden en que se insertarán los datos, y tales series de
inserciones consecutivas no son infrecuentes en los datos del mundo real.
Las estructuras más sofisticadas llamadas árboles binarios “balanceados” tienen modificaciones en sus métodos de inserción que
aseguran que el árbol se mantenga tupido, sin importar en qué orden se inserten los elementos. Esto es complicado, ya que requiere
manipular la estructura del árbol después de las inserciones, pero la manipulación de la estructura en sí no puede tardar demasiado
o se pierde el beneficio de la inserción. Ejemplos de árboles binarios balanceados incluyen los llamados árboles rojo-negros, y los
árboles AVL (que llevan el nombre de Georgy Adelson-Velsky y Evgenii Landis, quienes los describieron por primera vez).

Estructura Insertar un artículo Obtener el más pequeño

Lista Simple Ordenada

Lista Enlazada Ordenada

Árbol binario “tupida”

Árbol binario “no tupida”

Árbol binario balanceado

2.13.7 https://espanol.libretexts.org/@go/page/55105
En la práctica, no hacemos una distinción entre árboles binarios “tupidos” y “unbushy”: los árboles simples de búsqueda binaria
son tanto para .insert_item () como para .get_smaller () , porque no podemos garantizar bushiness
(recordemos que asumimos el peor de los casos al analizar un algoritmo) . Por lo tanto, las aplicaciones reales utilizan árboles AVL,
árboles rojo-negros u otras estructuras de datos más sofisticadas.

Ejercicios
1. Agregue los métodos “pass the buck” a BinaryTree y su clase Node para .print_in_order () y
.print_reverse_order () , haciendo que los elementos se impriman en orden ordenado e inverso, respectivamente.
2. Agrega métodos .count_nodes () que devuelven el número total de elementos almacenados en el árbol. ¿Cuánto
tiempo lleva, en notación de orden? ¿Depende esto de si el árbol es tupido o no? Si es así, ¿cuál sería el tiempo de ejecución de
un árbol tupido versus un árbol no tupido?
3. Agrega métodos .count_leaves () que devuelven el número total de “leaves” (nodos con None en left_n y
right_n ).
4. Los árboles de búsqueda binarios se llaman así porque pueden determinar de manera fácil y eficiente si un elemento de consulta
está presente. Agregue métodos .is_item_present () que devuelven True si existe un elemento de consulta en el
árbol y False en caso contrario (similar a LinkedList .is_item_present () ). ¿Cuánto tiempo lleva, en
notación de orden? ¿Depende esto de si el árbol es tupido o no? Si es así, ¿cuál sería el tiempo de ejecución de un árbol tupido
versus un árbol no tupido?
5. Modifique el código de árbol binario para que los elementos duplicados no se puedan almacenar en nodos separados.

Volver a Clasificación
Anteriormente dejamos el tema de la clasificación habiendo desarrollado bubblesort , un método para ordenar
una simple lista de ítems. Ciertamente podemos hacerlo mejor.
Una de las características interesantes del método insert_item () utilizado por los nodos tanto en el árbol como en la lista
enlazada es que este método, para cualquier nodo dado, se llama a sí mismo, pero en otro nodo. En realidad, no hay múltiples
copias del método almacenadas en la RAM; más bien, se comparte un solo método entre ellos, y solo el parámetro self está
cambiando realmente. Entonces, este método (que es solo una función asociada a una clase) en realidad se está llamando a sí
mismo.
De manera relacionada, resulta que cualquier función (no asociada a un objeto) puede llamarse a sí misma. Supongamos que
quisiéramos calcular la función factorial, definida como
<span translate=\ [factorial (n) = n\ times (n-1)\ times (n-2)\ times\ dots\ times2\ times1\.\]” title="Rendido por Quicklatex.com” src=”

https://bio.libretexts.org/@api/deki...8200ba4_l3.png "/>
Una de las características más interesantes de la función factorial es que se puede definir en términos de sí misma:
<span translate=\ [factorial (n) =\ begin {cases} 1,\\ n\ times factorial (n-1),\ end {cases}\ begin {aligned} &\ text {if} n = 1,\\ &\ text

{de lo contrario.} \ end {aligned}\]” title="Renderizado por Quicklatex.com” src=”


https://bio.libretexts.org/@api/deki...c10a9a6_l3.png "/>
Si quisiéramos calcular factorial (7) , una forma lógica de pensar sería: “primero, voy a computar el factorial de 6, luego
multiplicarlo por 7”. Esto reduce el problema a la computación factorial (6) , que lógicamente podemos resolver de la
misma manera. Eventualmente vamos a querer calcular factorial (1) , y darnos cuenta de que es solo 1. El código sigue
esta lógica impecablemente:
II.13_29_PY_153_Factorial

Por sorprendente que pueda ser, este bit de código realmente funciona. [4] La razón es que el parámetro n es una variable local, y
así en cada llamada de la función es independiente de cualquier otra variable n que pueda existir. [5] La llamada a
factorial (7) tiene una n igual a 7, que llama factorial (6) , que a su vez obtiene su propia n igual a 6 , y
así sucesivamente. Cada llamada espera en la línea subanswer = factorial (n-1) , y solo cuando se alcanza
factorial (1) los retornos comienzan a filtrarse de nuevo en la cadena de llamadas. Debido a que llamar a una función es
una operación rápida ( ), el tiempo que se tarda en calcular factorial (n) es , uno por cada llamada y suma
calculada en cada nivel.

2.13.8 https://espanol.libretexts.org/@go/page/55105
alt

Esta estrategia, una función que se llama a sí misma, se llama recursión. Por lo general, hay al menos dos casos considerados por
una función recursiva: (1) el caso base, que devuelve una respuesta inmediata si los datos son lo suficientemente simples, y (2) el
caso recursivo, que calcula una o más subrespuestas y las modifica para devolver la respuesta final. En el caso recursivo, los datos
sobre los que operar deben acercarse “más” al caso base. Si no lo hacen, entonces la cadena de llamadas nunca terminará.
Aplicar recursividad a ordenar elementos es relativamente sencillo. La parte más difícil será determinar qué tan rápido corre para
una lista de tallas . Ilustraremos la estrategia general con un algoritmo llamado quicksort , descrito por primera vez en
1960 por Tony Hoare.
Para una estrategia general, implementaremos la ordenación recursiva en una función llamada quicksort () . Lo primero
que hay que verificar es si la lista es de longitud 1 o 0: si es así, simplemente podemos devolverla ya que ya está ordenada. (Este es
el caso base del método recursivo.) Si no, elegiremos un elemento “pivot” de la lista de entrada; normalmente, este será un
elemento aleatorio, pero usaremos el primer elemento como pivote para empezar. A continuación, dividiremos la lista de entrada en
tres listas: lt , que contiene los elementos menores que el pivote; eq , que contiene los elementos iguales al pivote; y gt ,
que contiene elementos mayores que el pivote. A continuación, ordenaremos lt y gt para producir lt_sorting y
gt_sorting . La respuesta, entonces, es una nueva lista que contiene primero los elementos de lt_sorting , luego los
elementos de eq , y finalmente los elementos de gt_sorting .
Las partes interesantes del código a continuación son las líneas resaltadas: ¿cómo ordenamos lt y gt para producir
lt_sorting y gt_sorting ?
II.13_31_PY_154_Quicksort_1

Podríamos usar la función ordenada () incorporada de Python, pero eso es claramente trampa. Podríamos usar bubblesort,
pero al hacerlo, nuestra función sufriría el mismo límite de tiempo que para bubblesort (decimos “límite de tiempo” porque la
notación de orden esencialmente proporciona un límite superior en el uso del tiempo). La respuesta es usar recursión: porque lt
y gt deben ser ambos más pequeños que la lista de entrada, como subproblemas se están acercando al caso base, ¡y podemos
llamar a quicksort () en ellos!
II.13_32_PY_155_Quicksort_use

Intentemos analizar el tiempo de ejecución de quicksort () : ¿será mejor que, igual o peor que bubblesort? Al igual que con
los árboles binarios cubiertos anteriormente, resulta que la respuesta es “depende”.
Primero, consideraremos cuánto tiempo tarda la función en ejecutarse, sin contar las subllamadas a quicksort () , en una
lista de tamaño . El primer paso es escoger un pivote, que es rápido. A continuación, dividimos la lista de entrada en tres
sublistas. Debido a que agregar a una lista es rápido, pero todos los elementos deben agregarse a uno de los tres, el tiempo
que se pasa en esta sección es . Después de las subllamadas, las versiones ordenadas de las listas deben agregarse a la lista de
respuestas , y debido a que hay cosas que agregar, también hay tiempo . Así, sin contar las subllamadas
recursivas, el tiempo de ejecución es más que es .
Pero ese no es el final de la historia, no podemos simplemente ignorar el tiempo que se tarda en ordenar las sublistas, aunque sean
más pequeñas. Por el bien del argumento, supongamos que siempre tenemos “suerte”, y el pivote pasa a ser el elemento mediano de
la lista de entrada, así que len (lt) y len (gt) son ambos aproximadamente . Podemos visualizar la ejecución de
las llamadas a funciones como una especie de “árbol de llamadas”, donde el tamaño de los nodos representa qué tan grande es la
lista de entrada.
II.13_33_Quicksort_Optimal

Aquí, el “nodo” superior representa el trabajo realizado por la primera llamada; antes de que pueda terminar, debe llamar para
ordenar la lista lt en la segunda capa, que nuevamente debe llamar para ordenar su propia lista lt , y así sucesivamente,
hasta la capa inferior, donde se alcanza un caso base. El camino de ejecución se puede rastrear en la figura a lo largo de la línea.
Para analizar el tiempo de ejecución del algoritmo, podemos señalar que la cantidad de trabajo realizado en cada capa es , por
lo que la cantidad total de trabajo es este valor multiplicado por el número de capas. En el caso de que los pivotes dividan las listas
aproximadamente por la mitad, el resultado es el mismo que el número de capas en el árbol binario tupido: . Así, en este
escenario idealizado, el tiempo total de ejecución es , que es mucho mejor que el de bubblesort. [6]
II.13_34_NSQ_VS_Nlogn

2.13.9 https://espanol.libretexts.org/@go/page/55105
Esta ecuación supone que los pivotes dividen las listas de entrada más o menos a la mitad, lo que es probable que sea el caso (en
promedio), si elegimos los pivotes al azar. Pero no elegimos al azar; usamos nums [0] . ¿Qué pasaría si la entrada a la lista
pasara a estar ya ordenada? En este caso, el pivote siempre sería el primer elemento, lt siempre estaría vacío, y gt tendría un
elemento menos. El “árbol de trabajo” también sería diferente.
II.13_35_Quicksort_SuboPTIMAL

En este caso “desafortunado”, el trabajo en cada nivel es aproximadamente , y así sucesivamente,


Rendido por
por lo que el trabajo total es cuál es
Quicklatex.com . ¿Significa
esto que en el peor de los casos quicksort () es tan lento como bubblesort? Desgraciadamente, sí. Pero algunas personas
muy inteligentes han analizado quicksort () ( asumiendo que el elemento pivote se elige al azar, en lugar de usar el
primer elemento) y han demostrado que en el caso promedio el tiempo de ejecución es , y las posibilidades de un
rendimiento significativamente peor son astronómicamente pequeñas. Además, un método similar conocido como “mergesort”
opera garantizando una división uniforme 50/50 (de otro tipo).

Algorithm Caso promedio Peor de los casos

Bubblesort

Quicksort

Mergesort

Usando selección aleatoria para pivot, quicksort () es rápido en la práctica, aunque también se utilizan frecuentemente
algoritmos mergesort y similares. ( .sort () y sort () de Python usan una variante de mergesort llamada “Timsort.”)
Aunque como se mencionó anteriormente, el análisis del peor de los casos es más frecuente en el análisis de algoritmos,
quicksort () es una de las pocas excepciones a esta regla.
Estas discusiones sobre algoritmos y estructuras de datos pueden parecer esotéricas, pero deberían ilustrar la belleza y creatividad
posibles en la programación. Además, los métodos definidos recursivamente y las sofisticadas estructuras de datos subyacen a
muchos métodos en bioinformática, incluyendo el alineamiento de secuencias por pares, el alineamiento guiado por referencias y
los modelos ocultos de Markov.

Ejercicios
1. El primer paso para escribir mergesort () es escribir una función llamada merge () ; debe tomar dos listas
ordenadas (juntas que comprenden elementos) y devolver una versión ordenada fusionada. Por ejemplo,
merge ([1, 2, 4, 5, 8, 9, 10, 15], [2, 3, 6, 11, 12, 13]) debe devolver la lista
[1, 2, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15] , y debe hacerlo en el tiempo , donde
está el número total de elementos de ambas listas. (Tenga en cuenta que .append () en una lista de Python es tiempo
, al igual que las expresiones matemáticas como c = a + b , pero otras operaciones de lista como .insert () no lo
son.)
mergesort () primero debe dividir la lista de entrada en dos piezas de tamaño casi igual (por ejemplo,
La función
first_half = input_list [0:len (input_list) /2] ); estas se pueden ordenar recursivamente con
mergesort () , y finalmente la función merge () se puede usar para combinar las piezas ordenadas en una sola
respuesta. Si todos los pasos en la función (sin contar las llamadas recursivas) son , entonces el tiempo total será
.
Implementar merge () y mergesort () .
2. Los números de Fibonacci (1, 1, 2, 3, 5, 8, 13, etc.) son, como los factoriales, definidos recursivamente:
<span translate=\ [\ text {Fib} (n) =\ begin {cases} 1\\ 1\\\ text {Fib} (n - 1) +\ text {Fib} (n - 2)\ end {cases}\ begin {aligned} &\

text {if} n = 1,\\ &\ text {if} n = 2,\\ &\ text {de lo contrario.} \ end {aligned}\]” title="Renderizado por Quicklatex.com” src=”
https://bio.libretexts.org/@api/deki...6eb643b_l3.png "/>
Escribe una función recursiva fib () que devuelva el número th de Fibonacci ( fib (1) debería devolver 1 ,
fib (3) debería devolver 2 , fib (10) debería devolver 55 ).

2.13.10 https://espanol.libretexts.org/@go/page/55105
3. A continuación, escriba una función llamada fib_loop () que devuelva el th Fibonacci usando un bucle simple.
¿Cuál es el tiempo de ejecución, en notación de orden, en términos de ? Compara cuánto tiempo se tarda en calcular
fib (35) versus fib_loop (35) , y luego prueba fib (40) versus fib_loop (40) . ¿Por qué crees que
fib () tarda tanto más? Intente dibujar los “árboles de llamada” para fib (1) , fib (2) , fib (3) ,
fib (4) , fib (5) y fib (6) . ¿Podrías adivinar cuál es el tiempo de ejecución de esta función en notación de
orden? ¿Te imaginas alguna manera de acelerarlo?

1. Al analizar un algoritmo en Python, sin embargo, no todas las líneas son un solo paso computacional. Por ejemplo, Python tiene
una función incorporada de sort () para ordenar, y aunque no es tan lenta como bubblesort, toma mucho más de un paso
ordenar millones de números. Si un algoritmo hace uso de una función como sorting () , también se debe considerar el
tiempo de ejecución de esa (basado en el tamaño de la entrada dada). Una función que llama a nuestra función bubblesort
times, por ejemplo, se ejecutaría en el tiempo .
2. No hay ninguna razón por la que un objeto Node tampoco pueda contener otra información. Por ejemplo, los nodos podrían
tener un self.snp para contener un objeto SNP también, siendo self.item la ubicación del SNP, por lo que los
SNP se almacenan en orden de sus ubicaciones.
3. Si ignoramos todas las referencias de los nodos self.left_n (es decir, todo el lado izquierdo del árbol), entonces la
ruta self.right_n de arriba a abajo a la derecha es una lista enlazada ordenada! Del mismo modo, la ruta de arriba a la
inferior izquierda es una lista enlazada con clasificación inversa.
4. Los factoriales se pueden calcular fácilmente (y un poco más eficientemente) con un bucle, pero estamos más interesados en
ilustrar el concepto de una función de autollamada.
5. Adicionalmente, las operaciones de definir una función y ejecutarla son disjuntas, por lo que no hay nada que impida que una
función se defina en términos de sí misma.
6. Esta prueba es una prueba orientada visualmente, y no terriblemente rigurosa. Una prueba más formalizada desarrollaría lo que
se conoce como una relación de recurrencia para el comportamiento del algoritmo, en forma de Rendido por Quicklatex.com ,a
resolver , representando el tiempo que se tarda en ordenar los ítems.

This page titled 2.13: Algoritmos y estructuras de datos is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by
Shawn T. O’Neil (OSU Press) .

2.13.11 https://espanol.libretexts.org/@go/page/55105
CHAPTER OVERVIEW
3: Programación en R
El lenguaje R se especializa en el análisis estadístico de datos, y también es bastante útil para visualizar grandes conjuntos de datos.
Esta tercera parte abarca los conceptos básicos de R como lenguaje de programación (tipos de datos, sentencias if, funciones,
bucles y cuándo usarlos) así como técnicas para análisis multitest a gran escala. Otros temas incluyen clases S3 y visualización de
datos con ggplot2.
3.1: Una introducción
3.2: Variables y Datos
3.3: Vectores
3.4: Funciones R
3.5: Listas y Atributos
3.6: Marcos de datos
3.7: Carácter y Datos Categóricos
3.8: Dividir, Aplicar, Combinar
3.9: Remodelación y unión de marcos de datos
3.10: Programación Procesal
3.11: Objetos y Clases en R
3.12: Datos de trazado y ggplot2

Miniaturas: Logo para R. (CC BY-SA 4.0; Hadley Wickham y otros en RStudio vía https://www.r-project.org/logo/ RStudio)

This page titled 3: Programación en R is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil
(OSU Press) .

1
3.1: Una introducción
El lenguaje de programación R tiene una rica historia, trazando sus raíces al lenguaje S originalmente desarrollado para la
computación estadística a mediados de la década de 1970 en (¿dónde más?) Laboratorios Bell. Posteriormente, el proyecto R de
código abierto amplió las capacidades de S al tiempo que incorporaba características de lenguajes como LISP y Scheme.
Muchas características de R se comparten con Python: ambas son lenguajes interpretados de alto nivel. (Para una discusión de
lenguajes interpretados vs. compilados, véase el capítulo 13, “Hola, Mundo”.) Ambos lenguajes proporcionan una amplia gama de
características y funciones para tareas comunes, y ambos lenguajes están reforzados por una asombrosa variedad de paquetes
adicionales para análisis más especializados. Superficialmente, gran parte de su sintaxis es similar, aunque debajo de la superficie
se encuentran diferencias significativas (y fascinantes).
Prácticamente, la principal diferencia entre los dos idiomas radica en qué características y funciones incorporadas están
disponibles, y qué paquetes están disponibles para descargar. Donde Python es considerado un lenguaje de “propósito general”, R
se especializa en análisis estadísticos. ¿Necesita construir un modelo mixto no lineal para una tabla grande de valores numéricos a
partir de un experimento multifactorial? R es probablemente la herramienta de elección. ¿Necesitas contar los motivos promotores
potenciales en un conjunto de secuencias grandes? Python es probablemente un mejor candidato. R admite la funcionalidad para
los tipos de análisis de cadenas cubiertos en la sección sobre Python (como el análisis de secuencias de ADN y expresiones
regulares), pero actualmente son más fáciles de trabajar con ellos en Python. Python proporciona un excelente trazado de datos a
través de la biblioteca matplotlib , pero la biblioteca ggplot2 de R se convirtió rápidamente en una de las herramientas
dominantes para la visualización de datos desde su lanzamiento inicial en 2005.
En lo que se refiere al análisis de datos biológicos, ambas lenguas han crecido rápidamente. Los paquetes bioconductores
en R proporcionan muchas herramientas bioinformáticas estadísticas, mientras que BioPython se enfoca en algunos métodos
estadísticos y muchos métodos orientados a secuencias, como el alineamiento múltiple. Al momento de escribir esto, ambos
lenguajes parecen dirigirse hacia un conjunto de características comunes: paquetes Python relativamente recientes como
pandas , numpy , scipy y statsmodels agregan funcionalidad que ha estado presente en R durante décadas,
mientras que R ha crecido en general funcionalidad y popularidad. Por ahora, sin embargo, ambos idiomas todavía hacen grandes
adiciones al repertorio de un biólogo computacional, y ambos son apoyados por comunidades grandes y comprometidas.
Entonces, ¿cuál de estos dos lenguajes (y por supuesto Python y R están lejos de ser las únicas dos opciones) debería aprender
primero un aspirante a biólogo computacional? Bueno, la colocación de Python en este libro no es casualidad. Para la mayoría de
los usuarios, Python es una mejor “experiencia de programación introductoria”, aunque la experiencia sea breve, por un par de
razones. Primero, gran parte de Python se diseñó pensando en la educación y la facilidad de uso, facilitando la transición al
pensamiento computacional y explicando su popularidad actual en los departamentos de Ciencias de la Computación de todo el
mundo. En segundo lugar, Python comparte más similitud con otros lenguajes “convencionales” como Java, C y C++ que R,
facilitando la transferencia de conceptos en caso de que uno desee continuar en el viaje de programación. Además, R contiene una
colección mucho más grande de tipos de datos y sintaxis especializada para trabajar con ellos, así como múltiples marcos para
cosas como la asignación de variables y la orientación de objetos. Podría decirse que los programadores R efectivos tienen más que
tener en cuenta a medida que funcionan.
R es un lenguaje notablemente flexible. Con tanta flexibilidad viene tanto el poder como las interesantes formas de pensar sobre la
programación. Si bien Python enfatiza el uso de for-loops y sentencias if para controlar el flujo del programa, R proporciona una
sintaxis alternativa para la manipulación de datos a través de declaraciones lógicas sofisticadas. (Los bucles for-y las declaraciones
if se discuten al final de esta sección.) Las funciones son bastante importantes en Python, pero en R adquieren tal importancia que
se nos exige pensar en ellas a un nivel superior (como tipos de datos que pueden ser operados por otras funciones). Para muchas de
las tareas estadísticas en las que R sobresale, el código de intérprete subyacente está altamente optimizado o paralelizado para que
los análisis de millones o miles de millones de puntos de datos se puedan completar rápidamente. Por último, muchos paquetes
excelentes están disponibles solo para R.
Sin embargo, en última instancia, la respuesta a “¿qué idioma debo aprender?” es tan dinámico como “¿qué idioma debo usar?”
Hay buenos argumentos que hacer a favor (y en contra) de todas las herramientas, y los tipos de habilidades que deseas adquirir y
las necesidades situacionales jugarán un papel importante en un momento dado. Algunos consejos: eventualmente, aprender a
programar en múltiples idiomas. ¡Los beneficios de aprender más de un idioma están fácilmente a la par con aprender a programar
en primer lugar!

3.1.1 https://espanol.libretexts.org/@go/page/55159
Hola, Mundo
R es un lenguaje interpretado, lo que significa que un programa R es un archivo de texto (o múltiples archivos de texto, en algunos
casos) con comandos que son interpretados por otro programa que interactúa con la CPU y la RAM a través del sistema operativo.
En la línea de comandos, el intérprete R es simplemente R , que podemos ejecutar y enviar comandos a uno a la vez.
III.1_1_R_1_R_CMD_Hola

Cuando terminemos de trabajar con el intérprete R de esta manera, podemos ejecutar quit (save = “no”) para salir,
instruyendo que no se deben guardar los datos temporales que no hayamos guardado ya explícitamente.
III.1_2_R_2_R_CMD_QUIT

Ocasionalmente necesitaremos trabajar con el intérprete R de esta manera, particularmente cuando necesitamos instalar paquetes.
En su mayor parte, sin embargo, ejecutaremos programas R como scripts ejecutables, al igual que hicimos para Python. En este
caso, utilizamos el intérprete Rscript a través del familiar #! /usr/bin/env Línea Rscript , que como siempre
debe ser la primera línea del archivo. (Consulte el capítulo 5, “Permisos y ejecutables”, para obtener más información sobre la
creación de archivos de script ejecutables en la línea de comandos).
III.1_3_R_3_RScript_Nano

Al igual que con otros tipos de script, podemos hacer que este script sea ejecutable con chmod y ejecutarlo.
III.1_4_R_4_RScript_execute

RStudio
La programación en R en la línea de comandos funciona tan bien como cualquier otro lenguaje, pero la forma más común de
programar en R hoy en día es utilizando un entorno de desarrollo integrado (IDE) conocido como RStudio. Los IDE gráficos como
RStudio ayudan al programador a escribir código, administrar los diversos archivos asociados a un proyecto y proporcionar
sugerencias y documentación durante el proceso de programación. Muchos IDE (como Eclipse y Xcode) son piezas complicadas de
software por derecho propio. El IDE de RStudio es moderadamente complejo, pero los desarrolladores han trabajado duro para
enfocarse en la facilidad de uso específicamente para el lenguaje de programación R. Está disponible para Windows, OS X y
Linux, en http://rstudio.com. La instalación de RStudio requiere primero instalar el intérprete R desde http://www.r-project.org.
III.1_5_rstudio_primero

Cuando se abre por primera vez, RStudio presenta tres paneles. A la izquierda está la misma interfaz con el intérprete que vimos
ejecutando R en la línea de comandos. En la parte inferior derecha hay un panel que presenta pestañas para un navegador de
archivos, un navegador de ayuda y un panel donde se pueden ver los gráficos. El panel superior derecho muestra el historial de
comandos que el intérprete ha ejecutado desde que se abrió y el “entorno global”, ilustrando cierta información sobre qué variables
y datos el intérprete tiene almacenados actualmente en la memoria.
¡Ninguno de estos tres paneles, sin embargo, es el que nos interesa principalmente! Para abrir el panel más importante, necesitamos
crear un nuevo archivo “R script”, un archivo de texto de comandos R, al igual que el script ejecutable en la línea de comandos.
Para ello, utilizamos el botón con un signo más verde.
III.1_6_RStudio_Nuevo_RScript

El nuevo panel es un editor para nuestro archivo de código. Aquí hemos ingresado tres líneas de código (¡una línea como
#! /usr/bin/env Rstudio solo es necesario para ejecutar scripts R en la línea de comandos).
III.1_7_rstudio_hello_world

El panel del editor de archivos contiene una serie de botones, cuatro de los cuales vale la pena discutir de inmediato. Primero, el
botón guardar (el pequeño disquete azul) guarda el archivo: los archivos de script R tradicionalmente obtienen la extensión de
archivo .R . Más a la derecha, el botón Ejecutar envía la sección resaltada de código a la ventana del intérprete, aunque
esa sección sea de varias líneas (las cuales son ejecutadas por el intérprete en secuencia, como ocurre con la mayoría de los
idiomas). El siguiente botón (con la flecha azul loopy) vuelve a ejecutar la sección de código ejecutada más recientemente, aunque
no esté resaltada. Por último, el botón Source ejecuta todo el código del archivo, al igual que lo haría la versión Rscript
en la línea de comandos. Las salidas de Run y Source se muestran en el panel intérprete a continuación, en texto negro.

3.1.2 https://espanol.libretexts.org/@go/page/55159
Tenga en cuenta que el botón Ejecutar permite al programador ejecutar líneas de código fuera de su orden natural; podríamos
ejecutar fácilmente las líneas 2, 3 y 4 en ese orden (resaltándolas con el mouse y haciendo clic en Ejecutar ) como podríamos
4 seguido de 3 seguido de 2 (resaltando y ejecutando cada uno a su vez). ¡Resulta que los programas suelen ser sensibles al orden
en que se ejecutan las líneas de código! Entonces, en la medida de lo posible, evita el botón Ejecutar y en su lugar usa el
botón Fuente . Esto significa que las secciones de código se volverán a ejecutar a medida que desarrolle sus programas. El
beneficio es que puede estar seguro de que su código también se ejecutará en un entorno que no sea de RStudio, y será menos
probable que cree código confuso. [1] En su mayor parte, no vamos a ilustrar código directamente en RStudio, sino más bien como
simples archivos de texto y fragmentos de ellos.

Bibliotecas/Paquetes
Si bien una instalación predeterminada del intérprete R incluye una gran cantidad de funciones y paquetes, muchas bibliotecas
adicionales se han puesto a disposición en CRAN (la Comprehensive R Archive Network), proporcionando nuevas funciones
importantes. Afortunadamente, instalar estas bibliotecas desde CRAN es fácil y requiere solo el intérprete R interactivo y una
conexión a internet. [2]
Como ejemplo, instalaremos el paquete stringr , que proporciona funciones adicionales para trabajar con tipos de datos
similares a caracteres (este será el primer paquete adicional que necesitaremos en capítulos posteriores). Para instalarlo en la
consola interactiva R, necesitamos ejecutar install.packages (“stringr”) . Se le puede preguntar si el paquete debe
estar instalado en una “biblioteca personal” y si crear dicha biblioteca personal, a lo que puede responder y . También se le puede
solicitar que seleccione una ubicación geográfica cercana desde la que descargar el paquete, también conocido como “espejo”.
Una vez instalado el paquete, usarlo en un script R es tan fácil como llamar primero a la biblioteca (“stringr”) o a la
biblioteca (stringr) , después de lo cual están disponibles las funciones proporcionadas por la biblioteca. En este
ejemplo, estamos usando la función str_split () proporcionada por el paquete stringr ; la salida impresa sería
“Hello” “world” en lugar de “Hello world” .
III.1_8_R_4_2_RScript_execute

Tenga en cuenta que install.packages () necesita ejecutarse solo una vez por paquete, y generalmente debe hacerse en
el intérprete R interactivo. La función library () necesitará ser utilizada (una vez) para cada biblioteca en cada script R que
la use. Estas llamadas generalmente se recopilan cerca de la parte superior del guión.

Ejercicios
1. Si estás trabajando en la línea de comandos, crea un archivo ejecutable interpretado por Rscript y haz que imprima alguna
información. Si prefieres probar RStudio, instálalo y crea un nuevo script R, haciendo que imprima varias líneas de texto y
usando el botón Source para ejecutar todo el script. Experimenta también con los botones “Run” y “Re-Run”.
2. Si estás trabajando en RStudio, usa la interfaz para crear un nuevo “Proyecto” (representado por un directorio en el sistema de
archivos que contiene datos, scripts R y otros archivos relacionados con un proyecto de análisis) y crea varios scripts R. Para
uno de los scripts que cree, intente hacer clic en el icono “Compilar Cuaderno” (parece un pequeño bloc de notas) para crear un
informe HTML, y guárdelo en el directorio del proyecto.
3. Si está utilizando RStudio, intente crear un nuevo archivo “R Markdown” en lugar de un archivo de script R. Los archivos R
Markdown le permiten mezclar “trozos” de código R junto con la documentación de texto, que luego se puede “tejer” en un
informe HTML muy bien formateado. Guarde el informe en el directorio del proyecto.
4. Instale la biblioteca stringr a través de la consola interactiva y escriba un script que utilice la función
str_split () en ella. Si está utilizando RStudio, también se pueden instalar bibliotecas en la pestaña “Paquetes” de la
interfaz de RStudio.

1. Por otro lado, algunas secciones de código pueden funcionar durante muchos minutos o incluso horas, por lo que podría
considerar cuidadosamente evitar volver a ejecutar esas secciones de código cuando sea necesario.
2. En R, los términos “biblioteca” y “paquete” se utilizan frecuentemente como sinónimos, pero técnicamente son distintos. La
biblioteca es el directorio donde se almacenan los paquetes (colecciones de código y datos que proporcionan la funcionalidad).

This page titled 3.1: Una introducción is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil
(OSU Press) .

3.1.3 https://espanol.libretexts.org/@go/page/55159
3.2: Variables y Datos
Como la mayoría de los idiomas, R nos permite asignar datos a variables. De hecho, podemos hacerlo usando el operador =
asignación o el operador <- , aunque este último es el más comúnmente encontrado y generalmente preferido.
III.2_1_R_5_Asignación

Aquí, print () es una función, que imprime el contenido de su parámetro (a la ventana del intérprete en RStudio, o salida
estándar en la línea de comandos). Esta función tiene el “efecto secundario” de imprimir la salida pero no devuelve nada. [1] Por el
contrario, la función abs () devuelve el valor absoluto de su entrada sin ningún otro efecto.
III.2_2_R_6_ABS_Retorno

El intérprete ignora # caracteres y cualquier cosa después de ellos en una sola línea, por lo que podemos usarlos para insertar
comentarios en nuestro código para explicación o para mejorar la legibilidad. Se ignoran las líneas en blanco, por lo que podemos
agregarlas para mejorar la legibilidad también.
Puede que tengas curiosidad por qué el extra [1] está incluido en la salida impresa; pronto volveremos a ese punto, pero por
ahora, que baste decir que el número 4.4 es el primero (y único) de una colección de valores que se está imprimiendo.
El lado derecho de una asignación generalmente se evalúa primero, por lo que podemos hacer cosas complicadas como reutilizar
nombres de variables en expresiones.
III.2_3_R_7_Variable_reutilizar

Los nombres de variables y funciones en R merecen una discusión especial. Hay una variedad de convenciones, pero una común
que usaremos es la misma convención que usamos para Python: los nombres de variables deben (1) consistir solo en letras y
números y guiones bajos, (2) comenzar con una letra minúscula, (3) usar guiones bajos para separar palabras y (4) ser significativos
y descriptivos para hacer código más legibles.
En R, los nombres de variables y funciones también pueden incluir el . carácter, que no contiene ningún significado especial (a
diferencia de muchos otros idiomas). Entonces, alpha.abs <- abs (alpha) no es algo raro de ver, aunque nos
quedaremos con la convención alpha_abs <- abs (alpha) . Las variables R pueden ser casi cualquier cosa, siempre y
cuando estemos dispuestos a rodear el nombre con caracteres de retroceso. Entonces, `alpha abs` <- abs (alpha)
sería una línea válida de código, al igual que una siguiente línea como print (`alpha abs`) , aunque esto no es
recomendable.

Números, enteros, caracteres y lógicas


Uno de los tipos de datos más básicos en R es el “numérico”, también conocido como número flotante, o número flotante en otros
idiomas. [2] R incluso apoya la notación científica para estos tipos.
III.2_4_R_8_Numérico

R también proporciona un tipo separado para enteros, números que no tienen un valor fraccional. Son importantes, pero se ven con
menos frecuencia en R principalmente porque los números se crean como números numéricos, aunque parezcan enteros.
III.2_5_R_9_entero

Es posible convertir tipos numéricos a tipos enteros reales con la función.integer () , y viceversa con la
función.numeric () .
III.2_6_R_10_ASNUMERIC

Al convertir a un tipo entero, se eliminan las partes decimales y, por lo tanto, los valores se redondean hacia 0 ( 4.8 se
convierte en 4 , y -4.8 se convertiría en -4 .)
El tipo de datos “carácter” contiene una cadena de caracteres (aunque, por supuesto, la cadena puede contener solo un carácter, o
ningún carácter como en " ). Estos se pueden especificar usando comillas simples o dobles.
III.2_7_R_11_Carácter

Concatenar cadenas de caracteres es más complicado en R que en algunos otros idiomas, así que lo cubriremos en el capítulo 32,
“Carácter y datos categóricos”. (La función cat () funciona de manera similar, y nos permite incluir caracteres especiales

3.2.1 https://espanol.libretexts.org/@go/page/55116
como tabulaciones y nuevas líneas usando \ t y \ n , respectivamente; cat (“Shawn\ to'neil”) generaría algo
como Shawn O'Neil .)
Los tipos de caracteres son diferentes de los enteros y los números, y no pueden ser tratados como ellos aunque se vean como ellos.
Sin embargo, las funciones.character () y as.numeric () convertirán cadenas de caracteres al tipo
respectivo si es posible hacerlo.
III.2_8_R_12_Character_to_Numeric

Por defecto, el intérprete R producirá una advertencia ( NA inducidos por la conversión ) si tal conversión no tiene
sentido, como en como.numeric (“Shawn”) . También es posible convertir un tipo numérico o entero a un tipo de
carácter, usando como.character () .
III.2_9_R_12_Numeric_a_Character

El tipo de datos “lógico”, conocido como tipo booleano en otros idiomas, es uno de los tipos más importantes para R. Estos tipos
simples almacenan ya sea el valor especial TRUE o el valor especial FALSE (por defecto, estos también pueden ser
representados por la taquigrafía T y F , aunque esta taquigrafía es menos preferida porque algunos codificadores usan
ocasionalmente T y F para nombres de variables también). Las comparaciones entre otros tipos devuelven valores lógicos (a
menos que resulten en una advertencia o error de algún tipo). Es posible comparar tipos de caracteres con comparadores como <
y > ; la comparación se realiza en orden lexicográfico (diccionario).
III.2_10_R_13_Logicals

Pero ten cuidado: en R (y Python), tales comparaciones también funcionan cuando tal vez deberían resultar en un error: los tipos de
caracteres pueden compararse válidamente con los tipos numéricos, y los valores de caracteres siempre se consideran más grandes.
Esta propiedad en particular ha resultado en una serie de errores de programación.
III.2_11_R_14_compare_numeric_string

R soporta < , > , <= , >= , == , y ! = comparaciones, y éstas tienen el mismo significado que para las comparaciones en
Python (ver capítulo 17, “Flujo de control condicional”, para más detalles). Para los tipos numéricos, R sufre de la misma
advertencia sobre la comparación de igualdad que Python y otros lenguajes: los errores de redondeo para números con expansiones
decimales pueden componerse de formas peligrosas, por lo que la comparación numérica para la igualdad debe hacerse con
cuidado. (Se puede ver esto tratando de ejecutar print (0.2 * 0.2/0.2 == 0.2) , lo que resultará en FALSO ;
[3]
nuevamente, vea el capítulo 17 para más detalles.
) La forma “oficial” de comparar dos números para la igualdad aproximada en R es
bastante torpe: iSue (all.equal (a, b)) devuelve TRUE si a y b son
aproximadamente iguales (o, si contienen múltiples valores, todos los elementos
son).
Exploraremos algunas alternativas en capítulos posteriores.
III.2_12_R_14_2_todos_iguales

Hablando de errores de programación, porque <- es el operador de asignación preferido pero = también es un operador de
asignación, se debe tener cuidado al codificar con estos y los operadores de comparación == o < . Considera las siguientes
afirmaciones similares, todas las cuales tienen diferentes significados.
alt

R también admite conexiones lógicas, aunque éstas adquieren una sintaxis ligeramente diferente a la de la mayoría de los otros
lenguajes.

Conectivo Significado Ejemplo (con a <- 7 , b <- 3 )


& y: True si ambos lados son True a < 8 & b == 3 # Verdadero
| o: True si uno o ambos lados son True a < 8 | b == 9 # Verdadero
not:Verdadero si lo siguiente es
! ! a < 3 # Verdadero
False

Estos pueden agruparse con paréntesis, y por lo general deben ser para evitar confusiones.

3.2.2 https://espanol.libretexts.org/@go/page/55116
III.2_14_R_16_Agrupación_lógica

Al combinar expresiones lógicas de esta manera, cada lado de un ampersand o | debe dar como resultado una lógica, el código a
== 9 | 7 no es lo mismo que un == 9 | a == 7 (y, de hecho, el primero siempre resultará en VERDADERO sin
previo aviso).
Debido a que R es un lenguaje tan dinámico, a menudo puede ser útil verificar a qué tipo de datos se refiere una variable en
particular. Esto se puede lograr con la función class () , que devuelve una cadena de caracteres del tipo apropiado.
III.2_15_R_17_Clase

Esto lo haremos con frecuencia a medida que continuemos aprendiendo sobre varios tipos de datos R.

Ejercicios
Dado un conjunto de variables, a, b , c y d , encuentran asignaciones de
ellas a VERDADERO o FALSO de tal manera que la variable de resultado
1. contenga VERDADERO .
III.2_16_R_17_2_HW_Truth_Values

2. Sin ejecutar el código, intente razonar en qué resultaría print (class (class (4.5))) .
3. Intente convertir un tipo de carácter como “1e-50" a un tipo numérico con.numeric () , y uno como
“1x10^5" . ¿Cuáles son los valores numéricos después de la conversión? Intente convertir el valor numérico
0.00000001 a un tipo de carácter: ¿cuál es la cadena producida? ¿Cuáles son los números más pequeños y grandes que
puedes crear?
4. La función is.numeric () devuelve el TRUE lógico si su entrada es de tipo numérico, y FALSE en caso contrario.
Las funciones es.character () , is.integer () e is.logical () hacen lo mismo para sus respectivos
tipos. Intente usar estos para probar si las variables específicas son tipos específicos.
5. ¿Qué sucede cuando ejecutas una línea como print (“ABC"* 4) ? ¿Qué pasa
con la impresión (“ABC” + 4) ? ¿Por qué crees que los resultados son lo que son? ¿Qué tal
imprimir (“ABC” + “DEF”) ? Por último, intente lo siguiente: imprimir (VERDADERO + 5) ,
imprimir (VERDADERO + 7) , imprimir (FALSO + 5) , imprimir (FALSO + 7) ,
imprimir (VERDADERO * 4) e imprimir (FALSO * 4) . ¿Qué crees que está pasando aquí?

1. El intérprete R también imprimirá el contenido de cualquier variable o valor devuelto sin ser asignado a una variable. Por
ejemplo, las líneas alfa y 3 + 4 son equivalentes a print (alpha) e print (3 + 4) . Tales impresiones
“sin impresión” son comunes en el código R, pero preferimos la llamada más explícita y legible a la función print () .
2. Esto refleja el uso más común del término “numérico” en R, aunque quizás no el más preciso. R tiene un tipo doble que
implementa números de coma flotante, y técnicamente tanto estos como los enteros son subtipos de números .
3. Debido a que los números enteros se almacenan por defecto como números numéricos (en lugar de enteros), esto puede causar
cierta incomodidad al intentar compararlos. Pero debido a que los números enteros se pueden almacenar exactamente como
números (sin redondear), declaraciones como 4 + 1 == 5 , equivalente a 4.0 + 1.0 == 5.0 , resultarían en
TRUE . Aún así, algunos casos de división pueden causar un problema, como en
(1/5) * (1/5)/(1/5) == (1/5) == (1/5) .

This page titled 3.2: Variables y Datos is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil
(OSU Press) .

3.2.3 https://espanol.libretexts.org/@go/page/55116
3.3: Vectores
Los vectores (similares a las matrices de tipo único en otros lenguajes) son colecciones ordenadas de tipos simples, generalmente
numéricos, enteros, caracteres o lógicas. Podemos crear vectores usando la función c () (para concatenar), que toma como
parámetros los elementos a poner en el vector:
III.3_1_R_18_Numeric_Vector

La función c () también puede tomar otros vectores como parámetros; “deconstruirá” todos los subvectores y devolverá un
vector grande, en lugar de un vector de vectores.
III.3_2_R_19_C_deconstructo

Podemos extraer elementos individuales de un vector usando la sintaxis [] ; aunque tenga en cuenta que, a diferencia de muchos
otros lenguajes, el primer elemento está en el índice 1.
III.3_3_R_20_Bracket_Extract

La función length () devuelve el número de elementos de un vector (o tipos similares, como listas, que cubriremos más
adelante) como un entero:
III.3_4_R_21_vec_longitud

Podemos usar esto para extraer el último elemento de un vector, por ejemplo.
imagen

Sin “datos desnudos”: los vectores tienen (a) clase


En lo que va de nuestra discusión sobre los tipos de datos de R, hemos estado haciendo una simplificación, o al menos hemos
estado dejando algo fuera. Incluso los valores individuales como el 4.6 numérico son en realidad vectores de longitud uno. Es
decir, gc_content <- 0.34 es equivalente a gc_content <- c (0.34) , y en ambos casos,
length (gc_content) devolverá 1 , que en sí mismo es un vector de longitud uno. Esto se aplica a los números,
enteros, lógicos y tipos de caracteres. Así, al menos comparado con otros lenguajes, R no tiene “datos desnudos”; el vector es la
unidad de datos más básica que tiene R. Esto es un poco más confuso para los tipos de caracteres que otros, ya que cada elemento
individual es una cadena de caracteres de cualquier longitud (incluyendo potencialmente la cadena “vacía” “” ).
III.3_6_R_22_2_Char_VEC

Esto explica bastante sobre R, incluyendo algunas curiosidades como por qué print (gc_content) imprime
[1] 0.34 . Esta salida indica que gc_content es un vector, cuyo primer elemento es 0.34 . Considere la función
seq () , que devuelve un vector de números; toma tres parámetros: [1] (1) el número en el que comenzar, (2) el número en el
que terminar, y (3) el tamaño del paso.
III.3_7_R_23_SEQ_Range

Cuando imprimimos el resultado, obtendremos una salida como la siguiente, donde la lista de números está formateada de tal
manera que abarca el ancho de la ventana de salida.
III.3_8_R_24_SEQ_RANGE_OUT

Los números entre paréntesis indican que el primer elemento del vector impreso es 1.0 , el decimosexto elemento es 8.5 y el
trigésimo primer elemento es 16.0 .
Por cierto, para producir una secuencia de enteros (en lugar de numéricos), se puede dejar el argumento de tamaño de paso, como
en seq (1,20) . Esto equivale a una taquigrafía comúnmente vista, 1:20 .
Si todos nuestros enteros, lógicas, etc. son realmente vectores, y podemos decir su tipo ejecutando la función class () en
ellos, entonces los vectores deben ser las cosas de las que estamos examinando la clase. Entonces, ¿y si intentamos mezclar tipos
dentro de un vector, por ejemplo, incluyendo un entero con algunas lógicas?
III.3_9_R_25_Mix_Class1

print (class (mix)) dará como resultado “integer” . De hecho, si intentamos imprimir mezcla con
Ejecutar
print (mix) , encontraríamos que las lógicas se han convertido en enteros!

3.3.1 https://espanol.libretexts.org/@go/page/55117
III.3_10_R_26_mix_class1_out

R ha optado por convertir TRUE en 1 y FALSE en 0 ; estos son valores binarios estándar para true y false, mientras que
no hay un valor lógico estándar para un entero dado. Del mismo modo, si se agrega un numérico, todo se convierte a numérico.
III.3_11_R_27_Mix_Class2

Y si se agrega una cadena de caracteres, todo se convierte en una cadena de caracteres (con 3.5 convirtiéndose en “3.5" ,
TRUE convirtiéndose en “TRUE” , y así sucesivamente).
III.3_12_R_28_MIX_CLASS3

En resumen, los vectores son la unidad más básica de datos en R, y no pueden mezclar tipos: R autoconvertirá cualquier tipo mixto
en un solo vector a un “denominador común más bajo”, en el orden de lógico (más específico), entero, numérico, carácter (más
general). Esto a veces puede resultar en errores difíciles de encontrar, especialmente al leer datos de un archivo. Si un archivo tiene
una columna de lo que parece ser números, pero un solo elemento no puede interpretarse como un número, todo el vector se puede
convertir a un tipo de carácter sin advertencia ya que se lee el archivo. Discutiremos la lectura de datos de archivos de texto
después de examinar los vectores y sus propiedades.

Subestablecimiento de Vectores, Sustitución Selectiva


Considere el hecho de que podemos usar la sintaxis [] para extraer elementos individuales de vectores:
III.3_13_R_29_extracto_vec1

Con base en lo anterior, sabemos que los 20 extraídos son un vector de longitud uno. El 2 utilizado entre corchetes también es
un vector de longitud uno; así la línea anterior es equivalente a second_el <- nums [c (2)] . ¿Significa esto que
podemos usar vectores más largos para extraer elementos? ¡Sí!
III.3_14_R_30_extracto_vec2

De hecho, los elementos extraídos incluso se colocaron en el vector de dos elementos resultante en el orden en que se extrajeron (el
tercer elemento seguido del segundo elemento). Podemos usar una sintaxis similar para reemplazar selectivamente elementos por
índices específicos en vectores.
III.3_15_R_31_selective_replace_index

El reemplazo selectivo es el proceso de reemplazar elementos seleccionados de un vector (o estructura similar) especificando qué
elementos reemplazar con [] sintaxis de indexación combinada con asignación <- . [2]
Los vectores R (y muchos otros tipos de contenedores de datos) pueden ser nombrados, es decir, asociados con un vector de
caracteres de la misma longitud. Podemos establecer y posteriormente obtener este vector de nombres usando la función
names () , pero la sintaxis es un poco extraña.
imagen

Los vectores con nombre, cuando se imprimen, también muestran sus nombres. El resultado de arriba:
III.3_17_R_33_Nombrado_Vec1_out

Los vectores nombrados pueden no parecer tan útiles ahora, pero el concepto será bastante útil más adelante. Los vectores con
nombre nos dan otra forma de subconjunto y reemplazar selectivamente en vectores: por nombre.
III.3_18_R_34_Nombre_Vec_Set

Aunque R no lo hace cumplir, los nombres deben ser únicos para evitar confusiones a la hora de seleccionar o reemplazar
selectivamente de esta manera. Al actualizar la puntuación de Estudiantes A y Estudiantes B, el cambio se refleja en la salida:
III.3_19_R_35_Nombrado_Vec_Set_out

Hay una forma final y extremadamente poderosa de subestablecer y reemplazar selectivamente en un vector: por vector lógico. Al
indexar con un vector de lógicas de la misma longitud que el vector a indexar, podemos extraer solo aquellos elementos donde el
vector lógico tenga un valor VERDADERO .
III.3_20_R_36_Selección_lógica

Si bien la indexación por número de índice y por nombre nos permite extraer elementos en cualquier orden dado, la indexación por
lógica no nos brinda esta posibilidad.

3.3.2 https://espanol.libretexts.org/@go/page/55117
También podemos realizar el reemplazo selectivo de esta manera; supongamos que Estudiantes A y C retoman sus cuestionarios y
mejoran moderadamente sus puntajes.
III.3_21_R_37_Lógical_Repuesto

Y la salida impresa:
III.3_22_R_38_Logical_Replacement_out

En este caso, la longitud del vector de reemplazo ( c (159, 169)) es igual al número de valores VERDADEROS en el
vector de indexación ( c (VERDADERO, FALSO, VERDADERO) ); exploraremos si esto es un requisito a continuación.
En resumen, tenemos tres formas importantes de indexar en/seleccionar desde/reemplazar selectivamente en vectores:
1. por vector de número de índice,
2. por vector de caracteres (si se nombra el vector), y
3. por vector lógico.

Operaciones vectorizadas, valores NA


Si los vectores son la unidad de datos más básica en R, todas las funciones y operadores con los que hemos estado trabajando,
como.numeric () , * e incluso comparaciones como > —funcionan implícitamente sobre vectores enteros.
III.3_23_R_39_Vectorizado_Conversion

En este ejemplo, cada elemento del vector de caracteres se ha convertido, de modo que la clase (numérica) devolverá
“numérico” . La cadena de caracteres final, “9b3x” , no se puede convertir razonablemente a un tipo numérico, por lo que
ha sido reemplazada por NA . Cuando esto sucede, el intérprete produce un mensaje de advertencia:
NA introducidos por coerción .
NA es un valor especial en R que indica datos faltantes o un cálculo fallido de algún tipo (como al intentar convertir “9b3x”
a un numérico).
La mayoría de las operaciones que involucran valores NA devuelven valores
NA; por ejemplo, NA + 3 devuelve NA , y muchas funciones que operan en vectores
enteros devuelven un NA si algún elemento es NA .
Un ejemplo canónico es la función mean () .
III.3_24_R_40_Mean_na

Dichas funciones suelen incluir un parámetro opcional que podemos dar, na.rm = TRUE , especificando que los valores NA
deben eliminarse antes de que se ejecute la función.
III.3_25_R_41_Mean_na_RM

Si bien esto es conveniente, hay una manera de eliminar los valores de NA de cualquier vector (ver más abajo).
Otros valores especiales en R incluyen NaN , para “Not a Number”, devueltos por cálculos como la raíz cuadrada de -1,
sqrt (-1) e Inf para “Infinity”, devueltos por cálculos como 1/0 . ( Inf/Inf , por cierto, devuelve NaN .)
Volviendo al concepto de operaciones vectorizadas, también se vectorizan operaciones aritméticas simples como + , * , / ,
- , ^ (exponente) y %% (módulo), lo que significa que una expresión como 3 * 7 es equivalente a
c (3) * c (7) . Cuando los vectores son más largos que un solo elemento, la operación se realiza elemento por elemento.
III.3_26_R_42_Vec_mult
III.3_27_multiplicación_vectorizada_

Si consideramos el operador * , toma dos entradas (numéricas o enteras) y devuelve una salida (numérica o entera) para cada par
de los vectores. Esto es bastante similar a la comparación > , que toma dos entradas (numéricas o enteras o caracteres) y devuelve
una lógica.
III.3_28_R_43_Vec_Comparar

3.3.3 https://espanol.libretexts.org/@go/page/55117
Reciclaje de vectores
¿Qué pasa si tratamos de multiplicar dos vectores que no tienen la misma longitud? Resulta que el más corto de los dos se
reutilizará según sea necesario, en un proceso conocido como reciclaje vectorial, o la reutilización del vector más corto en una
operación vectorizada.
III.3_29_R_44_Vector_Reciclaje

Esto funciona bien cuando se trabaja con vectores de longitud uno contra vectores más largos, ya que el vector de longitud uno se
reciclará según sea necesario.
III.3_30_R_45_Vector_Reciclado_Sencillo

Si la longitud del vector más largo no es un múltiplo de la longitud del más corto, sin embargo, el último reciclado irá solo a la
mitad.
III.3_31_R_46_vector_reciclado_noneven

Cuando esto sucede, el intérprete imprime una advertencia: la


longitud del objeto más larga no es un múltiplo de la longitud del objeto más
corta
. Son pocas las situaciones en las que este tipo de reciclaje parcial no sea un accidente, y se debe evitar.
El reciclaje de vectores también se aplica al reemplazo selectivo; por ejemplo, podemos reemplazar selectivamente cuatro
elementos de un vector con elementos de un vector de dos elementos:
III.3_32_R_46_Vector_Reciclado_Repuesto

Más a menudo reemplazaremos selectivamente elementos de un vector con un vector de longitud uno.
III.3_33_R_47_vector_reciclado_reemplazo_individual

Estos conceptos, cuando se combinan con indexación vectorial de diversos tipos, son bastante poderosos. Considera que una
expresión como valores > 35 es ella misma vectorizada, con el vector más corto (sosteniendo solo 35 ) siendo reciclado
de tal manera que lo que se devuelve es un vector lógico con valores VERDADEROS donde los elementos de valores son
mayores que 35 . Podríamos usar este vector como vector de indexación para reemplazo selectivo si lo deseamos.
III.3_34_R_48_vector_reciclado_reemplazo_lógico

Más sucintamente, en lugar de crear una variable temporal para select_vec , podemos colocar los
valores de expresión > 35 directamente entre paréntesis.
III.3_35_R_49_vector_reciclado_reemplazo_lógico2

Del mismo modo, podríamos usar el resultado de algo así como media (valores) para reemplazar todos los elementos de
un vector mayor que la media con 0 fácilmente, ¡sin importar el orden de los elementos!
III.3_36_R_50_vector_reciclado_reemplazo_logical3

Más a menudo, vamos a querer extraer dichos valores usando selección lógica.
III.3_37_R_51_vector_reciclado_selección

Este tipo de selecciones vectorizadas, especialmente cuando se combinan con vectores lógicos, son una parte poderosa e importante
de R, así que estudiarlas hasta que tengas confianza con la técnica.

Ejercicios
1. Supongamos que tenemos r como un rango de números de 1 a 30 en pasos de 0.3; r<- seq (1, 30, 0.3) . Usando
solo la función.integer () , la indexación lógica y las comparaciones como > , genera una secuencia
r_decimales que contiene todos los valores de r que no son enteros redondos. (Es decir, debe contener todos los
valores de r excepto 1.0, 2.0, 3.0, y así sucesivamente. Debe haber 297 de ellos.)
2. Mencionamos brevemente el operador %% , o “módulo”, que devuelve el resto de un número después de la división entera (por
ejemplo, 4%% 3 == 1 y 4%% 4 == 0 ; también se vectoriza). Dado cualquier vector r , por ejemplo
r <- seq (1, 30, 0.3) , producir un vector r_every_other que contiene todos los demás elementos de r .

3.3.4 https://espanol.libretexts.org/@go/page/55117
Es probable que desee usar %% , la comparación de igualdad == , y también puede que desee usar seq () para generar
un vector de índices de la misma longitud que r .
Vuelva a hacer lo mismo, pero modifique el código para extraer cada tercer elemento de r en un vector llamado
r_every_third .
3. Del capítulo 27, “Variables y Datos”, sabemos que las comparaciones como == , ! =, >= también están disponibles.
Además, ¡eso lo sabemos ! niega los valores de un vector lógico, mientras que & combina dos vectores lógicos con “y” y
| combina dos vectores lógicos con “o”. Úselos, junto con el operador %% discutido anteriormente, para producir un vector
div_3_4 de todos los enteros entre 1 y 1,000 (inclusive) que son uniformemente divisibles por 3 y uniformemente
divisibles por 4. (Hay 83 de ellos.) Crear otro, not_div_5_6 , de números que no sean divisibles uniformemente por 5 o 6.
(Son 667 de ellos. Por ejemplo, no se debe incluir 1,000 porque es divisible por 5, y 18 no debe incluirse porque es divisible por
6, sino 34 debe ser porque no es divisible por ninguno.)

Funciones Vectoriales Comunes


Como los vectores (específicamente los vectores numéricos) son tan ubicuos, R tiene docenas (cientos, en realidad) de funciones
que hacen cosas útiles con ellos. Si bien no podemos cubrirlos todos, podemos cubrir rápidamente algunos que serán importantes
en futuros capítulos.
Primero, ya hemos visto las funciones seq () y length () ; la primera genera un vector numérico que comprende una
secuencia de números, y la segunda devuelve la longitud de un vector como un vector entero de un solo elemento.
III.3_38_R_52_SEQ_LONGITUD

Presentados sin un ejemplo, la media () , sd () y la mediana () devuelven la media, la desviación estándar y la


mediana de un vector numérico, respectivamente. (Siempre que ninguno de los elementos de entrada sea NA , aunque los tres
aceptan el parámetro na.rm = TRUE ). Generalizando median () , la función quantile () devuelve el percentil Y
de una función, o múltiples percentiles si el segundo argumento tiene más de un elemento.
III.3_39_R_53_Cuantil

La salida es un vector numérico con nombre:


III.3_40_R_54_cuantile_out

La función unique () elimina duplicados en un vector, dejando los elementos restantes en orden de su primera aparición, y la
función rev () invierte un vector.
III.3_41_R_55_Unique_rev

Existe la función sort () , que ordena un vector (en orden natural para números y enteros, y orden lexicográfico (diccionario)
para vectores de caracteres). Quizás más interesante es la función order () , que devuelve un vector entero de índices que
describen dónde se necesitarían colocar los elementos originales del vector para producir un orden ordenado.
III.3_42_R_56_Orden

En este ejemplo, el vector de orden, 2 5 3 4 1 , indica que el segundo elemento de rev_uniq vendría primero, seguido
del quinto, y así sucesivamente. Así podríamos producir una versión ordenada de rev_uniq con
rev_uniq [order_rev_uniq] (en virtud de la selección basada en índices de vectores), o más sucintamente con
rev_uniq [order (rev_uniq)] .
III.3_43_pedido_ejemplo

Es importante destacar que esto nos permite reorganizar múltiples vectores con un orden común determinado por uno único. Por
ejemplo, dados dos vectores, id y score , que están relacionados por elementos, podríamos decidir reorganizar ambos
conjuntos en orden alfabético para id .
III.3_44_R_57_Order_Multisort

La función sample () devuelve un muestreo aleatorio a partir de un vector de un tamaño dado, ya sea con reemplazo o sin
como se especifica con el parámetro replace = ( FALSE es el valor predeterminado si no se especifica).
III.3_45_R_58_Muestra

3.3.5 https://espanol.libretexts.org/@go/page/55117
La función rep () repite un vector para producir un vector más largo. Podemos repetir de manera elemento por elemento, o
sobre todo el vector, dependiendo de si se usa o no el parámetro each = .
III.3_46_R_59_rep

Por último (pero no menos importante) para esta discusión es la función is.na () : dado un vector con elementos que
posiblemente sean valores NA , devuelve un vector lógico elementos enteros son TRUE en índices donde el original era NA ,
permitiéndonos indicar fácilmente cuál elementos de vectores son NA y los eliminan.
III.3_47_R_60_es_NA

Observe el uso del signo de exclamación en lo anterior para negar el vector lógico devuelto por is.na () .

Generación de datos aleatorios


R sobresale en trabajar con distribuciones de probabilidad, incluyendo generar muestras aleatorias a partir de ellas. Muchas
distribuciones son compatibles, incluyendo la Normal (Gaussiana), Log-Normal, Exponencial, Gamma, T de Student, y así
sucesivamente. Aquí solo veremos generar muestras a partir de unas pocas para utilizarlas en ejemplos futuros.
Primero, la función rnorm () genera un vector numérico de una longitud dada muestreado a partir de la distribución Normal
con media especificada (con media = ) y desviación estándar (con sd = ).
III.3_48_R_61_RNorm

De manera similar, la función runif () muestrear a partir de una distribución uniforme limitada por un valor mínimo y
máximo.
III.3_49_R_62_rUnif

El rexp () genera datos a partir de una distribución exponencial con un parámetro de “tasa” dado, controlando la tasa de
decaimiento de la función de densidad (la media de muestras grandes se acercará a 1.0/tasa ).
III.3_50_R_63_REXP
III.3_51_distribución_formas

R incluye una gran cantidad de pruebas estadísticas, aunque no vamos a estar cubriendo mucho en la forma de las estadísticas que
no sean algunos ejemplos de manejo. La función t.test () ejecuta una prueba t de student de dos lados comparando las
medias de dos vectores. Lo que se devuelve es un tipo de datos más complejo con clase “htest” .
III.3_52_R_63_2_Ttest

Cuando se imprime, este complejo tipo de datos se formatea a sí mismo en una salida agradable y legible por humanos:
III.3_53_R_63_3_ttest_out

Lectura y Escritura de Datos Tabulares, Envoltura de Líneas Largas


Antes de ir mucho más allá, vamos a querer poder importar datos a nuestros programas R desde archivos externos (que asumiremos
que son filas y columnas de datos en archivos de texto). Esto lo haremos con read.table () , y el resultado será un tipo de
datos conocido como “data frame” (o data.frame en código). Cubriremos los matices de los marcos de datos más adelante,
pero tenga en cuenta por ahora que pueden considerarse como una colección de vectores (de igual longitud), uno por cada columna
de la tabla.
Como ejemplo, supongamos que tenemos un archivo de texto separado por tabulaciones en nuestro directorio de trabajo actual
llamado states.txt . [3] Cada fila representa uno de los estados de Estados Unidos junto con información sobre población,
ingreso per cápita, tasa de analfabetismo, tasa de homicidios (por 100,000), porcentaje de graduados de secundaria y región (todos
medidos en la década de 1970). La primera fila contiene una línea de “encabezado” con nombres de columna.
III.3_54_R_63_4_Estados_Top

Posteriormente en el expediente, alguien ha decidido anotar la línea de Michigan, indicándola como el estado “manopla”:
III.3_55_R_63_5_Estados_Mitten

Como la mayoría de las funciones, read.table () toma muchos parámetros potenciales (23, de hecho), pero la mayoría de
ellos tienen valores predeterminados razonables. Aún así, hay cinco más o menos que comúnmente necesitaremos establecer.

3.3.6 https://espanol.libretexts.org/@go/page/55117
Debido a la necesidad de establecer tantos parámetros, el uso de read.table () a menudo resulta en una larga línea de
código. Afortunadamente, el intérprete R nos permite romper largas líneas sobre varias líneas, siempre y cuando cada línea termine
en un carácter que no complete la expresión (así el intérprete sabe que necesita seguir leyendo las siguientes líneas antes de
ejecutarlas). Las opciones de carácter común son la coma y el signo más. Cuando envolvemos una larga línea de esta manera, es
costumbre sangrar las siguientes líneas para indicar su continuidad de manera visual.
III.3_56_R_64_LEER_TABLE_ESTADOS

Al leer states.txt , el parámetro file = especifica el nombre del archivo a leer, mientras que header = TRUE
indica al intérprete que la primera línea del archivo da los nombres de columna (sin él, los nombres de columna serán “V1" ,
“V2" , “V3" y así sucesivamente). El parámetro sep = “\ t” indica que los caracteres de tabulación se utilizan para
separar las columnas en el archivo (el valor predeterminado es cualquier espacio en blanco), y comment.char = “#”
indica que # caracteres y cualquier cosa posterior a ellos deben ser ignorados mientras se lee el archivo (lo cual es apropiado,
como es evidente por la anotación # manopla en el archivo). El parámetro stringsasFactors = FALSE es más
críptico: le dice al intérprete que deje las columnas character-vector (como region en este ejemplo) como vectores de
caracteres, en lugar de convertirlas al tipo de datos de factor más sofisticado (para ser cubierto más adelante capítulos).
En este punto, la variable states contiene el marco de datos que contiene las columnas (vectores) de datos. Podemos
imprimirlo con print (states) , pero el resultado es bastante de salida:
III.3_57_R_65_Estados_Imprimir

Podría tener más sentido extraer solo las primeras 10 filas de datos e imprimirlas, lo que podemos hacer con la función
head () ( head () también puede extraer solo los primeros elementos de un vector largo).
III.3_58_R_66_CABEZA_ESTADOS

Las funciones nrow () y ncol () devuelven el número de filas y columnas de un marco de datos, respectivamente (que
se prefiere sobre length () , que devuelve el número de columnas); la función dim () devuelve un vector de dos
elementos con número de filas (en el índice 1) y número de columnas (en el índice 2).
Como se mencionó anteriormente, las columnas individuales de un marco de datos son (casi siempre) vectores. Para acceder a uno
de estos vectores individuales, podemos usar una sintaxis $ especial, con el nombre de la columna siguiendo la $ .
III.3_59_R_67_Estados_Col_extracto

Siempre y cuando el nombre de la columna sea lo suficientemente simple (en particular, siempre y cuando no tenga espacios),
entonces las comillas alrededor del nombre de la columna pueden omitirse (y a menudo son) omitidas.
III.3_60_R_68_Estados_col_extracto_sincotizaciones

Aunque esta sintaxis se puede utilizar para extraer una columna de un marco de datos como un vector, tenga en cuenta que también
se refiere al vector dentro del marco de datos. En cierto sentido, states$income es el vector almacenado en el marco de
datos de estados . Así podemos usar técnicas como el reemplazo selectivo para trabajar con ellos al igual que cualquier otro
vector. Aquí, reemplazaremos todas las instancias de “North Central” en el vector states$region con solo el término
“Central”, renombrando efectivamente la región. [4]
III.3_61_R_69_Estados_Col_Rename_Norte_Central

Escribir un marco de datos en un archivo separado por tabuladores se logra con la función write.table () . [5] Al igual que
con read.table () , write.table () puede tomar bastantes parámetros, la mayoría de los cuales tienen valores
predeterminados razonables. Pero hay seis más o menos que vamos a querer establecer con más frecuencia que otros. Escribamos
el marco de datos de estados modificados en un archivo llamado states_modified.txt como un archivo separado
por tabuladores.
III.3_62_R_69_2_Estados_escribir

Los dos primeros parámetros aquí son el marco de datos a escribir y el nombre del archivo en el que escribir. El parámetro
quote = FALSE especifica que las comillas no deben escribirse alrededor de los tipos de caracteres en la salida (por lo que la
columna name tendrá entradas como Alabama y Alaska en lugar de “Alabama” y “Alaska” ). El
sep = “\ t” indica que las pestañas deben separar las columnas, mientras que fila.names = FALSE indica que los
nombres de fila no deben escribirse (porque no contienen ninguna información significativa para este marco de datos), y

3.3.7 https://espanol.libretexts.org/@go/page/55117
col.names = TRUE indica que queremos la columna nombres de salida a la primera línea del archivo como una línea de
“encabezado”.

R y la línea de comandos Unix/Linux


En el capítulo 26, “Una introducción”, mencionamos que los scripts R se pueden ejecutar desde la línea de comandos usando el
#! /usr/bin/env Entorno ejecutable Rscript . (Las versiones anteriores de R requerían que el usuario
ejecutara un comando como R CMD BATCH Scriptname.r , pero hoy se prefiere usar Rscript ). Dedicamos más
discusión a interconectar Python con el entorno de línea de comandos que lo haremos R, en parte porque R no se usa con tanta
frecuencia de esa manera, sino también porque es bastante fácil.
Al usarread.table () , por ejemplo, los datos se pueden leer desde la entrada estándar usando el nombre de archivo
“stdin” . Cualquier cosa que se imprima a partir de un script R va a la salida estándar por defecto. Debido a que R hace una
buena cantidad de formato al imprimir, sin embargo, a menudo es más conveniente imprimir marcos de datos usando
write.table () especificando file = “” .
Finalmente, para obtener los parámetros de la línea de comandos en un script R como vector de caracteres, los
args de línea <- CommandArgs (TrailingOnly = TRUE) harán el truco. Aquí hay un script simple que leerá
una tabla en la entrada estándar, la escribirá en la salida estándar y también leerá e imprimirá cualquier argumento de línea de
comandos:
III.3_63_R_69_3_Stdin_Stdout

Intenta hacer que este script sea ejecutable en la línea de comandos, y ejecutarlo en p450s_blastp_yeast_top1.txt
con algo como cat p450s_blastp_yeast_top1.txt |. /stdin_stdout_ex.r arg1 'arg 2' .

Ejercicios
1. Supongamos que tenemos algún vector numérico de longitud impar (por ejemplo,
muestra<- c (3.2, 5.1, 2.5, 1.6, 7.9) o muestra <- runif (25, min = 0, max = 1) ).
Escribe algunas líneas de código que den como resultado la impresión de la mediana del vector, sin usar las funciones
median () o quantile () . Puede que le resulten útiles las funciones length () y as.integer () .
2. Si muestra es una muestra de una distribución exponencial, por ejemplo,
muestra <- rexp (1000, tasa = 1.5) , entonces la mediana de la muestra es generalmente menor que la
media. Genere un vector, entre_median_media , que contenga todos los valores de muestra que sean mayores que
(o iguales a) la mediana de la muestra, y menores que (o iguales a) la media de la muestra.
3. Lea en el archivo states.txt en un marco de datos como se describe. Extraiga un vector numérico llamado
asesino_lowincome que contenga tasas de asesinato solo para aquellos estados con ingresos per cápita menores que la
mediana del ingreso per cápita (puede usar la función median () esta vez). De igual manera, extraer un vector llamado
asesino_highincome que contiene tasas de asesinato solo para aquellos estados con mayor que (o igual a) el ingreso
per cápita medio. Ejecutar una prueba t.test () de dos muestras para determinar si las tasas medias de homicidios
son diferentes entre estos dos grupos.
4. Sea estados el marco de datos de información de estado descrito anteriormente. Describir lo que hacen las diversas
operaciones a continuación en términos de indexación, reemplazo selectivo, reciclaje de vectores y los tipos de datos
involucrados (por ejemplo, vectores numéricos y vectores lógicos). Para comenzar, la primera línea agrega una nueva columna
al marco de datos de estados llamada “newpop” que contiene la misma información que la columna
“population” . III.3_64_R_70_Describe_Selective_Replacement_HW
5. Determine el número de regiones únicas que se enumeran en el marco de datos de estados . Determinar el número de
regiones únicas representadas por estados con ingresos mayores a la mediana.
6. ¿Qué informa la función sum () para un vector numérico c (2, 3, 0, 1, 0, 2 ) ? ¿Qué tal para
c (1, 0, 0, 1, 1, 0 ) ? Y, finalmente, ¿qué tal para el vector lógico
c (VERDADERO, FALSO, FALSO, VERDADERO, CIERTO, FALSO ) ¿Cómo podría ser útil la función
sum () en un contexto lógico?

1. La mayoría de las funciones R toman una gran cantidad de parámetros, pero muchos de ellos son opcionales. En el siguiente
capítulo, veremos cómo se ven estos parámetros opcionales y cómo obtener una extensa lista de todos los parámetros que

3.3.8 https://espanol.libretexts.org/@go/page/55117
pueden tomar las funciones R incorporadas.
2. El término “reemplazo selectivo” no es ampliamente utilizado fuera de este libro. En algunas situaciones se emplea el término
“reemplazo condicional”, pero quisimos definir alguna terminología concreta para plasmar la totalidad de la idea.
3. Cuando se ejecuta en la línea de comandos, el directorio de trabajo actual se hereda del shell. En RStudio, el directorio de
trabajo actual se establece en el directorio “proyecto” si el archivo forma parte de una carpeta de proyecto. En cualquier caso, es
posible cambiar el directorio de trabajo desde dentro de R usando el directorio setwd () , como en
setwd (“/home/username/rproject”) en Unix/Linux y
setwd (“C: /Documents and settings/username/my documents/rproject”) en Windows.
También es posible especificar nombres de archivo por ruta absoluta, como en
/home/username/rproject/states.txt , sin importar el directorio de trabajo actual.
4. Si tiene alguna familiaridad con R, es posible que se haya topado con la función attach () , que toma un marco de datos
y da como resultado la creación de un vector separado para cada columna. Generalmente, “desmontar” un marco de datos de
esta manera es una mala idea; después de todo, ¡las columnas de un marco de datos generalmente están asociadas entre sí por
una razón! Además, esta función da como resultado la creación de muchas variables con nombres basados en los nombres de
columna del marco de datos. Debido a que estos nombres no están claramente delimitados en el código, es fácil crear errores
difíciles de encontrar y mezclar columnas de múltiples marcos de datos de esta manera.
5. También hay funciones más especializadas tanto para leer como para escribir datos tabulares, como read.csv () y
write.csv () . Nos hemos centrado en read.table () y write.table () porque son lo suficientemente
flexibles como para leer y escribir tablas en una variedad de formatos, incluyendo separados por comas, separados por
tabuladores, etc.

This page titled 3.3: Vectores is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil (OSU
Press) .

3.3.9 https://espanol.libretexts.org/@go/page/55117
3.4: Funciones R
Si bien podríamos seguir cubriendo la sintaxis vectorial única y poderosa de R, es hora de divertirnos y aprender sobre las
funciones. Las funciones en R son similares a sus contrapartes Python: encapsulan un bloque de código, haciéndolo reutilizable
además de permitirnos considerar el bloque aisladamente del resto del programa. Como ocurre con las funciones en la mayoría de
los idiomas, las funciones R constan de tres partes principales:
1. La entrada (parámetros dados a la función).
2. El bloque de código que se va a ejecutar usando esos parámetros. En R, los bloques se definen por un par de corchetes
coincidentes, { y } .
3. La salida de la función, llamada el valor de retorno. Esto puede ser opcional si la función “hace algo” (como print ())
en lugar de “devuelve algo”.
Consideremos el problema de determinar qué elementos de dos vectores numéricos, digamos vec1 y vec2 , están lo
suficientemente cerca como para igualarlos como para llamarlos iguales. Como se menciona en el capítulo 27, “Variables y Datos”,
la forma estándar de verificar si todos los elementos en dos vectores de igual longitud son aproximadamente iguales por pares es
usar iSue (all.equal (vec1, vec2)) , que devuelve un solo VERDADERO si este es el caso y un solo FALSO
si no.
III.4_1_R_71_TODAS LAS FUNCIONES_EQUAL_

Pero tal vez preferimos un vector lógico que indique qué elementos son aproximadamente iguales. La forma más sencilla de
hacerlo es comparando la diferencia absoluta entre los elementos con algún pequeño valor épsilon.
III.4_2_R_72_Equalish_Pre

Como revisión del último capítulo, lo que está sucediendo aquí es que la -operación se vectoriza sobre los lados izquierdo- y
derecho, produciendo un vector (usando reciclaje vectorial si uno de los dos fuera más corto, lo cual no es el caso aquí; ver capítulo
28), como es la función abs () , que lleva un vector y devuelve un vector de valores absolutos. De igual manera, el operador
< se vectoriza, y debido a que épsilon es un vector de longitud uno, por lo que se compara con todos los elementos del
resultado de abs (vec1 - vec2) utilizando reciclaje de vectores, para el resultado final de un vector lógico.
Debido a que este tipo de operación es algo que podríamos querer realizar muchas veces, podríamos escribir una función para ello.
En este caso, llamaremos a nuestra función equalish () ; aquí está el código R para definir y ejecutar dicha función.
III.4_3_R_72_ecualish_función_equalish_

Aquí hay muchas cosas a tener en cuenta. Primero, al definir una función, definimos los parámetros que puede tomar.
Los parámetros en las funciones R tienen una posición (a está en la posición 1,
b está en la posición 2 y épsilon está en la posición 3) y un nombre (a, b y
épsilon ).
Algunos parámetros pueden tener un valor predeterminado: el valor que deberían tener si no se especifica lo contrario, mientras que
otros parámetros pueden ser requeridos: el usuario de la función debe especificarlos. Los valores por defecto se asignan dentro de
la lista de parámetros con = (no <- como en la asignación de variables estándar).
El bloque que define las operaciones realizadas por la función está encerrado entre corchetes, generalmente con el corchete de
apertura en la misma línea que la definición de la lista de funciones/parámetros, y el corchete de cierre en su propia línea. Hemos
sangrado las líneas que pertenecen al bloque de función por dos espacios (una convención R). Aunque no se requiere, esta es una
buena idea, ya que hace que el código sea mucho más legible. El valor que devuelve la función se especifica con una llamada a una
función return () especial: las funciones solo pueden devolver un valor, aunque podría ser algo sofisticado como un vector
o un marco de datos. [1]
Después de que se haya definido una función, se le puede llamar, como en eq <- equalish (vec1, vec2) . Los
nombres de variables asociados con los datos fuera de la función (en este caso vec1 y vec2 ) no necesitan coincidir con los
nombres de los parámetros dentro de la función ( a y b ). Este es un punto importante al que volveremos.
En la llamada anterior, dejamos que el parámetro epsilon tome su valor predeterminado de 0.00001 . Alternativamente,
podríamos usar una comparación más estricta.
III.4_4_R_73_ecualish_función_estricta

3.4.1 https://espanol.libretexts.org/@go/page/55148
En R, los argumentos a las funciones pueden especificarse por posición (como en el ejemplo anterior), por nombre, o por una
combinación.
III.4_5_R_74_args_por_nombre

Muchas funciones R toman algunos parámetros requeridos y muchos parámetros no requeridos con valores predeterminados
razonables; este esquema de llamadas nos permite especificar los parámetros requeridos así como solo aquellos no requeridos que
deseamos cambiar.
En general, primero debe especificar parámetros por posición (si desea especificar alguno por posición), luego por nombre. Aunque
las siguientes convocatorias funcionarán, son bastante confusas.
imagen

Frecuentemente usamos parámetros predeterminados para especificar parámetros con nombre en funciones llamadas dentro de la
función que estamos definiendo. Aquí hay un ejemplo de una función que calcula la diferencia en medias de dos vectores; toma un
parámetro opcional Remove_NAS que por defecto es FALSE . Si esto se especifica como VERDADERO , el parámetro
na.rm en las llamadas a mean () se establece en TRUE también en el cálculo.
III.4_7_R_75_2_Diff_mean

Para la continuidad con otras funciones R, podría haber tenido más sentido llamar al parámetro na.rm ; en este caso,
modificaríamos las líneas de cálculo para que se leyeran como m1 <- mean (vec1, na.rm = na.rm) . Aunque pueda
parecer que el intérprete R estaría confundido por los nombres de variables duplicados, el hecho de que el parámetro mean ()
na.rm tenga el mismo nombre que la variable que se pasa no causará ningún problema.

Variables y alcance
Hagamos un experimento rápido. Dentro de nuestra función, el resultado de la variable ha sido asignado con el
resultado de línea <- abs (a - b) < épsilon . Después de ejecutar la función, ¿es posible acceder a esa
variable imprimiéndola?
III.4_8_R_77_Experiment1

¡La impresión no funciona!


III.4_9_R_78_experiment1_out

Esta variable no se imprime porque, como en la mayoría de los idiomas, las variables asignadas dentro de las funciones tienen un
alcance local a ese bloque de función. (El alcance de una variable es el contexto en el que se puede acceder a ella). Lo mismo
ocurre con las variables de parámetros: no tendríamos más éxito con print (a) , print (b) o print (epsilon)
fuera de la función.
Una de las mejores características de estas variables locales es que son independientes de cualquier variable que ya pueda existir.
Por ejemplo, la función crea una variable llamada result (que ahora sabemos que es una variable local con el alcance del
bloque de función). ¿Y si, fuera de nuestra función, también tuviéramos una variable de resultado utilizada para un
propósito completamente diferente? ¿Sobrescribiría la función su contenido?
III.4_10_R_79_Experiment2

Fiel a la independencia de la variable de resultado local dentro de la función, el contenido del resultado externo no se
sobrescribe.
III.4_11_R_80_Experimento2_fuera

Esta característica de cómo funcionan las variables dentro de las funciones puede parecer algo extraña, pero el resultado es
importante: las funciones pueden estar completamente encapsuladas. Si están diseñados correctamente, su uso no puede afectar el
contexto del código en el que se utilizan (la única forma en que las funciones R estándar pueden afectar al “mundo exterior” es
devolver algún valor). Las funciones que tienen esta propiedad y siempre devuelven el mismo valor dadas las mismas entradas (por
ejemplo, no tienen componente aleatorio) se denominan “puras”. Pueden tratarse como cajas negras abstractas y diseñarse
aisladamente del código que las utilizará, y el código que las utilice puede diseñarse sin tener en cuenta las partes internas de las
funciones que llama. Este tipo de diseño reduce drásticamente la carga cognitiva para el programador.

3.4.2 https://espanol.libretexts.org/@go/page/55148
Ahora, probemos el experimento inverso: si una variable se define fuera de la función (antes de que se llame), ¿se puede acceder a
ella desde dentro de la definición de bloque de función?
III.4_12_R_81_Experiment3

La falta de error en la salida indica que sí, el bloque de función puede acceder a tales variables externas:
III.4_13_R_82_Experiment3_out

Esto significa que es posible escribir funciones que no toman parámetros y simplemente acceder a las variables externas que
necesitarán para el cálculo.
III.4_14_R_83_Experiment4

Pero escribir tales funciones es bastante mala práctica. ¿Por qué? Porque aunque la función todavía no puede afectar al entorno
externo, ahora depende bastante del estado del entorno externo en el que se le llama. La función solo funcionará si existen variables
externas llamadas vec1 , vec2 y epsilon y tienen los tipos de datos correctos cuando se llama a la función.
Considera esto: la versión anterior de la función podría copiarse y pegarse en un
programa completamente diferente y aún así garantizarse que funcione (porque los
parámetros a y b son variables locales requeridas), pero ese no es el caso aquí.
Las mismas cuatro “reglas” para diseñar funciones en Python se aplican a R:
1. Las funciones solo deben acceder a variables locales que hayan sido asignadas dentro del bloque de función, o que hayan sido
pasadas como parámetros (ya sea requeridos o con valores predeterminados).
2. Documentar el uso de cada función con comentarios. ¿Qué parámetros se toman y qué tipos deberían ser? ¿Es necesario que los
parámetros se ajusten a alguna especificación o hay alguna advertencia sobre el uso de la función? Además, ¿qué se devuelve?
3. Las funciones no deberían ser “demasiado largas”. Esto es subjetivo y dependiente del contexto, pero la mayoría de los
programadores se sienten incómodos con funciones que tienen más de una página de largo en su ventana de editor. La idea es
que una función encapsula una idea única, pequeña y reutilizable. Si te encuentras escribiendo una función que es difícil de leer
y entender, considera dividirla en dos funciones que necesitan ser llamadas en secuencia, o una función corta que llame a otra
función corta.
4. ¡Escribe muchas funciones! Incluso si un bloque de código solo va a ser llamado una vez, está bien hacer una función de él (si
encapsula alguna idea o bloque bien separable). Después de todo, nunca se sabe si podría necesitar volver a usarlo, y solo el
acto de encapsular el código te ayuda a asegurar que sea correcto y olvidarte de él cuando trabajes en el resto de tu programa.

Paso de argumentos y semántica de variables


Hasta ahora, las diferencias que hemos visto entre Python y R han estado mayormente en el énfasis de R en las operaciones
vectorizadas. En capítulos posteriores, también veremos que R enfatiza el uso creativo de las funciones con más fuerza que Python
(lo que al menos debería ser una buena razón para estudiarlas bien).
Hay otra diferencia dramática entre estos dos lenguajes, que tiene que ver con las variables y su relación con los datos. Esto es
probablemente más fácil de ver con un par de ejemplos de código similares. Primero, aquí hay un código Python que declara una
lista de números nums , crea una nueva variable basada en la original llamada numsb , modifica el primer elemento de
numsb , y luego imprime ambos.
III.4_15_R_83_2_py_nums

La salida indica que nums y numsb son variables (o “nombres”, en el lenguaje Python) para los mismos datos subyacentes.
III.4_16_R_83_3_py_nums_out

El código R correspondiente y la salida revelan que R maneja variables de manera muy diferente:
III.4_17_R_83_4_R_nums
III.4_18_R_83_5_R_nums_out

Mientras que en Python es común que los mismos datos subyacentes sean referenciados por múltiples variables, en R, las variables
únicas casi siempre están asociadas con datos únicos. A menudo, estas semánticas se enfatizan en el contexto de variables locales
para funciones. Aquí está lo mismo, pero la operación está mediada por una llamada de función. Primero, la versión y salida de
Python:
III.4_19_R_83_6_Py_Func

3.4.3 https://espanol.libretexts.org/@go/page/55148
III.4_20_R_83_7_Py_Func_out

Y ahora la versión R y salida:


III.4_21_R_83_8_R_Func
III.4_22_R_83_9_R_Func_out

En el código Python, la variable local param es una nueva variable para los mismos datos subyacentes, mientras que en el
código R la variable param local es una nueva variable para nuevos datos subyacentes. Estos dos paradigmas se encuentran en
una amplia variedad de lenguajes; este último se conoce como “pass-by-value”, aunque uno podría pensarlo como “pass-by-copy”.
Esto no significa que R siempre cree una copia, usa una estrategia de “copiar sobre escritura” detrás de escena para evitar el exceso
de trabajo. En cuanto a la primera, la documentación de Python se refiere a ella como “pass-by-assignment”, y el efecto es similar a
“pass-by-reference”. (El término “pass-by-reference” tiene una definición técnica muy estrecha, pero a menudo se usa como un
“catch-all” para este tipo de comportamiento).
Hay ventajas e inconvenientes en ambas estrategias. El esquema algo más difícil utilizado por Python es a la vez más rápido y
permite implementaciones más fáciles de algunos algoritmos sofisticados (como las estructuras cubiertas en el capítulo 25,
“Algoritmos y estructuras de datos”). El esquema de paso por valor, por otro lado, puede ser más fácil de codificar, porque las
funciones que siguen la regla 1 anterior no pueden modificar subrepticiamente los datos: están “libres de efectos secundarios”.

Obteniendo Ayuda
El intérprete R viene con una amplia documentación para todas las funciones que están incorporadas. Ahora que sabemos escribir
funciones, leer esta documentación será fácil.
Se puede acceder a la página de ayuda para una función en particular ejecutando help (“function_name”) en la consola
interactiva, como en help (“t.test”) para obtener ayuda con la función t.test () .
III.4_23_R_84_Help1

Alternativamente, si el nombre de la función no contiene caracteres especiales, podemos usar la taquigrafía


? nombre_función , como en ? t.test . La ayuda se proporciona en una ventana interactiva en la que puedes usar las
teclas de flecha para desplazarte hacia arriba y hacia abajo.
III.4_24_R_85_Help1_out

Las páginas de ayuda generalmente tienen las siguientes secciones, y puede haber otras:
Descripción: Breve descripción de la función.
Uso: Un ejemplo de cómo se debe llamar a la función, generalmente listando los parámetros más importantes; los parámetros
con valores predeterminados se muestran con un signo igual.
Argumentos: Lo que hace cada parámetro aceptado por la función.
Detalles: Notas sobre la función y advertencias a tener en cuenta al usar la función.
Valor: Lo que devuelve la función.
Referencias: Cualquier artículo de revista pertinente o citas de libros. Estos son particularmente útiles para funciones
estadísticas complejas.
Ejemplos: Código de ejemplo usando la función. Desafortunadamente, se escriben muchos ejemplos para aquellos que están
familiarizados con los conceptos básicos de la función, e ilustran un uso más complejo.
Ver también: Otras funciones relacionadas que un usuario puede encontrar interesantes.
Si una función pertenece a un paquete (como str_split () en el paquete stringr ), se puede cargar primero el paquete
(con library (stringr)) y acceder a la ayuda de la función como de costumbre ( help (“str_split”) ), o
especificar el paquete directamente, como en help (“str_split”, package = “stringr”) . Se puede acceder a
una página de ayuda general para todas las funciones del paquete con ayuda (package = “stringr”) .
Por último, en la ventana interactiva, utilizando help.search (“average”) buscará en la documentación todas las
funciones mencionando el término “promedio” —el atajo para esto es ?? promedio.

3.4.4 https://espanol.libretexts.org/@go/page/55148
Ejercicios
1. A menudo deseamos “normalizar” un vector de números restando primero la media de cada número y luego dividiendo cada
uno por la desviación estándar de los números. Escribe una función llamada normalize_mean_sd () que tome dicho
vector y devuelva la versión normalizada. La función debería funcionar incluso si algún valor es NA (la versión normalizada
de NA debería ser simplemente NA ).
2. La función t.test () prueba si las medias de dos vectores numéricos son desiguales. Existen múltiples versiones de t -
tests: algunos asumen que las varianzas de los vectores de entrada son iguales, y otras no hacen esta suposición. Por defecto, ¿
t.test () asume varianzas iguales? ¿Cómo se puede cambiar este comportamiento?
3. Usando la documentación de ayuda, genere un vector de 100 muestras a partir de una distribución de Poisson con el parámetro
lambda (que controla la forma de la distribución) establecido en 2.0 .
4. La siguiente función calcula la diferencia en la media de dos vectores, pero rompe al menos una de las “reglas” para escribir
funciones. Arreglarlo para que se ajuste. (Tenga en cuenta que también le falta la documentación adecuada.)
III.4_25_R_85_2_FIX_FUNC

5. El siguiente código genera dos muestras aleatorias, y luego calcula e imprime la diferencia en el coeficiente de variación para
las muestras (definida como la desviación estándar dividida por la media). Explique cómo funciona este código, paso a paso, en
términos de variables locales, parámetros y valores devueltos. Y si inmediatamente antes de la
muestra1 <- rnorm (100, media = 4, sd = 2 ) , tuviéramos
resultado <- “Mensaje de prueba. " , y después de print (answer) , teníamos
print (result) ? ¿Qué se imprimiría y por qué? III.4_26_R_85_3_Explain_Func

1. Cualquier variable que se indique simplemente (sin asignación) en una función será devuelta. Entonces, esta definición es
equivalente: III.4_27_R_9000_ecualish_función_no_retorno Algunos programadores de R prefieren esta sintaxis; para este texto, sin embargo,
nos apegaremos a usar el return () más explícito. Esto también ayuda a diferenciar entre tales “devoluciones sin
retorno” y “impresiones sin impresión” (ver la nota al pie en el Capítulo 27, Variables y Datos).

This page titled 3.4: Funciones R is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil (OSU
Press) .

3.4.5 https://espanol.libretexts.org/@go/page/55148
3.5: Listas y Atributos
El siguiente tipo de datos importante en nuestro recorrido por R es la lista. Las listas son bastante similares a los vectores: son
colecciones ordenadas de datos, indexables por número de índice, vector lógico y nombre (si se nombra la lista). Sin embargo, las
listas pueden contener varios tipos diferentes de datos (incluidas otras listas). Supongamos que tenemos tres vectores diferentes que
representan alguna información sobre la planta Arabidopsis thaliana.
III.5_1_R_86_ATAL_DATA

Entonces podemos usar la función list () para reunir estos vectores en una sola unidad con la clase “list” .
III.5_2_R_87_athal_list

Gráficamente, podríamos representar esta lista así:


imagen

Aquí, la sintaxis [1] indica que los elementos de la lista son vectores (como en cuando se imprimen vectores). Al igual que los
vectores, las listas se pueden indexar por vector índice y vector lógico.
III.5_4_R_88_athal_sublist

Ambos de lo anterior asignan a la sublista de variables una lista parecida a:


imagen

Esto parece bastante sencillo: al subestablecer una lista con un vector de indexación se devuelve una lista más pequeña con los
elementos solicitados. Pero esta regla puede ser engañosa si olvidamos que un vector es el elemento más básico de los datos.
Debido a que 2 es el vector de longitud uno c (2) , athal [2] devuelve no el segundo elemento de la lista athal ,
sino más bien una lista de longitud uno con un solo elemento (el vector de ecotipos).
III.5_6_R_89_Atal_Eco_List

Una representación gráfica de esta lista:


imagen

Por lo tanto, necesitaremos una sintaxis diferente si queremos extraer un elemento individual de una lista. Esta sintaxis alternativa
es athal [[2]] .
III.5_8_R_90_Atal_Eco_List

Si quisiéramos extraer el segundo ecotipo directamente, necesitaríamos usar el


second_ecotype relativamente torpe <- athal [[2]] [2] , que accede al segundo elemento del vector
(accedido por [2] ) dentro del del segundo elemento de la lista (accedido por [[2]] ).
III.5_9_R_91_Athal_list_print

Cuando imprimimos una lista, esta estructura y la sintaxis de doble corchete se refleja en la salida.
III.5_10_R_92_athal_list_print_out

Listas con nombre, listas dentro de listas


Al igual que los vectores, las listas se pueden nombrar, asociadas con un vector de caracteres de igual longitud, usando la función
names () . Podemos usar un vector índice de nombres para extraer una sublista, y podemos usar la sintaxis [[]] para
extraer elementos individuales por nombre.
III.5_11_R_93_Atal_list_nombre_extracto1

Incluso podemos extraer elementos de una lista si el nombre del elemento que queremos se almacena en otra variable, usando la
sintaxis [[]] .
III.5_12_R_94_Atal_list_nombre_extracto2

Por muy divertida que sea esta sintaxis de doble corchete, debido a que extraer elementos de una lista por su nombre es una
operación común, hay un atajo usando sintaxis $ .
III.5_13_R_95_Atal_list_nombre_extracto3

3.5.1 https://espanol.libretexts.org/@go/page/55108
De hecho, si el nombre no contiene ningún carácter especial (espacios, etc.), entonces se pueden dejar las comillas.
III.5_14_R_96_Atal_list_nombre_extracto4

Este atajo es ampliamente utilizado y conveniente, pero, debido a que las comillas están implícitas, no podemos usar la sintaxis
$ para extraer un elemento por nombre si ese nombre está almacenado en una variable intermediaria. Por ejemplo, si
extract_name <- “ecotipos” , entonces athal$extract_name se expandirá a
athal [["extract_name"]] , y no obtendremos el vector de ecotipos. Este error común refleja un malentendido del
azúcar sintáctico empleado por R. Del mismo modo, la sintaxis $ no funcionará para nombres como “# Cromosomas”
porque ese nombre contiene un espacio y un carácter especial (por esta razón, los nombres de los elementos de la lista suelen
simplificarse).
Frecuentemente, la sintaxis $ se combina con la sintaxis vectorial si el elemento de la lista al que se hace referencia es un vector.
Por ejemplo, podemos extraer directamente el tercer ecotipo, o establecer el tercer ecotipo.
III.5_15_R_97_athal_list_nombre_dollar_vector

Continuando con este ejemplo, supongamos que tenemos otra lista que describe información sobre cada cromosoma. Podemos
comenzar con una lista vacía, y asignarle elementos por nombre.
III.5_16_R_98_Chrs_list

Esta lista de dos elementos se relaciona con A. thaliana, por lo que tiene sentido incluirla de alguna manera en la lista athal .
Afortunadamente, las listas pueden contener otras listas, por lo que asignaremos esta lista de chrs como elemento de la lista
athal .
III.5_17_R_99_Atal_estructura_completa

Las listas son un excelente contenedor para colecciones generales de datos heterogéneos en un solo “objeto” organizado. (Estos
difieren de los objetos Python en que no tienen métodos almacenados en ellos también, pero veremos cómo funciona R con
métodos en capítulos posteriores). Si corriéramos print (athal) en este punto, toda esta información sería impresa, pero
desafortunadamente de una manera bastante antipática:
III.5_18_R_100_athal_estructura_completa_print

Este resultado sí ilustra algo de interés, sin embargo. Podemos encadenar la sintaxis $ para acceder a elementos de listas y listas
contenidas por nombre. Por ejemplo, length <- athal$chrinfo$length extrae el vector de longitudes contenido en
la lista interna de ChrInfo , e incluso podemos modificar elementos de estos vectores con sintaxis como
athal$chrinfo$geneCounts [1] <- 7079 (quizás un nuevo gen fue descubierto recientemente en el primer
cromosoma). Ampliando la sintaxis un poco para usar corchetes dobles en lugar de notación $ , estas son equivalentes a
longitudes <- athal [["ChrInfo"]] [["Longitudes"]] y
athal [["ChrInfo"]] [["GeneCounts"]] [1] <- 7079 .

Atributos, Eliminación de Elementos, Estructura de Lista


Las listas son una excelente manera de organizar datos heterogéneos, especialmente cuando los datos se almacenan en una
asociación Nombre → Valor, [1] facilitando el acceso a los datos por nombre de carácter. Pero, ¿y si queremos buscar alguna
información asociada a un dato pero no representada en los propios datos? Esto sería un tipo de “metadatos”, y R nos permite
asociar metadatos a cualquier dato usando lo que se llama atributos. Supongamos que tenemos un vector simple de datos
normalmente distribuidos:
III.5_19_R_101_ATTR_NORM_VEC

Posteriormente, podríamos querer saber qué tipo de datos son estos: ¿se distribuyen normalmente, o algo más? Podemos resolver
este problema asignando el término “normal” como atributo de los datos. El atributo también necesita un nombre, al que
llamaremos “disttype” . Los atributos se asignan de manera similar a los nombres.
III.5_20_R_102_ATTR_NORM_VEC_ATTR

Cuando se imprime, la salida muestra los atributos que también se han asignado.
III.5_21_R_103_ATTR_NORM_VEC_ATTR_OUT

3.5.2 https://espanol.libretexts.org/@go/page/55108
Podemos extraer por separado un atributo dado de un elemento de datos, usando sintaxis como
sample_dist <- attr (sample, “disttype”) . Los atributos se utilizan ampliamente en R, aunque rara vez se
modifican en el uso diario del idioma. [2]
Para ampliar nuestro ejemplo de A. thaliana, asignemos un atributo de “reino” al vector especie.
III.5_22_R_104_ATHAL_ATHAL_ATRO

En este punto, hemos construido una estructura bastante sofisticada: una lista que contiene vectores (uno de los cuales tiene un
atributo) y otra lista, en sí misma que contiene vectores, con los diversos elementos de la lista siendo nombrados. Si fuéramos a
ejecutar print (athal) , veríamos una salida bastante desordenada. Afortunadamente, R incluye una alternativa a
print () llamada str (), que imprime muy bien la estructura de una lista (u otro objeto de datos). Aquí está el
resultado de llamar a str (athal) en este punto.
III.5_23_R_105_Athal_str

Eliminar un elemento o atributo de una lista es tan simple como asignarle el valor especial NULL .
III.5_24_R_106_Athal_Delete

La estructura impresa revela que esta información ha sido eliminada.


III.5_25_R_107_Atal_Delete_out

¿Cuál es el punto de toda esta lista detallada y asignación de atributos? Resulta ser bastante importante, porque muchas funciones R
devuelven exactamente este tipo de listas complejas cargadas de atributos. Considere la función t.test () , que compara las
medias de dos vectores para la igualdad estadística:
III.5_26_R_108_TTEST_PRINT

Cuando se imprime, el resultado es un resultado muy bien formateado y legible por humanos.
III.5_27_R_109_TTEST_PRINT_OUT

Si ejecutamos str (tresult) , sin embargo, encontramos la verdadera naturaleza de tresult : ¡es una lista!
III.5_28_R_110_TTEST_STR

Dado el conocimiento de esta estructura, podemos extraer fácilmente elementos específicos, como el valor p con
pval <- tresult$p.value o pval <- tresult [["p.value"]] .
Una nota final sobre listas: vectores (y otros tipos) se pueden convertir en una lista con la función.list () . Esto será útil
más adelante, porque las listas son uno de los tipos de datos más generales en R, y podemos utilizarlas para representaciones de
datos intermediarios.
III.5_29_R_110_2_com_lista_como

Ejercicios
El siguiente código genera primero una muestra aleatoria llamada a, y luego una
muestra llamada respuesta , donde cada elemento de respuesta es un elemento de
un veces 1.5 más algo de ruido aleatorio: III.5_30_R_110_3_LINEAR_DEPEND A continuación,
1. podemos crear fácilmente un modelo lineal que predice valores de
respuesta de a : III.5_31_R_110_4_LM_Modelo Cuando se imprime, la salida describe muy bien los parámetros del modelo.
III.5_32_R_110_5_LM_Model_outTambién podemos probar fácilmente la significancia de los parámetros con la función anova ()

(para ejecutar una prueba de análisis de varianza en el modelo). III.5_33_R_110_6_ANOVALa salida nuevamente muestra texto muy
bien formateado: III.5_34_R_110_7_Anova_out Del modelo , extraer el coeficiente de a en una variable llamada a_coeff (que
contendría solo el número 1.533367 para esta muestra aleatoria) .Siguiente, de vartest
extraer el valor p asociado con el coeficiente a en un vector llamado a_pval
(para esta muestra aleatoria, el valor p es 2.2e-16 ).
2. Escribe una función llamada simple_lm_pval () que automatiza el proceso anterior; debería tomar dos parámetros
(dos vectores numéricos potencialmente dependientes linealmente) y devolver el valor p asociado al primer coeficiente
(nonintercept).

3.5.3 https://espanol.libretexts.org/@go/page/55108
3. Cree una lista que contenga tres muestras aleatorias de diferentes distribuciones (por ejemplo, de rnorm () ,
runif () y rexp () ), y agregue un atributo para “disttype” a cada una. Use print () y str () en
la lista para examinar los atributos que agregó.
4. Algunos nombres se pueden usar con notación $ sin comillas; si l <- list (values = c (20, 30)) , entonces
print (l$values) imprimirá el vector interno. Por otro lado, si
l <- list (“val-entries” = c (20, 30)) , entonces se requieren cotizaciones como en forma
impresa (l$"val-entries”) . Por experimentación, determinar al menos cinco caracteres diferentes que requieren
el uso de comillas al usar notación $ .
5. Experimenta con las funciones is.list () y as.list () , probando cada una de ellas tanto en vectores como en
listas.

1. Las listas R se utilizan a menudo como diccionarios en Python y tablas hash en otros idiomas, debido a esta operación de
búsqueda de nombre → valor fácil y efectiva. Cabe señalar que (al menos a partir de R 3.3), las búsquedas de nombres en las
listas no son tan eficientes como las búsquedas de nombres en diccionarios Python u otras tablas hash verdaderas. Para una
operación eficiente y más idiomática de tabla/diccionario hash, también existe el hash del paquete disponible para instalar
con install.packages (“hash”) .
2. Por ejemplo, los nombres de un vector se almacenan como un atributo llamado “nombres”
—los nombres de líneas (puntuaciones) <- c (“Estudiante A”, “Estudiante B”,
“Estudiante C”)
y
attr (puntuaciones, “nombres”) <- c (“Estudiante A”, “Estudiante B”, “Estudiante
C”)
son (casi) equivalentes. Aún así, se recomienda usar funciones especializadas como names () en lugar de establecerlas
con attr () porque la función names () incluye comprobaciones adicionales sobre la cordura del vector names.

This page titled 3.5: Listas y Atributos is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil
(OSU Press) .

3.5.4 https://espanol.libretexts.org/@go/page/55108
3.6: Marcos de datos
En el capítulo 28, “Vectores”, presentamos brevemente los marcos de datos como tablas de almacenamiento de datos. Ahora que
tenemos una comprensión clara tanto de los vectores como de las listas, podemos describir fácilmente los marcos de datos. (Si te
saltaste apresuradamente los capítulos 28 y 30 para aprender sobre los marcos de datos, ¡ahora es el momento de volver a ellos!)
Los marcos de datos son esencialmente listas nombradas, donde los elementos son vectores que representan columnas. Pero los
marcos de datos proporcionan algunas características más que simples listas de vectores. Aseguran que los vectores de columna
componente sean siempre de la misma longitud, y nos permiten trabajar con los datos por fila así como por columna. Los marcos
de datos son algunos de los tipos de datos más útiles y ubicuos en R.
Si bien ya hemos cubierto el uso de la función read.table () para producir un marco de datos basado en el contenido de
un archivo de texto, también es posible crear un marco de datos a partir de un conjunto de vectores.
III.6_1_R_111_DF_desde_rasguño
alt

Cuando se imprimen, el contenido de los vectores de columna se muestra cuidadosamente, con los nombres de las columnas a lo
largo de la parte superior y los nombres de fila a lo largo del lado izquierdo
Al igual que con read.table () , la función data.frame () toma un argumento opcional
StringSasFactors , que especifica si los vectores de caracteres (como ids ) deben convertirse a tipos de factores (los
cubriremos en detalle más adelante). Por ahora, deshabilitaremos esta conversión.
Ejecutar str (gene_info) revela la naturaleza de lista del marco de datos:
III.6_3_R_113_DF_desde_Scratch_str

Al igual que elementos de listas, las columnas de marcos de datos no tienen que tener nombres, pero no tenerlos es poco común. La
mayoría de los marcos de datos obtienen nombres de columna cuando se crean (ya sea por read.table () o
data.frame () ), y si se desestablecen, generalmente son V1 , V2 , y así sucesivamente. Los nombres de las columnas
se pueden acceder y establecer con la función names () , o con la función colnames () más apropiada.
III.6_4_R_114_DF_Colnames

Para resaltar la naturaleza tipo lista de los marcos de datos, podemos trabajar con marcos de datos por columna de manera muy
similar a listas por elemento. Las tres líneas del siguiente ejemplo dan como resultado que sub_info sea un marco de datos de
dos columnas.
III.6_5_R_115_DF_extracto_cols

Por lo tanto, una expresión como gene_info [2] no devolvería un vector numérico de longitudes, sino un marco de datos
de una sola columna que contiene el vector numérico. Podemos usar la sintaxis [[]] y la sintaxis $ para referirnos también a
los vectores contenidos dentro de los marcos de datos (este último es mucho más común).
III.6_6_R_116_DF_EXTRACT_COL_ELEMENTS

Incluso podemos eliminar columnas de un marco de datos estableciendo el elemento en NULL , como en
gene_info$length <- NULL .
El verdadero encanto de los marcos de datos es que podemos extraer y de otra manera trabajar con ellos por fila. Así como los
marcos de datos tienen nombres de columna, también tienen nombres de fila: un vector de caracteres de la misma longitud que cada
columna. Desafortunadamente, por defecto, los nombres de fila son “1" , “2" , “3" , y así sucesivamente, pero cuando se
imprime el marco de datos, se dejan las comillas (ver el resultado de print (gene_info) arriba). Los nombres de fila son
accesibles a través de la función rownames () .
Los marcos de datos son indexables usando una sintaxis extendida [] : [,] <row_selector><column_selector>, donde
<row_selector>y <column_selector>son vectores. Al igual que con los vectores y listas, estos vectores de indexación/selección
pueden ser enteros (para seleccionar por índice), caracteres (para seleccionar por nombre) o lógicos (para seleccionar lógicamente).
También al igual que con los vectores, al indexar por posición de índice o nombre, se respeta el orden solicitado.
III.6_7_R_117_DF_Row_Col_Selection

3.6.1 https://espanol.libretexts.org/@go/page/55126
Aquí está el resultado resultante, ilustrando que “3" y “1" eran los nombres de las filas, que ahora aparecen en la primera y
segunda fila, respectivamente. [1]
III.6_8_R_118_DF_Row_Col_Selection

Si encuentra esto confuso, considere qué pasaría si primero asignáramos los nombres de fila del marco de datos original a algo más
razonable, antes de la extracción.
III.6_9_R_119_DF_Better_Row_Names

Ahora, cuando se imprimen, se revela la naturaleza de carácter de los nombres de las filas.
III.6_10_R_120_DF_Better_Row_Nombres_Imprimir

Finalmente, si uno de <row_selector>o <column_selector>no se especifica, entonces se incluyen todas las filas o columnas.
Como ejemplo, gene_info [c (3,1),] devuelve un marco de datos con la tercera y primera filas y las tres columnas,
mientras que gene_info [, c (“longitudes”, “ids”)] devuelve una con solo las columnas
“longitudes” e “ids” , pero todas las filas.

Operaciones de marco de datos


Debido a que los marcos de datos tienen mucho en común con las listas y filas, y las columnas se pueden indexar por número de
índice, nombre o vector lógico, hay muchas formas poderosas de manipularlos. Supongamos que queríamos extraer solo aquellas
filas donde la columna de longitudes es menor que 200 , o la columna gcs es menor que 0.3 .
III.6_11_R_121_DF_Selección_lógica

Esta sintaxis es concisa pero sofisticada. Mientras que gene_info$length se refiere al vector numérico llamado
“longitudes” en el marco de datos, el operador lógico < se vectoriza, reciclándose el elemento único 200 según sea
necesario. El mismo proceso ocurre para gene_info$gcs < 0.3 , y el operador lógico-or | es vectorizado, produciendo
un vector lógico usado posteriormente para seleccionar las filas de interés. Se
seleccionaría una versión aún más corta de estas dos líneas <- gene_info
[gene_info$longitudes < 200 | gene_info$gcs < 0.3,]
. La salida impresa:
III.6_12_R_121_DF_Lógical_Selección_Imprimir

Si quisiéramos extraer el vector gcs de este resultado, podríamos usar algo como selected_gcs <- selected$gcs
. A veces se utiliza una sintaxis más compacta, donde el $ y el nombre de la columna se anexan directamente a la sintaxis [] .
III.6_13_submarco_columna_1

Alternativamente, y quizás más claramente, primero podemos usar la notación $ para extraer la columna de interés, y luego usar
[] indexación lógica en el vector resultante.
III.6_14_submarco_columna_2

Debido a que subestablecer filas de marcos de datos por condición lógica es tan común, hay una función especializada para esta
tarea: subset () . El primer parámetro es el marco de datos desde el que seleccionar, y los parámetros posteriores son
expresiones lógicas basadas en nombres de columna dentro de ese marco de datos (se dejan comillas). Por ejemplo,
seleccionado <- subconjunto (gene_info, longitudes < 200 | gcs < 0.3) . Si se da más de una
expresión lógica, se combinan con & (y). Así subconjunto (gene_info, longitudes < 200, gcs < 0.3)
es equivalente a gene_info [gene_info$longitudes < 200 & gene_info$gcs < 0.3,] .
Si bien la función subset () es conveniente para extracciones simples, conocer los entresijos de [] la selección de
marcos de datos en lo que se refiere a listas y vectores es una herramienta poderosa. Considere la función order () , que, dado
un vector, devuelve un vector índice que se puede utilizar para ordenar. Al igual que con el uso de order () para
ordenar un vector, podemos usar order () para ordenar un marco de datos basado en una columna en particular.
III.6_15_R_122_DF_ORDERING

El resultado es un marco de datos ordenado por la columna de longitudes:


III.6_16_R_123_DF_Pedido_Salida

3.6.2 https://espanol.libretexts.org/@go/page/55126
Debido a que los marcos de datos obligan a todos los vectores de columna a tener la misma longitud, podemos crear nuevas
columnas asignándolas por su nombre y confiando en el reciclaje vectorial para llenar la columna según sea necesario. Vamos a
crear una nueva columna llamada gc_categories , que inicialmente se llena con valores NA , y luego usar el reemplazo
selectivo para asignar valores “low” o “high” dependiendo del contenido de la columna gcs .
III.6_17_R_124_DF_Newcol_Lowhigh

Si bien existen enfoques más automatizados para categorizar datos numéricos, el ejemplo anterior ilustra la flexibilidad y potencia
del marco de datos y la sintaxis vectorial cubiertos hasta ahora.
III.6_18_R_125_DF_Newcol_Lowhigh_out

Una nota final: mientras que la función head () devuelve los primeros elementos de un vector o lista, cuando se aplica a un
marco de datos, devuelve un marco de datos similar con solo las primeras filas.

Matrices y matrices
Dependiendo del tipo de análisis, es posible que se encuentre trabajando con matrices en R, que son esencialmente vectores
bidimensionales. Al igual que los vectores, todos los elementos de una matriz deben ser del mismo tipo, y los intentos de mezclar
tipos darán como resultado la autoconversión. Al igual que los marcos de datos, son bidimensionales, y por lo tanto se pueden
indexar con la <row_selector><column_selector>sintaxis [,] . También tienen nombres rownames () y
colnames () .
III.6_19_R_125_2_Matrix
III.6_20_R_125_3_Matrix_out

Hay una serie de funciones interesantes para trabajar con matrices, incluyendo det () (para calcular el determinante de una
matriz) y t () (para transponer una matriz).
Las matrices generalizan el concepto de datos multidimensionales; donde las matrices solo tienen dos dimensiones, las matrices
pueden tener dos, tres o más. La línea a <- array (c (1,1,1,1,2,2,2,2,3,3,3,3), dim = c (2,2,3)) ,
por ejemplo, crea una matriz tridimensional, compuesta por tres matrices de dos por dos. El elemento superior izquierdo se puede
extraer como un [1,1,1] .

Ejercicios
1. Lea el archivo states.txt en un marco de datos, como se describe en el Capítulo 28, Vectores. Extraiga un nuevo marco
de datos llamado states_name_pop que contenga solo las columnas para nombre y población .
2. Usando subset () , extraiga un marco de datos llamado states_gradincome_high que contenga todas las
columnas, y todas las filas donde el ingreso sea mayor que la mediana del ingreso o donde hs_grad sea mayor
que la mediana de hs_grad . (Hay 35 estados de este tipo enumerados en la tabla.) A continuación, haga la misma
extracción con la <row_selector><column_selector>sintaxis [,] .
3. Crear una tabla llamada states_by_region_income , donde las filas se ordenan primero por región y segundo por
ingresos. (La función order () puede tomar múltiples parámetros como en orden (vec1, vec2) ; se consideran
a su vez para determinar el orden).
4. Utilice la <row_selector><column_selector>sintaxis order () , colnames () y [,] para crear un marco de datos
states_cols_ordered , donde las columnas están ordenadas alfabéticamente (es decir, hs_grad primero, luego
ingreso , luego asesinato , entonces nombre , y así sucesivamente).
La función nrow () devuelve el número de filas en un marco de datos, mientras
que rev () invierte un vector y seq (a, b) devuelve un vector de números
5. enteros de a a b (inclusive).
Úselos para producir states_by_income_rev , que es el marco de datos de estados pero con filas que aparecen en
orden inverso de ingresos (ingresos más altos en la parte superior).
6. Intente convertir el marco de datos de estados en una matriz ejecutándolo a través de la función.matrix ( ) .
¿Qué pasa con los datos, y por qué crees que es eso?

1. Desafortunadamente, cuando se imprimen, las comillas se dejan fuera de los nombres de las filas, lo que suele llevar a los
programadores a pensar que los índices de fila son lo que se muestra. Del mismo modo, las comillas se dejan fuera de las

3.6.3 https://espanol.libretexts.org/@go/page/55126
columnas que son vectores de caracteres (como la columna ids en este ejemplo), lo que puede causar confusión si la
columna es un tipo de carácter con elementos como “1" , “2" , y así sucesivamente, potencialmente llevando al
programador a pensar en la columna es numérico. En un giro final de confusión, el uso de la <row_selector>
<column_selector>sintaxis [,] rompe un patrón común: [] la indexación de un vector siempre devuelve un vector, []
la indexación de una lista siempre devuelve una lista, y [,] <row_selector><column_selector> la indexación de un marco
de datos siempre devuelve un marco de datos, a menos que tenga una sola columna, en cuyo caso se devuelve la
columna/vector. El remedio para ello es [,, drop = FALSO] <row_selector><column_selector>, que instruye a R a no
“soltar” una dimensión.

This page titled 3.6: Marcos de datos is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil
(OSU Press) .

3.6.4 https://espanol.libretexts.org/@go/page/55126
3.7: Carácter y Datos Categóricos
Los conjuntos de datos científicos, especialmente los que se van a analizar estadísticamente, suelen contener entradas
“categóricas”. Si cada fila en un marco de datos representa una sola medición, entonces una columna podría representar si el valor
medido era de un “hombre” o “mujer”, o del grupo “control” o grupo de “tratamiento”. A veces estas categorías, aunque no
numéricas, tienen un orden intrínseco, como dosis “bajas”, “medias” y “altas”.
Lamentablemente, la mayoría de las veces, estas entradas no están codificadas para facilitar el análisis. Considere el archivo
separado por tabuladores expr_long_coded.txt , donde cada línea representa una lectura de expresión
(normalizada) para un gen (especificado por la columna ID) en un grupo de muestra dado. Este experimento probó los efectos de
un tratamiento químico en una especie de planta agrícola. El grupo de muestra codifica información sobre qué genotipo se probó
(ya sea C6 o L4 ), el tratamiento aplicado a las plantas (ya sea testigo o químico ), el tipo de tejido medido (ya sea
A , B o C para hoja, tallo o root), y números para réplicas estadísticas ( 1 , 2 o 3 ).
III.7_1_R_126_EXPR_LONG_CODID

Inicialmente, leeremos la tabla en un marco de datos. Para este conjunto de datos, es probable que queramos trabajar con la
información categórica de forma independiente, por ejemplo, extrayendo solo valores para el tratamiento químico. Esto sería
mucho más fácil si el marco de datos tuviera columnas individuales para genotipo , tratamiento , tejido y
replicación en lugar de una sola columna de muestra que abarca todo.
Una instalación básica de R incluye una serie de funciones para trabajar con vectores de caracteres, pero el paquete stringr
(disponible a través de install.packes (“stringr”) en la consola interactiva) recopila muchas de estas en un
conjunto de funciones bien nombradas con opciones comunes. Para una visión general, consulte
help (package = “stringr”) , pero en este capítulo cubriremos algunas de las funciones más importantes de ese
paquete.
III.7_2_R_126_EXPR_LONG_Coded_Read_Table

Columnas de división y encuadernación


La función str_split_fixed () del paquete stringr opera en cada elemento de un vector de caracteres,
dividiéndolo en pedazos basados en un patrón. Con esta función, podemos dividir cada elemento del vector
expr_long$sample en tres pedazos basados en el patrón “_” . El “patrón” podría ser una expresión regular, usando la
misma sintaxis que la utilizada por Python (y similar a la utilizada por sed).
III.7_3_R_127_EXPR_Long_Sample_Split

El valor devuelto por la función str_split_fixed () es una matriz: como vectores, las matrices solo pueden contener un
único tipo de datos (de hecho, son vectores con atributos que especifican el número de filas y columnas), pero al igual que los
marcos de datos se puede acceder con [<row_selector>, <column_ selector>] sintaxis. También pueden tener
nombres de filas y columnas.
III.7_4_R_127_expr_largo_muestra_split_print

De todos modos, es probable que queramos convertir la matriz en un marco de datos usando la función data.frame () y
asignar algunos nombres de columna razonables al resultado.
III.7_5_R_128_EXPR_Long_Sample_to_DF

En este punto, tenemos un marco de datos expr_long así como sample_split_df . Estos dos tienen el mismo número
de filas en un orden correspondiente, pero con columnas diferentes. Para obtener estos en un solo marco de datos, podemos usar la
función cbind () , que une dichos marcos de datos por sus columnas, y solo funciona si contienen el mismo número de filas.
III.7_6_R_128_2_CBind

Una impresión rápida (head (expr_long_split)) nos permite saber si vamos en la dirección correcta.
III.7_7_R_129_CBINDED_Imprimir

En este punto, el número de columnas en el marco de datos ha crecido, por lo que print () ha optado por envolver la
columna final alrededor de la salida impresa.

3.7.1 https://espanol.libretexts.org/@go/page/55160
Detectando y %en%
Todavía no tenemos columnas separadas para tejido y replicar , pero sí tenemos esta información codificada juntas en
una columna tissue. Debido a que estos valores se codifican sin un patrón para dividirlos obviamente,
str_split_fixed () puede no ser la solución más sencilla.
Aunque cualquier solución que suponga un conocimiento a priori de grandes contenidos de conjuntos de datos es peligrosa (ya que
los valores extraños tienen formas de introducirse en conjuntos de datos), una inspección rápida de los datos revela que los tipos de
tejido están codificados como A , B o C , con al parecer no hay otras posibilidades. De manera similar, los números
replicados son 1 , 2 y 3 .
Una función útil en el paquete stringr detecta la presencia de un patrón en cada entrada de un vector de caracteres,
devolviendo un vector lógico. Para la columna tejierep que contiene
“A1", “A3", “B1", “B2", “B3", “C1",... , por ejemplo,
str_detect (expr_long_split$tissue, “A”) devolvería el vector lógico VERDADERO,
VERDADERO, FALSO, FALSO, FALSO,... . Así podemos comenzar creando una nueva columna de tejido ,
inicialmente llena de valores de NA .
III.7_8_R_130_Tissue_na

Luego usaremos el reemplazo selectivo para llenar esta columna con el valor “A” donde la columna tissue tiene una
“A” como lo identifica str_detect () . De manera similar para “B” y “C” .
III.7_9_R_131_Tissue_str_detect

En el capítulo 34, “Remodelación y unión de marcos de datos”, también consideraremos métodos más avanzados para este tipo de
división de columnas basada en patrones. Además, aunque estamos trabajando con columnas de marcos de datos, es importante
recordar que siguen siendo vectores (existentes como columnas), y que las funciones que estamos demostrando operan
principalmente sobre y devuelven vectores.
Si nuestra suposición de que “A” , “B” y “C” eran los únicos tipos de tejido posibles, era correcta, no deberían quedar
valores de NA en la columna de tejido . Deberíamos verificar esta suposición intentando imprimir todas las filas donde la
columna de tejido es NA (usando la función is.na () , que devuelve un vector lógico).
III.7_10_R_131_tissue_str_detect_check

En este caso, se imprime un marco de datos con cero filas. Existe la posibilidad de que tipos de tejido como “AA” hayan sido
recodificados como valores simples de “A” usando esta técnica; para evitar este resultado, podríamos usar una expresión regular
más restrictiva en str_detect () , como “^A\ d$” , que solo coincidirá con elementos que comenzar con una sola
“A” seguida de un solo dígito. Consulte el capítulo 11, “Patrones (expresiones regulares)” y el capítulo 21, “Trucos
bioinformáticos y expresiones regulares”, para obtener más información sobre los patrones de expresión regular.
Se puede usar un conjunto similar de comandos para llenar una nueva columna de réplica.
III.7_11_R_131_REP_STR_Detectar

Nuevamente buscamos los valores de NA sobrantes, y encontramos que esta vez hay algunas filas donde la columna rep se
reporta como NA , al parecer porque algunas entradas en la tabla tienen un número replicado de 0 .
III.7_12_R_132_Rep_STR_Detect_Whoops

Hay algunas formas en las que podríamos manejar esto. Podríamos determinar cuáles deberían ser los números replicados de estas
cinco muestras; quizás de alguna manera fueron mal codificadas. Segundo, podríamos agregar “0" como una posibilidad de
réplica separada (por lo que algunos grupos estaban representados por cuatro réplicas, en lugar de tres). Alternativamente,
podríamos eliminar estas entradas misteriosas.
Finalmente, podríamos eliminar todas las mediciones para estos ID de genes, incluyendo las otras réplicas. Para este conjunto de
datos, optaremos por este último, ya que la existencia de estas mediciones “misteriosas” pone en duda la precisión de las otras
mediciones, al menos para este conjunto de cinco IDs.
Para ello, primero extraeremos un vector de los ID de genes “malos”, usando selección lógica en la columna id basada en
is.na () en la columna rep .

3.7.2 https://espanol.libretexts.org/@go/page/55160
III.7_13_R_133_Rep_STR_DETECT_Bad_IDS
III.7_14_R_133_2_REP_STR_DETECT_Bad_IDS_Out

Ahora, para cada elemento de la columna id , ¿cuáles son iguales a uno de los elementos en el vector bad_ids ?
Afortunadamente, R proporciona un operador %en% para este tipo de comparación de muchos contra muchos. Dados dos
vectores, %en% devuelve un vector lógico que indica qué elementos del vector izquierdo coinciden con uno de los elementos de
la derecha. Por ejemplo, c (3, 2, 5, 1) %en% c (1, 2) devuelve el vector lógico FALSO,
VERDADERO, FALSO, VERDADERO . Esta operación requiere comparar cada uno de los elementos del vector izquierdo con
cada uno de los elementos del vector derecho, por lo que el número de comparaciones es aproximadamente la longitud de las
primeras veces la longitud del segundo. Si ambos son muy grandes, tal operación podría llevar bastante tiempo terminar.
Sin embargo, podemos usar el operador %en% junto con la selección lógica para eliminar todas las filas que contienen un ID de
gen “malo”.
III.7_15_R_134_Remove_Bad_rows

En este punto, podríamos volver a verificar los valores de NA en la columna rep para asegurarnos de que los datos se hayan
limpiado adecuadamente. Si quisiéramos, también podríamos verificar la longitud (bad_rows [bad_rows]) para ver
cuántas filas malas se identificaron y eliminaron. (¿Ves por qué?)

Pegar
Si bien anteriormente discutimos dividir el contenido de los vectores de caracteres en múltiples vectores, ocasionalmente queremos
hacer lo contrario: unir el contenido de los vectores de caracteres en un solo vector de carácter, elemento por elemento. La función
str_c () de la biblioteca stringr realiza esta tarea.
III.7_16_R_135_STR_C

La función str_c () también es útil para imprimir frases muy bien formateadas para la depuración.
III.7_17_R_136_STR_C_Debug

La función Base-R equivalente a str_c () es paste () , pero mientras que el separador predeterminado para
str_c () es una cadena vacía, “” , el separador predeterminado para paste () es un solo espacio, "" ”. La función
Base-R equivalente para str_detect () es grepl () , y el equivalente más cercano a str_split_fixed ()
en Base-R es strsplit () . Sin embargo, como se mencionó anteriormente, se recomienda el uso de estas y otras funciones
stringr para este tipo de manipulación carácter-vector.

Factores
Por ahora, los factores se han mencionado varias veces de pasada, principalmente en el contexto del uso de
StringSasFactors = FALSE , para evitar que los vectores de caracteres se conviertan en tipos de factores cuando se
crean marcos de datos. Los factores son un tipo de datos relativamente exclusivo de R, y proporcionan una alternativa para
almacenar datos categóricos comparados con vectores de caracteres simples.
Un buen método para comprender los factores podría ser comprender una de las razones históricas de su desarrollo, aunque la
razón ya no sea relevante hoy en día. ¿Cuánto espacio requeriría la columna de tratamiento del marco de datos
experimental anterior para almacenar en la memoria, si el almacenamiento se hacía ingenuamente? Por lo general, un solo carácter
como “c” se puede almacenar en un solo byte (8 bits, dependiendo de la codificación), por lo que una entrada como
“química” requeriría 8 bytes, y “control” requeriría 7. Dado que hay ~360.000 entradas en la tabla completa, el
espacio total requerido sería de ~0.36 megabytes. Obviamente, la cantidad de espacio sería mayor para una tabla más grande, y
hace décadas incluso unos pocos megabytes de datos podrían representar un desafío.
Pero eso es para una codificación ingenua de los datos. Una alternativa podría ser codificar “químico” y “control”
como enteros simples 1 y 2 (4 bytes pueden codificar números enteros de -2.1 a 2.1 mil millones), así como una tabla de
búsqueda separada que mapee el entero 1 a “químico” y 2 a “controlar” . Esto sería un ahorro de espacio de
aproximadamente dos veces, o más si los plazos fueran más largos. Este tipo de mecanismo de almacenamiento y mapeo es
exactamente lo que proporcionan los factores. [1]

3.7.3 https://espanol.libretexts.org/@go/page/55160
Podemos convertir un vector (o factor) de caracteres en un factor usando la función factor () , y como de costumbre la
función head () se puede utilizar para extraer los primeros elementos.
III.7_18_R_137_Factor_create

Cuando se imprimen, los factores muestran sus niveles , así como los elementos de datos individuales codificados a niveles.
Observe que no se muestran las comillas generalmente asociadas con vectores de caracteres.
III.7_19_R_138_Factor_Create_Print

Es ilustrativo intentar usar las funciones str () y class () y attr () para profundizar en cómo se almacenan los
factores. ¿Son listas, como los resultados de la función t.test () , o algo más? Desafortunadamente, son relativamente
inmunes a la función str () ; str (factor_tratamiento) informa:
III.7_20_R_139_Factor_str

Este resultado ilustra que los datos


parecen estar codificados como enteros. Si fuéramos a ejecutar
print (class (treatment_factor)) , descubriríamos que su clase es “factor” .
Resulta que la clase de un tipo de datos se almacena como un atributo.
III.7_21_R_140_Factor_attr_class

Arriba, aprendimos que podíamos eliminar un atributo configurándolo en NULL . Vamos a establecer el atributo “class” en
NULL , y luego ejecutar str () en él.
III.7_22_R_141_Factor_attr_class_null_str
III.7_23_R_142_Factor_attr_class_null_str_print

¡Ajá! Esta operación revela la verdadera naturaleza de un factor: un vector entero, con un atributo de “niveles” que
almacena un vector de caracteres de etiquetas, y un atributo para “clase” que especifica la clase del vector como factor. Los
datos en sí se almacenan como 1 o 2 , pero el atributo levels tiene como primer elemento “químico” (y por lo tanto un
entero de 1 codifica “químico” ) y “control” como su segundo (entonces 2 codifica “control” ).
Este atributo especial de “clase” controla cómo funcionan funciones como str () e print () en un objeto, y si
queremos cambiarlo, esto se hace mejor usando la función de acceso class () en lugar de la función attr () como se
indicó anteriormente. Cambiemos la clase de nuevo a factor.
III.7_24_R_143_Factor_Reset_Class

Renombrar niveles de factor


Debido a que los niveles se almacenan como un atributo de los datos, podemos cambiar fácilmente los nombres de los niveles
modificando el atributo. Podemos hacer esto con la función attr () , pero como de costumbre, se prefiere una función de
acceso específica llamada levels () .
III.7_25_R_144_factor_cambio_niveles_1

¿Por qué se prefiere la función levels () sobre usar attr () ? Porque al usar attr () , no habría nada que nos
impida hacer algo irresponsable, como fijar los niveles a valores idénticos, como en c (“Agua”, “Agua”) . La función
levels () comprobará esto y otros absurdos.
Lo que la función levels () no puede verificar, sin embargo, es el significado semántico de los propios niveles. No sería
buena idea mezclar los nombres, para que “Química” en realidad se estuviera refiriendo a plantas tratadas con agua, y
viceversa:
III.7_26_R_145_Factor_cambio_niveles_malo

La razón por la que esto es una mala idea es que usar levels () solo modifica el atributo “levels” pero no hace nada a
los datos enteros subyacentes, rompiendo el mapeo.

Niveles de factor de reordenamiento


Aunque motivamos factores sobre la base del ahorro de memoria, en las versiones modernas de R, incluso los vectores de
caracteres se almacenan internamente usando una estrategia sofisticada, y las computadoras modernas generalmente tienen

3.7.4 https://espanol.libretexts.org/@go/page/55160
almacenes más grandes de RAM además. Aún así, hay otra motivación para los factores: el hecho de que los niveles puedan tener
un orden significativo. Algunas pruebas estadísticas podrían querer comparar ciertos subconjuntos de un marco de datos definidos
por un factor; por ejemplo, los valores numéricos asociados con niveles de factor “bajos” podrían compararse con los
etiquetados como “medio” , y esos a su vez deberían compararse con valores etiquetados como “altos” . Pero, dadas estas
etiquetas, no tiene sentido comparar las lecturas “bajas” directamente con las lecturas “altas” . Los factores
proporcionan una manera de especificar que los datos son categóricos, pero también que “bajo” < “medio” < “alto” .
Así podríamos precisar o cambiar el orden de los niveles dentro de un factor, para decir, por ejemplo, que el tratamiento del
“Agua” es de alguna manera menor que el tratamiento “Químico” . Pero no podemos hacer esto simplemente cambiando
el nombre de los niveles.
La forma más sencilla de especificar el orden de un vector o factor de carácter es convertirlo en un factor con factor ()
(aunque ya sea un factor) y especificar el orden de los niveles con el parámetro opcional levels = . Por lo general, si se está
suministrando un pedido específico, también vamos a querer especificar ordenado = VERDADERO . Los niveles
especificados por el parámetro levels = deben coincidir con las entradas existentes. Para renombrar simultáneamente los
niveles, también se puede usar el parámetro labels = .
III.7_27_R_146_Factor_ordenado

“Agua” en lugar de “control” , y el factor sabe que “Agua” < “Químico” . Si quisiéramos tener
Ahora, se usa
“Químico” < “Agua” , habríamos necesitado usar niveles = c (“químico”, “control”) y
etiquetas = c (“Química”, “Agua”) en la llamada al factor () .
III.7_28_R_146_Factor_Ordenado_Imprimir

Sin tener en cuenta el argumento labels = (usado solo cuando queremos renombrar niveles mientras se reordena), debido a
que el argumento levels = toma un vector de caracteres de las entradas únicas en el vector de entrada, estas podrían
precalcularse para mantener los niveles en un orden dado. Quizás nos gustaría ordenar los tipos de tejido en orden alfabético
inverso, por ejemplo:
III.7_29_R_147_Factor_pedido_tejidos
III.7_30_R_148_Factor_Ordered_Tissues_Print

En lugar de asignar a una variable tejes_factor separada, podríamos reemplazar la columna de marco de datos con el
vector ordenado asignando a expr_long_split$tissue .
A menudo deseamos ordenar los niveles de un factor de acuerdo con algunos otros datos. En nuestro ejemplo, podríamos querer
que el “primer” tipo de tejido sea el que tenga la expresión media más pequeña, y el último sea el que tenga la expresión media más
alta. Una función especializada, reorder () , hace que este tipo de pedidos sea rápido y relativamente indoloro. Se necesitan
tres parámetros importantes (entre otros opcionales):
1. El factor o vector de caracteres para convertir a un factor con niveles reordenados.
2. Un vector (generalmente numérico) de la misma longitud para usar como reordenamiento de datos.
3. Una función a utilizar para determinar qué hacer con el argumento 2.
He aquí un ejemplo canónico rápido. Supongamos que tenemos dos vectores (o columnas en un marco de datos), uno de especies
de peces muestreados (“lubina”, “salmón” o “trucha”) y otro de pesos correspondientes. Observe que los salmones son
generalmente pesados, las truchas son livianas y los bajos están en el medio.
III.7_31_R_149_Factor_Reorder_input

Si tuviéramos que convertir el vector especie en un factor con factor (especie) , obtendríamos el orden alfabético
predeterminado: lubina, salmón, trucha. Si preferimos organizar los niveles de acuerdo con la media de los pesos de grupo,
podemos usar reorder () :
III.7_32_R_150_Factor_Reordenar

Con esta asignación, species_factor será un factor ordenado con trucha < lubina < salmón . Esta pequeña
línea de código hace bastante, en realidad. Ejecuta la función mean () en cada grupo de pesos definidos por las diferentes
etiquetas de especies, ordena los resultados por esos medios y utiliza el orden de grupo correspondiente para establecer los niveles
de factores. Lo que es aún más impresionante es que podríamos haber usado con la misma facilidad la mediana en lugar de la

3.7.5 https://espanol.libretexts.org/@go/page/55160
media, o cualquier otra función que opere sobre un vector numérico para producir un resumen numérico. Esta idea de especificar
funciones como parámetros en otras funciones es uno de los poderosos enfoques “funcionales” que toma R, y estaremos viendo
más de ella.

Notas finales sobre los factores


En muchos sentidos, los factores funcionan de manera muy similar a los vectores de caracteres y viceversa; %en% y ==
pueden usarse para comparar elementos de factores tal como pueden con vectores de caracteres (por ejemplo,
tejidos_factor == “A” devuelve el vector lógico esperado).
Los factores obligan a que todos los elementos sean tratados como uno de los niveles nombrados en el atributo levels , o
NA en caso contrario. Un factor que codifica 1 y 2 como macho y hembra , por ejemplo, tratará a cualquier otro entero
subyacente como NA . Para obtener un factor que acepte niveles novedosos, el atributo levels primero debe modificarse con la
función levels () .
Por último, debido a que los factores funcionan de manera muy parecida a los vectores de caracteres, pero no imprimen sus citas,
puede ser difícil distinguirlas de otros tipos cuando se imprimen. Esto va para vectores de caracteres simples cuando forman parte
de marcos de datos. Considere la siguiente impresión de un marco de datos:
III.7_33_R_151_Factor_DF_Imprimir

Debido a que las comillas se dejan fuera al imprimir marcos de datos, es imposible decir a partir de esta salida simple que la
columna id es un vector de caracteres, la columna de tejido es un factor, la columna de conteo es un vector entero y
la columna de grupo es un factor. [2] Usar class () en columnas individuales de un marco de datos puede ser útil para
determinar qué tipos son realmente.

Ejercicios
1. En el archivo de anotación pz.ANNOT.txt , cada ID de secuencia (columna 1) puede asociarse con múltiples “números”
de ontología génica (GO) (columna 2) y un número de “términos” diferentes (columna 3). Muchos ID están asociados con
múltiples números GO, y no hay nada que impida que un número o término en particular se asocie con múltiples ID.
III.7_34_UNIX_159_2_PZ_ANNOT_SampleSi bien la mayoría de los ID de secuencia tienen un sufijo de guión bajo, no todos lo tienen.

Comience leyendo en este archivo (las columnas están separadas por tabulaciones) y luego extrayendo un marco de datos
suffix_only que contiene solo aquellas filas donde el ID de secuencia contiene un guion bajo. Del mismo modo,
extraiga un marco de datos no_suffix para filas donde los ID de secuencia no contengan un guion bajo.
A continuación, agregue a las columnas del marco de datos sufix_only para base_id y sufijo , donde los
ID base son las partes antes del subrayado y suficientes son las partes después del subrayado (por ejemplo, base_id es
“PZ7180000023260" y el sufijo es “APN” para el ID “PZ7180000023260_APN” ).
Por último, producir versiones de estos dos marcos de datos donde se haya eliminado el prefijo GO: de todas las entradas de
la segunda columna.
2. La línea s <- muestra (c (“0", “1", “2", “3", “4"), size = 100, replace = TRUE) genera
un vector de caracteres de 100 aleatorios “0" s, “1" s, “2" s, “3" s, y “4" s. Supongamos que “0"
significa “Totalmente en desacuerdo”, “1" significa “No estoy de acuerdo”, “2" significa “Neutral”, “3" significa
“De acuerdo” y “4" significa “Muy de acuerdo”. Convertir s en un factor ordenado con niveles
Muy en desacuerdo < En desacuerdo < Neutral < De acuerdo < Muy de acuerdo .
3. Al igual que los vectores, los marcos de datos (tanto filas como columnas) se pueden seleccionar por número de índice (vector
numérico), vector lógico o nombre (vector de caracteres). Supongamos que grade_current es un vector de caracteres
generado por
grade_current <- sample (c (“A”, “B”, “C”, “D”, “E”), size = 100, replace =
TRUE)
, y gpa es un vector numérico, como en gpa <- runif (100, min = 0.0, max = 4.0) . Además, los
agregamos como columnas a un marco de datos,
grades <- data.frame (current_grade, gpa, stringsasFactors = FALSE) .

3.7.6 https://espanol.libretexts.org/@go/page/55160
Nos interesa sacar todas las filas que tengan “A” , “B” o “C” en la columna current_grade . Describa, en
detalle, qué hace cada una de las tres posibles soluciones: III.7_35_R_151_2_en_prueba_confuso ¿Cómo interpreta R cada una (es decir, qué
intentará hacer R por cada una) y cuál sería el resultado? ¿Cuál (s) es (son) correctos? ¿Cuál reportará errores? ¿Las siguientes
tres líneas son diferentes de las tres anteriores en lo que R intenta hacer? III.7_36_R_151_3_en_prueba_confusing_subconjunto

1. Aunque los vectores de caracteres también se almacenan de manera eficiente en las versiones actuales de R, los factores aún
tienen usos únicos.
2. Los factores creados a partir de vectores enteros son un tipo especial de dolor de cabeza. Considera una línea como
h <- factor (c (4, 1, 5, 6, 4)) ; debido a que los factores son tratados como tipos de caracteres, éste se
convertiría de manera que se basa en c (“4", “1", “5", “6", “4") , donde el mapeo subyacente y el
almacenamiento tienen una relación casi arbitraria con los elementos. Prueba print (como.numeric (h)) para ver
el problema en el que uno puede meterse al mezclar estos tipos, así como class (h) <- NULL seguido de str (h)
para ver el mapeo subyacente y por qué ocurre esto.

This page titled 3.7: Carácter y Datos Categóricos is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn
T. O’Neil (OSU Press) .

3.7.7 https://espanol.libretexts.org/@go/page/55160
3.8: Dividir, Aplicar, Combinar
Aunque tocamos brevemente las funciones de escritura, la discusión hasta ahora ha sido en gran parte sobre los diversos tipos de
datos utilizados por R y la sintaxis y funciones para trabajar con ellos. Estos incluyen vectores de diversos tipos, listas, marcos de
datos, factores, indexación, reemplazo y reciclaje de vectores.
¿En algún momento no discutiremos analizar un conjunto de datos significativo? Sí, y ese punto es ahora. Continuaremos
trabajando con los datos de expresión génica del capítulo 32, “Carácter y datos categóricos”, después de que se hayan procesado
para incluir las columnas individuales apropiadas usando las funciones de la biblioteca stringr . Aquí está el código del
último capítulo que pre-procesó los datos de entrada:
III.8_1_R_152_Expresiones procesadas

A medida que continuemos, es probable que queramos volver a ejecutar nuestro script de análisis de diferentes maneras, sin tener
que volver a ejecutar todo el código anterior, ya que tarda unos minutos en ejecutar este script de preprocesamiento. Una buena
estrategia sería escribir este marco de datos “limpiado” en un archivo de texto usando write.table () , y volver a leerlo en
un script de solo análisis con read.table () . Sin embargo, solo por variedad, usemos la función save () , que
almacena cualquier objeto R (como una lista, vector o marco de datos) en un archivo en un formato binario comprimido.
III.8_2_R_154_Processed_Expressions_Guardar

La extensión de archivo tradicional para dicho archivo de objeto R es .Rdata . Dichos archivos de datos a menudo se
comparten entre los programadores de R.
Las primeras líneas del marco de datos guardado son todas lecturas para el mismo gen. Hay ~360.000 líneas en el marco de datos
completo, con lecturas para más de 11 mil genes. (El script anterior, que procesa el archivo expr_long_coded.txt , se
puede encontrar en el archivo Expr_Preprocess.r .) La siguiente figura muestra los resultados de
print (head (expr_long_split)) :
III.8_3_R_153_Processed_Expressions_head

Comencemos un nuevo script de análisis que cargue este marco de datos antes de comenzar nuestro análisis del mismo. Para este
script también podemos necesitar la biblioteca stringr , y vamos a incluir también la biblioteca dplyr (que suponemos
que se ha instalado por separado con install.packages (“dplyr”) en la consola interactiva).
III.8_4_R_155_Processed_expressions_load

La función load () crea una variable con el mismo nombre que se utilizó en save () , y por conveniencia hemos creado
una copia del marco de datos con un nombre de variable más corto, expr . Si bien este tipo de archivos de datos R son
convenientes para los usuarios de R, el beneficio de un enfoque write.table () sería que otros usuarios e idiomas tendrían
acceso más directo a los datos (por ejemplo, en Python o incluso Microsoft Excel).
Este conjunto de datos representa un análisis multifactorial de la expresión génica [1] en dos genotipos de una planta, donde se han
aplicado a cada uno un tratamiento control (agua) y un tratamiento químico (pesticida). Además, se probaron tres tipos de tejido
(A, B y C, para hoja, tallo y raíz, respectivamente) y se probaron dos o tres repeticiones para cada combinación para determinar el
poder estadístico. Estos datos representan lecturas de micromatrices y ya se han normalizado y por lo tanto están listos para su
análisis. [2] (Estos datos también son anonimizados para esta publicación a petición de los investigadores.)
Por ahora, nos centraremos en las variables de genotipo y tratamiento. Ignorando otras variables, esto nos da alrededor de 24
lecturas de expresión para cada gen.
III.8_5_Experiment_Design_Expressions

Este tipo de diseño es bastante común en los estudios de expresión génica. No es sorprendente que la expresión génica difiera entre
dos genotipos, y que la expresión génica difiera entre dos tratamientos. Pero los genotipos y tratamientos reaccionan
frecuentemente de formas interesantes. En este caso, quizás se sabe que el genotipo L4 crece más grande que el genotipo C6, y se
sabe que el tratamiento químico es dañino para las plantas. Curiosamente, parece que el tratamiento químico es menos dañino para
el genotipo L4.
alt

3.8.1 https://espanol.libretexts.org/@go/page/55139
La pregunta natural es: ¿qué genes podrían estar contribuyendo a la mayor durabilidad de L4 bajo exposición química? No
podemos simplemente buscar las diferencias en la expresión media entre genotipos (con, digamos, t.test ()) , ya que eso
capturaría muchos genes involucrados en el tamaño de la planta. Tampoco podemos buscar aquellos con diferencias de expresión
medias que sean grandes entre tratamientos, porque sabemos que muchos genes reaccionarán a un tratamiento químico pase lo que
pase. Incluso ambas señales juntas (lado izquierdo de la imagen de abajo) no son tan interesantes, ya que representan genes que se
ven afectados por el químico y están involucrados con el tamaño de la planta, pero pueden no tener nada que ver con características
únicas de L4 en el tratamiento químico. Lo que realmente nos interesa son los genes con una respuesta diferencial a la sustancia
química entre dos de los dos genotipos (lado derecho de la imagen de abajo).
alt

¿Significa eso que podemos buscar genes donde la diferencia media sea grande en un genotipo, pero no en el otro? ¿O dentro de un
tratamiento, pero no ese otro? No, porque muchos de estos interesantes escenarios se perderían:
alt

Para las mediciones de expresión de cada gen, necesitamos un modelo clásico de ANOVA (análisis de varianza). Después de
contabilizar la varianza explicada por la diferencia de genotipo (a la que podemos asignar un valor de p que indique si existe una
diferencia) así como la varianza explicada por el tratamiento (que también tendrá un valor p), es la variación sobrante—para la
interacción/ respuesta diferencial—significativa?

Una prueba de un solo gen, fórmulas


Para comenzar, ejecutaremos un ANOVA para un solo gen. [3] Primero, necesitaremos aislar un submarco de datos que consiste en
lecturas para un solo ID. Podemos usar la función unique () para obtener un vector de identificadores únicos, y luego el
operador %in% para seleccionar filas que coincidan solo con la primera de ellas.
III.8_9_R_156_EXTRACT_ONE_GENE

Este marco de datos contiene filas para un solo gen y columnas para expresión, genotipo, tratamiento y otros (podríamos verificar
esto con una simple print () si es necesario). Se desea ejecutar un ANOVA para estas variables, basado en un modelo lineal
que prediga valores de expresión a partir del genotipo, el tratamiento y la interacción de estos términos.
El siguiente paso es construir un modelo lineal simple usando la función lm () , que toma como único parámetro requerido una
“fórmula” que describa cómo deben relacionarse los valores predictores con los valores predichos. Observe el formato de la
fórmula:
response_vector ~ predictor_vector1 + predictor_vector2 + predictor_vector1:
predictor_vector2
, indicando que deseamos determinar cómo predecir valores en el vector de respuesta usando elementos de los vectores predictores.
III.8_10_R_157_LM_ONE_A

Si los vectores o factores de interés existen dentro de un marco de datos (como es el caso aquí), entonces la función lm ()
puede funcionar solo con nombres de columna y un argumento data = .
III.8_11_R_158_LM_ONE_B

Entonces podemos imprimir el modelo lineal devuelto con print (lm1) , así como su estructura con str (lm1) , para
ver que contiene bastante información como una lista con nombre:
III.8_12_R_159_LM_ONE_PRINT_STR

Antes de continuar, ¿cuál es esta expresión ~ genotipo + tratamiento + genotipo: tratamiento


“fórmula”? Como es habitual, podemos investigar un poco más asignándola a una variable y usando class () y str () .
III.8_13_R_160_Fórmula
III.8_14_R_160_2_formula_out

El resultado indica que la clase es de tipo “formula” y que tiene un atributo “.Environment” . Es un poco opaco, pero
un tipo de fórmula en R es un contenedor para un vector de caracteres de nombres de variables y una sintaxis para relacionarlos
entre sí. El atributo environment especifica dónde deben buscarse esas variables por defecto, aunque las funciones son libres de
ignorarlas (como en el caso de data = for lm () , que especifica que las variables deben considerarse nombres de columna
en el marco de datos dado). Las fórmulas ni siquiera necesitan especificar variables que realmente existen. Considere la siguiente

3.8.2 https://espanol.libretexts.org/@go/page/55139
fórmula y la función all.vars () , que inspecciona una fórmula y devuelve un vector de caracteres de los nombres de
variables únicos que aparecen en la fórmula.
III.8_15_R_161_formula_B

En fin, volvamos a ejecutar la prueba estadística para el gen único que hemos aislado. El resultado lm1 contiene el modelo que
predice expresiones de los otros términos (donde este “modelo” es una lista con un conjunto particular de elementos). Para obtener
los valores p asociados, tendremos que ejecutarlo a través de la función anova () de R.
III.8_16_R_162_GENE_ONE_ANOVA

Al imprimir el resultado se muestra que los valores de p (etiquetados “Pr (>F)” ) para el genotipo, el tratamiento y los
términos de interacción son 0.97, 0.41 y 0.56, respectivamente. Si queremos extraer los valores p individualmente, primero
tendremos que inspeccionar su estructura con str () , revelando que el resultado es tanto una lista como un marco de datos, no
es sorprendente porque un marco de datos es un tipo de lista. Los tres valores p se almacenan en el nombre “Pr (>F)” de la
lista.
III.8_17_R_163_Printout

Podemos así extraer el vector de valores p ya que


pvals1 <- anova1

3.8.3 https://espanol.libretexts.org/@go/page/55139
The resulting list will have the same three values as above, and the names of the
elements in the output list will be inherited from the input list (so
sample_irqs" title="Rendido por Quicklatex.com" height="2836" width="2358"
style="vertical-align: -4px;"> s1 contendrá
el primer rango o 5.44, para esta muestra aleatoria de todos modos).
Este es un ejemplo de la poderosa estrategia de “split-apply-combine” para el análisis de datos. En general, esta estrategia implica
dividir un conjunto de datos en pedazos, aplicar una operación o función a cada uno, y luego combinar los resultados en un único
conjunto de salida de alguna manera.
imagen

Cuando la entrada y la salida son ambas listas, esta operación también se conoce como “mapa”. De hecho, este paradigma subyace
a algunos frameworks de supercomputadoras de procesamiento paralelo como MapReduce de Google y la versión de código
abierto de Hadoop (aunque estos programas no están escritos en R).
Debido a que las listas son un tipo de datos tan flexible, lapply () es bastante útil. Aún así, en algunos casos, como este,
preferiríamos que la salida fuera un vector en lugar de una lista. Esto es lo suficientemente común como para garantizar una
función de conversión llamada unlist () que extrae todos los elementos y subelementos de una lista para producir un vector.
III.8_32_R_175_Unlist

Ser advertido: debido a que las listas son más flexibles que los vectores, si la lista dada a unlist () no es simple, los
elementos pueden estar presentes en el vector en un orden impar y serán coaccionados a un tipo de datos común.
La función lapply () también puede tomar como entrada un vector en lugar de una lista, y no es más que una de las muchas
funciones “apply” en R. Otros ejemplos incluyen apply () , que aplica una función a cada fila o columna de una matriz, [4] y
sapply () (para “simple apply”), que detecta si el tipo de retorno debe ser un vector o matriz dependiendo de la función
aplicada. Es fácil confundirse con esta diversidad, así que para comenzar, lo mejor es enfocarse en lapply () dada su
naturaleza relativamente sencilla (pero útil).

Recopilación de parámetros con...


La página de ayuda para lapply () es interesante. Especifica que la función toma tres parámetros: X (la lista a aplicar),
FUN (la función a aplicar), y ... , que la página de ayuda detalla como “argumentos opcionales a FUN ”.
III.8_33_R_176_DotDotDot

El ... parámetro es común en R, si un poco misterioso a veces. Este “parámetro” recoge cualquier número de parámetros
nombrados suministrados a la función en una sola unidad. Esta colección puede ser utilizada entonces por la función; a menudo se
pasan a funciones llamadas por la función.
Para ilustrar este parámetro, modifiquemos nuestra función ipercentile_range () para tomar dos parámetros
opcionales, un percentil inferior y superior a partir de los cuales se debe calcular el rango. Si bien el rango
intercuartílico se define como el rango en los datos del percentil 25 al 75, nuestra función podrá calcular el rango desde cualquier
percentil inferior o superior.
III.8_34_R_177_IPpercentile_rango_expandido

3.8.4 https://espanol.libretexts.org/@go/page/55139
Ahora podemos llamar a nuestra función como

ipercentile_range (muestra to get the


default interquartile range of the first sample, or as
ipercentile_range(samples" title="Rendido por Quicklatex.com" height="40"
width="586" style="vertical-align: -4px;"> s2, inferior = 0.05, superior = 0.95)
, para obtener el rango del percentil 90 medio. De igual manera, tenemos la
capacidad de suministrar los parámetros opcionales a través del ... de lapply () ,
que a su vez los suministrará a cada llamada de la función ipercentile_range
durante el paso de aplicación.
III.8_35_R_178_Lapply_ipercentile_dotdot

Esta sintaxis puede parecer un poco incómoda, pero algo así es necesario si esperamos pasar funciones que toman múltiples
parámetros como parámetros a otras funciones. Esto también debería ayudar a iluminar una de las características comúnmente
incomprendidas de R.
alt

Las funciones están en todas partes


Aunque en su mayoría ocultas a la vista, la mayoría (si no todas) las operaciones en R son en realidad llamadas a funciones. A
veces los nombres de las funciones pueden contener caracteres no alfanuméricos. En estos casos, podemos encerrar el nombre de la
función en backticks, como en iqr1 <- `ipercentile_range` (samples$s1) . (En el capítulo 27, “Variables y
datos”, aprendimos que cualquier nombre de variable se puede incluir en backticks de esta manera, aunque rara vez se recomienda).
¿Y algo así como el operador de suma humilde, + ? Funciona como una función: dadas dos entradas (vectores del lado izquierdo
y del lado derecho), devuelve una salida (la suma elemento por elemento).
III.8_37_R_179_Plus_Func_1

De hecho, + realmente es una función que toma dos parámetros, pero para llamarlo así necesitamos usar backticks.
III.8_38_R_180_Plus_Func_2

Esto podría considerarse algo sorprendente: la versión “legible por humanos” es convertida en una llamada de función
correspondiente detrás de escena por el intérprete. Esta última, la representación funcional es más fácil de usar para el intérprete
que la representación amigable con el ser humano. Este es un ejemplo de azúcar sintáctico: ajustes a un lenguaje que lo hacen “más
dulce” para los programadores humanos. R contiene bastante azúcar sintáctica. ¡Incluso el operador de asignación <- es una
llamada de función! La línea a <- c (1, 2, 3) es en realidad azúcar sintáctica para
`<-` (“a”, c (1, 2, 3)) .
Debido a que las funciones son solo un tipo de datos y los nombres de las funciones son variables, podemos reasignar fácilmente
nuevas funciones a nombres antiguos. Quizás una compañera ha dejado abierta su sesión R, así que decidimos redefinir + para
significar “al poder de”:
III.8_39_R_181_PLUS_REDEFINIR

Ok, tal vez eso sea un poco demasiado malo. Otra característica de R es que los nombres de función que comienzan y terminan con
% y toman dos parámetros señales de que la función puede ser utilizada como una función de infijo vía azúcar sintáctica.
III.8_40_R_182_ecualish_infix

Marco de datos azucarado Split-Apply-Combinar


Aunque las unidades básicas de datos en R son vectores, y las listas se utilizan para almacenar datos heterogéneos, los marcos de
datos son importantes como mecanismo de almacenamiento de datos tabulares. Debido a que lapply () puede aplicar una
función a cada elemento de una lista, también puede aplicar una función a cada columna de un marco de datos. Este hecho se puede
utilizar, por ejemplo, para extraer todas las columnas numéricas de un marco de datos con una aplicación de lapply () y
is.numeric () (este último devuelve TRUE cuando su entrada es de tipo numérico; dejaremos los detalles como un
ejercicio).

3.8.5 https://espanol.libretexts.org/@go/page/55139
Para la mayoría de los datos tabulares, sin embargo, no nos interesa aplicar independientemente una función a cada columna. Más
bien, más a menudo deseamos aplicar una función a diferentes conjuntos de filas agrupadas por una o más de las columnas. (Esto
es exactamente lo que queremos hacer con la función sub_df_to_pvals () que escribimos para nuestro análisis de
expresión génica). El paquete dplyr ( install.packages (“dplyr”) ) proporciona esta habilidad y es potente y
fácil de usar, una vez que nos acostumbramos a su azúcar sintáctica especializada.
Inicialmente, cubriremos dos funciones en el paquete dplyr , group_by () y do () :
group_by () agrega metadatos ( como atributos) a un marco de datos indicando qué columnas categóricas definen
grupos de filas y devuelve dicho marco de datos “agrupado”, mientras que do () aplica una función dada a cada grupo de un
marco de datos agrupado y devuelve un marco de datos de resultados con información de agrupación incluida.
Para ilustrar, primero crearemos un marco de datos simple en el que trabajar, representando muestras de peces de uno de dos lagos
diferentes: Green Lake o Detroit Lake.
III.8_41_R_183_Fish_DF

La impresión muy bien formateada:


III.8_42_R_184_Fish_DF_Print

Primero, agruparemos el marco de datos por la columna de especies usando group_by () . Esta función toma un marco de
datos (o matriz con nombre de columna o similar) y una lista de nombres de columna (sin comillas) que definen los grupos como
parámetros adicionales. Al igual que con el uso del parámetro data = para lm () , la función busca dentro del marco de
datos las columnas especificadas.
III.8_43_R_185_Fish_DF_Group

¿Este marco de datos es diferente del original? Sí, pero sobre todo en los atributos/metadatos. Cuando se imprime, obtenemos cierta
información sobre la “fuente” (donde se almacenan los datos, dplyr puede acceder a datos de bases de datos remotas) y los
grupos:
III.8_44_R_186_Fish_DF_Grupo_Imprimir

Prácticamente, a diferencia de los marcos de datos regulares, los marcos de datos “agrupados” imprimen solo las primeras filas y
columnas, incluso si tienen muchos miles de filas de largo. A veces esta es una característica indeseable; afortunadamente, ejecutar
data.frame () en un marco de datos agrupado devuelve una versión desagrupada. La clase () de un marco de datos
agrupado devuelve “data.frame” así como “tbl” , “tbl_df” y “grouped_df” . Porque un marco de datos
agrupado también es un marco de datos regular (¡y también es una lista!) , todavía podemos hacer todas las operaciones de
indexación elegantes sobre ellas cubiertas en capítulos anteriores.
La función do () aplica una función a cada grupo de filas de un marco de datos agrupado usando una estrategia split-apply-
combine. La función aplicada debe tomar como primer parámetro un marco de datos, y debe devolver un marco de datos. Vamos a
escribir una función que, dado un marco de datos o subtrama de datos (grupo) de los datos de peces, devuelve un marco de datos de
una sola fila con columnas para mean_weight y sd_weight . Tanto las funciones mean () como sd () pueden
tomar un argumento na.rm para eliminar cualquier valor NA potencial en sus entradas, así que quizás nuestra función debería
tomar un parámetro opcional similar.
III.8_45_R_187_MEAN_SD_PESO

Esta función toma como entrada un marco de datos con una columna para el peso y devuelve un marco de datos con
estadísticas de resumen. Podemos ejecutarlo en el marco de datos original:
III.8_46_R_187_2_MEAN_SD_Weight_run
III.8_47_R_187_3_Mean_sd_weight_run_out

Alternativamente, podemos llamar a do () en el marco de datos agrupado, diciéndole que ejecute mean_sd_weight ()
en cada grupo (sub-trama de datos). La sintaxis para do () difiere ligeramente de la de lapply () en que especificamos
a . para el argumento posicional que representa el submarco de datos.
III.8_48_R_187_MEAN_SD_PESO_DO

El resultado del do () es otro marco de datos agrupado, compuesto por las filas devueltas por la función aplicada. Observe que
se han agregado las columnas de agrupación, aunque no las especificamos en el ret_df dentro de la función.

3.8.6 https://espanol.libretexts.org/@go/page/55139
III.8_49_R_188_MEAN_SD_PESO_DO_OUT

Al desarrollar funciones que funcionan do () , es posible que se encuentre con un error como
con
Error: Los resultados no son marcos de datos en las posiciones: 1, 2 . Este mensaje de error
indica que la función no está devolviendo un tipo de trama de datos, que se requiere para do () . Para diagnosticar problemas
como este, puede agregar sentencias print () dentro de la función para inspeccionar el contenido de las variables a medida
que se aplica la función.
III.8_50_DPLYR_DO

También podemos agrupar marcos de datos por múltiples columnas, lo que resulta en un solo grupo por combinación de entradas
de columna. [5]
III.8_51_R_189_Two_Col_Grupo_DO
III.8_52_R_190_Two_col_grupo_do_out

Los valores de NA para algunas desviaciones estándar son el resultado de llamar sd () en un vector de longitud uno (porque
solo hubo una medición de trucha por lago).
Aunque la función aplicada debe tomar un marco de datos y devolver un marco de datos, no hay restricciones sobre la naturaleza
del marco de datos devuelto. Aquí nuestra función devuelve un marco de datos de una sola fila, pero podría devolver varias filas
que serían cosidas juntas en el paso de combinación. Como ejemplo, aquí hay una función que, dada una trama de datos, calcula la
media () de la columna de peso , y resta esa media de todas las entradas, devolviendo la trama de datos modificada (la
llamada “normalización media” de los datos).
III.8_53_R_191_MEAN_NORMALIZAR

¡Y entonces podemos fácilmente normalizar los datos por grupo!


III.8_54_R_192_MEAN_NORMALIZE_DO
III.8_55_R_193_MEAN_NORMALIZE_DO

En la salida anterior, -1.15 y 1.15 son las desviaciones de la media del grupo de truchas, y las otras son desviaciones de la
media para el grupo bajo.

Más Azúcar, Parámetros Opcionales, Resumir


Algo a tener en cuenta sobre el call to do () es que difiere sintácticamente de la llamada a lapply () . En do () ,
especificamos no solo la función a aplicar a cada grupo, sino también cómo se llamará esa función, usando . para denotar el
marco de datos del grupo de entrada. Esto es algo más claro cuando queremos especificar argumentos opcionales a la función
aplicada. En este caso, es posible que queramos especificar que los valores NA deben eliminarse estableciendo
remove_nas = TRUE en cada llamada a mean_sd_weight () .
III.8_56_R_194_do_opcional_param

Hablando de azúcar sintáctico, el paquete magrittr (que se instala y carga junto con dplyr , aunque escrito por un autor
diferente) proporciona una interesante función de infijo, %>% . Considere el patrón común anterior; después de la creación de un
marco de datos ( fish ), lo ejecutamos a través de una función para crear un resultado intermediario ( fish_by_species
de group_by ()) , ejecutarlo a través de otra función para obtener otro resultado ( stats_by_species de
mean_sd_weight () ), y así sucesivamente. Si bien este proceso es claro, podríamos evitar la creación de múltiples líneas
de código si estuviéramos dispuestos a anidar algunas de las llamadas a funciones.
III.8_57_R_195_DO_anidado

Esta línea de código ciertamente sufre algunos problemas de legibilidad. La función de infijo %>% suministra su lado izquierdo a
su lado derecho (ubicado por . en el lado derecho), devolviendo el resultado de la llamada a la función derecha. Así podemos
reescribir lo anterior de la siguiente manera.
alt

Observe la similitud entre esto y el encadenamiento de métodos en Python y tuberías stdin/stdout en la línea de comandos.
(También es posible omitir el . si sería el primer parámetro, como en
peces %>% group_by (species) %>% do (mean_sd_weight (.)) .)

3.8.7 https://espanol.libretexts.org/@go/page/55139
Demostramos que las funciones aplicadas por do () pueden devolver marcos de datos de varias filas (en el ejemplo de
normalización media), pero nuestra función mean_sd_weight () solo devuelve un marco de datos de una sola fila con
columnas hechas de estadísticas de resumen simples. Para este tipo de necesidad simplificada, el paquete dplyr proporciona
una función especializada llamada summary () , tomando un marco de datos agrupado y otros parámetros que indican cómo se
deben calcular dichas estadísticas de resumen grupales. Este ejemplo produce la misma salida que el anterior, sin la necesidad de la
función intermediaria mean_sd_weight () .
III.8_59_R_196_Resumir

El paquete dplyr proporciona bastantes otras características y funciones para explorar, pero incluso la simple combinación de
group_by () y do () representan un paradigma poderoso. Debido a que R es un lenguaje tan flexible, es probable que el
tipo de azúcar sintáctico avanzado que usa dplyr se vuelva más común. Aunque se requiere un cierto esfuerzo para
comprender estos lenguajes específicos de dominio (DSL) y cómo se relacionan con R “normal”, el tiempo empleado suele valer la
pena.

Ejercicios
1. El propósito principal de lapply () es ejecutar una función en cada elemento de una lista y cotejar los resultados en una
lista. Con algo de creatividad, también se puede utilizar para ejecutar una llamada de función idéntica una gran cantidad de
veces. En este ejercicio veremos cómo se distribuyen los valores p bajo el modelo “nulo”, cuando se comparan dos muestras
aleatorias de la misma distribución.
Comienza escribiendo una función null_pval () que genere dos muestras aleatorias con
rnorm (100, media = 0, sd = 1) , las compara con t.test () , y devuelve el valor p. La función debe
tomar un solo parámetro, digamos, x , pero en realidad no hacer ningún uso de ella.
A continuación, genere una lista de 10,000 números con 10k_nums_list <- as.list (seq (1,10000)) , y
llame a 10k_pvals_list <- lapply (10k_nums, null_pval) . Convierte esto en un vector con
10k_pvals_vec <- unlist (10k_pvals_list) e inspecciona la distribución con
hist (10k_pvals_vec) .
¿Qué revela esta prueba? ¿Qué hace el código que escribiste y por qué funciona? ¿Por qué la función necesita tomar un
parámetro x que ni siquiera se usa? ¿Qué sucede si cambias una de las muestras aleatorias para usar
rnorm (100, media = 0.1, sd = 1) ?
2. Si df es un marco de datos, el uso de la indexación [] lo trata como una lista de elementos (columnas). Escribe una
función llamada numeric_cols () que tome un marco de datos como parámetro y devuelva una versión del marco de
datos con solo las columnas numéricas guardadas. Es posible que desee hacer uso de lapply () , unlist () ,
como.numeric () , y el hecho de que las listas (y los marcos de datos cuando se tratan como listas) se pueden indexar
por vector lógico.
Como ejemplo, si
df1 <- data.frame (id = c (“PRQ”, “XL2", “BB4"), val = c (23, 45.6, 62)) , entonces
print (numeric_cols (df1)) debe imprimir un marco de datos con solo la columna val . Si
df2 <- data.frame (srn = c (461, 514), name = c (“Mel”, “Ben”), age = c (27,
24))
, entonces print (numeric_cols (df2)) debe imprimir un marco de datos con solo columnas srn y age .
3. Escribe una función llamada subset_rows () que tome dos parámetros: primero, un marco de datos df , y segundo,
un entero n . La función debe devolver un marco de datos que consiste en n filas aleatorias de df (puede encontrar la
función sample () de uso).
El marco de datos del iris es un conjunto de datos R incorporado comúnmente referenciado, que describe mediciones de
pétalos para una variedad de especies de iris. Aquí está la salida de print (head (iris)) :
III.8_60_R_196_2_IRIS_RESUMEN Use group_by () para agrupar el marco de datos del iris por la columna Species , y

luego do () junto con su función subset_rows () para generar un marco de datos que consta de 10 random hileras
por especie.

3.8.8 https://espanol.libretexts.org/@go/page/55139
4. Curiosamente, no hay nada que detenga una función llamada por do () (o lapply () ) de sí misma llamando a
do () (y/o lapply () ). Escriba un conjunto de funciones que compare cada especie en el marco de datos del
iris con todas las demás especies, reportando la diferencia media en Pétal.Ancho . (En esta impresión la diferencia
de medias compara especiesA con especiesB .)
III.8_61_R_196_3_Double_doPara lograr esto, querrá escribir una función compare_petal_widths () que tome dos sub-

marcos de datos, uno que contenga datos para la especie A ( sub_dfa ) y el otro para la especie B ( sub_dfb ), y
devuelva un marco de datos que contenga el nombre de la A especie, el nombre de la especie B y la diferencia de la media de
pétalos. Ancho . Puede probar su función extrayendo manualmente marcos de datos que representan setosa y
versicolor .
A continuación, escribe una función one_vs_all_by_species () que nuevamente tome dos parámetros; el primero
será un marco de subdatos que represente una sola especie ( sub_df ), pero el segundo será un marco de datos con datos para
todas las especies ( all_df ). Esta función debe agrupar all_df por especie, y usar do () para llamar
compare_petal_widths () en cada grupo enviando a lo largo de sub_df como parámetro secundario. Se debe
devolver el resultado.
Finalmente, una función all_vs_all_by_species () puede tomar una sola trama de datos df , agruparla por
especie y llamar a one_vs_all_by_species () en cada grupo, enviando a lo largo de df como parámetro
secundario, devolviendo el resultado. Al final, todo lo que se necesita es llamar a esta función en el iris .

Expresión Génica, Terminada


Con la discusión de split-apply-combine y dplyr en nuestro haber, volvamos a la tarea de crear y analizar un modelo lineal
para cada ID en el conjunto de datos de expresión génica. Como recordatorio, habíamos dejado de haber leído en el conjunto de
datos “limpiados”, extraer un marco de subdatos que representaba un único ID y escribir una función que toma dicho subcuadro de
datos y devuelve un marco de datos de una sola fila de valores p. (Ahora debería quedar claro por qué pasamos por la molestia de
asegurarnos de que nuestra función tome un marco de datos como parámetro y devuelva uno también).
III.8_62_R_197_GENE_EXP_REVISIÓN

Ahora, podemos usar group_by () en el marco de datos expr para agrupar por la columna id , y do () para
aplicar la función sub_df_to_pvals_df () a cada grupo. Sin embargo, en lugar de trabajar en todo el conjunto de datos,
creemos un expr10 para contener un marco de datos que represente mediciones para 10 IDs; si estamos satisfechos con los
resultados, siempre podemos analizar la tabla expr completa (aunque el conjunto de datos completo tarda solo un par de
minutos en analizarlo) .
III.8_63_R_198_GENE_EXP_Grupo_por_DO

El resultado es una tabla bien organizada de valores p para cada gen en el conjunto de datos:
III.8_64_R_199_GENE_EXP_GROUP_BY_DO_OUT

Hay un tema más importante a considerar para un análisis como este: la corrección de pruebas múltiples. Supongamos por un
momento que ninguno de los ~11.000 genes se expresa diferencialmente de ninguna manera. Debido a que los valores de p se
distribuyen uniformemente entre cero y uno bajo la hipótesis nula (sin diferencia), solo para la columna de genotipos
podríamos esperar ~11,000 * 0.05 = 550 aparentemente (pero erróneamente) diferencias significativas. Alterando el escenario,
supongamos que hubo alrededor de 50 genes que realmente se expresaron diferencialmente en el experimento: estos probablemente
tendrían valores de p pequeños, pero es difícil separarlos de los otros 550 valores aparentemente significativos.
Hay una variedad de soluciones a este problema. Primero, podríamos considerar un umbral de significación mucho más pequeño.
¿Qué umbral debería ser? Bueno, si quisiéramos que el número de valores de p aparentemente —pero no realmente—
significativos fuera pequeño (en promedio), digamos, 0.05, podríamos resolver para 11,000 * α = 0.05, sugiriendo un corte de α =
0.000004545. Esto se conoce como corrección Bonferroni. El problema es que esta es una corrección conservadora, y es probable
que incluso nuestros 50 genes significativos no pasen ese pequeño umbral.
La razón por la que la corrección de Bonferroni es tan conservadora es que hemos especificado que sólo aceptaremos que el
número promedio de falsos positivos sea 0.05, lo cual es bastante pequeño. Alternativamente podríamos decir que aceptaríamos 10
falsos positivos (y resolveríamos por 11,000 * α = 10); si el número de resultados guardados es de 100, eso es solo una tasa de
descubrimiento falso (FDR) del 10%, y así podemos esperar que el 90% de los resultados guardados sean reales (¡pero no sabremos

3.8.9 https://espanol.libretexts.org/@go/page/55139
cuáles son cuáles!). Por otro lado, si el número de resultados guardados termina siendo de 15, esa es una tasa de falsos
descubrimientos del 75%, lo que indica que podemos esperar que la mayoría de los resultados guardados sean falsos positivos.
En lugar de especificar el número de falsos positivos que estamos dispuestos a aceptar, en su lugar podemos especificar la tasa y
tratar con cualquier número de resultados guardados que salgan. Hay varios de estos métodos de “control de FDR” disponibles, y
algunos hacen más suposiciones sobre los datos que otros. Por ejemplo, los métodos de Benjamini y Hochberg (descritos en 1995 y
conocidos por las siglas “BH”) y Benjamini, Hochberg y Yekutieli (de 2001, conocidos como “BY”) difieren en la cantidad de
independencia que se supone que tienen las muchas pruebas. El método BY asume menos independencia entre las pruebas, pero
generalmente da como resultado más resultados para una tasa de FDR dada. (Consulte a su estadístico local o libro de texto de
estadísticas para obtener más detalles.)
La función p.fit () nos permite ejecutar los métodos de corrección Bonferroni, BH o BY. Se necesitan dos argumentos:
primero, un vector de valores p para ajustar, y, segundo, un método = parámetro que se puede establecer en
“bonferroni” , “BH” o “BY” . (Hay otras posibilidades, pero estas tres son las más utilizadas). Se devuelve un vector
de la misma longitud de valores de FDR ajustados; seleccionar todas las entradas con valores menores que Q equivale a establecer
un límite de tasa de FDR de Q.
En el análisis final de todos los genes, produciremos una columna By-ajustada para los valores de interacción p, y luego
seleccionaremos del conjunto de datos solo aquellas filas donde la tasa de FDR sea menor a 0.05. (El script de análisis completo se
encuentra en el archivo Expr_Analyze.r .)
III.8_65_R_200_FULL_P_AJUSTAR

Desafortunadamente (o afortunadamente, dependiendo de la cantidad de datos que se espera analizar más a fondo), solo se
identifica que un gen tiene una interacción significativa después de la corrección BY en este análisis. (Si se devolvieran 100,
esperaríamos que 5 de ellos fueran falsos positivos debido al corte de 0.05 FDR utilizado).
III.8_66_R_201_FULL_P_AJUST_OUT

Para los marcos de datos agrupados, print () omitirá la impresión de algunas columnas si no caben en la ventana de
visualización. Para ver todas las columnas (incluyendo nuestra nueva columna Interaction_by ), podemos convertir la
tabla agrupada de nuevo en un marco de datos regular e imprimir la cabeza () de la misma con
print (head (data.frame (pvals_interaction_sig))) . Para los curiosos, el valor Interaction_by
de este ID es 0.048.

Ejercicios
1. Gastamos una buena cantidad de trabajo asegurando que el marco de datos generado y devuelto por
sub_df_to_pvals_df () se generó mediante programación. Aprovecha este hecho haciendo de la fórmula un
parámetro secundario de la función, es decir, como función (sub_df, form) , de manera que dentro de la función el
modelo lineal se pueda producir como lm1 <- lm (form, data = sub_df) .
A continuación, ejecute el análisis con group_by () y do () , pero especifique alguna otra fórmula en la llamada
do () (tal vez algo así como expresión ~ genotipo + tratamiento + tejido ).
2. En un ejercicio anterior de este capítulo, escribimos una función que tomó una parte del marco de datos de CO2 y devolvió
un marco de datos con un valor p para una comparación de los valores de absorción de CO2 entre plantas refrigeradas y no
refrigeradas. Ahora, use group_by () y do () para ejecutar esta prueba para cada grupo conc , produciendo un
marco de datos de valores p. Use p.fit () con corrección de Bonferroni para agregar una columna adicional de valores
corregidos múltiples.
3. Si bien las funciones llamadas por do () deben tomar un marco de datos como su primer parámetro y deben devolver un
marco de datos, también son libres de realizar cualquier otra acción, como generar una trama o escribir un archivo. Utilice
group_by () , do () y write.table () para escribir el contenido del marco de datos de CO2 en siete
archivos de texto, uno por cada nivel conc . Denles el nombre conc_95.txt , conc_175.txt , y así
sucesivamente. Es posible que necesite paste () o str_c () (de la biblioteca stringr ) para generar los
nombres de archivo.

3.8.10 https://espanol.libretexts.org/@go/page/55139
1. Aquí usaremos el término coloquial “gen” para referirnos a un locus genético que produce ARN mensajero y su expresión para
ser representada por la cantidad de ARNm (de las diversas isoformas) presente en la célula en el momento del muestreo.
2. Las micromatrices son una tecnología más antigua para medir la expresión génica que han sido suplantadas en gran medida por
los métodos directos de RNA-seq. Para ambas tecnologías, los niveles generales de expresión varían entre muestras (por
ejemplo, debido a la eficiencia de la medición) y deben modificarse para que los valores de diferentes muestras sean
comparables; los datos de micromatrices a menudo también se transforman en preparación para el análisis estadístico.
Ambos pasos se han realizado para estos datos; hemos evitado la discusión de estos pasos preparatorios ya que son específicos
de la tecnología utilizada y un área de investigación activa para RNA-seq. Generalmente, los análisis de RNA-seq implican
trabajo tanto en la línea de comandos para el preprocesamiento de datos como en el uso de funciones de paquetes R
especializados.
3. Hay muchas formas interesantes en las que podríamos analizar estos datos. Por el bien de enfocarnos en R en lugar de métodos
estadísticos, nos apegaremos a un modelo ANOVA 2x2 por gen relativamente simple.
4. Debido a que la función apply () opera sobre matrices, convertirá silenciosamente cualquier marco de datos que se le
haya dado en una matriz. Debido a que las matrices pueden contener solo un tipo (mientras que los marcos de datos pueden
contener diferentes tipos en diferentes columnas), usar apply () en un marco de datos primero forzará una autoconversión
para que todas las columnas sean del mismo tipo. Como tal, casi nunca es correcto usar apply () en un marco de datos
con columnas de diferentes tipos.
5. Debido a que group_by () y funciones similares toman nombres de columna “desnudos” o “sin comillas”, pueden ser
difíciles de llamar dado un vector de caracteres. Para funciones en los paquetes dplyr y tidyr (cubiertas más adelante),
las versiones con _ como parte del nombre de la función soportan esto. Por ejemplo,
fish_by_species_lake <- group_by (fish, species, lake) puede ser reemplazado por
fish_by_species_lake <- group_by_ (fish, c (“species”, “lake”)) .

This page titled 3.8: Dividir, Aplicar, Combinar is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

3.8.11 https://espanol.libretexts.org/@go/page/55139
3.9: Remodelación y unión de marcos de datos
Mirando hacia atrás en el análisis split-apply-combine para los datos de expresión génica, está claro que la organización de los
datos funcionó a nuestro favor (después de dividir la columna de muestra en genodad pe , tratamiento y otras
columnas, al menos). En particular, cada fila del marco de datos representaba una sola medición, y cada columna representaba una
variable (valores que varían entre las mediciones). Esto nos permitió agrupar fácilmente las mediciones por la columna ID.
También funcionó bien al construir el modelo lineal, ya que lm () utiliza una fórmula que describe la relación de vectores o
columnas de igual longitud (por ejemplo, expr$expression ~ expr$treatment + expr$genotipo ), que son
directamente accesibles como columnas del marco de datos.
Los datos a menudo no se organizan en este formato “ordenado”. Este conjunto de datos, por ejemplo, estaba originalmente en un
archivo llamado expr_wide.txt con el siguiente formato:
III.9_1_R_202_Datos

Los formatos de datos como este suelen ser más convenientes para los lectores humanos que para las máquinas. Aquí, las columnas
id y anotación (esta última no mostrada) son variables como antes, pero los nombres de las otras columnas codifican una
variedad de variables, y cada fila representa un gran número de mediciones. Los datos en esta “forma” serían mucho más difíciles
de analizar con las herramientas que hemos cubierto hasta ahora.
Convertir entre este formato y el formato preferido (y viceversa) es el objetivo principal del paquete tidyr . Como es habitual,
este paquete se puede instalar en el intérprete interactivo con install.packages (“tidyr”) . El paquete más antiguo
reshape2 también maneja este tipo de reorganización de datos, y aunque las funciones proporcionadas allí son más flexibles y
potentes, tidyr cubre la mayoría de las necesidades a la vez que es más fácil de usar.

Reunión para el orden


La función de recopilación () en el paquete tidyr hace que la mayoría de los marcos de datos desordenados sean
más ordenados. El primer parámetro que se toma es el marco de datos a fijar, y el segundo y el tercero son los nombres de “clave”
y “valor” para las columnas recién creadas, respectivamente (sin comillas). Los parámetros restantes especifican los nombres de
columna que deben ordenarse (nuevamente, sin comillas). Supongamos que tenemos un marco de datos pequeño y desordenado
llamado expr_small , con columnas para id , anotación y columnas para expresión en los genotipos C6 y L4 .
alt

En este caso, ejecutaríamos la función gather() de la siguiente manera, donde sample y expression son los
nuevos nombres de columna a crear, y C6 y L4 son las columnas que necesitan ordenar. (Observe la falta de comillas en todos
los nombres de columna; esto es común al azúcar sintáctico de los paquetes tidyr y dplyr .)
III.9_3_R_203_Gather_small
III.9_4_R_204_Gather_small_out

Observe que los datos en las columnas no agrupadas, no ordenadas ( id y anotación ) se han repetido según sea necesario.
Si no se han especificado columnas para ordenar ( C6 y L4 en este caso), la recopilación () asume que todas las
columnas necesitan ser reorganizadas, resultando en solo dos columnas de salida ( muestra y expresión ). Esto sería
obviamente incorrecto en este caso.
Enumerar todos los nombres de columna que necesitan ordenar presenta un desafío cuando se trabaja con marcos de datos amplios,
como el conjunto completo de datos de expresión. Para reunir esta tabla, necesitaríamos ejecutar la
recopilación (expr_wide, sample, expression, c6_chemical_a1, c6_chemical_a3,
c6_chemical_b1, y así sucesivamente,
enumerando cada uno de los 35 nombres de columna. Afortunadamente, se reúnen () acepta un poco adicional de azúcar
sintáctico: usando - y especificando las columnas que no necesitan ser reunidas. Así podríamos recopilar el conjunto completo
de datos con
III.9_5_R_205_Gather_Full_Minus

La función join () toma algunos otros parámetros opcionales, por ejemplo, para eliminar filas con valores NA . Ver
ayuda (“reunir”) en el intérprete interactivo para más información.

3.9.1 https://espanol.libretexts.org/@go/page/55138
Desaglutinar con untar ()
Si bien esta organización de los datos, con cada fila siendo una observación y cada columna siendo una variable, suele ser más
conveniente para el análisis en R, compartir datos con otras personas a menudo no lo es. La gente tiene afinidad por leer tablas de
datos donde muchas mediciones se enumeran en una sola fila, mientras que las versiones “ordenadas” de las tablas de datos suelen
ser largas con muchas entradas repetidas. Incluso cuando se analizan programáticamente tablas de datos, a veces ayuda tener
múltiples mediciones en una sola fila. Si quisiéramos calcular la diferencia entre las expresiones de genotipo C6 y L4 para
cada condición de tratamiento, podríamos desear tener columnas C6_expression y L4_expression , para que luego
podamos usar resta vectorizada para calcular un C6_L4_ columna de diferencia .
La función spread () en el tidyr proporciona esto, y de hecho es el complemento
de la función gather.
Los tres parámetros importantes son (1) el marco de datos a propagar, (2) la columna a usar como la “clave” y (3) la columna para
usar como los “valores”. Considere el marco de datos expr_gathered_small desde arriba.
alt

Convertir este marco de datos de nuevo a la versión “amplia” es tan simple como:
III.9_7_R_206_Spread
III.9_8_R_207_Spread_out

Debido a que las entradas en el nombre de columna “clave” se convierten en nuevos nombres de columna, normalmente sería un
error usar una columna numérica aquí. En particular, si tuviéramos que mezclar el orden y en su lugar ejecutar
spread (expr_gathered_small, expression, sample) , terminaríamos con una columna por cada valor
único en la columna de expresión , que fácilmente podría numerar en los cientos de miles y probablemente fallaría al
intérprete.
En combinación group_by () ,
con do () y sumze () del paquete dplyr ,
se pueden usar gather_by () y spread () para agregar y analizar datos tabulares en un número casi
ilimitado de formas. Tanto los paquetes dplyr como tidyr incluyen una serie de otras funciones para trabajar con marcos
de datos, incluyendo filtrar filas o columnas por criterios de selección y organizar filas y columnas.

Dividir columnas
En el capítulo 32, “Carácter y datos categóricos”, aprendimos a trabajar con vectores de caracteres usando funciones como
str_split_fixed () para dividirlos en pedazos basados en un patrón, y str_detect () para producir un vector
lógico que indica qué elementos coincidían con un patrón. El paquete tidyr también incluye algunas funciones especializadas
para este tipo de operaciones. Considere un marco de datos pequeño expr_sample con columnas para id ,
expression y sample , como el marco de datos prelimpiado considerado en capítulos anteriores.
III.9_9_R_208_EXPR_Muestra

La función tidyr separate () se puede utilizar para dividir rápidamente una columna (carácter o factor) en múltiples
columnas basadas en un patrón. El primer parámetro es el marco de datos para trabajar, el segundo es la columna para dividir
dentro de ese marco de datos, el tercero especifica un vector de caracteres de nombres de columna recién divididos, y el cuarto
parámetro opcional sep = especifica el patrón (expresión regular) para dividir.
III.9_10_R_209_EXPR_Sample_Separar
III.9_11_R_210_Expr_Sample_Separate_out

De manera similar, la función extract () divide una columna en múltiples columnas basadas en un patrón (expresión
regular), pero el patrón es más general y requiere una comprensión de las expresiones regulares y la retroreferenciación usando
() grupos de captura. Aquí, usaremos el patrón de expresión regular “([A-Z]) ([0-9])” para hacer coincidir cualquier
letra mayúscula seguida de un solo dígito, cada uno de los cuales es capturado por un par de paréntesis. Estos valores se convertirán
en las entradas para las columnas recién creadas.
III.9_12_R_211_Expr_Sample_Extract
III.9_13_R_212_EXPR_MUESTRA_EXTRACT_OUT

3.9.2 https://espanol.libretexts.org/@go/page/55138
Aunque cubrimos expresiones regulares en capítulos anteriores, para entradas como C6_control_b3 donde asumimos que la
codificación está bien descrita, podríamos usar una expresión regular como
“(C6|L4) _ (control|chemical) _ (A|B|C) (1|2|3)” .
Si bien estas funciones son convenientes para trabajar con columnas de marcos de datos, una comprensión de
str_split_fixed () y str_detect () es útil para trabajar con datos de caracteres en general.

Unión/Fusión de Marcos de Datos, cbind () y rbind ()


Incluso después de que los marcos de datos han sido remodelados y de otra manera masajeados para facilitar los análisis,
ocasionalmente datos similares o relacionados están presentes en dos marcos de datos diferentes. Quizás las anotaciones para un
conjunto de ID de genes están presentes en un marco de datos, mientras que los valores p de los resultados estadísticos están
presentes en otro. Por lo general, tales tablas tienen una o más columnas en común, tal vez una columna id .
En ocasiones, cada entrada en una de las tablas tiene una entrada correspondiente en la otra, y viceversa. Más a menudo, algunas
entradas son compartidas, pero otras no. Aquí hay un ejemplo de dos pequeños marcos de datos donde este es el caso, llamados
alturas y edades .
alt

La función merge () (que viene con la instalación básica de R) puede unir rápidamente estos dos marcos de datos en uno
solo. Por defecto, encuentra todas las columnas que tienen nombres comunes y utiliza todas las entradas que coinciden en todas
esas columnas. Aquí está el resultado de la fusión (alturas, edades) :
III.9_15_R_213_Fusiones_alturas_edades

Esto es mucho más fácil de usar que el programa de unión de línea de comandos: puede unirse en múltiples columnas, y las filas no
necesitan estar en ningún orden común. Si nos gusta, podemos especificar opcionalmente un parámetro by = , para especificar
por qué nombres de columna unir como vector de caracteres de nombres de columna. Aquí está la
fusión (alturas, edades, por = c (“primero”)) :
III.9_16_R_214_Merge_alturas_ages_por_primera

Debido a que especificamos que la unión solo debería ocurrir por la primera columna, merge () asumió que las dos columnas
nombradas last podrían contener datos diferentes y por lo tanto deberían estar representadas por dos columnas diferentes en la
salida.
Por defecto, merge () produce una “unión interna”, lo que significa que las filas están presentes en la salida solo si las
entradas están presentes para las entradas izquierda ( alturas ) y derecha ( edades ). Podemos especificar
all = VERDADERO para realizar una “unión externa” completa. Aquí está la
fusión (alturas, edades, todo = VERDADERO) .
III.9_17_R_215_Merge_alturas_ages_todos

En este ejemplo, se han colocado valores NA para entradas que no están especificadas. A partir de aquí, las filas con entradas
NA en la columna de altura o edad se pueden eliminar con selección basada en filas e is.na () , o una “unión
externa izquierda” o “unión externa derecha”, se puede realizar con all.x = TRUE o all.y = TRUE , respectivamente .
En el capítulo 32, también observamos cbind () después de dividir vectores de caracteres en marcos de datos multicolumna.
Esta función une dos tramas de datos en una sola columna. No funcionará si los dos marcos de datos no tienen el mismo número de
filas, pero funcionará si los dos marcos de datos tienen nombres de columna que son idénticos, en cuyo caso el marco de datos de
salida podría tener confusamente varias columnas del mismo nombre. Esta función también dejará las filas del marco de datos en
su orden original, así que tenga cuidado de que el orden de las filas sea consistente antes de la vinculación. Generalmente, se
prefiere usar merge () para unir marcos de datos por columna a cbind () , incluso si esto significa asegurar que alguna
columna de identificador esté siempre presente para servir como columna de enlace.
La función rbind () combina dos marcos de datos que pueden tener diferentes números de filas pero tienen el mismo número
de columnas. Además, los nombres de columna de los dos marcos de datos deben ser idénticos. Si los tipos de datos son diferentes,
entonces después de combinarse con rbind () , las columnas de diferentes tipos se convertirán al tipo más general usando las
mismas reglas al mezclar tipos dentro de vectores.

3.9.3 https://espanol.libretexts.org/@go/page/55138
El uso de rbind () requiere que los datos de cada vector de entrada sean copiados para producir el marco de datos de salida,
incluso si el nombre de la variable se va a reutilizar como en df <- rbind (df, df2) . Siempre que sea posible, los
marcos de datos deben generarse con una estrategia split-apply-combine (como con group_by () y do () ) o una técnica
de remodelación, en lugar de con muchas aplicaciones repetidas de rbind () .

Ejercicios
1. Como se discutió en los ejercicios del Capítulo 33, Dividir, Aplicar, Combinar, el marco de datos de CO2 incorporado
contiene mediciones de las tasas de absorción de CO2 para diferentes plantas en diferentes ubicaciones bajo diferentes
concentraciones ambientales de CO2.
Utilice la función spread () en la biblioteca tidyr para producir un marco de datos CO2_spread que se vea así:
III.9_18_R_215_2_SPREAD_EJERCICIO A continuación, deshaga esta operación con una recopilación () , recreando el marco de

datos de CO2 como CO2_recreado .


2. Ocasionalmente, queremos “remodelar” un marco de datos mientras se computan simultáneamente los resúmenes de los datos.
El paquete reshape2 proporciona algunas funciones sofisticadas para este tipo de cálculo (específicamente melt () y
cast () ), pero también podemos realizar este tipo de tareas con group_by () y do () (o sumze () ) desde
el dplyr paquete en combinación con gathere () y spread () de tidyr .
A partir del marco de datos de CO2 , genere un marco de datos como el siguiente, donde las dos últimas columnas reportan la
absorción media para cada combinación Tipo / conc :
III.9_19_R_215_3_Reshape_alternative Es probable que desee comenzar por calcular las medias grupales apropiadas a partir del CO2
original datos.
3. Los marcos de datos incorporados castor1 y castero2 describen la temperatura corporal y las observaciones de
actividad de dos castores. Fusiona estos dos marcos de datos en uno único que contiene todos los datos y se ve así:
III.9_20_R_215_4_Reshape_alternative Observe la columna para el nombre —asegúrese de incluir esta columna para que quede claro a

qué castor corresponde cada medición!

This page titled 3.9: Remodelación y unión de marcos de datos is shared under a CC BY-NC-SA license and was authored, remixed, and/or
curated by Shawn T. O’Neil (OSU Press) .

3.9.4 https://espanol.libretexts.org/@go/page/55138
3.10: Programación Procesal
Para muchos (quizás la mayoría) lenguajes, las estructuras de flujo de control como for-loops y sentencias if son bloques de
construcción fundamentales para escribir programas útiles. Por el contrario, en R, podemos lograr mucho haciendo uso de
operaciones vectorizadas, reciclaje vectorial, indexación lógica, split-apply-combine, etc. En general, R enfatiza fuertemente las
operaciones vectorizadas y funcionales, y estos enfoques deben usarse cuando sea posible; también suelen estar optimizados para la
velocidad. Aún así, R proporciona el repertorio estándar de bucles y estructuras condicionales (que forman la base del paradigma
de la “programación procedimental”) que se puede utilizar cuando otros métodos son torpes o difíciles de diseñar. Ya hemos visto
funciones, por supuesto, que son increíblemente importantes en la gran mayoría de los lenguajes de programación.

Bucle condicional con bucles While-Loops


Un bucle while ejecuta un bloque de código una y otra vez, probando una condición dada (que debería ser o devolver un vector
lógico) antes de cada ejecución. En R, los bloques se delinean con un par de corchetes. La práctica estándar es colocar el primer
corchete de apertura en la línea, definiendo el bucle y el corchete de cierre en una línea por sí mismo, con el bloque envolvente
indentado por dos espacios.
III.10_1_R_216_MIENTRA-1

En la ejecución de lo anterior, cuando se alcanza la línea while (count < 4) , count < 4 se ejecuta y devuelve el
vector lógico de un solo elemento TRUE , dando como resultado que el bloque se ejecute e imprima “Count is:" y 1 , y
luego incrementando contar por 1. Entonces el bucle vuelve a la comprobación; el conteo siendo 2 es aún menor que 4,
por lo que ocurre la impresión y el conteo se incrementa nuevamente a 3. El bucle comienza de nuevo, se ejecuta la
comprobación, se imprime la salida y el recuento se incrementa a 4. El bucle vuelve a la parte superior, pero esta vez el
recuento < 4 da como resultado FALSO , por lo que se salta el bloque, y finalmente la ejecución pasa a imprimir
“¡Hecho!” .
III.10_2_R_217_While_1_out

Debido a que la comprobación ocurre al inicio, si el conteo comenzara en algún número mayor como 5 , entonces el bloque
de bucle se saltaría por completo y solo “¡Hecho!” se imprimirían.
Debido a que no existen “datos desnudos” en R y los vectores son la unidad más básica, la comprobación lógica dentro del
while () funciona con un vector lógico. En lo anterior, ese vector acaba de pasar a tener un solo elemento. ¿Y si la
comparación devolvió un vector más largo? En ese caso, solo el primer elemento del vector lógico será considerado por el
while () , y se emitirá una advertencia al efecto de condición tiene longitud > 1 . Esto puede tener algunas
consecuencias extrañas. Consideremos si en lugar de contar <- 1 , teníamos count <- c (1, 100) . Debido a la
naturaleza vectorizada de la suma, la salida sería:
III.10_3_R_218_While_2_out

Dos funciones prácticas pueden proporcionar una medida de seguridad frente a tales errores cuando se usan con condicionales
simples: any () y all () . Dado un vector lógico, estas funciones devuelven un vector lógico de un solo elemento que
indica si alguno, o todos, de los elementos en el vector de entrada son VERDADEROS . Por lo tanto, nuestro condicional
while -loop anterior podría codificarse mejor como while (any (count < 4)) . (¿Se puede decir la diferencia entre
esto y while (any (count) < 4) ?)

Generación de Datos Aleatorios Truncados, Parte I


Las características estadísticas únicas de R se pueden combinar con estructuras de flujo de control como bucles while de maneras
interesantes. R sobresale al generar datos aleatorios a partir de una distribución dada; por ejemplo,
rnorm (1000, media = 20, sd = 10) devuelve un vector numérico de 100 elementos con valores muestreados de
una distribución normal con media 20 y desviación estándar 10.
III.10_4_R_219_RNORM_HIST

Aunque cubriremos el trazado con más detalle más adelante, la función hist () nos permite producir un histograma básico a
partir de un vector numérico. (Hacerlo requiere una interfaz gráfica como Rstudio para mostrar fácilmente la salida. Consulte el
capítulo 37, “Datos de trazado y ggplot2 ”, para obtener detalles sobre el trazado en R.)

3.10.1 https://espanol.libretexts.org/@go/page/55107
III.10_5_rnorm_muestra_hist

¿Y si quisiéramos muestrear números originarios de esta distribución, pero limitados al rango de 0 a 30? Una forma de hacerlo es
“remuestrear” cualquier valor que se encuentre fuera de este rango. Esto truncará efectivamente la distribución a estos valores,
produciendo una nueva distribución con media y desviación estándar alteradas. (Este tipo de distribuciones “truncadas por
remuestreo” a veces son necesarias para fines de simulación).
Idealmente, nos gustaría una función que encapsula esta idea, así podemos decir
sample <- rnorm_trunc (0, 30, 1000, mean = 20, sd = 10) . Comenzaremos definiendo nuestra
función, y dentro de la función primero produciremos una muestra inicial.
III.10_6_R_220_RNORM_TRUNC_1

Observe el uso de media = media en la llamada a rnorm () . El lado derecho se refiere al parámetro pasado a la
función rnorm_trunc () , el lado izquierdo se refiere al parámetro tomado por rnorm () , y el intérprete no tiene
ningún problema con este uso.
Ahora, necesitaremos “arreglar” la muestra siempre y cuando cualquiera de los valores sea menor que menor o mayor que
superior . Comprobaremos tantas veces como sea necesario usando un bucle while.
III.10_7_R_220_RNORM_TRUNC_2

Si algún valor está fuera del rango deseado, no queremos simplemente probar un conjunto de muestras completamente nuevo,
porque la probabilidad de generar solo la muestra correcta (dentro del rango) es increíblemente pequeña, y estaríamos haciendo un
bucle durante bastante tiempo. Más bien, sólo remuestrearemos aquellos valores que lo necesiten, generando primero un vector
lógico de elementos “malos”. Después de eso, podemos generar un remuestreo del tamaño necesario y usar reemplazo selectivo
para reemplazar los elementos malos.
III.10_8_R_221_RNORM_TRUNC_2

Vamos a probarlo:
III.10_9_R_222_RNORM_TRUNC_2_Run

El histograma trazado refleja la naturaleza truncada del conjunto de datos:


III.10_10_RNORM_MUESTRA_HIST_TRUNC_OUT

IF-Declaraciones
Una sentencia if ejecuta un bloque de código basado en un condicional (como un bucle while, pero el bloque solo se puede ejecutar
una vez). Al igual que el check for while , solo se verifica el primer elemento de un vector lógico, por lo que se sugiere usar
any () y all () por seguridad a menos que estemos seguros de que la comparación dará como resultado un vector lógico
de un solo elemento.
III.10_11_R_223_IF_Básica

Al igual que las sentencias if en Python, cada condicional se verifica a su vez. Tan pronto como se evalúa como VERDADERO ,
ese bloque se ejecuta y se salta todo el resto. Sólo se ejecutará un bloque de una cadena if/else, y tal vez ninguno lo será. Se
requiere el bloque controlado si para iniciar la cadena, pero uno o más bloques controlados si están controlados
y el bloque final controlado por otro son opcionales.
En R, si queremos incluir uno o más bloques else if o un solo bloque else , las palabras clave else if ()
o bien deben aparecer en la misma línea que el corchete de cierre anterior. Esto difiere ligeramente de otros idiomas y es
resultado de la forma en que el intérprete R analiza el código.

Generación de Datos Aleatorios Truncados, Parte II


Uno de los problemas con nuestra función rnorm_trunc () es que si el rango deseado es pequeño, aún podría requerir
muchos esfuerzos de remuestreo para producir un resultado. Por ejemplo, llamar a
sample_trunc <- rnorm_trunc (15, 15.01, 1000, 20, 10) tardará mucho en terminar, ya que es raro
generar aleatoriamente valores entre 15 y 15.01 a partir de la distribución dada. En cambio, lo que nos podría gustar es que la

3.10.2 https://espanol.libretexts.org/@go/page/55107
función se dé por vencida después de algún número de remuestreos (digamos, 100,000) y devuelva un vector que contenga NA ,
indicando un cálculo fallido. Posteriormente, podemos verificar el resultado devuelto con is.na () .
III.10_12_R_224_RNORM_TRUNC_NA

El ejemplo hasta ahora ilustra algunas cosas diferentes:


1. NA puede actuar como marcador de posición en nuestras propias funciones, al igual que mean () devolverá NA si
alguno de los elementos de entrada es NA en sí mismo.
2. Las declaraciones IF pueden usar else si y else , pero no tienen que hacerlo.
3. Las funciones pueden regresar desde más de un punto; cuando esto sucede, la ejecución de la función se detiene incluso si está
dentro de un bucle. [1]
4. Los bloques de código, como los utilizados por los bucles while, las instrucciones if y las funciones, pueden anidarse. Es
importante aumentar el nivel de sangría para todas las líneas dentro de cada bloque anidado; de lo contrario, el código sería
difícil de leer.

For-Loops
For-loops son la última estructura de flujo de control que estudiaremos para R. Al igual que los bucles while, repiten un bloque de
código. For-loops, sin embargo, repiten un bloque de código una vez por cada elemento de un vector (o lista), estableciendo un
nombre de variable dado a cada elemento del vector (o lista) a su vez.
III.10_13_R_225_for_loop_basic
III.10_14_R_226_for_loop_basic_out

Si bien los bucles for son importantes en muchos otros lenguajes, no son tan cruciales en R. La razón es que muchas operaciones
están vectorizadas, y funciones como lapply () y do () proporcionan el cálculo repetido de una manera completamente
diferente.
Hay un poco de historia que sostiene que los bucles for son más lentos en R que en otros idiomas. Una forma de determinar si esto
es cierto (y en qué medida) es escribir un bucle rápido que itera un millón de veces, tal vez imprimiendo en cada iteración mil.
(Logramos esto manteniendo un contador de bucle y usando el operador de módulo %% para verificar si el resto del
contador dividido por 1000 es 0 .)
III.10_15_R_227_por_one_million

Cuando ejecutamos esta operación nos encontramos con que, aunque no del todo instantáneo, este bucle no tarda en absoluto. Aquí
hay una comparación rápida que mide el tiempo para ejecutar código similar para algunos idiomas diferentes (sin imprimir, lo que a
su vez lleva algún tiempo) en una MacBook Pro 2013.

Lenguaje Loop 1 Millón Loop 100 Millones

R (versión 3.1) 0.39 segundos ~30 segundos

Python (versión 3.2) 0.16 segundos ~12 segundos

Perl (versión 5.16) 0.14 segundos ~13 segundos

C (g++ versión 4.2.1) 0.0006 segundos 0.2 segundos

Si bien R es el más lento del grupo, es “meramente” el doble de lento que los otros lenguajes interpretados, Python y Perl.
El problema con R no es que los bucles for sean lentos, sino que uno de los usos más comunes para ellos, alargar iterativamente un
vector con c () o un marco de datos con rbind () , es muy lento. Si quisiéramos ilustrar este problema, podríamos
modificar el bucle anterior de tal manera que, en lugar de agregar uno a contador , aumentemos la longitud del vector
contador en uno con contador <- c (contador, 1) . También modificaremos la impresión para reflejar el
interés en la longitud del vector contador en lugar de su contenido.
III.10_16_R_228_for_one_millones_append

En este caso, encontramos que el bucle comienza tan rápido como antes, pero a medida que el vector contador se alarga, el
tiempo entre impresiones crece. Y sigue creciendo y creciendo, porque el tiempo que lleva hacer una sola iteración del bucle
depende de la longitud del vector contador (que está creciendo).

3.10.3 https://espanol.libretexts.org/@go/page/55107
Para entender por qué este es el caso, tenemos que inspeccionar el contador de línea <- c (contador, 1) , que
agrega un nuevo elemento al vector contador . Consideremos un conjunto relacionado de líneas, en donde se concatenan dos
vectores para producir un tercero.
III.10_17_R_229_Concatenate_lento

En lo anterior, los nombres serán el vector esperado de cuatro elementos, pero los
nombres_nombres y apellidos no han sido eliminados o alterados por esta operación, persisten y aún pueden
imprimirse o accederse de otra manera más tarde. Para eliminar o alterar elementos, el intérprete R copia la información de cada
vector más pequeño en un nuevo vector que está asociado con los nombres de las variables.
Ahora, podemos volver al contador <- c (contador, 1) . El lado derecho copia la información de ambas entradas
para producir un nuevo vector; éste se asigna luego al contador de variables. Hace poca diferencia para el intérprete que el
nombre de la variable se esté reutilizando y el vector original ya no será accesible: la función c () (casi) siempre crea un nuevo
vector en la RAM. La cantidad de tiempo que lleva hacer esta copia crece así junto con la longitud del vector contador .
alt

La cantidad total de tiempo que se tarda en agregar n elementos a un vector en un bucle for de esta manera es aproximadamente
, lo que significa que el tiempo que se tarda en hacer crecer una lista de n elementos crece cuadráticamente en su
longitud final! Este problema se agrava cuando se usa rbind () dentro de un for-loop para hacer crecer una trama de datos
fila por fila (como en algo así como df <- rbind (df, c (val1, val2, val3))) , ya que las columnas de
marcos de datos suelen ser vectores, haciendo de rbind () una aplicación repetida de c () .
Una solución a este problema en R es “preasignar” un vector (o marco de datos) del tamaño apropiado, y usar el reemplazo para
asignar a los elementos en orden usando un bucle cuidadosamente construido.
III.10_19_R_230_Pre_alloc

(Aquí simplemente estamos colocando valores de 1 en el vector, pero ciertamente son posibles ejemplos más sofisticados). Este
código se ejecuta mucho más rápido pero tiene la desventaja de requerir que el programador sepa de antemano qué tan grande
tendrá que ser el conjunto de datos. [2]
¿Significa esto que nunca debemos usar bucles en R? ¡Desde luego que no! A veces, el bucle es un ajuste natural para un problema,
especialmente cuando no implica el crecimiento dinámico de un vector, una lista o un marco de datos.

Ejercicios
1. Usando un bucle for y un vector preasignado, generar un vector de los primeros 100 números de Fibonacci, 1, 1, 2, 3, 5, 8, y así
sucesivamente (los dos primeros números de Fibonacci son 1; de lo contrario, cada uno es la suma de los dos anteriores).
2. Una línea como resultado <- readline (prompt = “¿Cuál es tu nombre?”) solicitará al usuario que
ingrese su nombre y almacenará el resultado como un vector de caracteres en el resultado . Usando esto, un bucle while y
una declaración if, podemos crear un simple juego de adivinación de números.
Comience estableciendo entrada <- 0 y rand a un entero aleatorio entre 1 y 100 con
la
rand <- muestra (seq (1,100), tamaño = 1) . A continuación, ¡mientras que la entrada! = rand :
Lee una conjetura del usuario y conviértela en un entero, almacenando el resultado en entrada . Si la
entrada < rand , imprima “¡Mayor!” , de lo contrario si ingresa > rand , imprima “¡Inferior!” ,
y de lo contrario reportar “¡Lo tienes!” .
3. La prueba t de Student emparejada evalúa si dos vectores revelan una diferencia significativa en cuanto a elementos. Por
ejemplo, podríamos tener una lista de puntuaciones de los estudiantes antes y después de una sesión de entrenamiento.
III.10_20_R_230_2_TTEST_PAREADOPero la prueba t pareada solo debe usarse cuando las diferencias (que en este caso podrían calcularse

con puntos_después - puntuaciones ) están normalmente distribuidas. Si no lo son, una mejor prueba es la
prueba de rango firmado de Wilcoxon:
III.10_21_R_230_3_Wilcoxon_pareado (Mientras que la prueba t verifica para determinar si la diferencia de medias es significativamente

diferente de 0 , la prueba de rango firmado de Wilcoxon verifica para determinar si la diferencia de mediana es
significativamente diferente de 0 .)

3.10.4 https://espanol.libretexts.org/@go/page/55107
El proceso de determinar si los datos se distribuyen normalmente no es fácil. Pero una función conocida como la prueba de
Shapiro está disponible en R, y prueba la hipótesis nula de que un vector numérico no se distribuye normalmente. Por lo tanto,
el valor p es pequeño cuando los datos no son normales. La desventaja de una prueba de Shapiro es que ésta y pruebas similares
tienden a ser sobresensibles a la no normalidad cuando se dan muestras grandes. La función shapiro.test () se puede
explorar ejecutando print (shapiro.test (rnorm (100, mean = 10, sd = 4))) e
print (shapiro.test (rexp (100, rate = 2.0))) .
Escribe una función llamada wilcox_or_ttest () que toma dos vectores numéricos de igual longitud como
parámetros y devuelve un valor p. Si el shapiro.test () reporta un valor p de menos de 0.05 sobre la diferencia de los
vectores, el valor p devuelto debería ser el resultado de una prueba con signo de rango de Wilcoxon. De lo contrario, el valor p
devuelto debería ser el resultado de un t.test () . La función también debe imprimir información sobre qué prueba se
está ejecutando. Pon a prueba tu función con datos aleatorios generados a partir de rexp () y rnorm () .

Una extensión funcional


Habiendo revisado las estructuras procesales de control-flujo si , while y for , vamos a ampliar nuestro ejemplo de
muestreo aleatorio truncado para explorar más características “funcionales” de R y cómo podrían ser utilizadas. La función que
diseñamos, rnorm_trunc () , devuelve una muestra aleatoria que normalmente se distribuye pero se limita a un rango dado
a través del remuestreo. La distribución de muestreo original se especifica por los parámetros mean = y sd = , los cuales se
pasan a la llamada a rnorm () dentro de rnorm_trunc () .
III.10_22_R_230_2_RNORM_TRUNC_REVISIÓN

¿Y si quisiéramos hacer lo mismo, pero para rexp () , qué muestras de una distribución exponencial tomando un parámetro
rate = ?
III.10_23_R_231_REXP

La distribución normalmente va de 0 a infinito, pero es posible que queramos remuestrear, digamos, de 1 a 4.


III.10_24_REXP_DIST

Una posibilidad sería escribir una función rexp_trunc () que opere de manera similar a la función rnorm_trunc ()
, con cambios específicos para el muestreo a partir de la distribución exponencial.
III.10_25_R_232_REXP_TRUNC

Las dos funciones rnorm_trunc () y rexp_trunc () son increíblemente similares, solo difieren en la función de
muestreo utilizada y los parámetros que se les pasan. ¿Podemos escribir una sola función para hacer ambos trabajos? Podemos, si
recordamos dos hechos importantes que hemos aprendido sobre funciones y parámetros en R.
1. Funciones como rnorm () y rexp () son un tipo de datos como cualquier otro y así se pueden pasar como
parámetros a otras funciones (como en la estrategia split-apply-combine).
2. El parámetro especial ... “recopila” parámetros para que las funciones puedan tomar parámetros arbitrarios.
Aquí, usaremos ... para recopilar un conjunto arbitrario de parámetros y pasarlos a llamadas a funciones internas. Al definir una
función a tomar ... , por lo general se especifica último. Entonces, escribiremos una función llamada sample_trunc ()
que toma cinco parámetros:
1. El límite inferior, inferior .
2. El límite superior, superior .
3. El tamaño de muestra a generar, contar .
4. La función a llamar para generar muestras, sample_func .
5. Parámetros adicionales para pasar a sample_func , ... .
III.10_26_R_233_Sample_trunc

Podemos llamar a nuestra función sample_trunc () usando cualquier número de funciones de muestreo. Hemos visto
rnorm () , que toma los parámetros mean = y sd = , y rexp () , que toma un parámetro rate = , pero hay
muchos otros, como dpois () , que genera distribuciones de Poisson y toma un parámetro lambda = .
III.10_27_R_234_Sample_trunc_use

3.10.5 https://espanol.libretexts.org/@go/page/55107
En el primer ejemplo anterior, la media = 20, sd = 10 se coteja en ... en la llamada a sample_trunc () ,
como es rate = 1.5 en el segundo ejemplo y lambda = 2 en el tercer ejemplo.
III.10_28_muestra_trunco_hists

Ejercicios
1. Como se discutió en un ejercicio anterior, las funciones t.test () y wilcox.test () toman dos vectores
numéricos como sus primeros parámetros, y devuelven una lista con una entrada $p.value . Escribe una función
pval_from_test () que tome cuatro parámetros: los dos primeros deben ser dos vectores numéricos (como en las
funciones t.test () y wilcox.test () ), el tercero debe ser una función de prueba (ya sea t.test o
wilcox.test ), y la cuarta debe ser cualquier parámetro opcional para transmitir ( ... ). Se debe devolver el valor p de
la ejecución de prueba.
Entonces deberíamos poder ejecutar la prueba así: III.10_29_R_234_2_Funcional_TTEST_Wilcox_Test Los cuatro valores pval1 , pval2 ,
pval3 y pval4 deben contener valores p simples.
1. Algunos programadores creen que las funciones deben tener solo un punto de retorno, y siempre debe ir al final de una función.
Esto se puede lograr inicializando la variable para que regrese al inicio de la función, modificándola según sea necesario en el
cuerpo, y finalmente regresando al final. Sin embargo, este patrón es más difícil de implementar en este tipo de función, donde
la ejecución de la función necesita romperse de un bucle.
2. Otra forma de alargar un vector o lista en R es asignarle al índice justo al final del mismo, como en
counter [length (counter) + 1] <- val . Uno podría estar tentado a usar tal construcción en un bucle for,
pensando que el resultado será que el vector contador se modifique “en su lugar” y evitando la costosa copia. Uno estaría
equivocado, al menos a partir del momento de escribir esto, porque esta línea es en realidad azúcar sintáctica para una llamada
de función y asignación: counter <- `[<-` (counter, length (counter) + 1, val), que sufre el
mismo problema que la llamada a la función c () .

This page titled 3.10: Programación Procesal is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

3.10.6 https://espanol.libretexts.org/@go/page/55107
3.11: Objetos y Clases en R
En los capítulos que cubren Python, pasamos bastante tiempo discutiendo objetos y sus planos, conocidos como clases. En
términos generales, un objeto es una colección de datos junto con funciones (llamadas “métodos” en este contexto) diseñadas
específicamente para trabajar en esos datos. Las clases comprenden las definiciones de esos métodos y datos.
Resulta que, si bien las funciones son el foco principal en R, los objetos también son una parte importante del lenguaje. (De
ninguna manera ninguno de estos conceptos son mutuamente excluyentes.) Si bien las definiciones de clase están muy bien
encapsuladas en Python, en R, las piezas están más distribuidas, al menos para el sistema “S3” más antiguo y más comúnmente
utilizado lo discutiremos aquí. [1] Teniendo esto en cuenta, lo mejor es examinar algunos objetos y métodos existentes antes de
intentar diseñar los nuestros propios. Consideremos la creación de un modelo pequeño y lineal para algunos datos de muestra.
III.11_1_R_235_Pequeño_LM_ANOVA

En el capítulo 30, “Listas y atributos”, aprendimos que funciones como lm () y anova () generalmente devuelven una
lista (o un marco de datos, que es un tipo de lista), y podemos inspeccionar la estructura con str () .
III.11_2_R_236_Pequeña_lm_Anova_str

Aquí hay un muestreo de las líneas de salida para cada llamada (hay bastantes piezas de datos contenidas en la lista
lm_result ):
III.11_3_R_237_pequefico_lm_anova_str_out

Si estos dos resultados son tan similares, ambos tipos de listas, entonces, ¿por qué las salidas son tan diferentes cuando llamamos a
print (lm_result)
III.11_4_R_238_Print_LM

e imprimir (anova_result) ?
III.11_5_R_239_Print_ANOVA

La forma en que se producen estas impresiones viene dictada por el atributo “class” de estas listas, “lm” para
lm_result y “anova” para anova_result . Si tuviéramos que eliminar este atributo, obtendríamos una salida
impresa predeterminada similar al resultado de str () . Hay varias formas de modificar o eliminar el atributo class de un dato:
usando la función de acceso attr () con attr (lm_result, “class”) <- NULL , configurándola usando el
accessor class () más preferido, como en class (lm_result) <- NULL , o usando la función unclass ()
aún más especializada, como en lm_result <- unclass (lm_result) . En cualquier caso, ejecutar
print (lm_result) después de una de estas tres opciones dará como resultado str () como impresión
predeterminada.
Ahora bien, ¿cómo produce R una salida diferente en función de este atributo de clase? Cuando llamamos
print (lm_result) , el intérprete nota que el atributo “class” está establecido en “lm” , y busca otra función con
un nombre diferente para ejecutar realmente: print.lm () . Del mismo modo, print (anova_result) llama a
print.anova () sobre la base de la clase de la entrada. Estas funciones especializadas asumen que la lista de entrada tendrá
ciertos elementos y producirá una salida específica para esos datos. Podemos ver esto tratando de confundir R estableciendo el
atributo class incorrectamente con class (anova_result) <- “lm” y luego print (anova_result) :
III.11_6_R_240_Print_Anova_Clase Broken

Observe que los nombres de clase forman parte de los nombres de las funciones. Esta es la forma de R de crear métodos, afirmando
que los objetos con clase “x” deben imprimirse con print.x () ; esto se conoce como despacho y la función general
print () se conoce como función genérica, cuyo propósito es despachar a un método apropiado (función específica de clase)
basado en el atributo class de la entrada.
En resumen, cuando llamamos print (result) en R, porque print () es una función genérica, el intérprete
comprueba el atributo “class” de result ; supongamos que la clase es “x” . Si existe un print.x () , se llamará
a esa función; de lo contrario, la impresión volverá a print.default () , que produce una salida similar a str () .
III.11_7_Despedido_flujo

3.11.1 https://espanol.libretexts.org/@go/page/55127
Hay muchas “impresiones” diferentes. métodos; podemos verlos con métodos (“print”) .
III.11_8_R_241_Print_Methods

De igual manera, hay una variedad de métodos “.lm” especializados en tratar datos que tienen un atributo de “clase” de
“lm” . Podemos verlas con métodos (class = “lm”) .
III.11_9_R_242_Print_Methods

El mensaje sobre las funciones no visibles que están siendo asteriscadas indica que, si bien estas funciones existen, no podemos
llamarlas directamente como en print.lm (lm_result) ; debemos usar el genérico print () . Muchas funciones
que hemos tratado son en realidad genéricos, incluyendo length () , mean () , hist () e incluso str () .
Entonces, a su manera R, también está bastante “orientado a objetos”. Una lista (u otro tipo, como un vector o un marco de datos)
con un atributo de clase dado constituye un objeto, y los diversos métodos especializados forman parte de la definición de clase.

Creando nuestras propias clases


Crear nuevos tipos de objetos y métodos no es algo que los programadores principiantes de R probablemente hagan a menudo. Aún
así, un ejemplo descubrirá más del funcionamiento interno de R y bien podría ser útil.
Primero, necesitaremos algún tipo de datos que queramos representar con un objeto. Para fines ilustrativos, utilizaremos los datos
devueltos por la función nrorm_trunc () definida en el capítulo 35, “Programación estructural”. En lugar de producir un
vector de muestras, también podríamos querer almacenar con ese vector la media del muestreo original y la desviación estándar
(porque los datos truncados tendrán una media real y una desviación estándar diferentes). También podríamos desear almacenar en
este objeto los límites superior e inferior solicitados. Debido a que todos estos datos son de diferentes tipos, tiene sentido
almacenarlos en una lista.
III.11_10_R_243_TRUNC_SAMPLE_CONSTRUCTOR

La función anterior devuelve una lista con los diversos elementos, incluyendo la propia muestra. También establece el atributo class
de la lista en truncated_normal_sample —by convention, este atributo class es el mismo que el nombre de la función.
Tal función que crea y devuelve un objeto con una clase definida se llama constructor.
Ahora, podemos crear una instancia de un objeto “truncated_normal_sample” e imprimirlo.
III.11_11_R_244_TRUNC_NORM_PRINT

Debido a que no hay ninguna función print.truncated_normal_sample () , sin embargo, el genérico


print () despacha a print.default () , y la salida no es agradable.
III.11_12_R_245_TRUNC_NORM_PRINT_OUT

Si queremos estilizar la impresión, necesitamos crear el método personalizado. También podríamos querer crear una función
mean () personalizada que devuelva la media de la muestra almacenada.
III.11_13_R_246_TRUNC_NORM_METODOS

La salida:
III.11_14_R_247_TRUNC_NORM_METODOS_OUT

Esta función de impresión personalizada es bastante cruda; técnicas de impresión más sofisticadas (como cat () y
paste () ) podrían usarse para producir una salida más amigable.
Hasta ahora, hemos definido un método personalizado mean.truncated_normal_sample () , que devuelve la media
de la muestra cuando llamamos a la función genérica mean () . Esto funciona porque la función genérica mean () ya
existe en R. ¿Y si quisiéramos llamar a un genérico llamado originalmean () , que devuelve original_mean del
objeto? En este caso, necesitamos crear nuestro propio método especializado así como la función genérica que despacha a ese
método. Así es como se ve eso:
III.11_15_R_248_originalmean

Estas funciones, el constructor, los métodos especializados y las funciones genéricas que aún no existen en R, necesitan definirse
solo una vez, pero se pueden llamar tantas veces como queramos. De hecho, los paquetes en R que se instalan usando
install.packages () a menudo son solo una colección de funciones, junto con documentación y otros materiales.

3.11.2 https://espanol.libretexts.org/@go/page/55127
La programación orientada a objetos es un tema grande, y solo hemos rayado la superficie. En particular, no hemos cubierto temas
como el polimorfismo, donde un objeto puede tener múltiples clases listadas en el atributo “class” . En R, el tema del
polimorfismo no es difícil de describir en un sentido técnico, aunque hacer un uso efectivo del mismo es un desafío en la ingeniería
de software. Si un objeto tiene varias clases, como “anova” y “data.frame” , y se llama a un genérico como
print () , el intérprete primero buscará print.anova () , y si eso falla, intentará print.data.frame () , y
en su defecto retrocederá en print.default () . Esto permite a los objetos capturar “es un tipo de” relaciones, por lo que
los métodos que funcionan con marcos de datos no tienen que ser reescritos para los objetos de clase anova.

Ejercicios
1. Muchas funciones en R son genéricas, incluyendo (como exploraremos en el capítulo 37, “Ploting Data y ggplot2 ”) la
función plot () , que produce salida gráfica. ¿Cuáles son todas las diferentes clases que se pueden trazar con la
parcela genérica () ? Un ejemplo es plot.lm () ; use help (“plot.lm”) para determinar qué se
traza cuando se le da una entrada con el atributo class de “lm” .
2. ¿Qué métodos están disponibles para los datos con un atributo de clase de “matriz” ? (Por ejemplo, ¿hay un
plot.matrix () o lm.matrix () ? ¿Qué otros hay?)
3. Crea tu propia clase de algún tipo, completa con un constructor que devuelve una lista con su conjunto de atributos de clase, un
método especializado para print () y un nuevo método genérico y asociado.
4. Explore usando otros recursos la diferencia entre el sistema de objetos S3 de R y su sistema de objetos S4.

1. Las versiones modernas de R no tienen uno, no dos, sino tres sistemas diferentes para crear y trabajar con objetos. Estaremos
discutiendo solo el más antiguo y aún más usado, conocido como S3. Los otros dos se llaman S4 y Clases de Referencia, esta
última de las cuales es más similar al sistema clase/objeto utilizado por Python. Para obtener más información sobre estos y
otros sistemas de objetos (y muchos otros temas avanzados de R), consulte Norman Matloff, The Art of R Programming (San
Francisco: No Starch Press, 2011), y Hadley Wickham, Advanced R (Londres: Chapman y Hall/CRC, 2014).

This page titled 3.11: Objetos y Clases en R is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

3.11.3 https://espanol.libretexts.org/@go/page/55127
3.12: Datos de trazado y ggplot2
R proporciona algunas de las herramientas de visualización de datos más potentes y sofisticadas de cualquier programa o lenguaje
de programación (aunque gnuplot mencionado en el capítulo 12, “Miscelánea”, también es bastante sofisticado, y Python se
está poniendo al día con bibliotecas cada vez más poderosas como matplotlib ). Una instalación básica de R proporciona un
conjunto completo de herramientas para el trazado, y hay muchas bibliotecas disponibles para la instalación que amplían o
complementan este conjunto básico. Uno de ellos es ggplot2 , que se diferencia de muchos otros paquetes de visualización de
datos en que está diseñado alrededor de una “gramática” de gráficos bien concebida. [1]
El paquete ggplot2 no es el más potente ni flexible; los gráficos proporcionados por defecto con R pueden tomar ese título.
Tampoco ggplot2 es lo más fácil: los programas más simples como Microsoft Excel son mucho más fáciles de usar. Lo que
ggplot2 proporciona es un notable equilibrio de potencia y facilidad de uso. Como beneficio adicional, los resultados suelen
ser de aspecto profesional con pocos ajustes, y la integración en R hace que la visualización de datos sea una extensión natural del
análisis de datos.

Una breve introducción a los gráficos Base-R


Aunque este capítulo se centra en el paquete ggplot2 , vale la pena tener al menos familiaridad pasajera con algunas de las
herramientas básicas de trazado incluidas con R. Primero, cómo se generan las gráficas depende de si estamos ejecutando R a
través de una interfaz gráfica de usuario (como RStudio) o en la línea de comandos vía la consola R interactiva o el script
ejecutable. Aunque escribir un programa no interactivo para producir parcelas puede parecer contradictorio, es beneficioso como
registro escrito de cómo se produjo la trama para futuras referencias. Además, el trazado es a menudo el resultado final de un
análisis complejo, por lo que tiene sentido pensar en la salida gráfica de manera muy similar a cualquier otra salida de programa
que necesite ser reproducible.
Cuando se trabaja con una interfaz gráfica como RStudio, las gráficas se muestran por defecto en una ventana emergente o en un
panel de trazado especial para su revisión. Alternativamente, o si estamos produciendo gráficas a través de un inicio de sesión
remoto de línea de comandos, cada parcela se guardará en un archivo PDF llamado Rplots.pdf . El nombre de este archivo
se puede cambiar llamando a la función pdf () , dando un nombre de archivo para escribir. Para terminar de escribir el archivo
PDF, se requiere una llamada a dev.off () (no toma parámetros).
La función de plotting más básica (distinta de hist () , que ya hemos visto) es plot () . Al igual que hist () ,
plot () es una función genérica que determina cómo debe ser la gráfica sobre la base de los atributos de clase de los datos
que se le dan. Por ejemplo, dados dos vectores numéricos de igual longitud, produce una gráfica de puntos.
III.12_1_R_249_Base_R_dotPlot

El contenido de dotplot.pdf :
alt

Para el resto de este capítulo, las llamadas pdf () y dev.off () no se especifican en ejemplos de código.
alt

Podemos darle a la función plot () una pista sobre el tipo de plot que queremos usando el parámetro type = ,
configurándolo en “p” para puntos, “l” para líneas, “b” para ambos, y así sucesivamente. Para el trazado vectorial
básico como el anterior, plot () respeta el orden en que aparecen los datos. Aquí está la salida de
plot (vecx, vecy, type = “l”) :
Habríamos tenido que ordenar uno o ambos vectores de entrada para obtener algo más razonable, si eso tiene sentido para los datos.
Otras funciones de trazado como hist () , curve () y boxplot () se pueden usar para producir otros tipos de
trazado. Algunos tipos de trazado (aunque no todos) se pueden agregar a los resultados previamente trazados como una capa
adicional estableciendo add = TRUE . Por ejemplo, podemos producir una gráfica de puntos de dos vectores aleatorios, junto
con un histograma con alturas de barras normalizadas usando hist () con
probabilidad = VERDADERO y add = VERDADERO .
III.12_4_R_250_base_r_dotplot_hist_add
alt

3.12.1 https://espanol.libretexts.org/@go/page/55149
Una trama como esta solo se verá razonable si los rangos de ejes son apropiados para ambas capas, lo cual debemos asegurarnos
nosotros mismos. Esto lo hacemos especificando rangos con los parámetros xlim = e ylim = en la llamada a
plot () , especificando los vectores longitud-dos.
III.12_6_R_251_base_r_dotplot_hist_add_limits
alt

Aquí hay una serie de reglas ocultas. Por ejemplo, se debe llamar a plot () antes de hist () , ya que add = TRUE
no es aceptado por la función plot () . Aunque hist () acepta los parámetros xlim e ylim , se ignoran cuando se
usa hist () con add = TRUE , por lo que deben especificarse en la llamada plot () en este ejemplo. Hay muchas
funciones de trazado individuales como plot () e hist () , y cada una toma docenas de parámetros con nombres como
“las” , “cex” , “pch” y “tck” (estos controlan la orientación de las etiquetas del eje y, fuente tamaño, formas de
punto y tamaño de marca de marca, respectivamente). Desafortunadamente, la documentación de todas estas funciones y
parámetros oscila entre escasa y confusamente compleja, aunque hay una serie de libros dedicados únicamente al tema de la trama
en R.
A pesar de sus complejidades, uno de los principales beneficios de usar plot () es que, como genérico, la salida trazada a
menudo se personaliza para el tipo de entrada. Como ejemplo, vamos a crear rápidamente algunos datos linealmente dependientes y
ejecutarlos a través de la función de modelado lineal lm () .
III.12_8_R_252_Base_R_Plot_LM

Si le damos el lm_result (una lista con atributo de clase “lm” ) a plot () , la llamada se enviará a plot.lm () ,
produciendo una serie de parcelas apropiadas para los parámetros y datos del modelo lineal. La primera de las cinco parcelas
siguientes fue producida por la llamada a plot (vecx, vecy) , mientras que las cuatro restantes son parcelas específicas
de modelos lineales y fueron producidas por la llamada única a plot (lm_result) como un archivo PDF de varias
páginas.
alt

Descripción general de ggplot2 y capas


Como se mencionó anteriormente, el paquete ggplot2 busca simplificar el proceso de trazado sin dejar de proporcionar una
gran cantidad de flexibilidad y potencia mediante la implementación de una “gramática” de construcción gráfica. Dada esta
estructura, vamos a tener que empezar por aprender algún vocabulario (más) especializado, seguido de alguna sintaxis (más)
especializada. Existen varias formas de interactuar con ggplot2 de diversa complejidad. Comenzaremos primero con lo más
complejo, para entender la estructura de la gramática, y luego pasaremos a métodos más simples que son más fáciles de usar (una
vez enmarcados en términos de la gramática).
A diferencia de la función plot () genérica, que puede trazar muchos tipos diferentes de datos (como en el ejemplo del
modelo lineal anterior), ggplot2 se especializa en trazar datos almacenados en marcos de datos.
Un “plot” en ggplot2 se compone de los siguientes componentes:
1. Una o más capas, cada una representando cómo se deben trazar algunos datos. Las capas de trazado son las piezas más
importantes, y constan de cinco subpartes:
1. Los datos (un marco de datos), que contiene los datos a trazar.
2. Una estadística , que significa “mapeo estadístico”. Por defecto, esta es la estadística de “identidad” (sin
modificación) pero podría ser algún otro mapeo que procese el marco de datos y produzca un nuevo conjunto de datos
que realmente se grafica.
3. Un geom , abreviatura de “objeto geométrico”, que especifica la forma a dibujar. Por ejemplo, “punto” especifica
que los datos deben trazarse con puntos (como en una gráfica de puntos), “línea” especifica que los datos deben
trazarse con líneas (como en una gráfica de líneas) y “bar” especifica que los datos deben trazarse con barras (como en
un histograma).
4. Un mapeo de “estética”, que describe cómo las propiedades del geom (por ejemplo, la posición x e y de los
puntos, o su color , tamaño , etc.) se relacionan con las columnas de los datos transformados por
estadísticas . Cada tipo de geom se preocupa por un conjunto específico de estética, aunque muchos son
comunes.

3.12.2 https://espanol.libretexts.org/@go/page/55149
5. Una posición para el geom ; generalmente, esto es “identidad” para sugerir que la posición del geom no
debe ser alterada. Las alternativas incluyen “jitter” , que agrega algo de ruido aleatorio a la ubicación (por lo que las
formas geom superpuestas son más fáciles de ver).
2. Una escala por cada estética utilizada en capas. Por ejemplo, el valor x de cada punto se produce en una escala
horizontal que podríamos desear limitar a un rango dado, y el valor de color de cada punto ocurre en una escala de colores
que podríamos desear variar de púrpura a naranja, o negro a azul.
3. Una especificación facetaria , que describe cómo deben ocurrir los datos de paneles en múltiples parcelas similares.
4. imagenUn sistema de coordenadas al que aplicar algunas escalas. Por lo general, no los modificaremos a partir de los
valores predeterminados. Por ejemplo, el valor predeterminado para x e y es usar un sistema de coordenadas
cartesianas , aunque polar es una opción.
5. Alguna información del tema , como tamaños de fuente y rotaciones, marcas de eje, relaciones de aspecto, colores de
fondo y similares.
6. Un conjunto de valores predeterminados. Los valores por defecto son algo extraño de enumerar como parte específica de una
parcela, ya que estas son las propiedades que no especificamos, pero ggplot2 las hace un uso intensivo, por lo que vale la
pena mencionarlas.
install.packages (“ggplot2") (en la consola interactiva) y se carga con
Cuando se instala con
biblioteca (ggplot2) , el paquete viene con un marco de datos llamado diamantes . Cada fila de este marco de
datos especifica cierta información sobre un solo diamante; con cerca de 54,000 filas y muchos tipos de columnas (incluyendo
numéricas y categóricas), es un excelente conjunto de datos con el que explorar el trazado.
III.12_11_R_253_cabeza_diamantes
III.12_12_R_254_cabeza_diamantes_fuera

Empecemos explorando el concepto más importante en la lista de definiciones anterior: la capa y sus cinco componentes. Para crear
una capa, podemos comenzar creando un objeto gg “vacío” llamando a ggplot () sin parámetros. A esto agregaremos
una capa con + y llamando a la función layer () , especificando los cinco componentes que queremos. [2] Debido a que
estos comandos de trazado se vuelven bastante largos, los dividimos sobre varias líneas (terminando líneas discontinuas con + o
, para que el intérprete sepa que el comando no está terminado) y los sangramos para ayudar a indicar dónde están
contribuyendo diferentes piezas.
III.12_13_R_255_Primera_Capa

III.12_14_GGPlot_Layer_1

Aquí, hemos especificado cada uno de los cinco componentes de capa descritos anteriormente. Para el mapeo de la estética,
existe una llamada interna a una función aes () que describe cómo la estética de los geoms ( x e y , y el color en este
caso) se relacionan con columnas de los datos ajustados por estadísticas (en este caso, las columnas de salida de la
estadística son idénticas a las columnas de entrada). [3] Finalmente, observamos que ggplot2 ha manejado sin problemas la
columna categórica de corte .
Para guardar el resultado en un archivo o cuando no se trabaja en una interfaz gráfica, podemos usar la función pdf () antes
de la llamada a plot () seguida de dev.off () , como hicimos para los gráficos Base-R. Alternativamente, podemos
usar la función especializada ggsave () , que también nos permite especificar el tamaño general de la gráfica (en pulgadas a
300 dpi por defecto para PDF).
III.12_15_R_256_GGSave

Agreguemos una capa a nuestra trama que también trazará puntos en los ejes x e y , por quilate y precio . Esta capa
adicional, sin embargo, utilizará una estadística “suave” , y no colorearemos los puntos. (En versiones recientes de
ggplot2 , este ejemplo de capa también requiere un params = list (method = “auto”) que establece el
método de suavizado de stat. A continuación veremos cómo escribir código más compacto con este y otros parámetros establecidos
automáticamente).
alt
III.12_17_GGPlot_Layer_2Smooth-1

3.12.3 https://espanol.libretexts.org/@go/page/55149
En este caso, los datos
originales han sido transformados por la estadística “suave” , por lo que
x = quilate, y = precio ahora especifica las columnas en el marco de datos transformado por estado. Si tuviéramos
que cambiar el geom de esta capa a “line” , obtendríamos una gráfica como abajo a la izquierda, y si añadimos un
color = cut en la llamada aes () , obtendríamos una gráfica como abajo a la derecha.
III.12_18_GGPlot_Layer_2Smooth_lines

En la trama derecha de arriba, se han creado múltiples líneas; son un poco difíciles de ver, pero veremos cómo solucionarlo en un
poco. Obsérvese que el orden de las capas importa: la segunda capa se trazó encima de la primera.
Esta segunda capa ilustra uno de los aspectos más confusos de ggplot2 , a saber, que los mapeos estéticos (propiedades de los
geoms) y los mapeos de estadísticas interactúan. En la segunda capa anterior, especificamos
aes (x = quilate, y = precio) , pero también especificamos la estadística “suave” , como consecuencia, los
datos subyacentes que representan el quilate y el precio fueron modificados por la estadística, y la estadística sabía qué
variables suavizar sobre la base de este mapeo estético.
Para un segundo ejemplo, veamos el stat “bin” y el geom “bar” , que juntos crean un histograma. La estadística “bin”
verifica el mapeo estético x para determinar qué columna bin en recuentos discretos, y también crea algunas columnas
completamente nuevas en los datos transformados por stats, incluida una llamada .. count.. . Los puntos extra indican al
usuario que la columna producida por la estadística es novedosa. El geom “bar” presta atención a la estética x (para
determinar la ubicación de cada barra en el eje horizontal) y la estética y (para determinar la altura de cada barra).
III.12_19_R_258_GGPlot_Bin_bar

El resultado de trazar lo anterior se muestra a continuación a la izquierda. Para completar el ejemplo, a continuación a la derecha se
muestra la misma parcela con geom = “punto” . Podemos ver fácilmente que los datos transformados por stats contienen
solo alrededor de 30 filas, con columnas para centros de bin ( quilates mapeados en x ) y recuentos ( .. count.. ). La
estadística “bin” también genera una .. densidad.. columna que puedes explorar.
III.12_20_GGPlot_Bin_PointBar

Valores predeterminados inteligentes, funciones de capa especializadas


La mayoría de nosotros probablemente estamos familiarizados con los tipos de parcelas estándar: histogramas, gráficas de líneas
ajustadas, diagramas de dispersión, etc. Por lo general, el mapeo estadístico utilizado determinará el tipo de parcela común y el
geom a usar. Por ejemplo, una estadística de agrupamiento probablemente vaya con un geom de barra para producir un histograma,
y el geom de punto se empareja más comúnmente con el estadístico de identidad. (De hecho, en versiones anteriores de
ggplot2 , si no se especificaba un stat o geom en la llamada a layer () , se usaba un valor por defecto sobre la base
del otro. Las versiones más recientes requieren que se especifiquen tanto geom = como stat = al llamar a
layer () .) Del mismo modo, cuando se utiliza una estadística de binning, el mapeo estético y por defecto es
.. count.. si no se especifica.
Debido a que es muy común especificar una gráfica especificando el mapeo estadístico a usar, ggplot2 () proporciona
funciones de capa especializadas que efectivamente mueven la especificación de la estadística al nombre de la función y usan el
geom predeterminado (aunque aún se puede cambiar). Del mismo modo, a menudo deseamos especificar una gráfica por el tipo de
geom y aceptar la estadística predeterminada para ese geom (aunque, nuevamente, esto se puede cambiar agregando un parámetro
stat = ). Aquí hay dos formas más de trazar el histograma izquierdo de arriba:
III.12_21_R_260_GGPlot_Hist_BAR_bin

geom_bar () , pero su defecto no es el estadístico de binning; de ahí el


(Hay una función de capa
geom_histograma alternativo () .) Para reproducir la parcela de arriba a la derecha, podríamos usar
stat_bin (data = diamantes, mapping = aes (x = quilates), geom = “punto”) .
Con tantos valores predeterminados establecidos, los comandos de trazado pueden llegar a ser bastante pequeños. Estas funciones
de capa especializadas representan los métodos más utilizados para trazar en ggplot2 , siendo flexibles y rápidos.
Otro ejemplo de los valores predeterminados que se están estableciendo es la función de capa geom_boxplot () , que utiliza
un geom “boxplot” (una caja con bigotes) y una estadística predeterminada “boxplot” El geom boxplot reconoce una

3.12.4 https://espanol.libretexts.org/@go/page/55149
serie de estéticas para las diversas piezas que posicionan la caja y los bigotes, incluyendo x , y , medio , superior ,
inferior , ymin e ymax . Afortunadamente, la mayoría de estos valores requeridos son creados por la estadística
boxplot y se establecen en consecuencia (al igual que los valores estéticos y predeterminados en .. count.. para
histogramas); solo se requieren las estéticas x e y para determinar las otras.
III.12_22_R_261_GGPlot_BoxPlot
III.12_23_GGPlot_BoxPlot

Hemos mapeado una variable discreta a x y una variable continua a y , ¡una gráfica de caja tendría mucho menos sentido al
revés! También hay una función de capa correspondiente stat_boxplot () , que especifica el stat y usa el geom
correspondiente predeterminado (boxplot).
Hasta ahora, hemos estado especificando individualmente los parámetros data = y mapping = para cada capa. Es común
verlas establecidas solo una vez en la llamada a ggplot () , en cuyo caso se heredan para todas las capas posteriores.
También podemos dejar fuera los datos = y mapping = nombres si primero especificamos los datos.
III.12_24_R_262_GGPlot_More_Defaults
III.12_25_GGPlot_More_Defaults

La mayoría de los usuarios de ggplot2 prefieren utilizar muchos de estos ajustes y valores predeterminados “fall-through”,
aunque especificaremos todos los datos y asignaciones para cada capa para mayor claridad. (Y sí, diferentes capas pueden usar
diferentes marcos de datos para sus datos = parámetro).
Hay muchas funciones de capa especializadas para estadísticas y geoms específicos. La documentación para ellos y otras
características de ggplot2 se puede encontrar en docs.ggplot2.org.

Ejercicios
1. Utilice la función Base-R plot () para producir una gráfica PDF que tenga el siguiente aspecto:
III.12_26_Log_Scatterplot_baser Hay 1,000 x valores distribuidos aleatoriamente (y uniformemente) entre 0 y 100, y los valores y siguen

una curva definida por el log () de x además de algo de ruido aleatorio.


2. La función hist () de R es una forma conveniente de producir un histograma rápido. Intente ejecutar
hist (rnorm (1000, media = 0, sd = 3)) . Lea la ayuda de hist () para determinar cómo aumentar el
número de “bins”.
3. Utilice la función layer () en ggplot2 para producir un histograma de los precios de los diamantes. A continuación,
intente usar geom_histogram () y stat_bin () . Para estas últimas parcelas, intente cambiar el número de bins
del valor predeterminado de 30.
4. Intente crear una gráfica de puntos de diamantes (punto geom, estadística de identidad) usando layer () con x
mapeado al color e y mapeado al precio . ¿Qué sucede cuando cambias la posición a “jitter” ? A
continuación, agregue una capa de diagrama de caja encima de esta capa de trama de puntos.

Más Estética y Expresiones Matemáticas


La función de capa geom_point () merece una atención especial, no solo porque las gráficas de dispersión son un tipo de
parcela particularmente útil para la exploración de datos, sino también porque podemos usarla para ilustrar más características del
paquete ggplot2 . Desafortunadamente, las gráficas de dispersión tienden a sufrir problemas de “sobretrazado” cuando el
número de puntos de datos es grande, como en ejemplos anteriores. Por ahora, vamos a sortear este problema generando un
subconjunto aleatorio de 1,000 diamantes para graficar, colocando la muestra en un marco de datos llamado dd .
III.12_27_R_263_GGPlot_Diamonds_Subsample

Primero, la capa geom_point () acepta una serie de estéticas que también podrían ser útiles en otras situaciones.
III.12_28_R_264_GGPlot_puntos_too_muchos_estética
III.12_29_GGPlot_Diamonds_too_Many_Aesthetics

El resultado es probablemente demasiada estética para mapear valores a partir de los datos, pero a veces con fines exploratorios (en
lugar de publicación), esto no es malo. En esta trama podemos ver algunas características interesantes de los datos. (1) Los
diamantes suelen ser cortados a tamaños en quilates que son números redondos (0.25, 0.5, 1.0, etc.). (2) El precio por quilate
(mapeado al color) es generalmente mayor para los diamantes más grandes. Esto significa que no solo son más caros los diamantes

3.12.5 https://espanol.libretexts.org/@go/page/55149
más grandes, sino que también son más caros por quilate (son más raros, después de todo). (3) Aunque es un poco difícil de ver en
esta trama, parece que los puntos con el mejor corte (mapeados a alfa, o “transparencia”) también son de profundidad intermedia (
mapeado al eje y). O, por lo menos, los valores atípicos en profundidad tienden a tener valores de “corte” más pobres.
Esta gráfica también ilustra que la estética generalmente se puede mapear a datos continuos o discretos, y ggplot2 manejará
los datos con gracia. Además, podemos especificar los datos como expresiones matemáticas de las columnas en el marco de datos
de entrada (o incluso otros datos), como hicimos con color = precio/quilate . Hacerlo nos permite explorar conjuntos
de datos de una manera potente y flexible. Podríamos discretizar rápidamente el precio por quilate fijando
color = precio/quilate > 5000 , por ejemplo, o podríamos reducir el rango de estos valores trazando con
color = log (precio/quilate) . En la subsección sobre escalas, sin embargo, veremos que ggplot2 contiene
algunas características amigables para trazar datos en escalas ajustadas al registro y escalas similares.
Debido a que esta trama está demasiado ocupada, vamos a arreglarla eliminando el mapeo alfa y tamaño, recordando la posibilidad
de una relación entre corte y profundidad para futuras exploraciones. También agregaremos una capa para una línea de ajuste
suavizado.
III.12_30_R_265_GGPlot_diamantes_carat_profundidad
III.12_31_GGPlot_diamantes_carat_profundidad

Las funciones de capa como layer () y geom_point () aceptan opciones adicionales además de datos ,
stat , geom y mapeo . En este ejemplo, la primera capa se vería mejor si fuera parcialmente transparente, de manera que
la capa superior pudiera destacar. Pero no queremos mapear la estética alfa a una propiedad de los datos, solo queremos que la
establezca en una constante para todos los geoms de la capa. Así podemos establecer alpha = 0.2 fuera del mapeo
aes () como una opción para la propia capa. Del mismo modo, podríamos querer que nuestra línea de ajuste sea roja, y
observamos de la documentación que la capa stat_smooth () toma un argumento de método que se puede establecer;
“auto” es el valor predeterminado para un suave no lineal y “lm” producirá un ajuste lineal.
III.12_32_R_266_GGPlot_Diamonds_Carat_Depth_Mod_Layers
III.12_33_GGPlot_Diamonds_Carat_Depth_Mod_Layers

Como nota general, ggplot2 impone que todas las capas compartan las mismas escalas. Por esta razón, no quisiéramos
trazar en una capa x = quilate, y = profundidad y en otra x = quilate, y = precio ; todos los valores
y serán forzados en una sola escala, y debido a que la profundidad oscila hasta ~70 mientras que los precios van hasta ~18,000,
los valores de profundidad serían indiscernible. Esto también se aplica a las asignaciones de color, si las utilizan varias capas.
El paquete ggplot2 hace cumplir estas reglas para facilitar la comprensión de las gráficas que utilizan múltiples capas. Por las
mismas razones, ggplot2 no admite múltiples ejes y (es decir, un eje y “izquierdo” y un eje y “derecho”), ni admite de forma
nativa gráficas tridimensionales que son difíciles de leer en una superficie bidimensional, como papel o un monitor de
computadora. [4]

Facetado
El facetado es una de las técnicas que puede ser bastante difícil de hacer en otros paquetes gráficos, pero es fácil en ggplot2 .
Las facetas implementan la idea de “pequeños múltiplos” que promueve Edward Tufte. [5] Diferentes subconjuntos de datos se
trazan en paneles independientes, pero cada panel usa los mismos ejes (escalas), lo que permite comparaciones fáciles entre
paneles. En ggplot2 () , podemos agregar una especificación de faceta agregando otra llamada a la función a la “cadena” de
funciones de capa que definen la gráfica. El facetado casi siempre se realiza en columnas discretas del marco de datos de entrada,
aunque a continuación se presentan algunas técnicas para discretizar columnas continuas.
La primera función facet-specification es facet_wrap () . Se necesita un parámetro requerido, tres parámetros menos
utilizados pero ocasionalmente útiles, y algunos más que no discutiremos aquí.
1. El primer parámetro es una fórmula que especifica las columnas del marco de datos de entrada para facetar por. Para
facet_wrap () , esta fórmula suele ser de la forma ~ <column>, sin ninguna “variable” especificada a la izquierda de
la ~ . Si deseamos facetar en todas las combinaciones de múltiples columnas de entrada, podemos usar
~ <column_1>+ <column_2>+... .
2. El parámetro nrow = controla el número de filas de paneles que deben aparecer en la salida.

3.12.6 https://espanol.libretexts.org/@go/page/55149
3. El parámetro ncol = controla el número de columnas de paneles que deben aparecer en la salida. (Los parámetros nrow
times ncol deben ser lo suficientemente grandes como para contener todos los paneles necesarios. Por defecto, nrow y
ncol se determinan automáticamente.)
4. El parámetro scales = se puede establecer en “fixed” (el valor predeterminado), lo que especifica que todos los ejes
deben escalarse de manera similar en los paneles. Establecer este parámetro en “free_x” permite que los ejes x varíen
entre los paneles, “free_y” permite que los ejes y varíen, y “libre” permite que ambas escalas varíen. Debido a que
las facetas suelen estar diseñadas para permitir comparaciones entre paneles, en la mayoría de las situaciones se deben evitar
ajustes distintos a los “fijos” .
Volviendo a las parcelas de puntos de los datos de diamantes, recordemos que parecía haber alguna relación entre el “corte” y la
“profundidad” de los diamantes, pero en las parcelas anteriores, esta relación era difícil de ver. Vamos a trazar lo mismo que arriba,
pero esta vez faceta en la columna de corte.
III.12_34_R_267_GGPlot_Diamonds_Carat_Depth_Mod_Layers_Facet
III.12_35_GGPlot_diamantes_carat_depth_mod_layers_facet

El facetado de esta manera produce un panel separado para cada tipo de corte, todo en los mismos ejes, pero cualquier cosa
específica de la gráfica (como las líneas de ajuste) se calcula de forma independiente. Esto revela que los diamantes de talla “ideal”
tienden a tener una profundidad de alrededor de 62, mientras que los cortes de menor calidad se desvían de este valor. Para facetar
en corte y color, la fórmula sería ~ cut + color , y se crearía un panel para cada combinación de corte/color.
La función facet_grid () funciona de manera muy parecida a facet_wrap () pero nos permite facetear de
acuerdo no a una sino a dos variables (o dos combinaciones de múltiples variables) simultáneamente. Una de las variables variará
entre filas de paneles y la otra variará entre columnas. El primer parámetro sigue siendo una fórmula, pero generalmente de la
forma ~ <column_1><column_2>. Reemplazemos el facet_wrap (~ cut) por facet_grid (cut ~ color)
:
III.12_36_R_268_GGPlot_Diamonds_carat_depth_mod_layers_facet2
III.12_37_GGPlot_diamantes_carat_depth_mod_layers_facet2

A medida que aumenta el número de facetas, la legibilidad puede disminuir, pero haber cortado a lo largo de las filas y el color a lo
largo de las columnas ciertamente ayuda a revelar tendencias. En esta especificación facetaria, vemos que los diamantes “justos”
son relativamente raros en el conjunto de datos, y los diamantes más grandes tienden a ser cortes premium o ideales en colores
pobres (I y J). La producción de archivos PDF con dimensiones más grandes en ggsave () puede hacer que las gráficas con
muchas características sean más legibles.
Al igual que facet_wrap () , facet_grid () puede tomar un parámetro scales = para permitir que los ejes x e
y del panel varíen, aunque de nuevo esto generalmente no es recomendable, ya que dificulta la comparación visual de los paneles.
La función facet_grid () incluye otro parámetro opcional, margins = , que cuando se establece en TRUE agrega
paneles “agregados” para cada columna y fila de paneles. Aquí está la trama con
facet_grid (cut ~ color, margins = TRUE) :
III.12_38_GGPlot_diamantes_carat_depth_mod_layers_facet2_márgenes

Al trazar una cuadrícula de facetas, también podemos usar un punto en la especificación para indicar que la cuadrícula debe
consistir en una sola fila o columna de paneles. Si bien facet_wrap () también puede producir solo una fila o cuadrícula, la
opción margins = TRUE se puede usar con facet_grid () para producir una fila o columna mientras se produce
simultáneamente un panel agregado. Aquí está la misma parcela con
facet_grid (. ~ corte, márgenes = VERDADERO) .
III.12_39_GGPLOT_DIAMES_CARAT_DEPTH_GRID_ONE_Layer

Las facetas generalmente se crean para diferentes valores de datos categóricos. ¡Un intento de facetar todos los diferentes valores
en una columna continua podría resultar en millones de paneles! (O, más probablemente, una instancia estrellada de R.) Aún así,
ocasionalmente queremos facetar sobre datos continuos produciendo “bins” discretizados individuales para valores. Considera si
quisiéramos facetar el precio/quilate del diamante, categorizado como “alto” (al menos $10,000) o “bajo” (menos de $10,000).
Desafortunadamente, las fórmulas tomadas por facet_wrap () y facet_grid () no permiten expresiones
vectorizadas, por lo que facet_wrap (~ precio/quilates < 10000) no funcionará.

3.12.7 https://espanol.libretexts.org/@go/page/55149
La solución es crear primero una nueva columna del
marco de datos antes de trazar, como en
dd$price_carat_low <- dd$precio/dd$carat < 10000 , para crear una nueva columna lógica, y luego facetar
en la columna recién creada con facet_wrap (~ price_carat_low) .
R también incluye una función llamada cut () específicamente para discretizar vectores continuos en factores. El primer
parámetro es el vector a discretizar, y el segundo parámetro es un número de bins (de igual tamaño) para dividir la entrada, o un
vector de puntos de interrupción para los bins.
III.12_40_R_269_GGPlot_Diamonds_Carat_Depth_Cut
III.12_41_GGPlot_Diamonds_Carat_Depth_Cut

El cut () también puede tomar un parámetro labels = , para los casos en que las etiquetas predeterminadas no son
suficientes.

Básculas
Cada estética utilizada en una parcela está asociada a una escala, ya sea la x e y o incluso el color o el tamaño . Para
las gráficas facetadas, generalmente cualquier ajuste de escala se comparte entre facetas, y para cada capa de una gráfica, todas las
propiedades de escala deben compartirse también. Los diferentes tipos de escalas (continuas para x e y , valores de color para
color , tamaños para tamaño ) se pueden modificar para cambiar el nombre de la escala, el rango, la ubicación de las
roturas (o marcas de selección) y las etiquetas de las roturas. Hay más que decir sobre las escalas de lo que se puede cubrir en este
libro, pero intentaremos llegar a los puntos más altos.
En lugar de seguir trazando el conjunto de datos de diamantes, carguemos un marco de datos más relevante biológicamente. Esta
tabla, almacenada en un archivo llamado contig_stats.txt , resume las estadísticas de secuencia de un ensamblaje de
genoma de novo. Cada fila representa un “cóntigo” del ensamblaje (una sola secuencia destinada a representar una pieza contigua
de la secuencia del genoma producida a partir de muchas piezas secuenciadas superpuestas).
III.12_42_Contig_stats

Cada cóntigo tiene un valor promedio_cobertura , que representa el número promedio de lecturas de secuencia que cubren
cada base en el cóntigo ensamblado. Estos valores pueden variar ampliamente, potencialmente debido a regiones duplicadas del
genoma que se ensamblaron erróneamente en un solo cóntigo. La columna Consensus_length indica la longitud del
cóntigo (en pares de bases), y la columna gccontent muestra el porcentaje de bases G o C del cóntigo.
III.12_43_R_270_GGPlot_Contig_Stats_Tabla
III.12_44_R_271_GGPlot_Contig_Stats_Tabla_Imprimir

Vamos a producir una gráfica de puntos para estos datos con Average_coverage en el eje x y
consensus_length en el y .
III.12_45_R_272_GGPlot_Contig_Stats_Plot1
imagen

El resultado no es muy bueno. Hay valores atípicos en ambos ejes, lo que nos impide ver alguna tendencia en los datos. Ajustemos
la escala x agregando una escala_x_continuous () , estableciendo name = para fijar el nombre de la escala, y
usando limits = para establecer los límites de la escala en c (0, 1000) . También usaremos el correspondiente
scale_y_continuous () para establecer un nombre propio para el eje y. (Para escalas discretas, como en el ejemplo
boxplot anterior, las funciones correspondientes son scale_x_discrete () y scale_y_discrete () .)
III.12_47_R_273_GGPlot_Contig_Stats_Plot2
imagen

Esto es mucho mejor. Parece que la mayoría de los cóntigos más largos tienen una cobertura de aproximadamente 100X.
Lamentablemente, esta estrategia deja fuera muchos grandes puntos de datos para mostrar las tendencias en la pequeña mayoría. En
su lugar, deberíamos ajustar logarítmicamente los datos trazando
aes (x = log (Average_coverage), y = consensus_length) , o log-ajustar la escala. Si logarítmicamente
ajustamos los datos, tendríamos que recordar que los valores están ajustados. Tiene más sentido modificar la propia escala y trazar
los datos originales en la escala no lineal. Esto se puede hacer suministrando un parámetro trans = a la escala y especificando
el nombre de una función de transformación soportada para aplicarla a la escala como un vector de caracteres de un solo elemento.

3.12.8 https://espanol.libretexts.org/@go/page/55149
De hecho, hagamos que las escalas x e y se ajusten logarítmicamente. Mientras estamos en ello, especificaremos marcas de
rotura explícitas para las escalas, así como etiquetas personalizadas para esas marcas de rotura.
III.12_49_R_274_GGPlot_Contig_Stats_Plot3

El resultado está abajo a la izquierda, y se ve bastante bien. Para un flourish, podemos agregar un
annotation_logticks (base = 10) para obtener marcas de marca escaladas logarítmicamente, que se muestran
abajo a la derecha.
imagen

Otras funciones de ajuste que podríamos haber usado para el parámetro trans = incluyen “log2" o “sqrt” , aunque
“log10" es una opción común con la que la mayoría de los espectadores estarán familiarizados.
Uno de los problemas que quedan con nuestra trama es que hay demasiados puntos de datos para encajar en este pequeño espacio;
esta trama tiene un problema de “sobretrazado”. Hay una variedad de soluciones para el sobretrazado, incluido el muestreo
aleatorio, el establecimiento de la transparencia de los puntos o el uso de un tipo de parcela diferente en conjunto. Convertiremos
esta trama en un tipo de mapa de calor, donde los puntos cercanos entre sí se agruparán en una sola “celda”, cuyo color representará
alguna estadística de los puntos en esa celda. [6]
Hay dos funciones de
capa que podemos usar para tal mapa de calor, stat_summary_2d () y
stat_summary_hex () . El primero produce celdas cuadradas, y el segundo hexágonos. (La capa
stat_summary_hex () requiere que tengamos el paquete “binhex” instalado a través de
install.packages (“binhex”) en la consola interactiva.) Usaremos stat_summary_hex () , ya que es un
poco más divertido. Esta función de capa requiere más del número habitual de parámetros y estética:
1. Los datos = marco de datos a trazar (como de costumbre).
2. El mapeo = estética para establecer vía aes () (como de costumbre), requiriendo x , la variable a bin por en el eje
x ; y , la variable a bin por en el eje y ; y z , la variable a celdas de color por.
3. Un parámetro fun = , especificando la función a aplicar a los valores z para cada celda para determinar el color.
Esto podría ser un poco confuso sin un ejemplo, así que reemplacemos la capa dotplot con una capa
stat_summary_hex () que traza x e y de la misma manera, pero coloreando las celdas por la media gccontent
de puntos dentro de esa celda.
III.12_51_R_275_GGPlot_Contig_Stats_Plot4

El resultado, abajo a la izquierda, tiene celdas coloreadas por la función media aplicada a todos los valores gccontent
(desde la estética z ), pero no revela cuántos puntos están presentes en cada celda. Para ello, podemos usar fun = length ,
que devuelve el número de elementos en un vector en lugar de la media, resultando en la gráfica de abajo a la derecha.
imagen

Al igual que las ubicaciones x e y en las escalas, los colores de las celdas también existen en una escala. En este caso,
podemos controlarlo con el modificador scale_fill_gradient () . (Las líneas y otros colores no rellenos se controlan
con scale_color_gradient () , y las escalas de color discretas se controlan con scale_fill_descrete () y
scale_color_discrete () ). Esto significa que las escalas de color pueden nombrarse, transformarse y recibir límites,
roturas y etiquetas. A continuación, la cadena "#BFBCFF" especifica el color púrpura claro en la parte superior de la escala,
basado en el esquema de codificación de colores RGB.
III.12_53_R_275_GGPlot_Contig_Stats_Plot5
imagen

También hemos incluido un ajuste trans = “log10" en esta escala de colores, lo que indica que se puede transformar al
igual que otras escalas continuas. Usar un ajuste log10 en una escala de colores puede ser o no una buena idea. Para este
conjunto de datos, ilustra más claramente la distribución de cóntigos en el espacio trazado, pero dificulta las comparaciones entre
celdas y con la leyenda.

El sistema de color RGB


RBG significa rojo, verde, azul. Los monitores de computadora y otros dispositivos digitales muestran colores por las intensidades
variables de pequeños dispositivos emisores de luz roja, verde y azul. Un triplete de estos constituye un solo píxel en la pantalla. El

3.12.9 https://espanol.libretexts.org/@go/page/55149
esquema RGB codifica un color como # <RR><GG><BB>, donde <RR>codifica la cantidad de luz roja, <GG>la cantidad
de verde y <BB>la cantidad de azul. Estos valores pueden oscilar entre 00 (off) y FF (completamente encendido); estos
son números codificados en formato hexadecimal, lo que significa que después del 9 , el siguiente dígito es A , luego B , y así
sucesivamente, hasta F. (Contando desde 49 , por ejemplo, los siguientes números son 4A , 4B ,. 4F , 50 , 51 ,
etc.) ¿Por qué rojo, verde y azul? La mayoría de los humanos tienen tres tipos de células cónicas en sus ojos, ¡y cada uno es más
sensible a la luz roja, verde o azul! Por lo tanto, un esquema RGB puede representar casi todos los colores que los humanos pueden
ver, aunque al final estamos limitados por las gradaciones (el <RR><GG><BB>formato # solo puede tomar alrededor de 16.7
millones de valores diferentes) y la calidad de los dispositivos emisores de luz (muchos de los cuales no pueden producir luz muy
brillante o estar completamente apagado en funcionamiento). El daltonismo es una anomalía genética causada por tener solo uno o
dos de los tres tipos de cono, y unos pocos individuos raros pero afortunados poseen cuatro tipos de cono junto con una mayor
agudeza en la visión del color (tetracromacia).
Algo a tener en cuenta al diseñar escalas de color es que no todas se crean por igual: un porcentaje justo de espectadores tendrá
algún tipo de daltonismo, y otro porcentaje justo de espectadores preferirá imprimir una trama en una impresora en blanco y negro.
La función scale_color_brewer () ayuda al usuario a seleccionar buenas paletas de colores; se basa en el trabajo
encontrado en colorbrewer2.org. Otros tipos de escala se pueden ajustar de manera similar, incluyendo alfa (transparencia) y los
tamaños de puntos y líneas.

Coordenadas
Además de modificar las propiedades de las escalas, también podemos modificar cómo se interpretan esas escalas en la parcela
general y en relación entre sí. Algunas de las modificaciones de coordenadas son menos comunes, pero otras (como
coord_equal () , abajo) son útiles. A menudo, los ajustes de coordenadas se ilustran considerando una gráfica de puntos o
una gráfica de barras en coordenadas polares.
III.12_55_R_276_GGPlot_Diamonds_Polar
imagen

La función coord_polar () requiere un parámetro theta = para indicar qué eje ( x o y ) se debe mapear al ángulo
de rotación; el eje restante se mapea al radio. Esta modificación de coordenadas se puede utilizar para producir parcelas
interesantes, especialmente cuando el geom utilizado es “barra” o “línea” .
La función coord_flip () se puede utilizar para voltear los ejes x e y . Esta característica es especialmente útil cuando
el deseo es producir un histograma horizontal o un diagrama de caja.
III.12_57_R_277_GGPlot_Diamonds_Flip
imagen

Al establecer la estética de relleno , las barras secundarias se apilan, que es la predeterminada. Para una gráfica con barras
presentadas lado a lado, se puede agregar el argumento position = “dodge” a la llamada a la capa geom_bar () .
Para un último ajuste de coordenadas, los valores mapeados a los ejes horizontal y vertical a veces son directamente comparables,
pero el rango es diferente. En nuestra submuestra aleatoria de diamantes dd , por ejemplo, las columnas x y z se miden
ambas en milímetros (representando el “ancho” y la “altura” del diamante), pero los valores z son generalmente más pequeños.
A modo de ilustración, una sola unidad en el eje horizontal debe tener el mismo tamaño que una sola unidad en el eje vertical, pero
ggplot2 no garantiza dicho tamaño por defecto. Podemos especificar el tamaño agregando un ajuste de coordenadas
coord_equal () .
III.12_59_R_278_GGPlot_Diamonds_Equal
III.12_60_GGPlot_Diamonds_Equal

Observe que sin coord_equal () en la izquierda anterior, los tamaños de los ejes no son del todo comparables, pero las
marcas de cuadrícula son cuadrados perfectos en la parte superior derecha. El ajuste coord_equal () también se puede
hacer con parcelas ajustadas logarítmicamente, y a veces puede producir una salida más agradable incluso si los dos ejes no están
en las mismas unidades. Aquí hay una comparación con y sin coord_equal () aplicada a la versión final de la gráfica de
longitud contra cobertura del cóntigo anterior.
imagen

3.12.10 https://espanol.libretexts.org/@go/page/55149
Como siempre, hay una serie de otros ajustes de coordenadas que son posibles, y solo hemos cubierto algunos de los más útiles o
interesantes. Consulte docs.ggplot2.org para obtener la documentación completa.

Tematización, anotaciones, texto y fluctuación


Hasta ahora, hemos cubierto la personalización de cómo se trazan los datos y se representan los ejes/coordenadas, pero no hemos
tocado las propiedades “auxiliares” de la trama como títulos, colores de fondo y tamaños de fuente. Estos son parte del “tema” de la
trama, y muchos aspectos del tema son ajustados por la función theme () . La excepción es la adición de un título a una trama,
que se logra con la función ggtitle () .
Las partes basadas en texto de una trama están organizadas jerárquicamente (ver la documentación para theme () para ver la
lista completa). Por ejemplo, al modificar el parámetro text = se modificarán todos los elementos de texto, mientras que al
modificar axis.text = se ajustan las etiquetas de tick a lo largo de ambos ejes, y axis.text.x = especifica las
propiedades de solo las etiquetas de tick del eje x. Otros elementos text-theme incluyen legend d.text , axis.title
(para nombres de ejes), plot.title y strip.text (para etiquetas de facetas).
Para ajustar las propiedades de estos elementos, utilizamos una llamada a element_text () dentro de la llamada a
theme () . Podemos producir una trama rápida contando diamantes por su corte y claridad, por ejemplo, estableciendo un
título de trama y cambiando el tamaño general del texto a 16 , y solo el tamaño del título a 20 . Reducir el texto puede ser
especialmente útil para los casos en que las etiquetas facetarias o las etiquetas temáticas son demasiado grandes para adaptarse a
sus respectivas cajas.
III.12_62_R_279_GGPlot_Count_Theme
imagen

Las etiquetas utilizadas para los descansos a veces son más largas de lo que caben cómodamente en los ejes. Aparte de cambiar su
tamaño, a veces ayuda a inclinarlos 30 o 45 grados. Al hacerlo, también se ve mejor establecer hjust = 1 para justificar a la
derecha las etiquetas.
III.12_64_R_280_GGPlot_Count_Theme_Angle
imagen

Como podría sugerir este ejemplo, la tematización es en sí misma un tema complejo dentro de ggplot2 , y hay muchas
opciones y funciones internas especiales que modifican casi todos los aspectos de una trama.
Aunque ggsave () acepta parámetros width = y height = especificando el tamaño general del archivo de salida,
debido a que estos parámetros incluyen las etiquetas de leyenda y eje, la región de trazado tiene su relación de aspecto determinada
automáticamente. Indicar que la región de trazado debe tomar una relación de aspecto específica (definida como la altura de la
región sobre el ancho) también ocurre dentro de una llamada theme () .
III.12_66_R_281_GGPlot_Count_Theme_Aspect
imagen

El lector observador pudo haber notado que, por defecto, todas las regiones de trazado en ggplot2 utilizan un fondo gris claro.
Esto es intencional: la idea es que una trama con fondo blanco, cuando se incrusta en un manuscrito, deje un “agujero” visual,
interrumpiendo el flujo del texto. El tono de gris elegido para el fondo predeterminado está destinado a mezclarse con el tono
general de una columna de texto.
Algunos usuarios prefieren usar un fondo blanco más tradicional, pero hacerlo requiere ajustar múltiples elementos, incluido el
fondo en sí, las líneas de cuadrícula, etc. Entonces, ggplot2 incluye una serie de funciones que pueden cambiar el tema
general, como theme_bw () .
III.12_68_R_282_GGPlot_Points_Theme_BW
imagen

Debido a que las llamadas a theme_bw () et al. modifican todos los elementos temáticos, si queremos modificar también
elementos temáticos individuales con theme () , esos deben agregarse a la cadena después de la llamada a theme_bw ()
.
Una característica de ggplot2 aún no cubierta es el uso de texto dentro de las gráficas, que no son ajustes de tema sino tipos
especiales de capas de trazado. La función de capa geom_text () facilita la creación de “dotplots” donde cada punto está

3.12.11 https://espanol.libretexts.org/@go/page/55149
representado por una etiqueta de texto en lugar de un punto. Aquí hay un ejemplo trazando los primeros 30 diamantes por quilate y
precio, etiquetados por su corte.
III.12_70_R_283_GGPlot_geom_text

En el resultado (abajo a la izquierda), es difícil ver que en algunos casos se trazan múltiples diamantes en la misma ubicación. Este
tipo de sobretrazado también puede ocurrir con puntos; agregar una opción position = “jitter” a la capa
geom_text () modifica ligeramente la ubicación de todos los geoms para que destaquen (abajo a la derecha).
III.12_71_GGPlot_Diamonds_geom_text

Varias estéticas del texto se pueden mapear a valores de los datos, incluyendo tamaño , ángulo , color y alfa . Al
igual que con otras capas, para cambiar el tamaño de fuente (u otra propiedad) para todos los puntos a un valor constante, la
instrucción debe darse fuera de la llamada aes () .
Las etiquetas de texto individuales, así como segmentos de línea individuales, rectángulos, puntos y otras geomas, se pueden
agregar con una capa annotate () . Tal capa toma como primer argumento el nombre del geom que se agregará, y
posteriormente cualquier estética que deba establecerse para ese geom (sin una llamada a aes () ). Aquí hay una ilustración,
terminando el ejemplo anterior de la trama longitud/cobertura. (El hjust = 0 en la anotación de texto indica que el texto
debe estar justificado a la izquierda con respecto a la referencia x e y .)
III.12_72_R_284_GGPlot_Contigs_Anotado
imagen

Idealmente, nos esforzaríamos por producir gráficos listos para la publicación con código bien documentado y fácilmente editable.
En algunos casos, sin embargo, los ajustes o anotaciones se agregan más fácilmente en programas de edición gráfica como Adobe
Photoshop o Illustrator (o alternativas de código abierto como Inkscape), siempre y cuando no se altere la interpretación y el
significado de los datos.

Ejercicios
1. Utilice ggplot2 para explorar el trio.sample.vcf que analizamos en capítulos anteriores. ¿Se puede visualizar de
manera efectiva la distribución de las ubicaciones de SNP a través de los cromosomas?
2. Al ejecutar la función data () (sin parámetros) en la consola interactiva R se enumerarán los conjuntos de datos
incorporados que están disponibles. Por ejemplo, USArrestes describe estadísticas de arrestos en
estados de Estados Unidos en 1973, con columnas para tasas per cápita de asesinato, violación y asalto, y también una para el
porcentaje de residentes estatales en áreas urbanas (ver ayuda (USArrestos) para más detalles).
Primero, vea si puede reproducir esta trama, donde hay una faceta para cada estado, y una barra para cada tipo de delito: Es
posible III.12_74_GGPlot_Crime_EX1 que primero deba manipular los datos creando una columna para nombres de estados (en lugar de
usar los nombres de fila) y “reuniendo” algunas de las columnas con tidyr .
A continuación, vea si se puede reproducir la trama, pero de tal manera que los paneles estén ordenados por la tasa global de
criminalidad (Asesinato + Violación + Asalto).
3. Encuentra al menos otra forma de ilustrar los mismos datos. Entonces, encontrar la manera de incorporar en la visualización el
porcentaje de población de cada estado en las zonas urbanas.
4. Genera un conjunto de datos mediante observación o experimentación, ¡y visualízalo!

1. La idea de una gramática gráfica fue introducida por primera vez en 1967 por Jacques Bertin en su libro Semiologie Graphique
(París: Gauthier-Villars, 1967) y posteriormente ampliada en 2005 por Leland Wilkinson et al. en The Grammar of Graphics
(Nueva York: Springer, 2005). Hadley Wickham, “A Layered Grammar of Graphics”, Journal of Computational and Graphical
Statistics 19 (2010): 3—28, describió originalmente la implementación de R.
2. Dada la información de capítulos anteriores sobre objetos, es posible que haya adivinado que el tipo de objeto devuelto por
ggplot () es una lista con conjunto de atributos de clase, donde la clase se establece en “gg” y “ggplot” .
Además, se define un método `+.gg` () especializado para los objetos de la clase “gg” , y + no es solo azúcar
sintáctico para `+` () , sino también una función genérica que se envía!
3. Si estás leyendo esto en blanco y negro, entonces tendrás que confiar en que los colores diferencien los puntos. Más adelante
discutiremos la importancia de elegir esquemas de color que funcionen tanto en escala de grises como para lectores daltónicos,

3.12.12 https://espanol.libretexts.org/@go/page/55149
aunque aún no hemos cubierto los conceptos de código por hacerlo.
4. Sin embargo, podemos explorar rápidamente datos multidimensionales mediante el uso de estéticas adicionales como el color,
el tamaño, la forma, el alfa, etc. Si se requieren múltiples ejes y o gráficas tridimensionales, algunos otros paquetes R pueden
proporcionar estas características, al igual que la utilidad de línea de comandos gnuplot .
5. Muchos en la comunidad de visualización de datos consideran los libros de Tufte, especialmente The Visual Display of
Quantitative Information (Cheshire, CT: Graphics Press, 1983), como elementos básicos. Estos volúmenes son hermosos por
derecho propio como ejemplos de presentación de información.
6. Estas funciones se pueden utilizar para crear mapas de calor, pero generalmente las filas y columnas de un mapa de calor se
pueden ordenar de diferentes maneras. Muchos paquetes están disponibles para trazar mapas de calor de diversos tipos en R,
quizás uno de los más interesantes es el paquete NeatMap , que se basa en ggplot2 .

This page titled 3.12: Datos de trazado y ggplot2 is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T.
O’Neil (OSU Press) .

3.12.13 https://espanol.libretexts.org/@go/page/55149
CHAPTER OVERVIEW
Volver Materia
Índice
Glosario

Volver Materia is shared under a not declared license and was authored, remixed, and/or curated by LibreTexts.

1
Índice
Índice is shared under a not declared license LibreTexts.
and was authored, remixed, and/or curated by

1 https://espanol.libretexts.org/@go/page/55156
Glosario
Ejemplo y Direcciones emerg
Glosario is shared under a not declared license
entes]
Palab La (Opci (Opci (Opci (Opci and was authored, remixed, and/or curated by
ras (o defini onal) onal) onal) onal) (Ej. LibreTexts.
palab ción Imag Leye Enlac Fuent (Ej. “Rel
CC- Glossary has no license indicated.
ras recon en nda e e para “Gen acion La
BY-
que oce para para exter Defin ético, ado infa bio.li
SA;
tiene mayú mostr la no o ición Here con me brete
Delm
n la scula ar con image intern ditari gene doble xts.or
ar
mism sy la n o o, so hélic g/
Larse
a minú defini ADN here e
n
defini scula ción ...”) ncia”
ción) s [No )
se
muest Entradas en el glosario
ra en Palab Defin Imag Leye Enlac Fuent
el ra (s) ición en nda e e
Glosa
rio, Pala Defi
solo bra nició
en las de n de
págin mues mues
as tra 1 tra 1

1 https://espanol.libretexts.org/@go/page/55155
Index
Glossary
Sample Word 1 | Sample Definition 1
Detailed Licensing
Overview
Title: Libro: Una cartilla para la biología computacional (O'Neil)
Webpages: 59
Applicable Restrictions: Noncommercial
All licenses found:
CC BY-NC-SA 4.0: 69.5% (41 pages)
Undeclared: 30.5% (18 pages)

By Page
Libro: Una cartilla para la biología computacional (O'Neil) - 2.4: Entrada y salida de archivos - CC BY-NC-SA 4.0
CC BY-NC-SA 4.0 2.5: Flujo de control condicional - CC BY-NC-SA 4.0
Front Matter - Undeclared 2.6: Funciones de Python - CC BY-NC-SA 4.0
TitlePage - Undeclared 2.7: Interfaz de línea de comandos - CC BY-NC-SA
InfoPage - Undeclared 4.0
Table of Contents - Undeclared 2.8: Diccionarios - CC BY-NC-SA 4.0
Licensing - Undeclared 2.9: Knick-knacks bioinformáticos y expresiones
regulares - CC BY-NC-SA 4.0
Materia Frontal - Undeclared
2.10: Variables y Alcance - CC BY-NC-SA 4.0
TitlePage - Undeclared 2.11: Objetos y Clases - CC BY-NC-SA 4.0
InfoPage - Undeclared 2.12: Interfaces de Programación de Aplicaciones,
Tabla de Contenidos - Undeclared Módulos, Paquetes, Azúcar Sintáctico - CC BY-NC-
Acerca del Autor - Undeclared SA 4.0
01: Introducción a Unix - Undeclared 2.13: Algoritmos y estructuras de datos - CC BY-NC-
1: Introducción a Unix/Linux - CC BY-NC-SA 4.0 SA 4.0
1.1: Contexto - CC BY-NC-SA 4.0 3: Programación en R - CC BY-NC-SA 4.0
1.2: Inicio de sesión - CC BY-NC-SA 4.0 3.1: Una introducción - CC BY-NC-SA 4.0
1.3: La línea de comandos y el sistema de 3.2: Variables y Datos - CC BY-NC-SA 4.0
archivos - CC BY-NC-SA 4.0 3.3: Vectores - CC BY-NC-SA 4.0
1.4: Trabajar con archivos y directorios - CC BY- 3.4: Funciones R - CC BY-NC-SA 4.0
NC-SA 4.0 3.5: Listas y Atributos - CC BY-NC-SA 4.0
1.5: Permisos y Ejecutables - CC BY-NC-SA 4.0 3.6: Marcos de datos - CC BY-NC-SA 4.0
1.6: Instalación de software (Bioinformática) - CC 3.7: Carácter y Datos Categóricos - CC BY-NC-SA 4.0
BY-NC-SA 4.0 3.8: Dividir, Aplicar, Combinar - CC BY-NC-SA 4.0
1.7: Línea de comandos BLAST - CC BY-NC-SA 3.9: Remodelación y unión de marcos de datos - CC
4.0 BY-NC-SA 4.0
1.8: Las corrientes estándar - CC BY-NC-SA 4.0 3.10: Programación Procesal - CC BY-NC-SA 4.0
1.9: Clasificación, Primera y Última Líneas - CC 3.11: Objetos y Clases en R - CC BY-NC-SA 4.0
BY-NC-SA 4.0 3.12: Datos de trazado y ggplot2 - CC BY-NC-SA 4.0
1.10: Filas y Columnas - CC BY-NC-SA 4.0 Back Matter - Undeclared
1.11: Patrones (Expresiones Regulares) - CC BY-
Index - Undeclared
NC-SA 4.0
Glossary - Undeclared
1.12: Miscelánea - CC BY-NC-SA 4.0
Detailed Licensing - Undeclared
2: Programación en Python - CC BY-NC-SA 4.0
Volver Materia - Undeclared
2.1: Hola, Mundo - CC BY-NC-SA 4.0
Índice - Undeclared
2.2: Tipos de datos elementales - CC BY-NC-SA 4.0
Glosario - Undeclared
2.3: Colecciones y Looping- Listas y para - CC BY-
NC-SA 4.0

1 https://espanol.libretexts.org/@go/page/157037
2 https://espanol.libretexts.org/@go/page/157037

You might also like