Todo en una sola página

Content from Organización de datos con hojas de cálculo


Última actualización: 2026-11-11 | Mejora esta página

Tiempo estimado: 60 minutos

Hoja de ruta

Preguntas

  • ¿Cómo organizar los datos en tabla?

Objetivos

  • Conozca las hojas de cálculo, sus fortalezas y debilidades.
  • ¿Cómo formateamos los datos en hojas de cálculo para un uso eficaz de los mismos?
  • Conozca los errores comunes en las hojas de cálculo y cómo corregirlos.
  • Organice sus datos siguiendo los principios de datos ordenados.
  • Conozca los formatos de hojas de cálculo basados en texto, como los formatos separados por comas (CSV) o por tabulaciones (TSV).

Este episodio se basa en la lección Data Analysis and Visualisation in R for Ecologists de Data Carpentries.

Programas de hojas de cálculo


Pregunta

  • ¿Cuáles son los principios básicos para utilizar hojas de cálculo para una buena organización de datos?

Objetivo

  • Describa las mejores prácticas para organizar los datos para que las computadoras puedan aprovechar al máximo los conjuntos de datos.

Punto clave

  • Una buena organización de los datos es la base de cualquier proyecto de investigación.

Una buena organización de los datos es la base de su proyecto de investigación. La mayoría de los investigadores disponen de datos o realizan la entrada de datos en hojas de cálculo. Los programas de hojas de cálculo son interfaces gráficas muy útiles para diseñar tablas de datos y gestionar funciones muy básicas de control de calidad de los datos. Véase también Broman and Woo (2018).

Esquema de la hoja de cálculo

Las hojas de cálculo son útiles para la entrada de datos. Por lo tanto, tenemos mucha información en hojas de cálculo. Gran parte de su tiempo como investigador se dedicará a esta etapa de «manipulación de datos». No es lo más divertido, pero es necesario. Le enseñaremos cómo pensar en la organización de los datos y algunas prácticas para una manipulación de datos más eficaz.

Lo que esta lección no le enseñará

  • Cómo realizar estadísticas en una hoja de cálculo
  • Cómo hacer gráficos en una hoja de cálculo
  • Cómo escribir código en programas de hojas de cálculo

Si busca hacer esto, una buena referencia es Head First Excel, publicada por O’Reilly.

¿Por qué no enseñamos el análisis de datos en hojas de cálculo?

  • El análisis de datos en hojas de cálculo suele requerir mucho trabajo manual. Si desea cambiar un parámetro o ejecutar un análisis con un nuevo conjunto de datos, normalmente tiene que volver a hacerlo todo a mano. (Sabemos que es posible crear macros, pero consulte el siguiente punto).

  • También resulta difícil rastrear o reproducir análisis estadísticos o de gráficos realizados en programas de hojas de cálculo cuando se desea volver al trabajo o alguien solicita detalles de su análisis.

Existen muchos programas de hojas de cálculo. Dado que la mayoría de los participantes utilizan Excel como su programa principal, esta lección hará uso de ejemplos de Excel. Otro programa de hojas de cálculo gratuito que también puede utilizarse es LibreOffice. Los comandos pueden diferir un poco entre programas, pero la idea general es la misma.

Los programas de hojas de cálculo abarcan muchas de las tareas que necesitamos realizar como investigadores. Podemos utilizarlos para:

  • Ingreso de datos
  • Organización de datos
  • Selección de subconjuntos y ordenación de datos
  • Estadística
  • Representación gráfica

Los programas de hojas de cálculo utilizan tablas para representar y mostrar datos. Los datos formateados como tablas son también el tema principal de este capítulo, y veremos cómo organizar los datos en tablas de manera estandarizada para garantizar un análisis posterior eficiente.

Discusión

Desafío: Analice los siguientes puntos con su vecino

  • ¿Has utilizado hojas de cálculo en tu investigación, cursos o en casa?
  • ¿Qué tipo de operaciones realizas en las hojas de cálculo?
  • ¿Para qué crees que son útiles las hojas de cálculo?
  • ¿Alguna vez ha hecho algo por error en un programa de hojas de cálculo que le haya hecho sentirse frustrado o triste?

Problemas con las hojas de cálculo

Las hojas de cálculo son útiles para la entrada de datos, pero en realidad tendemos a utilizar los programas de hojas de cálculo para mucho más que solo la entrada de datos. Las utilizamos para crear tablas de datos para publicaciones, generar estadísticas resumidas y elaborar figuras.

Generar tablas para publicaciones en una hoja de cálculo no es lo ideal: a menudo, al dar formato a una tabla de datos para una publicación, presentamos estadísticas resumidas clave de una manera que no está pensada para leerse como datos, y suele implicar un formato especial (fusionar celdas, crear bordes, embellecerla). Le recomendamos realizar este tipo de operaciones dentro de su software de edición de documentos.

Las dos últimas aplicaciones, la generación de estadísticas y figuras, deben utilizarse con cautela: debido a la naturaleza gráfica y de arrastre y soltar de los programas de hojas de cálculo, puede resultar muy difícil, si no imposible, replicar sus pasos (mucho menos rastrear los de otros), particularmente si sus estadísticas o figuras requieren realizar cálculos más complejos. Además, al realizar cálculos en una hoja de cálculo, es fácil aplicar accidentalmente una fórmula ligeramente diferente a múltiples celdas adyacentes. Al utilizar un programa de estadísticas basado en línea de comandos como R o SAS, es prácticamente imposible aplicar un cálculo a una observación de su conjunto de datos pero no a otra, a menos que lo haga intencionadamente.

Uso de hojas de cálculo para la entrada y limpieza de datos

En esta lección, asumiremos que lo más probable es que utilice Excel como su programa de hojas de cálculo principal; existen otros (gnumeric, Calc de OpenOffice) y su funcionalidad es similar, pero Excel parece ser el programa más utilizado por biólogos e investigadores biomédicos.

En esta lección hablaremos de:

  1. Formateo de tablas de datos en hojas de cálculo
  2. Problemas de formato
  3. Exportación de datos

Formateo de tablas de datos en hojas de cálculo


Preguntas

  • ¿Cómo formateamos los datos en hojas de cálculo para un uso eficaz de los mismos?

Objetivos

  • Describa las mejores prácticas para la entrada y el formato de datos en hojas de cálculo.

  • Aplicar las mejores prácticas para organizar variables y observaciones en una hoja de cálculo.

Puntos clave

  • Nunca modifique sus datos brutos. Haga siempre una copia antes de realizar cualquier cambio.

  • Lleva un registro de todos los pasos que sigas para limpiar tus datos en un archivo de texto plano.

  • Organice sus datos siguiendo los principios de datos ordenados.

El error más común es tratar los programas de hojas de cálculo como cuadernos de laboratorio; es decir, depender del contexto, de las notas en el margen, de la disposición espacial de los datos y de los campos para transmitir información. Como seres humanos, podemos (generalmente) interpretar estas cosas, pero las computadoras no ven la información de la misma manera y, a menos que le expliquemos a la computadora qué significa cada detalle (¡y eso puede resultar difícil!), no podrá comprender cómo encajan nuestros datos entre sí.

Gracias a la potencia de las computadoras, podemos gestionar y analizar datos de manera mucho más eficaz y rápida, pero para aprovechar esa potencia, debemos preparar nuestros datos para que la computadora pueda comprenderlos (y las computadoras son muy literales).

Por eso es extremadamente importante configurar tablas bien formateadas desde el principio —antes de empezar siquiera a introducir datos de su primer experimento preliminar—. La organización de los datos es la base de su proyecto de investigación. Puede facilitar o dificultar el trabajo con sus datos a lo largo de todo el análisis, por lo que vale la pena reflexionar al momento de introducir los datos o configurar su experimento. Puede organizar las cosas de diferentes maneras en hojas de cálculo, pero algunas de estas opciones pueden limitar su capacidad para trabajar con los datos en otros programas o para que usted mismo, dentro de 6 meses, o su colaborador trabajen con los datos.

Nota: los mejores diseños/formatos (así como el software y las interfaces) para la entrada y análisis de datos pueden ser diferentes. Es importante tener esto en cuenta y, idealmente, automatizar la conversión de uno a otro.

Seguimiento de sus análisis

Cuando trabajas con hojas de cálculo, durante la limpieza o análisis de datos, es muy fácil terminar con una hoja de cálculo que se vea muy diferente de aquella con la que empezaste. Para poder reproducir tus análisis o averiguar qué hiciste cuando un revisor o instructor solicite un análisis diferente, debes

  • crea un archivo nuevo con tus datos limpios o analizados. No modifiques el conjunto de datos original, ¡o nunca sabrás desde dónde empezaste!

  • lleve un registro de los pasos que haya seguido en la limpieza o el análisis. Debe registrar estos pasos tal como lo haría con cualquier paso de un experimento. Recomendamos hacerlo en un archivo de texto plano almacenado en la misma carpeta que el archivo de datos.

Este podría ser un ejemplo de una configuración de hoja de cálculo:

SCreenshot of a spreadsheet and a README.txt file.
Spreadsheet example setip

Ponga en práctica estos principios hoy mismo durante sus ejercicios.

Aunque el control de versiones queda fuera del alcance de este curso, puede consultar la lección de Carpentries sobre ‘Git’ para aprender a mantener el control de versiones de sus datos. Consulte también esta entrada de blog para un tutorial rápido o Perez-Riverol et al. (2016) para un caso de uso más orientado a la investigación.

Estructuración de datos en hojas de cálculo

Las reglas fundamentales para utilizar programas de hojas de cálculo con datos:

  1. Coloque todas sus variables en columnas: lo que está midiendo, como ‘peso’ o ‘temperatura’.
  2. Coloque cada observación en su propia fila.
  3. No combine varias piezas de información en una sola celda. A veces parece ser una sola cosa, pero piense si esa es la única manera en que querría poder utilizar o ordenar esos datos.
  4. Deja los datos brutos brutos: ¡no los cambies!
  5. Exporte los datos limpios a un formato basado en texto, como el formato CSV (valores separados por comas). Esto garantiza que cualquier persona pueda utilizar los datos, y es requisito de la mayoría de los repositorios de datos.

Por ejemplo, disponemos de datos de pacientes que visitaron varios hospitales en Bruselas, Bélgica. Registraron la fecha de la visita, el hospital, el sexo de los pacientes, el peso y el grupo sanguíneo.

Si tuviéramos que llevar el seguimiento de los datos de esta manera:

A table showing the blood and rhesus types encoded together in one column.
Multiple variable in a single column.

el problema es que los grupos ABO y Rhesus están en la misma columna de tipo Blood. Por lo tanto, si quisieran consultar todas las observaciones del grupo A o analizar las distribuciones de peso por grupo ABO, resultaría complicado hacerlo con esta configuración de datos. Si, en cambio, colocamos los grupos ABO y Rhesus en columnas diferentes, puede ver que sería mucho más sencillo.

A table showing the blood and rhesus types encoded together in different columns.
Variables encoded in a single column each.

Una regla importante al configurar una hoja de datos es que las columnas se utilizan para las variables y las filas se utilizan para las observaciones:

  • las columnas son variables
  • las filas son observaciones
  • las celdas son valores individuales
Discusión

Desafío: Tomaremos un conjunto de datos desordenado y describiremos cómo lo limpiaríamos.

  1. Descargue un conjunto de datos desordenado haciendo clic en este enlace.

  2. Abra los datos en un programa de hojas de cálculo.

  3. Puede ver que hay dos pestañas. Los datos contienen diversas variables clínicas registradas en varios hospitales de Bruselas durante la primera y segunda ola de COVID-19 en 2020. Como puede ver, los datos se han registrado de manera diferente durante las olas de marzo y noviembre. Ahora usted es la persona encargada de este proyecto y quiere poder comenzar a analizar los datos.

  4. Junto con la persona que te acompaña, identifica qué es lo que falla en esta hoja de cálculo. También analiza los pasos que tendrías que seguir para limpiar las pestañas de la primera y segunda ola, y para reunirlas todas en una sola hoja de cálculo.

Importante: No olvide nuestro primer consejo: cree un nuevo archivo (o una nueva pestaña) para los datos limpios y nunca modifique sus datos originales (brutos).

Después de realizar este ejercicio, analizaremos en grupo qué fallaba en estos datos y cómo los corregirías.

Discusión

Desafío: Una vez que hayas organizado los datos, responde a las siguientes preguntas:

  • ¿Cuántos hombres y mujeres participaron en el estudio?
  • ¿Cuántos tipos A, AB y B se han analizado?
  • ¿Como arriba, pero descontando las muestras contaminadas?
  • ¿Cuántos Rhesus + y - se han analizado?
  • ¿Cuántos donantes universales (O-) se han analizado?
  • ¿Cuál es el peso promedio de los hombres AB?
  • ¿Cuántas muestras se han analizado en los diferentes hospitales?

Una excelente referencia, especialmente en lo que respecta a la programación en R, es el artículo Tidy Data Wickham (2014).

Errores comunes en hojas de cálculo


Preguntas

  • ¿Cuáles son algunos de los desafíos comunes al formatear datos en hojas de cálculo y cómo podemos evitarlos?

Objetivos

  • Identificar y resolver problemas comunes de formato de hojas de cálculo.

Puntos clave

  • Evite utilizar múltiples tablas dentro de una sola hoja de cálculo.
  • Evite distribuir los datos en múltiples pestañas.
  • Registre los ceros como ceros.
  • Utilice un valor nulo adecuado para registrar datos faltantes.
  • No utilice el formato para transmitir información ni para que su hoja de cálculo se vea bonita.
  • Coloque los comentarios en una columna separada.
  • Registre las unidades en los encabezados de las columnas.
  • Incluya solo una pieza de información en cada celda.
  • Evite los espacios, los números y los caracteres especiales en los encabezados de las columnas.
  • Evite los caracteres especiales en sus datos.
  • Registre los metadatos en un archivo de texto plano independiente.

Hay algunos errores potenciales que debe tener en cuenta tanto en sus propios datos como en los de colaboradores o de Internet. Si conoce estos errores y el posible efecto negativo en el análisis de datos posterior y en la interpretación de los resultados, esto podría motivarlo a usted y a los miembros de su proyecto a intentar evitarlos. Realizar pequeños cambios en la forma en que formatea sus datos en hojas de cálculo puede tener un gran impacto en la eficiencia y la fiabilidad cuando se trata de la limpieza y el análisis de datos.

Uso de múltiples tablas

Una estrategia común consiste en crear múltiples tablas de datos dentro de una sola hoja de cálculo. Esto confunde a la computadora, ¡así que no lo hagas! Cuando creas múltiples tablas dentro de una sola hoja de cálculo, estableces asociaciones erróneas entre elementos para la computadora, que ve cada fila como una observación. También podrías estar utilizando el mismo nombre de campo en varios lugares, lo que dificultará la limpieza de tus datos para convertirlos en un formato utilizable. El siguiente ejemplo ilustra el problema:

A sheet with many subtable.
A sheet with many subtable.

En el ejemplo anterior, la computadora verá (por ejemplo) la fila 4 y asumirá que todas las columnas A-AF se refieren a la misma muestra. Esta fila representa, en realidad, cuatro muestras distintas (muestra 1 para cada una de cuatro fechas de recolección diferentes: 29 de mayo, 12 de junio, 19 de junio y 26 de junio), así como algunas estadísticas resumidas calculadas (un promedio (avr) y un error estándar de medición (SEM)) para dos de esas muestras. Otras filas presentan problemas similares.

Uso de múltiples pestañas

Pero, ¿qué pasa con las pestañas del libro de trabajo? Parece una forma sencilla de organizar los datos, ¿verdad? Pues bien, sí y no. Al crear pestañas adicionales, no permites que la computadora identifique las relaciones que ya existen en los datos (debes introducir funciones específicas de la aplicación de hojas de cálculo o programación para garantizar dicha conexión). Digamos, por ejemplo, que creas una pestaña separada para cada día en que tomas una medición.

Esto no es una buena práctica por dos razones:

  1. es más probable que introduzcas inconsistencias en tus datos por accidente si, cada vez que tomas una medición, empiezas a registrar los datos en una pestaña nueva, y

  2. incluso si logras evitar que entren todas las inconsistencias, añadirás un paso adicional antes de analizar los datos, ya que tendrás que combinar estos datos en una sola tabla de datos. Tendrás que indicar explícitamente al ordenador cómo combinar las pestañas —y si las pestañas tienen un formato inconsistente, podrías incluso tener que hacerlo manualmente.

La próxima vez que ingrese datos y vaya a crear otra pestaña o tabla, pregúntese si podría evitar añadir esta pestaña añadiendo otra columna a su hoja de cálculo original. Utilizamos múltiples pestañas en nuestro ejemplo de un archivo de datos desordenado, pero ahora ha visto cómo puede reorganizar sus datos para consolidarlos entre pestañas.

Tu hoja de datos puede volverse muy larga a lo largo del experimento. Esto dificulta la introducción de datos si no puedes ver tus encabezados en la parte superior de la hoja de cálculo. Pero no repitas tu fila de encabezados. Las filas de encabezados repetidas pueden mezclarse fácilmente con los datos, lo que podría causar problemas más adelante. En su lugar, puedes congelar los encabezados de las columnas para que sigan siendo visibles incluso cuando tengas una hoja de cálculo con muchas filas.

No completar los ceros

Puede que, al medir algo, el resultado sea habitualmente cero, por ejemplo, el número de veces que se observa un conejo en la encuesta. ¿Para qué molestarse en escribir el número cero en esa columna, cuando la mayoría son ceros?

Sin embargo, hay una diferencia entre un cero y una celda en blanco en una hoja de cálculo. Para la computadora, un cero es, en realidad, un dato. Lo midió o contó. Una celda en blanco significa que no se midió y la computadora lo interpretará como un valor desconocido (también conocido como valor nulo o faltante).

Es probable que las hojas de cálculo o los programas estadísticos malinterpreten las celdas vacías que usted pretende que sean ceros. Al no introducir el valor de su observación, le indica a su computadora que represente esos datos como desconocidos o faltantes (null). Esto puede causar problemas en los cálculos o análisis posteriores. Por ejemplo, la media de un conjunto de números que incluye un único valor null siempre es null (porque la computadora no puede adivinar el valor de las observaciones faltantes). Por ello, es muy importante registrar los ceros como ceros y los datos realmente faltantes como nulls.

Uso de valores nulos problemáticos

Ejemplo: utilizar -999 u otros valores numéricos (o cero) para representar datos faltantes.

Soluciones:

Existen algunas razones por las que los valores nulos se representan de manera diferente dentro de un conjunto de datos. A veces, los valores nulos confusos se registran automáticamente desde el dispositivo de medición. Si ese es el caso, no hay mucho que hacer, pero puede abordarse en la limpieza de datos con una herramienta como OpenRefine antes del análisis. En otras ocasiones, se utilizan valores nulos diferentes para transmitir diferentes razones por las cuales los datos no están presentes. Esta es información importante que debe capturarse, pero en efecto se está utilizando una sola columna para capturar dos piezas de información. Al igual que con el uso del formato para transmitir información, sería conveniente crear aquí una nueva columna como ‘data_missing’ y utilizar dicha columna para capturar las diferentes razones.

Sea cual sea el motivo, es un problema que los datos desconocidos o faltantes se registren como -999, 999 o 0.

Muchos programas estadísticos no reconocerán que estos están destinados a representar valores faltantes (nulos). La forma en que se interpreten estos valores dependerá del software que utilice para analizar sus datos. Es fundamental utilizar un indicador de valores nulos claramente definido y consistente.

Los espacios en blanco (la mayoría de las aplicaciones) y NA (para R) son buenas opciones. White et al. (2013) explican opciones adecuadas para indicar valores nulos en diferentes aplicaciones de software en su artículo:

A table extracted from a paper showing commonly used null values.
Commonly used null values.

Uso de formato para transmitir información

Ejemplo: resaltar celdas, filas o columnas que deben excluirse de un análisis, dejando filas en blanco para indicar separaciones en los datos.

A table showing several cells highlighted in yellow used to encode sample contamination.
Colours used to ecode information.

Solución: crear un nuevo campo para codificar qué datos deben excluirse.

Same table as above with a new variable encoding contambination.
A new variable encoding sample contamination

Uso de formato para que la hoja de datos se vea bien

Ejemplo: fusionar celdas.

Solución: Si no se tiene cuidado, ajustar el formato de una hoja de cálculo para que sea más estética puede comprometer la capacidad de su computadora para detectar asociaciones en los datos. Las celdas unidas harán que sus datos sean ilegibles para el software estadístico. Considere reestructurar sus datos de tal manera que no necesite unir celdas para organizarlos.

Colocar comentarios o unidades en celdas

La mayoría del software de análisis no puede leer los comentarios de Excel o LibreOffice, y se confundiría con los comentarios colocados dentro de las celdas de datos. Como se describió anteriormente respecto al formato, cree otro campo si necesita añadir notas a las celdas. Del mismo modo, no incluya unidades en las celdas: idealmente, todas las mediciones que coloque en una sola columna deberían estar en la misma unidad, pero si por alguna razón no lo están, cree otro campo y especifique la unidad en la que se encuentra la celda.

Ingresar más de una pieza de información en una celda

Ejemplo: Registrar los grupos ABO y Rhesus en una sola celda, como A+, B+, A-, …

Solución: No incluya más de una pieza de información en una sola celda. Incluir varios datos en una sola celda limitará las formas en que se pueden analizar los datos. Si necesita ambas mediciones, diseñe su hoja de datos para incluir esta información. Por ejemplo, incluya una columna para el grupo ABO y otra para el grupo Rhesus.

Uso de nombres de campo problemáticos

Elija nombres de campos descriptivos, pero tenga cuidado de no incluir espacios, números ni caracteres especiales de ningún tipo. Los espacios pueden ser interpretados erróneamente por analizadores que utilizan el espacio en blanco como delimitador, y algunos programas no admiten nombres de campos que sean cadenas de texto que comiencen con números.

Los guiones bajos (_) son una buena alternativa a los espacios. Considere escribir los nombres en camel case (como este: ExampleFileName) para mejorar la legibilidad. Tenga en cuenta que las abreviaturas que tengan sentido en este momento puede que no sean tan claras dentro de 6 meses, pero no abuse de los nombres excesivamente largos. Incluir las unidades en los nombres de los campos evita confusiones y permite que otros interpreten sus campos fácilmente.

Ejemplos

Nombre aceptable Alternativa recomendable Evitar
Max_temp_C MaxTemp Maximum Temp (°C)
Precipitation_mm Precipitation precmm
Mean_year_growth MeanYearGrowth Mean growth/year
sex sex M/F
weight weight w.
cell_type CellType Cell Type
Observation_01 first_observation 1st Obs

Uso de caracteres especiales en datos

Ejemplo: Trata su programa de hojas de cálculo como un procesador de textos al escribir notas, por ejemplo, copiando datos directamente de Word u otras aplicaciones.

Solución: Esta es una estrategia común. Por ejemplo, al escribir textos más extensos en una celda, es frecuente incluir saltos de línea, guiones em, etc., en la hoja de cálculo. Asimismo, al copiar datos desde aplicaciones como Word, se incluyen el formato y caracteres no estándar especiales (como comillas alineadas a la izquierda y a la derecha). Al exportar estos datos a un entorno de programación/estadística o a una base de datos relacional, pueden ocurrir problemas graves, como que las líneas se corten por la mitad y se produzcan errores de codificación.

La mejor práctica general es evitar añadir caracteres como saltos de línea, tabulaciones y tabulaciones verticales. En otras palabras, trate una celda de texto como si fuera un formulario web sencillo que solo puede contener texto y espacios.

Inclusión de metadatos en la tabla de datos

Ejemplo: Añades una leyenda en la parte superior o inferior de tu tabla de datos que explique el significado de las columnas, las unidades, las excepciones, etc.

Solución: Registrar datos sobre sus datos (“metadatos”) es esencial. Es posible que conozca a la perfección su conjunto de datos mientras lo recopila y analiza, pero es poco probable que recuerde, tras unos meses, un año o más, que la variable “sglmemgp” significa miembro individual del grupo, por ejemplo, o el algoritmo exacto que utilizó para transformar una variable o crear una derivada.

Asimismo, existen muchas razones por las que otras personas podrían querer examinar o utilizar sus datos: para comprender sus hallazgos, verificarlos, revisar su publicación enviada, replicar sus resultados, diseñar un estudio similar o incluso archivar sus datos para que otros puedan acceder a ellos y reutilizarlos. Si bien los datos digitales son, por definición, legibles por máquinas, comprender su significado es tarea humana. No se puede sobreestimar la importancia de documentar sus datos durante la fase de recopilación y análisis de su investigación, especialmente si su trabajo formará parte del registro académico.

Sin embargo, los metadatos no deben incluirse en el propio archivo de datos. A diferencia de una tabla en un artículo o de un archivo complementario, los metadatos (en forma de leyendas) no deben incluirse en un archivo de datos, ya que esta información no es dato, e incluirlos puede alterar la forma en que los programas informáticos interpretan el archivo de datos. En su lugar, los metadatos deben almacenarse como un archivo independiente en el mismo directorio que el archivo de datos, preferiblemente en formato de texto plano y con un nombre que lo asocie claramente con el archivo de datos. Debido a que los archivos de metadatos están en formato de texto libre, también permiten codificar comentarios, unidades, información sobre cómo se codifican los valores nulos, etc., que son importantes de documentar pero pueden alterar el formato del archivo de datos.

Además, los metadatos a nivel de archivo o base de datos describen cómo se relacionan los archivos que conforman el conjunto de datos; en qué formato se encuentran; y si sustituyen a otros archivos o han sido sustituidos por ellos. Un archivo readme.txt a nivel de carpeta es la forma clásica de registrar todos los archivos y carpetas de un proyecto.

(El texto sobre metadatos está adaptado del curso en línea Research Data MANTRA de EDINA y Data Library, Universidad de Edimburgo. MANTRA está licenciado bajo una Licencia Creative Commons Atribución 4.0 Internacional.)

Exportación de datos


Pregunta

  • ¿Cómo podemos exportar datos de hojas de cálculo de manera que resulten útiles para aplicaciones posteriores?

Objetivos

  • Guardar datos de hojas de cálculo en formatos de archivo universales.
  • Exportar datos de una hoja de cálculo a un archivo CSV.

Puntos clave

  • Los datos almacenados en formatos comunes de hojas de cálculo a menudo no se leerán correctamente en software de análisis de datos, lo que introducirá errores en los datos.

  • Exportar datos desde hojas de cálculo a formatos como CSV o TSV los coloca en un formato que la mayoría de los programas pueden utilizar de manera consistente.

Guardar los datos con los que va a trabajar para sus análisis en el formato de archivo predeterminado de Excel (*.xls o *.xlsx - según la versión de Excel) no es una buena idea. ¿Por qué?

  • Porque es un formato propietario, y es posible que en el futuro la tecnología necesaria para abrir el archivo deje de existir (o se vuelva muy poco común), lo que hará difícil, si no imposible, abrir el archivo.

  • Otros programas de hojas de cálculo podrían no ser capaces de abrir archivos guardados en un formato propietario de Excel.

  • Diferentes versiones de Excel pueden gestionar los datos de manera distinta, lo que provoca inconsistencias. Fechas es un ejemplo bien documentado de inconsistencias en el almacenamiento de datos.

  • Por último, cada vez más revistas y organismos de subvenciones exigen que deposite sus datos en un repositorio de datos, y la mayoría de ellos no aceptan el formato Excel. Debe estar en uno de los formatos analizados a continuación.

  • Los puntos anteriores también se aplican a otros formatos, como los formatos de datos abiertos utilizados por LibreOffice / Open Office. Estos formatos no son estáticos y no se analizan de la misma manera por diferentes paquetes de software.

Almacenar los datos en un formato universal, abierto y estático ayudará a resolver este problema. Pruebe el formato separado por tabulaciones (valores separados por tabulaciones o TSV) o el separado por comas (valores separados por comas o CSV). Los archivos CSV son archivos de texto plano donde las columnas están separadas por comas, de ahí el nombre de «valores separados por comas» o CSV. La ventaja de un archivo CSV frente a un archivo de Excel/SPSS/etc. es que podemos abrir y leer un archivo CSV utilizando prácticamente cualquier software, incluidos los editores de texto plano como TextEdit o NotePad. Los datos de un archivo CSV también pueden importarse fácilmente a otros formatos y entornos, como SQLite y R. No estamos atados a una versión específica de un programa costoso al trabajar con archivos CSV, por lo que es un buen formato para lograr la máxima portabilidad y durabilidad. La mayoría de los programas de hojas de cálculo pueden guardar fácilmente en formatos de texto separados por comas, como CSV, aunque es posible que aparezca una advertencia durante la exportación del archivo.

Para guardar un archivo abierto en Excel en formato CSV:

  1. En el menú superior, seleccione ‘Archivo’ y ‘Guardar como’.
  2. En el campo ‘Formato’, seleccione ‘Valores separados por comas’ (*.csv) de la lista.
  3. Revise dos veces el nombre del archivo y la ubicación donde desea guardarlo y pulse ‘Guardar’.

Una nota importante para la compatibilidad retroactiva: ¡puedes abrir archivos CSV en Excel!

Excel menu to export data in a csv format on MacOS
Saving an Excel file to CSV.

Una nota sobre R y xls: Existen paquetes de R que pueden leer archivos xls (así como hojas de cálculo de Google). Incluso es posible acceder a diferentes hojas de trabajo en los documentos xls.

Pero

  • algunos de estos solo funcionan en Windows.
  • esto equivale a sustituir una exportación (sencilla pero manual) a csv por una complejidad/dependencias adicionales en el código de R para el análisis de datos.
  • Siguen aplicándose las mejores prácticas de formato de datos.
  • ¿Realmente hay alguna buena razón por la cual csv (o algo similar) no sea adecuado?

Consideraciones sobre las comas

En algunos conjuntos de datos, los propios valores de los datos pueden incluir comas (,). En ese caso, es muy probable que el software que utilice (incluido Excel) muestre los datos incorrectamente en columnas. Esto se debe a que las comas que forman parte de los valores de los datos se interpretarán como delimitadores.

Por ejemplo, nuestros datos podrían verse así:

species_id,genus,species,taxa
AB,Amphispiza,bilineata,Bird
AH,Ammospermophilus,harrisi,Rodent, not censused
AS,Ammodramus,savannarum,Bird
BA,Baiomys,taylori,Rodent

En el registro AH,Ammospermophilus,harrisi,Rodent, not censused, el valor de taxa incluye una coma (Rodent, not censused). Si intentamos leer lo anterior en Excel (u otro programa de hojas de cálculo), obtendremos algo como esto:

A table illustrating ill-imported data that contained a comma in a cell. That cell has been split in two cells.
The risks of having commas inside comma-separated data.

El valor de taxa se dividió en dos columnas (en lugar de situarse en una sola columna D). Esto puede derivar en una serie de errores adicionales. Por ejemplo, la columna adicional se interpretará como una columna con muchos valores faltantes (y sin un encabezado adecuado). Además, el valor de la columna D para el registro de la fila 3 (es decir, aquel en el que el valor de ‘taxa’ contenía la coma) ahora es incorrecto.

Si desea almacenar sus datos en formato csv y prevé que los valores de sus datos puedan contener comas, puede evitar el problema analizado anteriormente poniendo los valores entre comillas (““). Al aplicar esta regla, nuestros datos podrían verse así:

species_id,genus,species,taxa
"AB","Amphispiza","bilineata","Bird"
"AH","Ammospermophilus","harrisi","Rodent, not censused"
"AS","Ammodramus","savannarum","Bird"
"BA","Baiomys","taylori","Rodent"

Ahora, abrir este archivo como un csv en Excel no provocará la aparición de una columna adicional, ya que Excel solo utilizará las comas que se encuentran fuera de las comillas como caracteres delimitadores.

Alternativamente, si trabaja con datos que contienen comas, es probable que necesite utilizar otro delimitador al trabajar en una hoja de cálculo1. En este caso, considere utilizar tabulaciones como delimitador y trabajar con archivos TSV. Los archivos TSV pueden exportarse desde programas de hojas de cálculo de la misma manera que los archivos CSV.

Si está trabajando con un conjunto de datos ya existente en el que los valores de los datos no están incluidos entre ““, pero en el que se utilizan comas tanto como delimitadores como dentro de los valores de los datos, es posible que se encuentre con un problema grave de limpieza de datos. Si el conjunto de datos que está manejando contiene cientos o miles de registros, limpiarlos manualmente (ya sea eliminando las comas de los valores de los datos o colocando los valores entre comillas -”“) no solo llevará horas y horas, sino que podría terminar introduciendo accidentalmente muchos errores.

La limpieza de conjuntos de datos es uno de los problemas principales en muchas disciplinas científicas. El enfoque depende casi siempre del contexto particular. No obstante, es una buena práctica limpiar los datos de manera automatizada, por ejemplo, escribiendo y ejecutando un script. Las lecciones de Python y R le proporcionarán las bases para desarrollar las habilidades necesarias para crear scripts pertinentes.

Resumen


The tidy data analysis circle, iterating over data transformation, visualisation and modelling.
A typical data analysis workflow.

Un flujo de trabajo típico de análisis de datos se ilustra en la figura anterior, donde los datos se transforman, visualizan y modelan repetidamente. Esta iteración se repite varias veces hasta que se comprendan los datos. Sin embargo, en muchos casos reales, la mayor parte del tiempo se dedica a limpiar y preparar los datos, en lugar de analizarlos y comprenderlos realmente.

Un flujo de trabajo de análisis de datos ágil, con varias iteraciones rápidas del ciclo transformar/visualizar/modelar, solo es viable si los datos están formateados de manera predecible y se puede analizar la información sin tener que mirarla y/o corregirla.

Puntos Clave
  • Una buena organización de los datos es la base de cualquier proyecto de investigación.
Broman, Karl W., and Kara H. Woo. 2018. “Data Organization in Spreadsheets.” The American Statistician 72 (1): 2–10. https://doi.org/10.1080/00031305.2017.1375989.
Perez-Riverol, Yasset, Laurent Gatto, Rui Wang, et al. 2016. “Ten Simple Rules for Taking Advantage of Git and GitHub.” PLOS Computational Biology 12 (7): 1–11. https://doi.org/10.1371/journal.pcbi.1004947.
White, Ethan P., Elita Baldridge, Zachary T. Brym, Kenneth J. Locey, Daniel J. McGlinn, and Sarah R. Supp. 2013. “Nine Simple Ways to Make It Easier to (Re)use Your Data.” PeerJ PrePrints 1 (July): e7v2. https://doi.org/10.7287/peerj.preprints.7v2.
Wickham, Hadley. 2014. “Tidy Data.” Journal of Statistical Software, Articles 59 (10): 1–23. https://doi.org/10.18637/jss.v059.i10.

  1. Esto es especialmente relevante en los países europeos, donde la coma se utiliza como separador decimal. En tales casos, el separador de valores predeterminado en un archivo csv será el punto y coma (;), o los valores se encerrarán sistemáticamente entre comillas.↩︎

Content from R y RStudio


Última actualización: 2026-11-11 | Mejora esta página

Tiempo estimado: 30 minutos

Hoja de ruta

Preguntas

  • ¿Qué son R y RStudio?

Objetivos

  • Describa el propósito de los paneles Script, Console, Environment y Plots de RStudio.
  • Organizar archivos y directorios para un conjunto de análisis como un proyecto de R, y comprender el propósito del directorio de trabajo.
  • Utilice la interfaz de ayuda integrada de RStudio para buscar más información sobre las funciones de R.
  • Demostrar cómo proporcionar información suficiente para la resolución de problemas con la comunidad de usuarios de R.

Este episodio se basa en la lección Análisis de datos y visualización en R para ecólogos de Data Carpentries.

¿Qué es R? ¿Qué es RStudio?


El término R se utiliza para referirse al lenguaje de programación, al entorno para el cálculo estadístico y al software que interpreta los scripts escritos con él.

RStudio es actualmente una forma muy popular no solo de escribir sus scripts de R, sino también de interactuar con el software R1. Para funcionar correctamente, RStudio necesita R y, por lo tanto, ambos deben estar instalados en su computadora.

La Hoja de trucos del IDE de RStudio proporciona mucha más información de la que se abordará aquí, pero puede resultar útil para aprender atajos de teclado y descubrir nuevas funciones.

¿Por qué aprender R?


R no requiere hacer muchos clics, y eso es algo bueno.

La curva de aprendizaje puede ser más pronunciada que con otros softwares, pero con R, los resultados de tu análisis no dependen de recordar una sucesión de selecciones y clics, sino de una serie de comandos escritos, ¡y eso es algo bueno! Así que, si quieres volver a realizar tu análisis porque has recopilado más datos, no tienes que recordar qué botón pulsaste y en qué orden para obtener tus resultados; solo tienes que ejecutar tu script de nuevo.

Trabajar con scripts deja claros los pasos que utilizó en su análisis, y el código que escribe puede ser revisado por otra persona que pueda darle retroalimentación y detectar errores.

Trabajar con scripts te obliga a tener una comprensión más profunda de lo que estás haciendo, y facilita tu aprendizaje y comprensión de los métodos que utilizas.

El código de R es ideal para la reproducibilidad

La reproducibilidad significa que otra persona (incluido tu yo del futuro) puede obtener los mismos resultados a partir del mismo conjunto de datos al utilizar el mismo código de análisis.

R se integra con otras herramientas para generar manuscritos o informes a partir de su código. Si recopila más datos o corrige un error en su conjunto de datos, las figuras y las pruebas estadísticas de su manuscrito o informe se actualizan automáticamente.

Cada vez más revistas y organismos de financiación exigen que los análisis sean reproducibles, por lo que conocer R le dará una ventaja ante estos requisitos.

R es interdisciplinario y extensible

Con más de 10 000 paquetes2 que se pueden instalar para ampliar sus capacidades, R proporciona un marco de trabajo que permite combinar enfoques estadísticos de numerosas disciplinas científicas para adaptarlos de la mejor manera al marco analítico que necesite para analizar sus datos. Por ejemplo, R cuenta con paquetes para el análisis de imágenes, SIG, series temporales, genética poblacional y mucho más.

Two graphs showing the exponential increase of the number of CRAN packages from 2000 to 2018. The figure on the right shows linear trend for the log of number of packages.
Exponential increase of the number of packages available on CRAN, the Comprehensive R Archive Network. From the R Journal, Volume 10/2, December 2018.

R funciona con datos de cualquier forma y tamaño

Las habilidades que aprendas con R escalan fácilmente según el tamaño de tu conjunto de datos. Ya sea que tu conjunto de datos tenga cientos o millones de filas, no marcará mucha diferencia.

R está diseñado para el análisis de datos. Incluye estructuras de datos y tipos de datos especiales que facilitan el manejo de datos faltantes y de factores estadísticos.

R puede conectarse con hojas de cálculo, bases de datos y muchos otros formatos de datos, ya sea en su computadora o en la web.

R genera gráficos de alta calidad

Las funcionalidades de representación gráfica en R son extensas y le permiten ajustar cualquier aspecto de su gráfico para transmitir de la manera más eficaz el mensaje de sus datos.

R cuenta con una comunidad amplia y acogedora

Miles de personas utilizan R a diario. Muchas de ellas están dispuestas a ayudarle a través de listas de correo y sitios web como Stack Overflow, o en la comunidad de RStudio. Estas amplias comunidades de usuarios se extienden a áreas especializadas como la bioinformática. Un subconjunto de dicha comunidad de R es Bioconductor, un proyecto científico para el análisis y la comprensión «de datos de ensayos biológicos actuales y emergentes». Este taller fue desarrollado por miembros de la comunidad de Bioconductor; para obtener más información sobre Bioconductor, consulte el taller complementario “The Bioconductor Project”.

R no solo es gratuito, sino que también es de código abierto y multiplataforma

Cualquiera puede inspeccionar el código fuente para ver cómo funciona R. Gracias a esta transparencia, hay menos probabilidades de que ocurran errores y, si usted (o alguien más) encuentra algunos, puede informar y corregir errores.

Conocer RStudio


Comencemos aprendiendo sobre RStudio, que es un entorno de desarrollo integrado (IDE) para trabajar con R.

El producto de código abierto RStudio IDE es gratuito bajo la Licencia Pública General Affero (AGPL) v3. El RStudio IDE también está disponible con una licencia comercial y soporte prioritario por correo electrónico de Posit, Inc.

Utilizaremos el IDE RStudio para escribir código, navegar por los archivos de nuestro ordenador, inspeccionar las variables que vamos a crear y visualizar los gráficos que generaremos. RStudio también puede utilizarse para otras tareas (por ejemplo, control de versiones, desarrollo de paquetes, creación de aplicaciones Shiny) que no abordaremos durante el taller.

Screenshot of the RStudio interface showing the typical four panel setup.
RStudio interface screenshot. Clockwise from top left: Source, Environment/History, Files/Plots/Packages/Help/Viewer, Console.

La ventana de RStudio se divide en 4 “paneles”:

  • el panel Source para sus scripts y documentos (arriba a la izquierda, en la disposición predeterminada)
  • tu Entorno/Historial (arriba a la derecha),
  • tus Archivos/Gráficos/Paquetes/Ayuda/Visor (abajo a la derecha), y
  • la Consola de R (abajo a la izquierda).

La ubicación de estos paneles y su contenido pueden personalizarse (consulte el menú, Tools -> Global Options -> Pane Layout).

Una de las ventajas de usar RStudio es que toda la información necesaria para escribir código está disponible en una sola ventana. Además, gracias a los numerosos atajos, la autocompletación y el resaltado de los principales tipos de archivos que se utilizan al desarrollar en R, RStudio facilitará la escritura y reducirá la probabilidad de cometer errores.

Configuración inicial


Es una buena práctica mantener un conjunto de datos, análisis y texto relacionados de forma autónoma en una sola carpeta, denominada directorio de trabajo. Todos los scripts dentro de esta carpeta pueden entonces utilizar rutas relativas a los archivos que indiquen dónde se encuentra un archivo dentro del proyecto (en contraposición a las rutas absolutas, que apuntan a dónde está el archivo en una computadora específica). Trabajar de esta manera facilita considerablemente el desplazamiento del proyecto en su computadora y su compartición con otros, sin preocuparse por si los scripts subyacentes seguirán funcionando.

RStudio ofrece un conjunto de herramientas útiles para lograrlo a través de su interfaz de “Proyectos”, que no solo crea un directorio de trabajo para usted, sino que también recuerda su ubicación (lo que permite navegar rápidamente hasta él) y, opcionalmente, conserva la configuración personalizada y los archivos abiertos para facilitar la reanudación del trabajo tras un descanso. Siga los pasos a continuación para crear un “Proyecto R” para este tutorial.

  1. Inicie RStudio.
  2. En el menú File, haga clic en New project. Elija New directory y luego New project.
  3. Ingrese un nombre para esta nueva carpeta (o “directorio”) y elija una ubicación conveniente para ella. Este será su directorio de trabajo para esta sesión (o todo el curso) (p. ej., bioc-intro).
  4. Haga clic en Create project.
  5. (Opcional) Establezca las preferencias en ‘Nunca’ para no guardar el espacio de trabajo en RStudio.

Las preferencias predeterminadas de RStudio suelen funcionar bien, pero guardar un espacio de trabajo en .RData puede resultar engorroso, especialmente si se trabaja con conjuntos de datos más grandes. Para desactivar esto, vaya a Herramientas –> ‘Opciones globales’ y seleccione la opción ‘Nunca’ para ‘Guardar espacio de trabajo en .RData’ al salir.

Screenshot of the RStudio General Options panel.
Set ‘Save workspace to .RData on exit’ to ‘Never’

Para evitar problemas de codificación de caracteres entre Windows y otros sistemas operativos, vamos a configurar UTF-8 por defecto:

Screenshot of the RStudio Code Options panel.
Set the default text encoding to UTF-8 to save us headache in the coming future. (Figure from the link above).

Organización de su directorio de trabajo

Utilizar una estructura de carpetas consistente en todos sus proyectos ayudará a mantener todo organizado y también facilitará la búsqueda y archivado de elementos en el futuro. Esto puede resultar especialmente útil cuando tiene múltiples proyectos. En general, puede crear directorios (carpetas) para scripts, datos y documentos.

  • data/ Utilice esta carpeta para almacenar sus datos brutos y los conjuntos de datos intermedios que pueda crear para un análisis particular. Por razones de transparencia y procedencia de los datos, debe siempre mantener accesible una copia de sus datos brutos y realizar de forma programática (es decir, con scripts, en lugar de manualmente) tanta limpieza y preprocesamiento de datos como sea posible. Separar los datos brutos de los datos procesados también es una buena idea. Por ejemplo, podría tener los archivos data/raw/tree_survey.plot1.txt y ...plot2.txt separados de un archivo data/processed/tree.survey.csv generado por el script scripts/01.preprocess.tree_survey.R.
  • documents/ Este sería un lugar para guardar esquemas, borradores y otros textos.
  • scripts/ (o src) Este sería el lugar donde guardar los scripts de R para diferentes análisis o gráficos, y posiblemente una carpeta separada para sus funciones (más sobre esto más adelante).

Es posible que necesite directorios o subdirectorios adicionales según las necesidades de su proyecto, pero estos deben constituir la estructura básica de su directorio de trabajo.

Flow diagram showing the structure of the working directory and various directory of a typical RStuio project.
Example of a working directory structure.

Para este curso, necesitaremos una carpeta data/ para almacenar nuestros datos brutos, y utilizaremos data_output/ para cuando aprendamos a exportar datos como archivos CSV, y la carpeta fig_output/ para las figuras que guardaremos.

Discusión

Desafío: crea la estructura de directorios de tu proyecto

En la pestaña Files a la derecha de la pantalla, haga clic en New Folder y cree una carpeta llamada data dentro de su directorio de trabajo recién creado (por ejemplo, ~/bioc-intro/data). (Alternativamente, escriba dir.create("data") en su consola de R). Repita estas operaciones para crear las carpetas data_output/ y fig_output.

Vamos a guardar el script en la raíz de nuestro directorio de trabajo porque solo utilizaremos un archivo y así será más sencillo.

Su directorio de trabajo debería verse ahora así:

Screenshot of RStudio's Files panel.
How it should look like at the beginning of this lesson

La gestión de proyectos también es aplicable a los proyectos de bioinformática, por supuesto3. William Noble ( Noble (2009) ) propone la siguiente estructura de directorios:

Los nombres de los directorios están en una fuente grande y los nombres de los archivos en una fuente más pequeña. Aquí solo se muestra un subconjunto de los archivos. Tenga en cuenta que las fechas están formateadas <year>-<month>-<day> para que puedan ordenarse cronológicamente. El código fuente src/ms-analysis.c se compila para crear bin/ms-analysis y está documentado en doc/ms-analysis.html. Los archivos README en los directorios de datos especifican quién descargó los archivos de datos desde qué URL y en qué fecha. El script controlador results/2009-01-15/runall genera automáticamente los tres subdirectorios split1, split2 y split3, correspondientes a tres divisiones de validación cruzada. El script bin/parse-sqt.py es llamado por ambos scripts controladores runall.

Figure form Nobel et al. (2009) showing a complex bioinformatics project structure.
Directory structure for a sample bioinformatics project.

El aspecto más importante de un directorio de proyecto bien definido y bien documentado es permitir que alguien que no esté familiarizado con el proyecto4 pueda

  1. comprender de qué trata el proyecto, qué datos están disponibles, qué análisis se ejecutaron y qué resultados se produjeron y, lo más importante,

  2. repetir el análisis —con nuevos datos o cambiando algunos parámetros del análisis.

El directorio de trabajo

El directorio de trabajo es un concepto importante que conviene comprender. Es el lugar desde donde R buscará y guardará los archivos. Cuando escribe código para su proyecto, debe hacer referencia a los archivos en relación con la raíz de su directorio de trabajo y solo necesitar los archivos dentro de esta estructura.

El uso de proyectos de RStudio facilita esto y garantiza que el directorio de trabajo esté configurado correctamente. Si necesita comprobarlo, puede utilizar getwd(). Si por alguna razón su directorio de trabajo no es el correcto, puede cambiarlo en la interfaz de RStudio navegando por el explorador de archivos donde debería estar su directorio de trabajo, haciendo clic en el icono del engranaje azul More y seleccionando Set As Working Directory. Alternativamente, puede utilizar setwd("/path/to/working/directory") para restablecer su directorio de trabajo. Sin embargo, sus scripts no deben incluir esta línea, ya que fallará en la computadora de otra persona.

Ejemplo

El esquema siguiente representa el directorio de trabajo bioc-intro con los subdirectorios data y fig_output, y 2 archivos en este último:

bioc-intro/data/
          /fig_output/fig1.pdf
          /fig_output/fig2.png

Si estuviéramos en el directorio de trabajo, podríamos referirnos al archivo fig1.pdf utilizando la ruta relativa bioc-intro/fig_output/fig1.pdf o la ruta absoluta /home/user/bioc-intro/fig_output/fig1.pdf.

Si estuviéramos en el directorio data, utilizaríamos la ruta relativa ../fig_output/fig1.pdf o la misma ruta absoluta /home/user/bioc-intro/fig_output/fig1.pdf.

Interacción con R


La base de la programación consiste en escribir instrucciones que la computadora siga y, a continuación, indicarle que siga dichas instrucciones. Escribimos, o codificamos, instrucciones en R porque es un lenguaje común que tanto la computadora como nosotros podemos entender. Llamamos a las instrucciones comandos e indicamos a la computadora que siga las instrucciones ejecutando (o corriendo) dichos comandos.

Hay dos formas principales de interactuar con R: utilizando la consola o utilizando scripts (archivos de texto plano que contienen su código). El panel de la consola (en RStudio, el panel inferior izquierdo) es el lugar donde se pueden escribir comandos en lenguaje R y ejecutarlos inmediatamente mediante la computadora. También es donde se mostrarán los resultados de los comandos que se hayan ejecutado. Puede escribir comandos directamente en la consola y presionar Enter para ejecutarlos, pero se perderán al cerrar la sesión.

Dado que queremos que nuestro código y flujo de trabajo sean reproducibles, es mejor escribir los comandos que deseemos en el editor de scripts y guardar el script. De este modo, queda un registro completo de lo que hemos hecho, y cualquier persona (¡incluidos nosotros mismos en el futuro!) puede replicar fácilmente los resultados en su propio ordenador. Tenga en cuenta, sin embargo, que el simple hecho de escribir los comandos en el script no los ejecuta automáticamente; todavía es necesario enviarlos a la consola para su ejecución.

RStudio permite ejecutar comandos directamente desde el editor de scripts utilizando el atajo Ctrl + Enter (en Macs, también funcionará Cmd + Return). El comando de la línea actual del script (indicado por el cursor) o todos los comandos del texto seleccionado actualmente se enviarán a la consola y se ejecutarán al pulsar Ctrl + Enter. Puede encontrar otros atajos de teclado en este cheatsheet de RStudio sobre el IDE de RStudio.

En algún momento de su análisis, es posible que desee comprobar el contenido de una variable o la estructura de un objeto, sin necesariamente dejar un registro de ello en su script. Puede escribir estos comandos y ejecutarlos directamente en la consola. RStudio proporciona los atajos Ctrl + 1 y Ctrl + 2 que le permiten saltar entre los paneles del script y de la consola.

Si R está listo para recibir comandos, la consola de R muestra un indicador >. Si recibe un comando (escribiéndolo, copiándolo y pegándolo o enviándolo desde el editor de scripts mediante Ctrl + Enter), R intentará ejecutarlo y, cuando esté listo, mostrará los resultados y volverá con un nuevo indicador > para esperar nuevos comandos.

Si R sigue esperando que ingrese más datos porque el comando aún está incompleto, la consola mostrará un indicador +. Esto significa que aún no ha terminado de introducir un comando completo. Esto ocurre porque no ha «cerrado» un paréntesis o una comilla; es decir, no tiene el mismo número de paréntesis izquierdos que derechos, ni el mismo número de comillas de apertura y cierre. Cuando esto suceda, y crea que ha terminado de escribir su comando, haga clic dentro de la ventana de la consola y presione Esc; esto cancelará el comando incompleto y lo devolverá al indicador >.

¿Cómo aprender más durante y después del curso?


El material que abordaremos durante este curso le dará una primera idea de cómo puede utilizar R para analizar datos para su propia investigación. Sin embargo, necesitará aprender más para realizar operaciones avanzadas, como limpiar su conjunto de datos, utilizar métodos estadísticos o crear gráficos atractivos5. La mejor manera de volverse competente y eficiente en R, al igual que con cualquier otra herramienta, es utilizarlo para abordar sus preguntas de investigación reales. Como principiante, puede resultar intimidante tener que escribir un script desde cero y, dado que muchas personas ponen su código a disposición en línea, modificar código existente para adaptarlo a su propósito podría facilitarle el inicio.

Fake book cover showing a kitten with title 'Changing Stuff and Seeing What Happens.'

Buscar ayuda


Utilice la interfaz de ayuda integrada de RStudio para buscar más información sobre las funciones de R

Screenshot of RStudio's Help panel.
RStudio help interface.

Una de las formas más rápidas de obtener ayuda es utilizar la interfaz de ayuda de RStudio. Por defecto, este panel se encuentra en el panel inferior derecho de RStudio. Como se ve en la captura de pantalla, al escribir la palabra “Mean”, RStudio intenta también ofrecer una serie de sugerencias que podrían interesarle. La descripción se muestra entonces en la ventana de visualización.

Conozco el nombre de la función que quiero usar, pero no estoy seguro de cómo usarla

Si necesita ayuda con una función específica, supongamos barplot(), puede escribir:

R

?barplot

Si solo necesita recordar los nombres de los argumentos, puede usar:

R

args(lm)

Quiero usar una función que haga X, debe haber una función para ello, pero no sé cuál…

Si busca una función para realizar una tarea específica, puede utilizar la función help.search(), que se invoca mediante el signo de interrogación doble ??. Sin embargo, esta solo busca en los paquetes instalados páginas de ayuda que coincidan con su solicitud de búsqueda.

R

??kruskal

Si no logra encontrar lo que busca, puede utilizar el sitio web rdocumentation.org, que realiza búsquedas en los archivos de ayuda de todos los paquetes disponibles.

Por último, una búsqueda genérica en Google o en internet con “R <task>” a menudo te dirigirá ya sea a la documentación del paquete correspondiente o a un foro útil donde alguien más ya haya planteado tu pregunta.

Estoy atascado… recibo un mensaje de error que no entiendo

Comience buscando el mensaje de error en Google. Sin embargo, esto no siempre funciona muy bien porque, a menudo, los desarrolladores de paquetes dependen de la captura de errores proporcionada por R. Termina obteniendo mensajes de error generales que podrían no ser muy útiles para diagnosticar un problema (por ejemplo, “subscript out of bounds”). Si el mensaje es muy genérico, también podría incluir el nombre de la función o del paquete que está utilizando en su consulta.

Sin embargo, debes consultar Stack Overflow. Busca utilizando la etiqueta [r]. La mayoría de las preguntas ya han recibido respuesta, pero el desafío consiste en usar las palabras adecuadas en la búsqueda para encontrar las respuestas:

https://stackoverflow.com/questions/tagged/r

La Introducción a R también puede resultar compleja para personas con poca experiencia en programación, pero es un buen lugar para comprender los fundamentos del lenguaje R.

La FAQ de R es densa y técnica, pero está llena de información útil.

Solicitar ayuda

La clave para recibir ayuda de alguien es que comprenda rápidamente tu problema. Debes facilitar lo máximo posible la identificación de dónde podría estar el problema.

Intente utilizar las palabras correctas para describir su problema. Por ejemplo, un paquete no es lo mismo que una biblioteca. La mayoría de las personas entenderán a qué se refiere, pero otras tienen opiniones muy marcadas sobre la diferencia de significado. El punto clave es que esto puede confundir a quienes intentan ayudarle. Sea lo más preciso posible al describir su problema.

Si es posible, intente reducir lo que no funciona a un ejemplo reproducible sencillo. Si puede reproducir el problema utilizando una tabla de datos muy pequeña en lugar de la de 50 000 filas y 10 000 columnas, proporcione la tabla pequeña junto con la descripción de su problema. Cuando sea apropiado, intente generalizar lo que está haciendo para que incluso personas que no pertenecen a su campo puedan entender la pregunta. Por ejemplo, en lugar de utilizar un subconjunto de su conjunto de datos real, cree uno genérico pequeño (3 columnas, 5 filas). Para obtener más información sobre cómo escribir un ejemplo reproducible, consulte este artículo de Hadley Wickham.

Para compartir un objeto con otra persona, si es relativamente pequeño, puede utilizar la función dput(). Esta generará código R que puede usarse para recrear exactamente el mismo objeto que el que hay en la memoria:

R

## iris is an example data frame that comes with R and head() is a
## function that returns the first part of the data frame
dput(head(iris))

SALIDA

structure(list(Sepal.Length = c(5.1, 4.9, 4.7, 4.6, 5, 5.4),
    Sepal.Width = c(3.5, 3, 3.2, 3.1, 3.6, 3.9), Petal.Length = c(1.4,
    1.4, 1.3, 1.5, 1.4, 1.7), Petal.Width = c(0.2, 0.2, 0.2,
    0.2, 0.2, 0.4), Species = structure(c(1L, 1L, 1L, 1L, 1L,
    1L), levels = c("setosa", "versicolor", "virginica"), class = "factor")), row.names = c(NA,
6L), class = "data.frame")

Si el objeto es más grande, proporcione el archivo bruto (es decir, su archivo CSV) junto con su script hasta el punto del error (y tras eliminar todo lo que no sea relevante para su problema). Alternativamente, especialmente si su pregunta no está relacionada con una tabla de datos, puede guardar cualquier objeto de R en un archivo[^export]:

R

saveRDS(iris, file="/tmp/iris.rds")

Sin embargo, el contenido de este archivo no es legible para el ser humano y no puede publicarse directamente en Stack Overflow. En su lugar, puede enviarse por correo electrónico a alguien que pueda leerlo con el comando readRDS() (aquí se asume que el archivo descargado se encuentra en una carpeta Downloads en el directorio personal del usuario):

R

some_data <- readRDS(file="~/Downloads/iris.rds")

Por último, pero no menos importante, incluya siempre la salida de sessionInfo(), ya que proporciona información crucial sobre su plataforma, las versiones de R y los paquetes que está utilizando, así como otra información que puede ser muy útil para comprender su problema.

R

sessionInfo()

SALIDA

R version 4.6.2 (2026-10-29)
Platform: aarch64-apple-darwin23
Running under: macOS Golden Gate 27.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] C.UTF-8/C.UTF-8/C.UTF-8/C/C.UTF-8/C.UTF-8

time zone: Asia/Tokyo
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base

loaded via a namespace (and not attached):
[1] compiler_4.6.2 cli_3.6.6      tools_4.6.2    otel_0.2.0     knitr_1.52
[6] xfun_0.62      rlang_1.3.0    png_0.1-9      evaluate_1.0.5

¿Dónde pedir ayuda?

  • La persona que está sentada a tu lado durante el curso. No dudes en hablar con tu vecino durante el taller, comparar tus respuestas y pedir ayuda.
  • Tus colegas amigables: si conoces a alguien con más experiencia que tú, es posible que pueda y esté dispuesto a ayudarte.
  • Stack Overflow: si tu pregunta no ha sido respondida anteriormente y está bien formulada, es probable que obtengas una respuesta en menos de 5 min. Recuerda seguir sus pautas sobre cómo hacer una buena pregunta.
  • La lista de correo R-help: la lee mucha gente (incluida la mayor parte del equipo central de R), mucha gente publica en ella, pero el tono puede ser bastante seco y no siempre es muy acogedor para los usuarios nuevos. Si tu pregunta es válida, es probable que obtengas una respuesta muy rápido, pero no esperes que venga acompañada de emoticonos. Además, aquí, más que en cualquier otro lugar, asegúrate de usar el vocabulario correcto (de lo contrario, podrías recibir una respuesta que señale el mal uso de tus palabras en lugar de responder a tu pregunta). También tendrás más éxito si tu pregunta trata sobre una función básica en lugar de un paquete específico.
  • Si su pregunta trata sobre un paquete específico, consulte si existe una lista de correo para este. Por lo general, figura en el archivo DESCRIPTION del paquete, al que se puede acceder mediante packageDescription("name-of-package"). También podría intentar enviar un correo electrónico directamente al autor del paquete o abrir un issue en el repositorio de código (por ejemplo, GitHub).
  • También existen algunas listas de correo específicas por tema (GIS, filogenética, etc…); la lista completa está disponible en la página de la lista de correo de R-project.

Más recursos

Paquetes de R


Cargando paquetes

Como hemos visto anteriormente, los paquetes de R desempeñan un papel fundamental en R. Para hacer uso de la funcionalidad de un paquete, suponiendo que esté instalado, primero debemos cargarlo para poder utilizarlo. Esto se hace con la función library(). A continuación, cargamos ggplot2.

R

library("ggplot2")

Instalación de paquetes

El repositorio de paquetes predeterminado es la Comprehensive R Archive Network (CRAN), y cualquier paquete disponible en CRAN puede instalarse con la función install.packages(). A continuación, por ejemplo, instalamos el paquete dplyr, del cual aprenderemos más adelante.

R

install.packages("dplyr")

Este comando instalará el paquete dplyr así como todas sus dependencias, es decir, todos los paquetes de los que depende para funcionar.

Otro repositorio principal de paquetes de R es gestionado por Bioconductor. Los paquetes de Bioconductor se gestionan e instalan mediante un paquete dedicado, concretamente BiocManager, que puede instalarse desde CRAN con

R

install.packages("BiocManager")

Los paquetes individuales como SummarizedExperiment (lo utilizaremos más adelante), DESeq2 (para el análisis de RNA-Seq) y cualquier otro de Bioconductor o CRAN pueden instalarse posteriormente con BiocManager::install.

R

BiocManager::install("SummarizedExperiment")
BiocManager::install("DESeq2")

Por defecto, BiocManager::install() también revisará todos los paquetes instalados para ver si hay versiones más recientes disponibles. Si las hay, los mostrará y le preguntará si desea Update all/some/none? [a/s/n]:, y luego esperará su respuesta. Aunque debería procurar tener las versiones de los paquetes más actualizadas, en la práctica recomendamos actualizar los paquetes únicamente en una sesión de R nueva, antes de que se cargue ningún paquete.

Puntos Clave
  • Comience a usar R y RStudio
Noble, William Stafford. 2009. “A Quick Guide to Organizing Computational Biology Projects.” PLOS Computational Biology 5 (7): 1–5. https://doi.org/10.1371/journal.pcbi.1000424.

  1. A diferencia de utilizar R directamente desde la consola de línea de comandos. Existen otros programas que interactúan con R y se integran con él, pero RStudio es particularmente adecuado para principiantes, al tiempo que ofrece numerosas funciones muy avanzadas.↩︎

  2. es decir, complementos que otorgan a R nueva funcionalidad, como el análisis de datos de bioinformática.↩︎

  3. En este curso, consideramos la bioinformática como la ciencia de datos aplicada a datos biológicos o biomédicos.↩︎

  4. Esa persona podría ser, y muy probablemente será, tu yo del futuro, un par de meses o años después de que se hayan realizado los análisis.↩︎

  5. Presentaremos la mayoría de estos aquí (excepto las estadísticas), pero solo lograremos rozar la superficie de la gran cantidad de cosas que se pueden hacer con R.↩︎

Content from Introducción a R


Última actualización: 2026-11-11 | Mejora esta página

Tiempo estimado: 120 minutos

Hoja de ruta

Preguntas

  • Primeros comandos en R

Objetivos

  • Defina los siguientes términos en relación con R: objeto, asignar, llamar, función, argumentos, opciones.
  • Asignar valores a objetos en R.
  • Aprenda a nombrar objetos
  • Utilice comentarios para documentar el script.
  • Realizar operaciones aritméticas sencillas en R.
  • Llama a funciones y utiliza argumentos para cambiar sus opciones predeterminadas.
  • Inspeccionar el contenido de los vectores y manipular dicho contenido.
  • Seleccionar subconjuntos y extraer valores de vectores.
  • Analizar vectores con datos faltantes.

Este episodio se basa en la lección Data Analysis and Visualisation in R for Ecologists de Data Carpentries.

Creación de objetos en R


Puede obtener resultados de R simplemente escribiendo matemáticas en la consola:

R

3 + 5

SALIDA

[1] 8

R

12 / 7

SALIDA

[1] 1.714286

Sin embargo, para hacer cosas útiles e interesantes, necesitamos asignar valores a objetos. Para crear un objeto, debemos darle un nombre seguido del operador de asignación <- y el valor que queremos asignarle:

R

weight_kg <- 55

<- es el operador de asignación. Asigna valores de la derecha a objetos de la izquierda. Por lo tanto, tras ejecutar x <- 3, el valor de x es 3. La flecha puede interpretarse como 3 entra en x. Por razones históricas, también se puede usar = para las asignaciones, pero no en todos los contextos. Debido a las ligeras diferencias en la sintaxis, es buena práctica utilizar siempre <- para las asignaciones.

En RStudio, escribir Alt + - (presionar Alt al mismo tiempo que la tecla -) escribirá <- con un solo toque de tecla en una PC, mientras que escribir Option + - (presionar Option al mismo tiempo que la tecla -) hace lo mismo en una Mac.

Nombres de variables

Los objetos pueden recibir cualquier nombre, como x, current_temperature o subject_id. Es recomendable que los nombres de los objetos sean explícitos y no demasiado largos. No pueden comenzar con un número (2x no es válido, pero x2 sí). R distingue entre mayúsculas y minúsculas (por ejemplo, weight_kg es diferente de Weight_kg). Hay algunos nombres que no pueden utilizarse porque son nombres de funciones fundamentales en R (por ejemplo, if, else, for; consulte la página del manual Palabras reservadas en R para obtener una lista completa). En general, aunque esté permitido, lo mejor es no utilizar otros nombres de funciones (por ejemplo, c, T, mean, data, df, weights). En caso de duda, consulte la ayuda para ver si el nombre ya está en uso. También es recomendable evitar los puntos (.) dentro del nombre de un objeto, como en my.dataset. Hay muchas funciones en R con puntos en sus nombres por razones históricas, pero dado que los puntos tienen un significado especial en R (para los métodos) y en otros lenguajes de programación, lo mejor es evitarlos. También se recomienda utilizar sustantivos para los nombres de los objetos y verbos para los nombres de las funciones. Es importante ser coherente en el estilo de su código (dónde coloca los espacios, cómo nombra los objetos, etc.). Utilizar un estilo de codificación coherente hace que su código sea más fácil de leer tanto para usted mismo en el futuro como para sus colaboradores. En R, algunas de las guías de estilo más populares son la de Google, el estilo de tidyverse y la guía de estilo de Bioconductor. La de tidyverse es muy exhaustiva y puede parecer abrumadora al principio. Puede instalar el paquete lintr para comprobar automáticamente si hay problemas en el estilo de su código.

Objetos vs. variables: Lo que se conoce como objects en R se conoce como variables en muchos otros lenguajes de programación. Dependiendo del contexto, object y variable pueden tener significados drásticamente diferentes. Sin embargo, en esta lección, ambos términos se utilizan como sinónimos. Para más información, consulte aquí.

Al asignar un valor a un objeto, R no imprime nada. Puede obligar a R a imprimir el valor utilizando paréntesis o escribiendo el nombre del objeto:

R

weight_kg <- 55    # doesn't print anything
(weight_kg <- 55)  # but putting parenthesis around the call prints the value of `weight_kg`

SALIDA

[1] 55

R

weight_kg          # and so does typing the name of the object

SALIDA

[1] 55

Ahora que R tiene weight_kg en la memoria, podemos realizar operaciones aritméticas con él. Por ejemplo, es posible que queramos convertir este peso a libras (el peso en libras es 2,2 veces el peso en kg):

R

2.2 * weight_kg

SALIDA

[1] 121

También podemos cambiar el valor de un objeto asignándole uno nuevo:

R

weight_kg <- 57.5
2.2 * weight_kg

SALIDA

[1] 126.5

Esto significa que asignar un valor a un objeto no cambia los valores de otros objetos. Por ejemplo, guardemos el peso del animal en libras en un nuevo objeto, weight_lb:

R

weight_lb <- 2.2 * weight_kg

y luego cambiar weight_kg a 100.

R

weight_kg <- 100
Discusión

Desafío:

¿Cuál crees que es el contenido actual del objeto weight_lb? ¿126.5 o 220?

Comentarios


El carácter de comentario en R es #; todo lo que aparezca a la derecha de un # en un script será ignorado por R. Es útil dejar notas y explicaciones en sus scripts.

RStudio facilita la forma de comentar o descomentar un párrafo: después de seleccionar las líneas que desee comentar, presione simultáneamente Ctrl + Shift + C en su teclado. Si solo desea comentar una línea, puede colocar el cursor en cualquier punto de dicha línea (es decir, no es necesario seleccionar toda la línea) y luego presionar Ctrl + Shift + C.

Discusión

Desafío

¿Cuáles son los valores después de cada instrucción en lo siguiente?

R

mass <- 47.5            # mass?
age  <- 122             # age?
mass <- mass * 2.0      # mass?
age  <- age - 20        # age?
mass_index <- mass/age  # mass_index?

Funciones y sus argumentos


Las funciones son “scripts predefinidos” que automatizan conjuntos de comandos más complejos, incluyendo operaciones, asignaciones, etc. Muchas funciones están predefinidas o pueden ponerse a disposición importando paquetes de R (hablaremos más de esto más adelante). Una función suele recibir una o más entradas denominadas argumentos. Las funciones a menudo (aunque no siempre) devuelven un valor. Un ejemplo típico sería la función sqrt(). La entrada (el argumento) debe ser un número, y el valor de retorno (de hecho, la salida) es la raíz cuadrada de ese número. Ejecutar una función (‘correrla’) se denomina llamar a la función. Un ejemplo de llamada a función es:

R

b <- sqrt(a)

Aquí, el valor de a se pasa a la función sqrt(), la función sqrt() calcula la raíz cuadrada y devuelve el valor, que luego se asigna al objeto b. Esta función es muy sencilla, ya que solo acepta un argumento.

El «valor» de retorno de una función no tiene por qué ser numérico (como el de sqrt()), ni tiene por qué ser un único elemento: puede ser un conjunto de elementos, o incluso un conjunto de datos. Veremos esto cuando leyamos archivos de datos en R.

Los argumentos pueden ser cualquier cosa, no solo números o nombres de archivos, sino también otros objetos. El significado exacto de cada argumento varía según la función y debe consultarse en la documentación (ver más abajo). Algunas funciones aceptan argumentos que pueden ser especificados por el usuario o, si se omiten, asumen un valor predeterminado: estos se denominan opciones. Las opciones se utilizan habitualmente para modificar la forma en que funciona la función, como si ignora los «valores incorrectos» o qué símbolo utilizar en un gráfico. Sin embargo, si desea algo específico, puede especificar el valor de su elección, que se utilizará en lugar del predeterminado.

Probemos una función que pueda recibir múltiples argumentos: round().

R

round(3.14159)

SALIDA

[1] 3

Aquí, hemos llamado a round() con un solo argumento, 3.14159, y ha devuelto el valor 3. Esto se debe a que el valor predeterminado es redondear al número entero más cercano. Si queremos más dígitos, podemos ver cómo hacerlo obteniendo información sobre la función round. Podemos usar args(round) o consultar la ayuda de esta función mediante ?round.

R

args(round)

SALIDA

function (x, digits = 0, ...)
NULL

R

?round

Vemos que si queremos un número diferente de dígitos, podemos escribir digits=2 o cuantos queramos.

R

round(3.14159, digits = 2)

SALIDA

[1] 3.14

Si proporcionas los argumentos exactamente en el mismo orden en que están definidos, no es necesario asignarles nombres:

R

round(3.14159, 2)

SALIDA

[1] 3.14

Y si se indican los nombres de los argumentos, se puede cambiar su orden:

R

round(digits = 2, x = 3.14159)

SALIDA

[1] 3.14

Es una buena práctica colocar primero los argumentos obligatorios (como el número que se está redondeando) en la llamada a la función, y especificar los nombres de todos los argumentos opcionales. Si no lo haces, alguien que lea tu código podría tener que consultar la definición de una función con argumentos desconocidos para comprender qué estás haciendo. Al especificar los nombres de los argumentos, también te proteges contra posibles cambios futuros en la interfaz de la función, que podrían añadir nuevos argumentos entre los existentes.

Vectores y tipos de datos


Un vector es el tipo de dato más común y básico en R, y es prácticamente el motor de R. Un vector se compone de una serie de valores, como números o caracteres. Podemos asignar una serie de valores a un vector utilizando la función c(). Por ejemplo, podemos crear un vector de pesos de animales y asignarlo a un nuevo objeto weight_g:

R

weight_g <- c(50, 60, 65, 82)
weight_g

SALIDA

[1] 50 60 65 82

Un vector también puede contener caracteres:

R

molecules <- c("dna", "rna", "protein")
molecules

SALIDA

[1] "dna"     "rna"     "protein"

Las comillas alrededor de “dna”, “rna”, etc., son esenciales aquí. Sin las comillas, R asumirá que existen objetos llamados dna, rna y protein. Como estos objetos no existen en la memoria de R, aparecerá un mensaje de error.

Existen muchas funciones que permiten inspeccionar el contenido de un vector. length() indica cuántos elementos contiene un vector determinado:

R

length(weight_g)

SALIDA

[1] 4

R

length(molecules)

SALIDA

[1] 3

Una característica importante de un vector es que todos los elementos son del mismo tipo de dato. La función class() indica la clase (el tipo de elemento) de un objeto:

R

class(weight_g)

SALIDA

[1] "numeric"

R

class(molecules)

SALIDA

[1] "character"

La función str() proporciona una visión general de la estructura de un objeto y de sus elementos. Es una función útil al trabajar con objetos grandes y complejos:

R

str(weight_g)

SALIDA

 num [1:4] 50 60 65 82

R

str(molecules)

SALIDA

 chr [1:3] "dna" "rna" "protein"

Puede utilizar la función c() para añadir otros elementos a su vector:

R

weight_g <- c(weight_g, 90) # add to the end of the vector
weight_g <- c(30, weight_g) # add to the beginning of the vector
weight_g

SALIDA

[1] 30 50 60 65 82 90

En la primera línea, tomamos el vector original weight_g, añadimos el valor 90 al final y guardamos el resultado de nuevo en weight_g. Luego añadimos el valor 30 al principio, guardando nuevamente el resultado en weight_g.

Podemos hacer esto una y otra vez para ampliar un vector o armar un conjunto de datos. A medida que programamos, esto puede resultar útil para añadir los resultados que estamos recopilando o calculando.

Un vector atómico es el tipo de datos más sencillo de R y es un vector lineal de un único tipo. Arriba, vimos 2 de los 6 tipos principales de vectores atómicos que utiliza R: "character" y "numeric" (o "double"). Estos son los elementos básicos a partir de los cuales se construyen todos los objetos de R. Los otros 4 tipos de vectores atómicos son:

  • "logical" para TRUE y FALSE (el tipo de dato booleano)
  • "integer" para números enteros (p. ej., 2L, el L indica a R que se trata de un número entero)
  • "complex" para representar números complejos con partes real e imaginaria (p. ej., 1 + 4i) y eso es todo lo que diremos sobre ellos
  • "raw" para flujos de bits que no analizaremos más adelante

Puedes comprobar el tipo de tu vector utilizando la función typeof() y pasando tu vector como argumento.

Los vectores son una de las muchas estructuras de datos que utiliza R. Otras importantes son las listas (list), las matrices (matrix), las tablas de datos (data.frame), los factores (factor) y los arrays (array).

Desafío

Desafío:

Hemos visto que los vectores atómicos pueden ser de tipo carácter, numérico (o double), entero y lógico. Pero, ¿qué sucede si intentamos mezclar estos tipos en un único vector?

R los convierte implícitamente para que tengan todos el mismo tipo

Desafío

Desafío:

¿Qué pasará en cada uno de estos ejemplos? (pista: usa class() para comprobar el tipo de datos de tus objetos y escribe sus nombres para ver qué sucede):

R

num_char <- c(1, 2, 3, "a")
num_logical <- c(1, 2, 3, TRUE, FALSE)
char_logical <- c("a", "b", "c", TRUE)
tricky <- c(1, 2, 3, "4")

R

class(num_char)

SALIDA

[1] "character"

R

num_char

SALIDA

[1] "1" "2" "3" "a"

R

class(num_logical)

SALIDA

[1] "numeric"

R

num_logical

SALIDA

[1] 1 2 3 1 0

R

class(char_logical)

SALIDA

[1] "character"

R

char_logical

SALIDA

[1] "a"    "b"    "c"    "TRUE"

R

class(tricky)

SALIDA

[1] "character"

R

tricky

SALIDA

[1] "1" "2" "3" "4"
Desafío

Desafío:

¿Por qué crees que sucede?

Los vectores pueden tener un solo tipo de dato. R intenta convertir el contenido de este vector (mediante coerción) para encontrar un denominador común que no pierda ninguna información.

Desafío

Desafío:

¿Cuántos valores en combined_logical son "TRUE" (como cadena de caracteres) en el siguiente ejemplo?

R

num_logical <- c(1, 2, 3, TRUE)
char_logical <- c("a", "b", "c", TRUE)
combined_logical <- c(num_logical, char_logical)

Solo uno. No hay memoria de los tipos de datos anteriores, y la coerción ocurre la primera vez que se evalúa el vector. Por lo tanto, el TRUE en num_logical se convierte en un 1 antes de que se convierta en un "1" en combined_logical.

R

combined_logical

SALIDA

[1] "1"    "2"    "3"    "1"    "a"    "b"    "c"    "TRUE"
Desafío

Desafío:

En R, llamamos coerción al proceso de convertir objetos de una clase a otra. Estas conversiones ocurren según una jerarquía, mediante la cual algunos tipos se convierten preferentemente a otros. ¿Puedes dibujar un diagrama que represente la jerarquía de cómo se convierten estos tipos de datos?

lógico → numérico → carácter ← lógico

Selección de subconjuntos de vectores


Si queremos extraer uno o varios valores de un vector, debemos proporcionar uno o varios índices entre corchetes. Por ejemplo:

R

molecules <- c("dna", "rna", "peptide", "protein")
molecules[2]

SALIDA

[1] "rna"

R

molecules[c(3, 2)]

SALIDA

[1] "peptide" "rna"    

También podemos repetir los índices para crear un objeto con más elementos que el original:

R

more_molecules <- molecules[c(1, 2, 3, 2, 1, 4)]
more_molecules

SALIDA

[1] "dna"     "rna"     "peptide" "rna"     "dna"     "protein"

Los índices de R comienzan en 1. Los lenguajes de programación como Fortran, MATLAB, Julia y R comienzan a contar desde 1, ya que es lo que suelen hacer los seres humanos. Los lenguajes de la familia C (incluidos C++, Java, Perl y Python) cuentan desde 0 porque es más sencillo para las computadoras.

Por último, también es posible obtener todos los elementos de un vector, excepto algunos elementos especificados, utilizando índices negativos:

R

molecules ## all molecules

SALIDA

[1] "dna"     "rna"     "peptide" "protein"

R

molecules[-1] ## all but the first one

SALIDA

[1] "rna"     "peptide" "protein"

R

molecules[-c(1, 3)] ## all but 1st/3rd ones

SALIDA

[1] "rna"     "protein"

R

molecules[c(-1, -3)] ## all but 1st/3rd ones

SALIDA

[1] "rna"     "protein"

Selección condicional de subconjuntos


Otra forma común de obtener un subconjunto es utilizar un vector lógico. TRUE seleccionará el elemento con el mismo índice, mientras que FALSE no lo hará:

R

weight_g <- c(21, 34, 39, 54, 55)
weight_g[c(TRUE, FALSE, TRUE, TRUE, FALSE)]

SALIDA

[1] 21 39 54

Por lo general, estos vectores lógicos no se escriben a mano, sino que son el resultado de otras funciones o pruebas lógicas. Por ejemplo, si quisiera seleccionar únicamente los valores superiores a 50:

R

## will return logicals with TRUE for the indices that meet
## the condition
weight_g > 50

SALIDA

[1] FALSE FALSE FALSE  TRUE  TRUE

R

## so we can use this to select only the values above 50
weight_g[weight_g > 50]

SALIDA

[1] 54 55

Puede combinar múltiples pruebas utilizando & (ambas condiciones son ciertas, Y) o | (al menos una de las condiciones es cierta, O):

R

weight_g[weight_g < 30 | weight_g > 50]

SALIDA

[1] 21 54 55

R

weight_g[weight_g >= 30 & weight_g == 21]

SALIDA

numeric(0)

Aquí, < significa “menor que”, > “mayor que”, >= “mayor o igual que” y == “igual a”. El doble signo de igual == es una prueba de igualdad numérica entre el lado izquierdo y el derecho, y no debe confundirse con el signo único =, que realiza la asignación de variables (de manera similar a <-).

Una tarea común es buscar ciertas cadenas en un vector. Se podría utilizar el operador “o” | para comprobar la igualdad con múltiples valores, pero esto puede volverse rápidamente tedioso. La función %in% permite comprobar si se encuentra alguno de los elementos de un vector de búsqueda:

R

molecules <- c("dna", "rna", "protein", "peptide")
molecules[molecules == "rna" | molecules == "dna"] # returns both rna and dna

SALIDA

[1] "dna" "rna"

R

molecules %in% c("rna", "dna", "metabolite", "peptide", "glycerol")

SALIDA

[1]  TRUE  TRUE FALSE  TRUE

R

molecules[molecules %in% c("rna", "dna", "metabolite", "peptide", "glycerol")]

SALIDA

[1] "dna"     "rna"     "peptide"
Desafío

Desafío:

¿Puedes descifrar por qué "four" > "five" devuelve TRUE?

R

"four" > "five"

SALIDA

[1] TRUE

Al utilizar > o < en cadenas, R compara su orden alfabético. En este caso, "four" aparece después de "five" y, por lo tanto, es mayor que este.

Nombres


Es posible asignar nombres a cada elemento de un vector. El bloque de código a continuación muestra un vector inicial sin nombres, cómo se establecen y se recuperan los nombres.

R

x <- c(1, 5, 3, 5, 10)
names(x) ## no names

SALIDA

NULL

R

names(x) <- c("A", "B", "C", "D", "E")
names(x) ## now we have names

SALIDA

[1] "A" "B" "C" "D" "E"

Cuando un vector tiene nombres, es posible acceder a los elementos por su nombre, además de por su índice.

R

x[c(1, 3)]

SALIDA

A C
1 3 

R

x[c("A", "C")]

SALIDA

A C
1 3 

Datos faltantes


Dado que R fue diseñado para analizar conjuntos de datos, incluye el concepto de datos faltantes (algo poco común en otros lenguajes de programación). Los datos faltantes se representan en los vectores como NA.

Al realizar operaciones con números, la mayoría de las funciones devolverán NA si los datos con los que se trabaja incluyen valores faltantes. Esta característica dificulta pasar por alto los casos en los que se manejan datos faltantes. Puede añadir el argumento na.rm = TRUE para calcular el resultado ignorando los valores faltantes.

R

heights <- c(2, 4, 4, NA, 6)
mean(heights)

SALIDA

[1] NA

R

max(heights)

SALIDA

[1] NA

R

mean(heights, na.rm = TRUE)

SALIDA

[1] 4

R

max(heights, na.rm = TRUE)

SALIDA

[1] 6

Si sus datos incluyen valores faltantes, es posible que desee familiarizarse con las funciones is.na(), na.omit() y complete.cases(). Consulte a continuación ejemplos.

R

## Extract those elements which are not missing values.
heights[!is.na(heights)]

SALIDA

[1] 2 4 4 6

R

## Returns the object with incomplete cases removed.
## The returned object is an atomic vector of type `"numeric"`
## (or `"double"`).
na.omit(heights)

SALIDA

[1] 2 4 4 6
attr(,"na.action")
[1] 4
attr(,"class")
[1] "omit"

R

## Extract those elements which are complete cases.
## The returned object is an atomic vector of type `"numeric"`
## (or `"double"`).
heights[complete.cases(heights)]

SALIDA

[1] 2 4 4 6
Desafío

Desafío:

  1. Utilizando este vector de alturas en pulgadas, crea un nuevo vector sin los valores NA.

R

heights <- c(63, 69, 60, 65, NA, 68, 61, 70, 61, 59, 64, 69, 63, 63, NA, 72, 65, 64, 70, 63, 65)
  1. Utilice la función median() para calcular la mediana del vector heights.
  2. Utilice R para determinar cuántas personas del conjunto miden más de 67 pulgadas.

R

heights_no_na <- heights[!is.na(heights)]
## or
heights_no_na <- na.omit(heights)

R

median(heights, na.rm = TRUE)

SALIDA

[1] 64

R

heights_above_67 <- heights_no_na[heights_no_na > 67]
length(heights_above_67)

SALIDA

[1] 6

Generación de vectores


Constructores

Existen algunas funciones para generar vectores de diferentes tipos. Para generar un vector de números, se puede utilizar el constructor numeric(), proporcionando la longitud del vector de salida como parámetro. Los valores se inicializarán con 0.

R

numeric(3)

SALIDA

[1] 0 0 0

R

numeric(10)

SALIDA

 [1] 0 0 0 0 0 0 0 0 0 0

Téngase en cuenta que si solicitamos un vector numérico de longitud 0, obtenemos exactamente eso:

R

numeric(0)

SALIDA

numeric(0)

Existen constructores similares para caracteres y valores lógicos, denominados character() y logical(), respectivamente.

Desafío

Desafío:

¿Cuáles son los valores predeterminados para los vectores de caracteres y lógicos?

R

character(2) ## the empty character

SALIDA

[1] "" ""

R

logical(2)   ## FALSE

SALIDA

[1] FALSE FALSE

Repetir elementos

La función rep permite repetir un valor un número determinado de veces. Si queremos inicializar un vector numérico de longitud 5 con el valor -1, por ejemplo, podríamos hacer lo siguiente:

R

rep(-1, 5)

SALIDA

[1] -1 -1 -1 -1 -1

Del mismo modo, para generar un vector lleno de valores faltantes, lo cual suele ser una buena forma de comenzar, sin establecer supuestos sobre los datos a recolectar:

R

rep(NA, 5)

SALIDA

[1] NA NA NA NA NA

rep puede aceptar como entrada vectores de cualquier longitud (en lo anterior, utilizamos vectores de longitud 1) y de cualquier tipo. Por ejemplo, si queremos repetir los valores 1, 2 y 3 cinco veces, haríamos lo siguiente:

R

rep(c(1, 2, 3), 5)

SALIDA

 [1] 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
Desafío

Desafío:

¿Y si quisiéramos repetir los valores 1, 2 y 3 cinco veces, pero obtener cinco 1, cinco 2 y cinco 3 en ese orden? Hay dos posibilidades; consulte ?rep o ?sort para obtener ayuda.

R

rep(c(1, 2, 3), each = 5)

SALIDA

 [1] 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3

R

sort(rep(c(1, 2, 3), 5))

SALIDA

 [1] 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3

Generación de secuencias

Otra función muy útil es seq, para generar una secuencia de números. Por ejemplo, para generar una secuencia de enteros del 1 al 20 en pasos de 2, se utilizaría:

R

seq(from = 1, to = 20, by = 2)

SALIDA

 [1]  1  3  5  7  9 11 13 15 17 19

El valor predeterminado de by es 1 y, dado que la generación de una secuencia de un valor a otro con pasos de 1 se utiliza con frecuencia, existe un atajo:

R

seq(1, 5, 1)

SALIDA

[1] 1 2 3 4 5

R

seq(1, 5) ## default by

SALIDA

[1] 1 2 3 4 5

R

1:5

SALIDA

[1] 1 2 3 4 5

Para generar una secuencia de números del 1 al 20 con una longitud final de 3, se utilizaría:

R

seq(from = 1, to = 20, length.out = 3)

SALIDA

[1]  1.0 10.5 20.0

Muestras aleatorias y permutaciones

Un último grupo de funciones útiles son aquellas que generan datos aleatorios. La primera, sample, genera una permutación aleatoria de otro vector. Por ejemplo, para determinar un orden aleatorio para el examen oral de 10 estudiantes, primero asigno a cada estudiante un número del 1 al 10 (por ejemplo, según el orden alfabético de su nombre) y luego:

R

sample(1:10)

SALIDA

 [1]  9  4  7  1  2  5  3 10  6  8

Sin argumentos adicionales, sample devolverá una permutación de todos los elementos del vector. Si quisiera una muestra aleatoria de un tamaño determinado, establecería este valor como segundo argumento. A continuación, extraigo 5 letras aleatorias del alfabeto contenido en el vector letters predefinido:

R

sample(letters, 5)

SALIDA

[1] "s" "a" "u" "x" "j"

Si quisiera un resultado mayor que el vector de entrada, o poder extraer algunos elementos varias veces, tendría que establecer el argumento replace en TRUE:

R

sample(1:5, 10, replace = TRUE)

SALIDA

 [1] 2 1 5 5 1 1 5 5 2 2
Desafío

Desafío:

Al probar las funciones anteriores, habrá advertido que las muestras son, en efecto, aleatorias y que no se obtiene la misma permutación dos veces. Para poder reproducir estos muestreos aleatorios, se puede establecer manualmente la semilla de generación de números aleatorios con set.seed() antes de extraer la muestra aleatoria.

Prueba esta función con tu compañero. Primero, extrae dos permutaciones aleatorias de 1:10 de forma independiente y observa que obtienes resultados diferentes.

Ahora establezca la semilla con, por ejemplo, set.seed(123) y repita el muestreo aleatorio. Observe que ahora obtiene los mismos muestreos aleatorios.

Repita estableciendo una semilla diferente.

Diferentes permutaciones

R

sample(1:10)

SALIDA

 [1]  9  1  4  3  6  2  5  8 10  7

R

sample(1:10)

SALIDA

 [1]  4  9  7  6  1 10  8  3  2  5

Mismas permutaciones con la semilla 123

R

set.seed(123)
sample(1:10)

SALIDA

 [1]  3 10  2  8  6  9  1  7  5  4

R

set.seed(123)
sample(1:10)

SALIDA

 [1]  3 10  2  8  6  9  1  7  5  4

Una semilla diferente

R

set.seed(1)
sample(1:10)

SALIDA

 [1]  9  4  7  1  2  5  3 10  6  8

R

set.seed(1)
sample(1:10)

SALIDA

 [1]  9  4  7  1  2  5  3 10  6  8

Toma de muestras de una distribución normal

La última función que veremos es rnorm, que extrae una muestra aleatoria de una distribución normal. A continuación se muestran dos distribuciones normales con medias 0 y 100 y desviaciones estándar 1 y 5, denotadas como N(0, 1) y N(100, 5).

Two figures illustraing normal distributonis N(0,1) and N(100,5).
Two normal distributions: N(0, 1) on the left and N(100, 5) on the right.

Los tres argumentos, n, mean y sd, definen el tamaño de la muestra y los parámetros de la distribución normal, es decir, la media y su desviación estándar. Los valores predeterminados de estos dos parámetros son 0 y 1, respectivamente.

R

rnorm(5)

SALIDA

[1]  0.69641761  0.05351568 -1.31028350 -2.12306606 -0.20807859

R

rnorm(5, 2, 2)

SALIDA

[1]  1.3744268 -0.1164714  2.8344472  1.3690969  3.6510983

R

rnorm(5, 100, 5)

SALIDA

[1] 106.45636  96.87448  95.62427 100.71678 107.12595

Ahora que hemos aprendido a escribir scripts y los conceptos básicos de las estructuras de datos de R, estamos listos para empezar a trabajar con datos más extensos y aprender sobre las tablas de datos.

Puntos Clave
  • Cómo interactuar con R

Content from Comenzando con los datos


Última actualización: 2026-11-11 | Mejora esta página

Tiempo estimado: 60 minutos

Hoja de ruta

Preguntas

  • Primer análisis de datos en R

Objetivos

  • Describa qué es un data.frame.
  • Cargar datos externos de un archivo .csv en una tabla de datos.
  • Resume el contenido de una tabla de datos.
  • Describa qué es un factor.
  • Convertir entre cadenas y factores.
  • Reordenar y renombrar factores.
  • Formatear fechas.
  • Exportar y guardar datos.

Este episodio se basa en la lección Data Analysis and Visualisation in R for Ecologists de Data Carpentries.

Presentación de los datos de expresión génica


Vamos a utilizar una parte de los datos publicados por Blackmore et al. (2017), The effect of upper-respiratory infection on transcriptomic changes in the CNS. El objetivo del estudio fue determinar el efecto de una infección de las vías respiratorias superiores sobre los cambios en la transcripción de ARN que ocurren en el cerebelo y la médula espinal tras la infección. Ratones C57BL/6 de ocho semanas de edad, emparejados por sexo, fueron inoculados con solución salina o con Influenza A por vía intranasal, y los cambios transcriptómicos en los tejidos del cerebelo y de la médula espinal se evaluaron mediante RNA-seq en los días 0 (no infectados), 4 y 8.

El conjunto de datos se almacena como un archivo de valores separados por comas (CSV). Cada fila contiene información sobre una sola medición de expresión de ARN, y las once primeras columnas representan:

Columna Descripción
gene El nombre del gen que se midió
sample El nombre de la muestra en la que se midió la expresión génica
expression El valor de la expresión génica
organism El organismo/especie - aquí todos los datos provienen de ratones
age La edad del ratón (aquí todos los ratones tenían 8 semanas)
sex El sexo del ratón
infection El estado de infección del ratón, es decir, infectado con Influenza A o no infectado.
strain La cepa de Influenza A.
time La duración de la infección (en días).
tissue El tejido que se utilizó para el experimento de expresión génica, es decir, el cerebelo o la médula espinal.
mouse El identificador único del ratón.

Vamos a utilizar la función de R download.file() para descargar el archivo CSV que contiene los datos de expresión génica, y utilizaremos read.csv() para cargar en memoria el contenido del archivo CSV como un objeto de clase data.frame. Dentro del comando download.file, la primera entrada es una cadena de caracteres con la URL de origen. Esta URL de origen descarga un archivo CSV de un repositorio de GitHub. El texto después de la coma ("data/rnaseq.csv") es el destino del archivo en su máquina local. Necesitará tener una carpeta en su máquina llamada "data" donde descargará el archivo. Por lo tanto, este comando descarga el archivo remoto, le asigna el nombre "rnaseq.csv" y lo añade a una carpeta preexistente llamada "data".

R

download.file(url = "https://github.com/carpentries-incubator/bioc-intro/raw/main/episodes/data/rnaseq.csv",
              destfile = "data/rnaseq.csv")

Ya está listo para cargar los datos:

R

rna <- read.csv("data/rnaseq.csv")

Esta instrucción no produce ninguna salida porque, como probablemente recuerde, las asignaciones no muestran nada. Si queremos comprobar que los datos se han cargado, podemos ver el contenido de la tabla de datos escribiendo su nombre:

R

rna

Vaya… fue mucha salida. Al menos significa que los datos se cargaron correctamente. Revisemos la parte superior (las primeras 6 líneas) de esta tabla de datos utilizando la función head():

R

head(rna)

SALIDA

     gene     sample expression     organism age    sex  infection  strain time
1     Asl GSM2545336       1170 Mus musculus   8 Female InfluenzaA C57BL/6    8
2    Apod GSM2545336      36194 Mus musculus   8 Female InfluenzaA C57BL/6    8
3 Cyp2d22 GSM2545336       4060 Mus musculus   8 Female InfluenzaA C57BL/6    8
4    Klk6 GSM2545336        287 Mus musculus   8 Female InfluenzaA C57BL/6    8
5   Fcrls GSM2545336         85 Mus musculus   8 Female InfluenzaA C57BL/6    8
6  Slc2a4 GSM2545336        782 Mus musculus   8 Female InfluenzaA C57BL/6    8
      tissue mouse ENTREZID
1 Cerebellum    14   109900
2 Cerebellum    14    11815
3 Cerebellum    14    56448
4 Cerebellum    14    19144
5 Cerebellum    14    80891
6 Cerebellum    14    20528
                                                                       product
1                               argininosuccinate lyase, transcript variant X1
2                                       apolipoprotein D, transcript variant 3
3 cytochrome P450, family 2, subfamily d, polypeptide 22, transcript variant 2
4                         kallikrein related-peptidase 6, transcript variant 2
5                Fc receptor-like S, scavenger receptor, transcript variant X1
6          solute carrier family 2 (facilitated glucose transporter), member 4
     ensembl_gene_id external_synonym chromosome_name   gene_biotype
1 ENSMUSG00000025533    2510006M18Rik               5 protein_coding
2 ENSMUSG00000022548             <NA>              16 protein_coding
3 ENSMUSG00000061740             2D22              15 protein_coding
4 ENSMUSG00000050063             Bssp               7 protein_coding
5 ENSMUSG00000015852    2810439C17Rik               3 protein_coding
6 ENSMUSG00000018566           Glut-4              11 protein_coding
                            phenotype_description
1           abnormal circulating amino acid level
2                      abnormal lipid homeostasis
3                        abnormal skin morphology
4                         abnormal cytokine level
5 decreased CD8-positive alpha-beta T cell number
6              abnormal circulating glucose level
  hsapiens_homolog_associated_gene_name
1                                   ASL
2                                  APOD
3                                CYP2D6
4                                  KLK6
5                                 FCRL2
6                                SLC2A4

R

## Try also
## View(rna)

Nota

read.csv() asume que los campos están delimitados por comas; sin embargo, en varios países, la coma se utiliza como separador decimal y el punto y coma (;) como delimitador de campos. Si desea leer este tipo de archivos en R, puede utilizar la función read.csv2(). Se comporta exactamente como read.csv(), pero utiliza parámetros diferentes para los separadores decimal y de campos. Si trabaja con otro formato, ambos pueden ser especificados por el usuario. Consulte la ayuda de read.csv() escribiendo ?read.csv para obtener más información. También existe la función read.delim() para leer archivos de datos separados por tabulaciones. Es importante tener en cuenta que todas estas funciones son, en realidad, funciones wrapper para la función principal read.table() con argumentos diferentes. Por lo tanto, los datos anteriores también podrían haberse cargado utilizando read.table() con el argumento de separación como ,. El código es el siguiente:

R

rna <- read.table(file = "data/rnaseq.csv",
                  sep = ",",
                  header = TRUE)

El argumento header debe establecerse en TRUE para poder leer los encabezados, ya que, por defecto, read.table() tiene el argumento header establecido en FALSE.

¿Qué son las tablas de datos?


Las tablas de datos son la estructura de datos de facto para la mayoría de los datos en formato tabular, y es lo que utilizamos para estadísticas y gráficos.

Una tabla de datos puede crearse manualmente, pero lo más común es que se generen mediante las funciones read.csv() o read.table(); en otras palabras, al importar hojas de cálculo desde su disco duro (o de la web).

Una tabla de datos es la representación de los datos en formato de tabla, donde las columnas son vectores que tienen todos la misma longitud. Debido a que las columnas son vectores, cada columna debe contener un único tipo de dato (por ejemplo, caracteres, enteros, factores). Por ejemplo, aquí hay una figura que representa una tabla de datos compuesta por un vector numérico, uno de caracteres y uno lógico.

Schematic representation of a tablular structure with different colours representing different columns and variables types.
Conceptual representation of a data.frame

Podemos ver esto al inspeccionar la estructura de una tabla de datos con la función str():

R

str(rna)

SALIDA

'data.frame':	32428 obs. of  19 variables:
 $ gene                                 : chr  "Asl" "Apod" "Cyp2d22" "Klk6" ...
 $ sample                               : chr  "GSM2545336" "GSM2545336" "GSM2545336" "GSM2545336" ...
 $ expression                           : int  1170 36194 4060 287 85 782 1619 288 43217 1071 ...
 $ organism                             : chr  "Mus musculus" "Mus musculus" "Mus musculus" "Mus musculus" ...
 $ age                                  : int  8 8 8 8 8 8 8 8 8 8 ...
 $ sex                                  : chr  "Female" "Female" "Female" "Female" ...
 $ infection                            : chr  "InfluenzaA" "InfluenzaA" "InfluenzaA" "InfluenzaA" ...
 $ strain                               : chr  "C57BL/6" "C57BL/6" "C57BL/6" "C57BL/6" ...
 $ time                                 : int  8 8 8 8 8 8 8 8 8 8 ...
 $ tissue                               : chr  "Cerebellum" "Cerebellum" "Cerebellum" "Cerebellum" ...
 $ mouse                                : int  14 14 14 14 14 14 14 14 14 14 ...
 $ ENTREZID                             : int  109900 11815 56448 19144 80891 20528 97827 118454 18823 14696 ...
 $ product                              : chr  "argininosuccinate lyase, transcript variant X1" "apolipoprotein D, transcript variant 3" "cytochrome P450, family 2, subfamily d, polypeptide 22, transcript variant 2" "kallikrein related-peptidase 6, transcript variant 2" ...
 $ ensembl_gene_id                      : chr  "ENSMUSG00000025533" "ENSMUSG00000022548" "ENSMUSG00000061740" "ENSMUSG00000050063" ...
 $ external_synonym                     : chr  "2510006M18Rik" NA "2D22" "Bssp" ...
 $ chromosome_name                      : chr  "5" "16" "15" "7" ...
 $ gene_biotype                         : chr  "protein_coding" "protein_coding" "protein_coding" "protein_coding" ...
 $ phenotype_description                : chr  "abnormal circulating amino acid level" "abnormal lipid homeostasis" "abnormal skin morphology" "abnormal cytokine level" ...
 $ hsapiens_homolog_associated_gene_name: chr  "ASL" "APOD" "CYP2D6" "KLK6" ...

Inspección de objetos data.frame


Ya vimos cómo las funciones head() y str() pueden ser útiles para comprobar el contenido y la estructura de una tabla de datos. Aquí tienes una lista no exhaustiva de funciones para hacerte una idea del contenido/estructura de los datos. ¡Probémoslas!

Tamaño:

  • dim(rna) - devuelve un vector con el número de filas como primer elemento y el número de columnas como segundo elemento (las dimensiones del objeto).
  • nrow(rna) - devuelve el número de filas.
  • ncol(rna) - devuelve el número de columnas.

Contenido:

  • head(rna) - muestra las primeras 6 filas.
  • tail(rna) - muestra las últimas 6 filas.

Nombres:

  • names(rna) - devuelve los nombres de las columnas (sinónimo de colnames() para objetos data.frame).
  • rownames(rna) - devuelve los nombres de las filas.

Resumen:

  • str(rna) - estructura del objeto e información sobre la clase, la longitud y el contenido de cada columna.
  • summary(rna) - estadísticas resumidas para cada columna.

Nota: la mayoría de estas funciones son “genéricas”; pueden utilizarse con otros tipos de objetos además de data.frame.

Desafío

Desafío:

Basándote en la salida de str(rna), ¿puedes responder a las siguientes preguntas?

  • ¿Cuál es la clase del objeto rna?
  • ¿Cuántas filas y cuántas columnas tiene este objeto?
  • clase: tabla de datos
  • cuántas filas: 32428, cuántas columnas: 19

Indexación y selección de subconjuntos de tablas de datos


Nuestra tabla de datos rna tiene filas y columnas (tiene 2 dimensiones); si queremos extraer algunos datos específicos de ella, necesitamos especificar las “coordenadas” que deseamos. Los números de fila vienen primero, seguidos de los números de columna. No obstante, tenga en cuenta que las diferentes formas de especificar estas coordenadas dan lugar a resultados con clases distintas.

R

# first element in the first column of the data frame (as a vector)
rna[1, 1]
# first element in the 6th column (as a vector)
rna[1, 6]
# first column of the data frame (as a vector)
rna[, 1]
# first column of the data frame (as a data.frame)
rna[1]
# first three elements in the 7th column (as a vector)
rna[1:3, 7]
# the 3rd row of the data frame (as a data.frame)
rna[3, ]
# equivalent to head_rna <- head(rna)
head_rna <- rna[1:6, ]
head_rna

: es una función especial que crea vectores numéricos de enteros en orden creciente o decreciente; pruebe 1:10 y 10:1, por ejemplo. Consulte la sección sobre la generación de vectores para obtener más detalles.

También puede excluir ciertos índices de una tabla de datos utilizando el signo “-”:

R

rna[, -1]          ## The whole data frame, except the first column
rna[-c(7:66465), ] ## Equivalent to head(rna)

Se pueden seleccionar subconjuntos de las tablas de datos mediante sus índices (como se mostró anteriormente), pero también usando directamente los nombres de sus columnas:

R

rna["gene"]       # Result is a data.frame
rna[, "gene"]     # Result is a vector
rna[["gene"]]     # Result is a vector
rna$gene          # Result is a vector

En RStudio, puede utilizar la función de autocompletado para obtener los nombres completos y correctos de las columnas.

Desafío

Desafío

  1. Crea un data.frame (rna_200) que contenga únicamente los datos de la fila 200 del conjunto de datos rna.

  2. ¿Observas cómo nrow() te dio el número de filas de un data.frame?

  • Utilice ese número para extraer únicamente esa última fila de la tabla de datos inicial rna.

  • Compáralo con lo que ves como la última fila usando tail() para asegurarte de que coincide con lo esperado.

  • Extraiga esa última fila utilizando nrow() en lugar del número de fila.

  • Crea una nueva tabla de datos (rna_last) a partir de esa última fila.

  1. Utilice nrow() para extraer la fila que se encuentra en el centro de la tabla de datos rna. Guarde el contenido de esta fila en un objeto llamado rna_middle.

  2. Combina nrow() con la notación - anterior para reproducir el comportamiento de head(rna), manteniendo únicamente las filas del 1 al 6 del conjunto de datos rna.

R

## 1.
rna_200 <- rna[200, ]
## 2.
## Saving `n_rows` to improve readability and reduce duplication
n_rows <- nrow(rna)
rna_last <- rna[n_rows, ]
## 3.
rna_middle <- rna[n_rows / 2, ]
## 4.
rna_head <- rna[-(7:n_rows), ]

Factores


Los factores representan datos categóricos. Se almacenan como enteros asociados a etiquetas y pueden ser ordenados o no ordenados. Aunque los factores parecen (y a menudo se comportan) como vectores de caracteres, R los trata en realidad como vectores de enteros. Por lo tanto, es necesario tener mucho cuidado al tratarlos como cadenas.

Una vez creados, los factores solo pueden contener un conjunto de valores predefinidos, conocidos como niveles. Por defecto, R siempre ordena los niveles alfabéticamente. Por ejemplo, si tiene un factor con 2 niveles:

R

sex <- factor(c("male", "female", "female", "male", "female"))

R asignará 1 al nivel "female" y 2 al nivel "male" (ya que f precede a m, a pesar de que el primer elemento de este vector es "male"). Puede comprobar esto utilizando la función levels() y puede encontrar el número de niveles usando nlevels():

R

levels(sex)

SALIDA

[1] "female" "male"  

R

nlevels(sex)

SALIDA

[1] 2

A veces, el orden de los factores no es relevante; en otras ocasiones, es posible que desee especificar el orden porque tiene un significado (por ejemplo, “bajo”, “medio”, “alto”), mejora la visualización o es necesario para un tipo particular de análisis. En este caso, una forma de reordenar nuestros niveles en el vector sex sería:

R

sex ## current order

SALIDA

[1] male   female female male   female
Levels: female male

R

sex <- factor(sex, levels = c("male", "female"))
sex ## after re-ordering

SALIDA

[1] male   female female male   female
Levels: male female

En la memoria de R, estos factores se representan mediante enteros (1, 2, 3), pero son más informativos que estos porque se describen a sí mismos: las etiquetas "female" y "male" son más descriptivas que 1 y 2. ¿Cuál de los enteros corresponde a “male” (macho)? No se puede saber solo a partir de los valores enteros. Los factores, en cambio, incluyen esa información. Esto resulta especialmente útil cuando hay muchos niveles (como el biotipo génico en nuestro conjunto de datos de ejemplo).

Cuando sus datos se almacenan como un factor, puede utilizar la función plot() para obtener una vista rápida del número de observaciones representadas por cada nivel del factor. Veamos el número de machos y hembras en nuestros datos.

R

plot(sex)
Barplot showing the number of 'male' and 'female' occurrences.
Bar plot of the number of females and males.

Conversión a carácter

Si necesita convertir un factor a un vector de caracteres, utilice as.character(x).

R

as.character(sex)

SALIDA

[1] "male"   "female" "female" "male"   "female"

Renombrar factores

Si queremos renombrar este factor, basta con cambiar sus niveles:

R

levels(sex)

SALIDA

[1] "male"   "female"

R

levels(sex) <- c("M", "F")
sex

SALIDA

[1] M F F M F
Levels: M F

R

plot(sex)
Barplot showing the number of 'M' and 'F' occurrences.
Desafío

Desafío:

  • Cambiar el nombre de “F” y “M” a “Female” y “Male”, respectivamente.

R

levels(sex)

SALIDA

[1] "M" "F"

R

levels(sex) <- c("Male", "Female")
Desafío

Desafío:

Hemos visto cómo se crean las tablas de datos al usar read.csv(), pero también pueden crearse manualmente con la función data.frame(). Hay algunos errores en esta data.frame creada a mano. ¿Puedes detectarlos y corregirlos? ¡No dudes en experimentar!

R

animal_data <- data.frame(
       animal = c(dog, cat, sea cucumber, sea urchin),
       feel = c("furry", "squishy", "spiny"),
       weight = c(45, 8 1.1, 0.8))
  • faltan comillas alrededor de los nombres de los animales
  • falta un registro en la columna “feel” (probablemente de uno de los animales peludos)
  • falta una coma en la columna de peso
Desafío

Desafío:

¿Puedes predecir la clase de cada una de las columnas en el siguiente ejemplo?

Verifica tus conjeturas usando str(country_climate):

  • ¿Son lo que esperabas? ¿Por qué? ¿Por qué no?

  • Inténtalo de nuevo añadiendo stringsAsFactors = TRUE después de la última variable al crear la tabla de datos. ¿Qué está pasando ahora? También se puede establecer stringsAsFactors al leer hojas de cálculo basadas en texto en R utilizando read.csv().

R

country_climate <- data.frame(
       country = c("Canada", "Panama", "South Africa", "Australia"),
       climate = c("cold", "hot", "temperate", "hot/temperate"),
       temperature = c(10, 30, 18, "15"),
       northern_hemisphere = c(TRUE, TRUE, FALSE, "FALSE"),
       has_kangaroo = c(FALSE, FALSE, FALSE, 1)
       )

R

country_climate <- data.frame(
       country = c("Canada", "Panama", "South Africa", "Australia"),
       climate = c("cold", "hot", "temperate", "hot/temperate"),
       temperature = c(10, 30, 18, "15"),
       northern_hemisphere = c(TRUE, TRUE, FALSE, "FALSE"),
       has_kangaroo = c(FALSE, FALSE, FALSE, 1)
       )
str(country_climate)

SALIDA

'data.frame':	4 obs. of  5 variables:
 $ country            : chr  "Canada" "Panama" "South Africa" "Australia"
 $ climate            : chr  "cold" "hot" "temperate" "hot/temperate"
 $ temperature        : chr  "10" "30" "18" "15"
 $ northern_hemisphere: chr  "TRUE" "TRUE" "FALSE" "FALSE"
 $ has_kangaroo       : num  0 0 0 1

La conversión automática del tipo de datos a veces es una ventaja y a veces una molestia. Tenga en cuenta que existe, aprenda las reglas y verifique dos veces que los datos que importa en R sean del tipo correcto dentro de su tabla de datos. Si no es así, utilícelo para detectar errores que pudieron haberse introducido durante la entrada de datos (por ejemplo, una letra en una columna que solo debería contener números).

Más información en este tutorial de RStudio

Matrices


Antes de continuar, ahora que hemos aprendido sobre las tablas de datos, repasemos la instalación de paquetes y conozcamos un nuevo tipo de dato: el matrix. Al igual que el data.frame, una matriz tiene dos dimensiones, filas y columnas. Pero la diferencia principal es que todas las celdas de una matrix deben ser del mismo tipo: numeric, character, logical, … En ese sentido, las matrices se asemejan más a un vector que a un data.frame.

El constructor predeterminado para una matriz es matrix. Recibe un vector de valores para llenar la matriz y el número de filas y/o columnas1. Los valores se colocan por columnas, como se ilustra a continuación.

R

m <- matrix(1:9, ncol = 3, nrow = 3)
m

SALIDA

     [,1] [,2] [,3]
[1,]    1    4    7
[2,]    2    5    8
[3,]    3    6    9
Desafío

Desafío:

Utilizando la función installed.packages(), crea una matriz character que contenga la información sobre todos los paquetes actualmente instalados en tu computadora. Explórala.

R

## create the matrix
ip <- installed.packages()
head(ip)
## try also View(ip)
## number of package
nrow(ip)
## names of all installed packages
rownames(ip)
## type of information we have about each package
colnames(ip)

A menudo resulta útil crear matrices de datos aleatorias de gran tamaño como datos de prueba. El siguiente ejercicio le pide que cree dicha matriz con datos aleatorios extraídos de una distribución normal de media 0 y desviación estándar 1, lo cual puede hacerse con la función rnorm().

Desafío

Desafío:

Construya una matriz de dimensiones 1000 por 3 con datos normalmente distribuidos (media 0, desviación estándar 1)

R

set.seed(123)
m <- matrix(rnorm(3000), ncol = 3)
dim(m)

SALIDA

[1] 1000    3

R

head(m)

SALIDA

            [,1]        [,2]       [,3]
[1,] -0.56047565 -0.99579872 -0.5116037
[2,] -0.23017749 -1.03995504  0.2369379
[3,]  1.55870831 -0.01798024 -0.5415892
[4,]  0.07050839 -0.13217513  1.2192276
[5,]  0.12928774 -2.54934277  0.1741359
[6,]  1.71506499  1.04057346 -0.6152683

Formato de fechas


Uno de los problemas más comunes que presentan los usuarios nuevos (¡y experimentados!) de R es convertir la información de fecha y hora en una variable adecuada y utilizable durante los análisis.

Nota sobre las fechas en programas de hojas de cálculo

Las fechas en las hojas de cálculo suelen almacenarse en una sola columna. Aunque esto parece la forma más natural de registrar las fechas, en realidad no es la mejor práctica. Una aplicación de hojas de cálculo mostrará las fechas de una manera aparentemente correcta (para un observador humano), pero la forma en que realmente gestiona y almacena las fechas puede resultar problemática. A menudo es mucho más seguro almacenar las fechas con el AÑO, el MES y el DÍA en columnas separadas, o como el AÑO y el DÍA DEL AÑO en columnas separadas.

Los programas de hojas de cálculo como LibreOffice, Microsoft Excel, OpenOffice, Gnumeric, … tienen formas diferentes (y a menudo incompatibles) de codificar las fechas (incluso dentro del mismo programa entre versiones y sistemas operativos). Además, Excel puede convertir elementos que no son fechas en fechas ( Zeeberg et al. (2004) ), por ejemplo, nombres o identificadores como MAR1, DEC1, OCT4. Por lo tanto, si se evita el formato de fecha en general, es más sencillo identificar estos problemas.

La sección Dates as data de la lección de Data Carpentry ofrece información adicional sobre los peligros de las fechas en las hojas de cálculo.

Vamos a utilizar la función ymd() del paquete lubridate (que pertenece a tidyverse; obtenga más información en el sitio web de tidyverse). lubridate se instala como parte de la instalación de tidyverse. Cuando carga el tidyverse (library(tidyverse)), se cargan los paquetes principales (los paquetes utilizados en la mayoría de los análisis de datos). lubridate, sin embargo, no pertenece al tidyverse principal, por lo que debe cargarlo explícitamente con library(lubridate).

Comience cargando el paquete necesario:

R

library("lubridate")

ymd() acepta un vector que representa el año, el mes y el día, y lo convierte en un vector Date. Date es una clase de datos que R reconoce como una fecha y que puede manipularse como tal. El argumento que requiere la función es flexible, pero, como buena práctica, es un vector de caracteres formateado como “YYYY-MM-DD”.

Creemos un objeto de fecha y examinemos su estructura:

R

my_date <- ymd("2015-01-01")
str(my_date)

SALIDA

 Date[1:1], format: "2015-01-01"

Ahora concatenemos el año, el mes y el día, proporcionados por separado: obtenemos el mismo resultado:

R

# sep indicates the character to use to separate each component
my_date <- ymd(paste("2015", "1", "1", sep = "-"))
str(my_date)

SALIDA

 Date[1:1], format: "2015-01-01"

Ahora familiaricémonos con un flujo de trabajo típico para manipular fechas. El pequeño conjunto de datos que aparece a continuación almacena las fechas en columnas separadas: year, month y day.

R

x <- data.frame(year = c(1996, 1992, 1987, 1986, 2000, 1990, 2002, 1994, 1997, 1985),
                month = c(2,  3,  3, 10,  1,  8,  3,  4,  5,  5),
                day = c(24,  8,  1,  5,  8, 17, 13, 10, 11, 24),
                value = c(4,  5,  1,  9,  3,  8, 10,  2,  6,  7))
x

SALIDA

   year month day value
1  1996     2  24     4
2  1992     3   8     5
3  1987     3   1     1
4  1986    10   5     9
5  2000     1   8     3
6  1990     8  17     8
7  2002     3  13    10
8  1994     4  10     2
9  1997     5  11     6
10 1985     5  24     7

Ahora aplicamos esta función al conjunto de datos x. Primero creamos un vector de caracteres a partir de las columnas year, month y day de x utilizando paste():

R

paste(x$year, x$month, x$day, sep = "-")

SALIDA

 [1] "1996-2-24" "1992-3-8"  "1987-3-1"  "1986-10-5" "2000-1-8"  "1990-8-17"
 [7] "2002-3-13" "1994-4-10" "1997-5-11" "1985-5-24"

Este vector de caracteres puede utilizarse como argumento para ymd():

R

ymd(paste(x$year, x$month, x$day, sep = "-"))

SALIDA

 [1] "1996-02-24" "1992-03-08" "1987-03-01" "1986-10-05" "2000-01-08"
 [6] "1990-08-17" "2002-03-13" "1994-04-10" "1997-05-11" "1985-05-24"

El vector Date resultante puede añadirse a x como una nueva columna llamada date:

R

x$date <- ymd(paste(x$year, x$month, x$day, sep = "-"))
str(x) # notice the new column, with 'date' as the class

SALIDA

'data.frame':	10 obs. of  5 variables:
 $ year : num  1996 1992 1987 1986 2000 ...
 $ month: num  2 3 3 10 1 8 3 4 5 5
 $ day  : num  24 8 1 5 8 17 13 10 11 24
 $ value: num  4 5 1 9 3 8 10 2 6 7
 $ date : Date, format: "1996-02-24" "1992-03-08" ...

Asegurémonos de que todo haya funcionado correctamente. Una forma de inspeccionar la nueva columna es utilizar summary():

R

summary(x$date)

SALIDA

        Min.      1st Qu.       Median         Mean      3rd Qu.         Max.
"1985-05-24" "1988-01-11" "1993-03-24" "1993-03-18" "1997-01-20" "2002-03-13" 

Tenga en cuenta que ymd() requiere que el año, el mes y el día estén en ese orden. Si, por ejemplo, dispone del día, el mes y el año, necesitaría dmy().

R

dmy(paste(x$day, x$month, x$year, sep = "-"))

SALIDA

 [1] "1996-02-24" "1992-03-08" "1987-03-01" "1986-10-05" "2000-01-08"
 [6] "1990-08-17" "2002-03-13" "1994-04-10" "1997-05-11" "1985-05-24"

lubdridate cuenta con numerosas funciones para gestionar todas las variaciones de fecha.

Resumen de los objetos de R


Hasta ahora, hemos visto varios tipos de objetos de R que varían en el número de dimensiones y en si podían almacenar un único tipo de dato o múltiples tipos de datos:

  • vector: una dimensión (tienen una longitud), un único tipo de dato.
  • matrix: dos dimensiones, un único tipo de dato.
  • data.frame: dos dimensiones, un tipo por columna.

Listas


Las listas son un tipo de datos que aún no hemos visto, pero que resulta útil conocer y que se desprende del resumen que acabamos de ver:

  • list: una dimensión, cada elemento puede ser de un tipo de dato diferente.

A continuación, creemos una lista que contenga un vector de números, caracteres, una matriz, una tabla de datos y otra lista:

R

l <- list(1:10, ## numeric
          letters, ## character
          installed.packages(), ## a matrix
          cars, ## a data.frame
          list(1, 2, 3)) ## a list
length(l)

SALIDA

[1] 5

R

str(l)

SALIDA

List of 5
 $ : int [1:10] 1 2 3 4 5 6 7 8 9 10
 $ : chr [1:26] "a" "b" "c" "d" ...
 $ : chr [1:261, 1:17] "litedown" "metabolomicsWorkbenchR" "mgcv" "MultiAssayExperiment" ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:261] "litedown" "metabolomicsWorkbenchR" "mgcv" "MultiAssayExperiment" ...
  .. ..$ : chr [1:17] "Package" "LibPath" "Version" "Priority" ...
 $ :'data.frame':	50 obs. of  2 variables:
  ..$ speed: num [1:50] 4 4 7 7 8 9 10 10 10 11 ...
  ..$ dist : num [1:50] 2 10 4 22 16 10 18 26 34 17 ...
 $ :List of 3
  ..$ : num 1
  ..$ : num 2
  ..$ : num 3

La selección de subconjuntos de una lista se realiza utilizando [] para obtener una nueva sublista o [[]] para extraer un único elemento de esa lista (mediante índices o nombres, si sus elementos tienen nombre).

R

l[[1]] ## first element

SALIDA

 [1]  1  2  3  4  5  6  7  8  9 10

R

l[1:2] ## a list of length 2

SALIDA

[[1]]
 [1]  1  2  3  4  5  6  7  8  9 10

[[2]]
 [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s"
[20] "t" "u" "v" "w" "x" "y" "z"

R

l[1]   ## a list of length 1

SALIDA

[[1]]
 [1]  1  2  3  4  5  6  7  8  9 10

Exportación y guardado de datos tabulares


Hemos visto cómo leer una hoja de cálculo basada en texto en R utilizando la familia de funciones read.table. Para exportar un data.frame a una hoja de cálculo basada en texto, podemos utilizar el conjunto de funciones write.table (write.csv, write.delim, …). Todas ellas reciben la variable a exportar y el archivo al que se desea exportar. Por ejemplo, para exportar los datos rna al archivo my_rna.csv en el directorio data_output, ejecutaríamos:

R

write.csv(rna, file = "data_output/my_rna.csv")

Ahora se puede compartir este nuevo archivo CSV con otros colaboradores que no estén familiarizados con R. Tenga en cuenta que, aunque haya comas en algunos de los campos de data.frame (véase, por ejemplo, la columna “product”), R encerrará cada campo entre comillas por defecto y, por lo tanto, podremos leerlo correctamente en R, a pesar de que también se utilicen comas como separadores de columnas.

Puntos Clave
  • Datos en tabla en R
Zeeberg, Barry R., Joseph Riss, David W. Kane, et al. 2004. “Mistaken Identifiers: Gene Name Errors Can Be Introduced Inadvertently When Using Excel in Bioinformatics.” BMC Bioinformatics 5 (1): 80. https://doi.org/10.1186/1471-2105-5-80.

  1. Basta con indicar el número de filas o el de columnas, ya que la otra dimensión se puede deducir de la longitud del vector de valores. Pruebe qué ocurre si la cantidad de valores y el número de filas/columnas no son compatibles.↩︎

Content from Manipulación y análisis de datos con dplyr


Última actualización: 2026-11-11 | Mejora esta página

Tiempo estimado: 150 minutos

Hoja de ruta

Preguntas

  • Análisis de datos en R utilizando el metapaquete tidyverse

Objetivos

  • Describa el propósito de los paquetes dplyr y tidyr.
  • Describa varias de sus funciones que son extremadamente útiles para manipular datos.
  • Describa el concepto de los formatos de tabla ancho y largo, y vea cómo reestructurar una tabla de datos de un formato a otro.
  • Demostrar cómo unir tablas.

Este episodio se basa en la lección Data Analysis and Visualisation in R for Ecologists de Data Carpentries.

Manipulación de datos mediante dplyr y tidyr


La selección de subconjuntos mediante corchetes es práctica, pero puede resultar engorrosa y difícil de leer, especialmente en operaciones complicadas.

Algunos paquetes pueden facilitar considerablemente nuestra tarea al manipular datos. Los paquetes en R son, básicamente, conjuntos de funciones adicionales que nos permiten hacer más cosas. Las funciones que hemos estado utilizando hasta ahora, como str() o data.frame(), vienen integradas en R; cargar paquetes puede darnos acceso a otras funciones específicas. Antes de usar un paquete por primera vez, es necesario instalarlo en la máquina y, posteriormente, importarlo en cada sesión posterior de R cuando lo necesitemos.

  • El paquete dplyr ofrece herramientas potentes para tareas de manipulación de datos. Está diseñado para funcionar directamente con tablas de datos, con muchas tareas de manipulación optimizadas.

  • Como veremos más adelante, a veces queremos que una tabla de datos se reorganice para poder realizar ciertos análisis específicos o para su visualización. El paquete tidyr aborda este problema común de reorganización de datos y proporciona herramientas para manipular los datos de manera ordenada.

Para obtener más información sobre dplyr y tidyr después del taller, puede consultar este cheatsheet práctico de transformación de datos con dplyr y este de tidyr.

  • El paquete tidyverse es un “paquete paraguas” que instala varios paquetes útiles para el análisis de datos que funcionan bien juntos, como tidyr, dplyr, ggplot2, tibble, etc. Estos paquetes nos ayudan a trabajar e interactuar con los datos. Nos permiten realizar muchas acciones con sus datos, como crear subconjuntos, transformarlos, visualizarlos, etc.

Si realizó la configuración, ya debería haber instalado el paquete tidyverse. Compruebe si lo tiene intentando cargarlo desde la biblioteca:

R

## load the tidyverse packages, incl. dplyr
library("tidyverse")

Si obtuviste un mensaje de error there is no package called ‘tidyverse’, es que aún no has instalado el paquete para esta versión de R. Para instalar el paquete tidyverse, escribe:

R

BiocManager::install("tidyverse")

Si tuviste que instalar el paquete tidyverse, ¡no olvides cargarlo en esta sesión de R utilizando el comando library() anterior!

Cargando datos con tidyverse


En lugar de read.csv(), cargaremos nuestros datos utilizando la función read_csv() (fíjese en el _ en lugar del .), del paquete tidyverse readr.

R

rna <- read_csv("data/rnaseq.csv")

## view the data
rna

SALIDA

# A tibble: 32,428 × 19
   gene    sample  expression organism   age sex   infection strain  time tissue
   <chr>   <chr>        <dbl> <chr>    <dbl> <chr> <chr>     <chr>  <dbl> <chr>
 1 Asl     GSM254…       1170 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 2 Apod    GSM254…      36194 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 3 Cyp2d22 GSM254…       4060 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 4 Klk6    GSM254…        287 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 5 Fcrls   GSM254…         85 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 6 Slc2a4  GSM254…        782 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 7 Exd2    GSM254…       1619 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 8 Gjc2    GSM254…        288 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 9 Plp1    GSM254…      43217 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
10 Gnb4    GSM254…       1071 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
# ℹ 32,418 more rows
# ℹ 9 more variables: mouse <dbl>, ENTREZID <dbl>, product <chr>,
#   ensembl_gene_id <chr>, external_synonym <chr>, chromosome_name <chr>,
#   gene_biotype <chr>, phenotype_description <chr>,
#   hsapiens_homolog_associated_gene_name <chr>

Nótese que la clase de los datos se denomina ahora «tibble».

Los tibbles ajustan algunos de los comportamientos de los objetos de tabla de datos que presentamos anteriormente. La estructura de datos es muy similar a la de una tabla de datos. Para nuestros propósitos, las únicas diferencias son que:

  1. Muestra el tipo de datos de cada columna debajo de su nombre. Tenga en cuenta que <dbl> es un tipo de datos definido para almacenar valores numéricos con puntos decimales.

  2. Solo imprime las primeras filas de datos y solo tantas columnas como quepan en una pantalla.

Ahora vamos a aprender algunas de las funciones dplyr más comunes:

  • select(): seleccionar columnas
  • filter(): seleccionar filas según condiciones
  • mutate(): crear nuevas columnas utilizando información de otras columnas
  • group_by() y summarise(): crear estadísticas resumidas sobre datos agrupados
  • arrange(): ordenar los resultados
  • count(): contar valores discretos

Selección de columnas y filtrado de filas


Para seleccionar columnas de una tabla de datos, utilice select(). El primer argumento de esta función es la tabla de datos (rna), y los argumentos siguientes son las columnas a conservar.

R

select(rna, gene, sample, tissue, expression)

SALIDA

# A tibble: 32,428 × 4
   gene    sample     tissue     expression
   <chr>   <chr>      <chr>           <dbl>
 1 Asl     GSM2545336 Cerebellum       1170
 2 Apod    GSM2545336 Cerebellum      36194
 3 Cyp2d22 GSM2545336 Cerebellum       4060
 4 Klk6    GSM2545336 Cerebellum        287
 5 Fcrls   GSM2545336 Cerebellum         85
 6 Slc2a4  GSM2545336 Cerebellum        782
 7 Exd2    GSM2545336 Cerebellum       1619
 8 Gjc2    GSM2545336 Cerebellum        288
 9 Plp1    GSM2545336 Cerebellum      43217
10 Gnb4    GSM2545336 Cerebellum       1071
# ℹ 32,418 more rows

Para seleccionar todas las columnas excepto algunas determinadas, coloque un “-” delante de la variable para excluirla.

R

select(rna, -tissue, -organism)

SALIDA

# A tibble: 32,428 × 17
   gene    sample   expression   age sex   infection strain  time mouse ENTREZID
   <chr>   <chr>         <dbl> <dbl> <chr> <chr>     <chr>  <dbl> <dbl>    <dbl>
 1 Asl     GSM2545…       1170     8 Fema… Influenz… C57BL…     8    14   109900
 2 Apod    GSM2545…      36194     8 Fema… Influenz… C57BL…     8    14    11815
 3 Cyp2d22 GSM2545…       4060     8 Fema… Influenz… C57BL…     8    14    56448
 4 Klk6    GSM2545…        287     8 Fema… Influenz… C57BL…     8    14    19144
 5 Fcrls   GSM2545…         85     8 Fema… Influenz… C57BL…     8    14    80891
 6 Slc2a4  GSM2545…        782     8 Fema… Influenz… C57BL…     8    14    20528
 7 Exd2    GSM2545…       1619     8 Fema… Influenz… C57BL…     8    14    97827
 8 Gjc2    GSM2545…        288     8 Fema… Influenz… C57BL…     8    14   118454
 9 Plp1    GSM2545…      43217     8 Fema… Influenz… C57BL…     8    14    18823
10 Gnb4    GSM2545…       1071     8 Fema… Influenz… C57BL…     8    14    14696
# ℹ 32,418 more rows
# ℹ 7 more variables: product <chr>, ensembl_gene_id <chr>,
#   external_synonym <chr>, chromosome_name <chr>, gene_biotype <chr>,
#   phenotype_description <chr>, hsapiens_homolog_associated_gene_name <chr>

Esto seleccionará todas las variables de rna excepto tissue y organism.

Para seleccionar filas basándose en un criterio específico, utilice filter():

R

filter(rna, sex == "Male")

SALIDA

# A tibble: 14,740 × 19
   gene    sample  expression organism   age sex   infection strain  time tissue
   <chr>   <chr>        <dbl> <chr>    <dbl> <chr> <chr>     <chr>  <dbl> <chr>
 1 Asl     GSM254…        626 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 2 Apod    GSM254…      13021 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 3 Cyp2d22 GSM254…       2171 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 4 Klk6    GSM254…        448 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 5 Fcrls   GSM254…        180 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 6 Slc2a4  GSM254…        313 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 7 Exd2    GSM254…       2366 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 8 Gjc2    GSM254…        310 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 9 Plp1    GSM254…      53126 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
10 Gnb4    GSM254…       1355 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
# ℹ 14,730 more rows
# ℹ 9 more variables: mouse <dbl>, ENTREZID <dbl>, product <chr>,
#   ensembl_gene_id <chr>, external_synonym <chr>, chromosome_name <chr>,
#   gene_biotype <chr>, phenotype_description <chr>,
#   hsapiens_homolog_associated_gene_name <chr>

R

filter(rna, sex == "Male" & infection == "NonInfected")

SALIDA

# A tibble: 4,422 × 19
   gene    sample  expression organism   age sex   infection strain  time tissue
   <chr>   <chr>        <dbl> <chr>    <dbl> <chr> <chr>     <chr>  <dbl> <chr>
 1 Asl     GSM254…        535 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 2 Apod    GSM254…      13668 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 3 Cyp2d22 GSM254…       2008 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 4 Klk6    GSM254…       1101 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 5 Fcrls   GSM254…        375 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 6 Slc2a4  GSM254…        249 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 7 Exd2    GSM254…       3126 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 8 Gjc2    GSM254…        791 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 9 Plp1    GSM254…      98658 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
10 Gnb4    GSM254…       2437 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
# ℹ 4,412 more rows
# ℹ 9 more variables: mouse <dbl>, ENTREZID <dbl>, product <chr>,
#   ensembl_gene_id <chr>, external_synonym <chr>, chromosome_name <chr>,
#   gene_biotype <chr>, phenotype_description <chr>,
#   hsapiens_homolog_associated_gene_name <chr>

Ahora imaginemos que nos interesan los homólogos humanos de los genes de ratón analizados en este conjunto de datos. Esta información se encuentra en la última columna del tibble rna, llamada hsapiens_homolog_associated_gene_name. Para visualizarla fácilmente, crearemos una nueva tabla que contenga únicamente las 2 columnas gene y hsapiens_homolog_associated_gene_name.

R

genes <- select(rna, gene, hsapiens_homolog_associated_gene_name)
genes

SALIDA

# A tibble: 32,428 × 2
   gene    hsapiens_homolog_associated_gene_name
   <chr>   <chr>
 1 Asl     ASL
 2 Apod    APOD
 3 Cyp2d22 CYP2D6
 4 Klk6    KLK6
 5 Fcrls   FCRL2
 6 Slc2a4  SLC2A4
 7 Exd2    EXD2
 8 Gjc2    GJC2
 9 Plp1    PLP1
10 Gnb4    GNB4
# ℹ 32,418 more rows

Algunos genes de ratón no tienen homólogos humanos. Estos pueden obtenerse mediante filter() y la función is.na(), que determina si algo es un NA.

R

filter(genes, is.na(hsapiens_homolog_associated_gene_name))

SALIDA

# A tibble: 4,290 × 2
   gene     hsapiens_homolog_associated_gene_name
   <chr>    <chr>
 1 Prodh    <NA>
 2 Tssk5    <NA>
 3 Vmn2r1   <NA>
 4 Gm10654  <NA>
 5 Hexa     <NA>
 6 Sult1a1  <NA>
 7 Gm6277   <NA>
 8 Tmem198b <NA>
 9 Adam1a   <NA>
10 Ebp      <NA>
# ℹ 4,280 more rows

Si queremos conservar solo los genes de ratón que tengan un homólogo humano, podemos insertar un símbolo “!” que niega el resultado, de modo que solicitamos cada fila en la que hsapiens_homolog_associated_gene_name no sea un NA.

R

filter(genes, !is.na(hsapiens_homolog_associated_gene_name))

SALIDA

# A tibble: 28,138 × 2
   gene    hsapiens_homolog_associated_gene_name
   <chr>   <chr>
 1 Asl     ASL
 2 Apod    APOD
 3 Cyp2d22 CYP2D6
 4 Klk6    KLK6
 5 Fcrls   FCRL2
 6 Slc2a4  SLC2A4
 7 Exd2    EXD2
 8 Gjc2    GJC2
 9 Plp1    PLP1
10 Gnb4    GNB4
# ℹ 28,128 more rows

Pipes


¿Qué pasa si quieres seleccionar y filtrar al mismo tiempo? Hay tres formas de hacerlo: usar pasos intermedios, funciones anidadas o pipes.

Con pasos intermedios, se crea una tabla de datos temporal y se utiliza como entrada para la siguiente función, de la siguiente manera:

R

rna2 <- filter(rna, sex == "Male")
rna3 <- select(rna2, gene, sample, tissue, expression)
rna3

SALIDA

# A tibble: 14,740 × 4
   gene    sample     tissue     expression
   <chr>   <chr>      <chr>           <dbl>
 1 Asl     GSM2545340 Cerebellum        626
 2 Apod    GSM2545340 Cerebellum      13021
 3 Cyp2d22 GSM2545340 Cerebellum       2171
 4 Klk6    GSM2545340 Cerebellum        448
 5 Fcrls   GSM2545340 Cerebellum        180
 6 Slc2a4  GSM2545340 Cerebellum        313
 7 Exd2    GSM2545340 Cerebellum       2366
 8 Gjc2    GSM2545340 Cerebellum        310
 9 Plp1    GSM2545340 Cerebellum      53126
10 Gnb4    GSM2545340 Cerebellum       1355
# ℹ 14,730 more rows

Esto es legible, pero puede saturar el espacio de trabajo con muchos objetos intermedios que hay que nombrar individualmente. Con múltiples pasos, puede resultar difícil hacer un seguimiento de ellos.

También puedes anidar funciones (es decir, una función dentro de otra), de esta manera:

R

rna3 <- select(filter(rna, sex == "Male"), gene, sample, tissue, expression)
rna3

SALIDA

# A tibble: 14,740 × 4
   gene    sample     tissue     expression
   <chr>   <chr>      <chr>           <dbl>
 1 Asl     GSM2545340 Cerebellum        626
 2 Apod    GSM2545340 Cerebellum      13021
 3 Cyp2d22 GSM2545340 Cerebellum       2171
 4 Klk6    GSM2545340 Cerebellum        448
 5 Fcrls   GSM2545340 Cerebellum        180
 6 Slc2a4  GSM2545340 Cerebellum        313
 7 Exd2    GSM2545340 Cerebellum       2366
 8 Gjc2    GSM2545340 Cerebellum        310
 9 Plp1    GSM2545340 Cerebellum      53126
10 Gnb4    GSM2545340 Cerebellum       1355
# ℹ 14,730 more rows

Esto es práctico, pero puede resultar difícil de leer si hay demasiadas funciones anidadas, ya que R evalúa la expresión de adentro hacia afuera (en este caso, filtrando y luego seleccionando).

La última opción, los pipes, es una incorporación reciente a R. Los pipes permiten tomar la salida de una función y enviarla directamente a la siguiente, lo cual resulta útil cuando es necesario realizar muchas operaciones sobre el mismo conjunto de datos.

Los pipes en R se representan como %>% (disponible a través del paquete magrittr) o |> (a través de base R). Si utilizas RStudio, puedes escribir el pipe con Ctrl + Shift + M si tienes un PC o Cmd + Shift + M si tienes un Mac.

En el código anterior, utilizamos el pipe para enviar primero el conjunto de datos rna a través de filter() para conservar las filas donde sex es Male, y luego a través de select() para conservar únicamente las columnas gene, sample, tissue y expression.

El pipe |> toma el objeto a su izquierda y lo pasa directamente como primer argumento a la función a su derecha; ya no es necesario incluir explícitamente la tabla de datos como argumento de las funciones filter() y select().

R

rna |>
  filter(sex == "Male") |>
  select(gene, sample, tissue, expression)

SALIDA

# A tibble: 14,740 × 4
   gene    sample     tissue     expression
   <chr>   <chr>      <chr>           <dbl>
 1 Asl     GSM2545340 Cerebellum        626
 2 Apod    GSM2545340 Cerebellum      13021
 3 Cyp2d22 GSM2545340 Cerebellum       2171
 4 Klk6    GSM2545340 Cerebellum        448
 5 Fcrls   GSM2545340 Cerebellum        180
 6 Slc2a4  GSM2545340 Cerebellum        313
 7 Exd2    GSM2545340 Cerebellum       2366
 8 Gjc2    GSM2545340 Cerebellum        310
 9 Plp1    GSM2545340 Cerebellum      53126
10 Gnb4    GSM2545340 Cerebellum       1355
# ℹ 14,730 more rows

A algunas personas les resulta útil leer el pipe como la palabra “luego”. Por ejemplo, en el ejemplo anterior, tomamos la tabla de datos rna, luego filtramos las filas con sex == "Male" mediante filter, luego seleccionamos las columnas gene, sample, tissue y expression mediante select.

Las funciones dplyr por sí mismas son algo sencillas, pero al combinarlas en flujos de trabajo lineales mediante el pipe, podemos realizar manipulaciones más complejas de tablas de datos.

Si queremos crear un nuevo objeto con esta versión reducida de los datos, podemos asignarle un nombre nuevo:

R

rna3 <- rna |>
  filter(sex == "Male") |>
  select(gene, sample, tissue, expression)

rna3

SALIDA

# A tibble: 14,740 × 4
   gene    sample     tissue     expression
   <chr>   <chr>      <chr>           <dbl>
 1 Asl     GSM2545340 Cerebellum        626
 2 Apod    GSM2545340 Cerebellum      13021
 3 Cyp2d22 GSM2545340 Cerebellum       2171
 4 Klk6    GSM2545340 Cerebellum        448
 5 Fcrls   GSM2545340 Cerebellum        180
 6 Slc2a4  GSM2545340 Cerebellum        313
 7 Exd2    GSM2545340 Cerebellum       2366
 8 Gjc2    GSM2545340 Cerebellum        310
 9 Plp1    GSM2545340 Cerebellum      53126
10 Gnb4    GSM2545340 Cerebellum       1355
# ℹ 14,730 more rows
Desafío

Desafío:

Utilizando pipes, seleccione un subconjunto de los datos rna para conservar las observaciones de ratones hembra en el momento 0, donde el gen tenga una expresión superior a 50000, y conserve únicamente las columnas gene, sample, time, expression y age.

R

rna |>
  filter(expression > 50000,
         sex == "Female",
         time == 0 ) |>
  select(gene, sample, time, expression, age)

SALIDA

# A tibble: 9 × 5
  gene   sample      time expression   age
  <chr>  <chr>      <dbl>      <dbl> <dbl>
1 Plp1   GSM2545337     0     101241     8
2 Atp1b1 GSM2545337     0      53260     8
3 Plp1   GSM2545338     0      96534     8
4 Atp1b1 GSM2545338     0      50614     8
5 Plp1   GSM2545348     0     102790     8
6 Atp1b1 GSM2545348     0      59544     8
7 Plp1   GSM2545353     0      71237     8
8 Glul   GSM2545353     0      52451     8
9 Atp1b1 GSM2545353     0      61451     8

Crear y modificar columnas (mutate)


A menudo querrá crear nuevas columnas basándose en los valores de columnas existentes, por ejemplo, para realizar conversiones de unidades o para hallar la proporción de valores en dos columnas. Para ello, utilizaremos mutate().

Para crear una nueva columna de tiempo en horas:

R

rna |>
  mutate(time_hours = time * 24) |>
  select(time, time_hours)

SALIDA

# A tibble: 32,428 × 2
    time time_hours
   <dbl>      <dbl>
 1     8        192
 2     8        192
 3     8        192
 4     8        192
 5     8        192
 6     8        192
 7     8        192
 8     8        192
 9     8        192
10     8        192
# ℹ 32,418 more rows

También puede crear una segunda columna nueva basada en la primera columna nueva dentro de la misma llamada a mutate():

R

rna |>
  mutate(time_hours = time * 24,
         time_mn = time_hours * 60) |>
  select(time, time_hours, time_mn)

SALIDA

# A tibble: 32,428 × 3
    time time_hours time_mn
   <dbl>      <dbl>   <dbl>
 1     8        192   11520
 2     8        192   11520
 3     8        192   11520
 4     8        192   11520
 5     8        192   11520
 6     8        192   11520
 7     8        192   11520
 8     8        192   11520
 9     8        192   11520
10     8        192   11520
# ℹ 32,418 more rows
Desafío

Desafío

Crea una nueva tabla de datos a partir de los datos rna que cumpla los siguientes criterios: contenga únicamente las columnas gene, chromosome_name, phenotype_description, sample y expression. Los valores de expresión deben estar transformados logarítmicamente. Esta tabla de datos debe contener únicamente genes ubicados en cromosomas sexuales, asociados a un phenotype_description y con una expresión logarítmica superior a 5.

Pista: ¡piensa en cómo deben ordenarse los comandos para generar esta tabla de datos!

R

rna |>
  mutate(expression = log(expression)) |>
  select(gene, chromosome_name, phenotype_description, sample, expression) |>
  filter(chromosome_name == "X" | chromosome_name == "Y") |>
  filter(!is.na(phenotype_description)) |>
  filter(expression > 5)

SALIDA

# A tibble: 649 × 5
   gene   chromosome_name phenotype_description                sample expression
   <chr>  <chr>           <chr>                                <chr>       <dbl>
 1 Plp1   X               abnormal CNS glial cell morphology   GSM25…      10.7
 2 Slc7a3 X               decreased body length                GSM25…       5.46
 3 Plxnb3 X               abnormal coat appearance             GSM25…       6.58
 4 Rbm3   X               abnormal liver morphology            GSM25…       9.32
 5 Cfp    X               abnormal cardiovascular system phys… GSM25…       6.18
 6 Ebp    X               abnormal embryonic erythrocyte morp… GSM25…       6.68
 7 Cd99l2 X               abnormal cellular extravasation      GSM25…       8.04
 8 Piga   X               abnormal brain development           GSM25…       6.06
 9 Pim2   X               decreased T cell proliferation       GSM25…       7.11
10 Itm2a  X               no abnormal phenotype detected       GSM25…       7.48
# ℹ 639 more rows

Análisis de datos de split-apply-combine


Muchas tareas de análisis de datos pueden abordarse utilizando el paradigma split-apply-combine: dividir los datos en grupos, aplicar algún análisis a cada grupo y, a continuación, combinar los resultados. dplyr facilita enormemente esto mediante el uso de la función group_by().

R

rna |>
  group_by(gene)

SALIDA

# A tibble: 32,428 × 19
# Groups:   gene [1,474]
   gene    sample  expression organism   age sex   infection strain  time tissue
   <chr>   <chr>        <dbl> <chr>    <dbl> <chr> <chr>     <chr>  <dbl> <chr>
 1 Asl     GSM254…       1170 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 2 Apod    GSM254…      36194 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 3 Cyp2d22 GSM254…       4060 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 4 Klk6    GSM254…        287 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 5 Fcrls   GSM254…         85 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 6 Slc2a4  GSM254…        782 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 7 Exd2    GSM254…       1619 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 8 Gjc2    GSM254…        288 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 9 Plp1    GSM254…      43217 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
10 Gnb4    GSM254…       1071 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
# ℹ 32,418 more rows
# ℹ 9 more variables: mouse <dbl>, ENTREZID <dbl>, product <chr>,
#   ensembl_gene_id <chr>, external_synonym <chr>, chromosome_name <chr>,
#   gene_biotype <chr>, phenotype_description <chr>,
#   hsapiens_homolog_associated_gene_name <chr>

La función group_by() no realiza ningún procesamiento de datos, sino que agrupa los datos en subconjuntos: en el ejemplo anterior, nuestro tibble inicial de 32428 observaciones se divide en 1474 grupos basándose en la variable gene.

Del mismo modo, podríamos decidir agrupar el tibble por las muestras:

R

rna |>
  group_by(sample)

SALIDA

# A tibble: 32,428 × 19
# Groups:   sample [22]
   gene    sample  expression organism   age sex   infection strain  time tissue
   <chr>   <chr>        <dbl> <chr>    <dbl> <chr> <chr>     <chr>  <dbl> <chr>
 1 Asl     GSM254…       1170 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 2 Apod    GSM254…      36194 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 3 Cyp2d22 GSM254…       4060 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 4 Klk6    GSM254…        287 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 5 Fcrls   GSM254…         85 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 6 Slc2a4  GSM254…        782 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 7 Exd2    GSM254…       1619 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 8 Gjc2    GSM254…        288 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 9 Plp1    GSM254…      43217 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
10 Gnb4    GSM254…       1071 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
# ℹ 32,418 more rows
# ℹ 9 more variables: mouse <dbl>, ENTREZID <dbl>, product <chr>,
#   ensembl_gene_id <chr>, external_synonym <chr>, chromosome_name <chr>,
#   gene_biotype <chr>, phenotype_description <chr>,
#   hsapiens_homolog_associated_gene_name <chr>

Aquí nuestro tibble inicial de 32428 observaciones se divide en 22 grupos según la variable sample.

Una vez agrupados los datos, las operaciones posteriores se aplicarán a cada grupo de forma independiente.

La función summarise()

group_by() se utiliza a menudo junto con summarise(), que reduce cada grupo a un resumen de una sola fila.

group_by() acepta como argumentos los nombres de las columnas que contienen las variables categóricas para las cuales se desea calcular las estadísticas resumidas. Por lo tanto, para calcular la media de expression por gen:

R

rna |>
  group_by(gene) |>
  summarise(mean_expression = mean(expression))

SALIDA

# A tibble: 1,474 × 2
   gene     mean_expression
   <chr>              <dbl>
 1 AI504432         1053.
 2 AW046200          131.
 3 AW551984          295.
 4 Aamp             4751.
 5 Abca12              4.55
 6 Abcc8            2498.
 7 Abhd14a           525.
 8 Abi2             4909.
 9 Abi3bp           1002.
10 Abl2             2124.
# ℹ 1,464 more rows

También podríamos querer calcular los niveles de expresión medios de todos los genes en cada muestra:

R

rna |>
  group_by(sample) |>
  summarise(mean_expression = mean(expression))

SALIDA

# A tibble: 22 × 2
   sample     mean_expression
   <chr>                <dbl>
 1 GSM2545336           2062.
 2 GSM2545337           1766.
 3 GSM2545338           1668.
 4 GSM2545339           1696.
 5 GSM2545340           1682.
 6 GSM2545341           1638.
 7 GSM2545342           1594.
 8 GSM2545343           2107.
 9 GSM2545344           1712.
10 GSM2545345           1700.
# ℹ 12 more rows

Pero también podemos agrupar por múltiples columnas:

R

rna |>
  group_by(gene, infection, time) |>
  summarise(mean_expression = mean(expression))

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene, infection, and time.
ℹ Output is grouped by gene and infection.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, infection, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 4,422 × 4
# Groups:   gene, infection [2,948]
   gene     infection    time mean_expression
   <chr>    <chr>       <dbl>           <dbl>
 1 AI504432 InfluenzaA      4           1104.
 2 AI504432 InfluenzaA      8           1014
 3 AI504432 NonInfected     0           1034.
 4 AW046200 InfluenzaA      4            152.
 5 AW046200 InfluenzaA      8             81
 6 AW046200 NonInfected     0            155.
 7 AW551984 InfluenzaA      4            302.
 8 AW551984 InfluenzaA      8            342.
 9 AW551984 NonInfected     0            238
10 Aamp     InfluenzaA      4           4870
# ℹ 4,412 more rows

Una vez agrupados los datos, también se pueden resumir múltiples variables al mismo tiempo (y no necesariamente en la misma variable). Por ejemplo, podríamos añadir una columna que indique la mediana de expression por gen y por condición:

R

rna |>
  group_by(gene, infection, time) |>
  summarise(mean_expression = mean(expression),
            median_expression = median(expression))

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene, infection, and time.
ℹ Output is grouped by gene and infection.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, infection, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 4,422 × 5
# Groups:   gene, infection [2,948]
   gene     infection    time mean_expression median_expression
   <chr>    <chr>       <dbl>           <dbl>             <dbl>
 1 AI504432 InfluenzaA      4           1104.             1094.
 2 AI504432 InfluenzaA      8           1014               985
 3 AI504432 NonInfected     0           1034.             1016
 4 AW046200 InfluenzaA      4            152.              144.
 5 AW046200 InfluenzaA      8             81                82
 6 AW046200 NonInfected     0            155.              163
 7 AW551984 InfluenzaA      4            302.              245
 8 AW551984 InfluenzaA      8            342.              287
 9 AW551984 NonInfected     0            238               265
10 Aamp     InfluenzaA      4           4870              4708
# ℹ 4,412 more rows
Desafío

Desafío

Calcular el nivel de expresión medio del gen “Dok3” según los puntos temporales.

R

rna |>
  filter(gene == "Dok3") |>
  group_by(time) |>
  summarise(mean = mean(expression))

SALIDA

# A tibble: 3 × 2
   time  mean
  <dbl> <dbl>
1     0  169
2     4  156.
3     8   61 

Conteo

Al trabajar con datos, a menudo queremos conocer el número de observaciones encontradas para cada factor o combinación de factores. Para esta tarea, dplyr proporciona count(). Por ejemplo, si quisiéramos contar el número de filas de datos para cada muestra infectada y no infectada, haríamos lo siguiente:

R

rna |>
    count(infection)

SALIDA

# A tibble: 2 × 2
  infection       n
  <chr>       <int>
1 InfluenzaA  22110
2 NonInfected 10318

La función count() es una abreviatura de algo que ya hemos visto: agrupar por una variable y resumirlo contando el número de observaciones en ese grupo. En otras palabras, rna |> count(infection) es equivalente a:

R

rna |>
    group_by(infection) |>
    summarise(n = n())

SALIDA

# A tibble: 2 × 2
  infection       n
  <chr>       <int>
1 InfluenzaA  22110
2 NonInfected 10318

El ejemplo anterior muestra el uso de count() para contar el número de filas/observaciones para un factor (es decir, infection). Si quisiéramos contar una combinación de factores, como infection y time, especificaríamos el primer y el segundo factor como argumentos de count():

R

rna |>
    count(infection, time)

SALIDA

# A tibble: 3 × 3
  infection    time     n
  <chr>       <dbl> <int>
1 InfluenzaA      4 11792
2 InfluenzaA      8 10318
3 NonInfected     0 10318

lo que equivale a esto:

R

rna |>
  group_by(infection, time) |>
  summarise(n = n())

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by infection and time.
ℹ Output is grouped by infection.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(infection, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 3 × 3
# Groups:   infection [2]
  infection    time     n
  <chr>       <dbl> <int>
1 InfluenzaA      4 11792
2 InfluenzaA      8 10318
3 NonInfected     0 10318

A veces resulta útil ordenar el resultado para facilitar las comparaciones. Podemos usar arrange() para ordenar la tabla. Por ejemplo, podríamos querer organizar la tabla anterior por tiempo:

R

rna |>
  count(infection, time) |>
  arrange(time)

SALIDA

# A tibble: 3 × 3
  infection    time     n
  <chr>       <dbl> <int>
1 NonInfected     0 10318
2 InfluenzaA      4 11792
3 InfluenzaA      8 10318

o por conteos:

R

rna |>
  count(infection, time) |>
  arrange(n)

SALIDA

# A tibble: 3 × 3
  infection    time     n
  <chr>       <dbl> <int>
1 InfluenzaA      8 10318
2 NonInfected     0 10318
3 InfluenzaA      4 11792

Para ordenar en orden descendente, necesitamos añadir la función desc():

R

rna |>
  count(infection, time) |>
  arrange(desc(n))

SALIDA

# A tibble: 3 × 3
  infection    time     n
  <chr>       <dbl> <int>
1 InfluenzaA      4 11792
2 InfluenzaA      8 10318
3 NonInfected     0 10318
Desafío

Desafío

  1. ¿Cuántos genes se analizaron en cada muestra?
  2. Utilice group_by() y summarise() para evaluar la profundidad de secuenciación (la suma de todos los recuentos) en cada muestra. ¿Qué muestra tiene la mayor profundidad de secuenciación?
  3. Seleccione una muestra y evalúe el número de genes según el biotipo.
  4. Identifique los genes asociados con la descripción del fenotipo “abnormal DNA methylation” y calcule su expresión media (en log) en el momento 0, el momento 4 y el momento 8.

R

## 1.
rna |>
  count(sample)

SALIDA

# A tibble: 22 × 2
   sample         n
   <chr>      <int>
 1 GSM2545336  1474
 2 GSM2545337  1474
 3 GSM2545338  1474
 4 GSM2545339  1474
 5 GSM2545340  1474
 6 GSM2545341  1474
 7 GSM2545342  1474
 8 GSM2545343  1474
 9 GSM2545344  1474
10 GSM2545345  1474
# ℹ 12 more rows

R

## 2.
rna |>
  group_by(sample) |>
  summarise(seq_depth = sum(expression)) |>
  arrange(desc(seq_depth))

SALIDA

# A tibble: 22 × 2
   sample     seq_depth
   <chr>          <dbl>
 1 GSM2545350   3255566
 2 GSM2545352   3216163
 3 GSM2545343   3105652
 4 GSM2545336   3039671
 5 GSM2545380   3036098
 6 GSM2545353   2953249
 7 GSM2545348   2913678
 8 GSM2545362   2913517
 9 GSM2545351   2782464
10 GSM2545349   2758006
# ℹ 12 more rows

R

## 3.
rna |>
  filter(sample == "GSM2545336") |>
  count(gene_biotype) |>
  arrange(desc(n))

SALIDA

# A tibble: 13 × 2
   gene_biotype                           n
   <chr>                              <int>
 1 protein_coding                      1321
 2 lncRNA                                69
 3 processed_pseudogene                  59
 4 miRNA                                  7
 5 snoRNA                                 5
 6 TEC                                    4
 7 polymorphic_pseudogene                 2
 8 unprocessed_pseudogene                 2
 9 IG_C_gene                              1
10 scaRNA                                 1
11 transcribed_processed_pseudogene       1
12 transcribed_unitary_pseudogene         1
13 transcribed_unprocessed_pseudogene     1

R

## 4.
rna |>
  filter(phenotype_description == "abnormal DNA methylation") |>
  group_by(gene, time) |>
  summarise(mean_expression = mean(log(expression))) |>
  arrange()

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene and time.
ℹ Output is grouped by gene.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 6 × 3
# Groups:   gene [2]
  gene   time mean_expression
  <chr> <dbl>           <dbl>
1 Xist      0            6.95
2 Xist      4            6.34
3 Xist      8            7.13
4 Zdbf2     0            6.27
5 Zdbf2     4            6.27
6 Zdbf2     8            6.19

Reestructuración de datos


En el tibble rna, las filas contienen valores de expresión (la unidad) que están asociados a una combinación de otras 2 variables: gene y sample.

Todas las demás columnas corresponden a variables que describen la muestra (organism, age, sex, …) o el gen (gene_biotype, ENTREZ_ID, product, …). Las variables que no varían según los genes o las muestras tendrán el mismo valor en todas las filas.

R

rna |>
  arrange(gene)

SALIDA

# A tibble: 32,428 × 19
   gene     sample expression organism   age sex   infection strain  time tissue
   <chr>    <chr>       <dbl> <chr>    <dbl> <chr> <chr>     <chr>  <dbl> <chr>
 1 AI504432 GSM25…       1230 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 2 AI504432 GSM25…       1085 Mus mus…     8 Fema… NonInfec… C57BL…     0 Cereb…
 3 AI504432 GSM25…        969 Mus mus…     8 Fema… NonInfec… C57BL…     0 Cereb…
 4 AI504432 GSM25…       1284 Mus mus…     8 Fema… Influenz… C57BL…     4 Cereb…
 5 AI504432 GSM25…        966 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
 6 AI504432 GSM25…        918 Mus mus…     8 Male  Influenz… C57BL…     8 Cereb…
 7 AI504432 GSM25…        985 Mus mus…     8 Fema… Influenz… C57BL…     8 Cereb…
 8 AI504432 GSM25…        972 Mus mus…     8 Male  NonInfec… C57BL…     0 Cereb…
 9 AI504432 GSM25…       1000 Mus mus…     8 Fema… Influenz… C57BL…     4 Cereb…
10 AI504432 GSM25…        816 Mus mus…     8 Male  Influenz… C57BL…     4 Cereb…
# ℹ 32,418 more rows
# ℹ 9 more variables: mouse <dbl>, ENTREZID <dbl>, product <chr>,
#   ensembl_gene_id <chr>, external_synonym <chr>, chromosome_name <chr>,
#   gene_biotype <chr>, phenotype_description <chr>,
#   hsapiens_homolog_associated_gene_name <chr>

Esta estructura se denomina long-format, ya que una columna contiene todos los valores y las demás columnas enumeran el contexto de dicho valor.

En ciertos casos, el long-format no es realmente «legible para el ser humano» y se prefiere otro formato, el wide-format, como una forma más compacta de representar los datos. Este es el caso habitual de los valores de expresión génica, que los científicos suelen ver como matrices, donde las filas representan los genes y las columnas las muestras.

En este formato, resultaría, por lo tanto, sencillo explorar la relación entre los niveles de expresión génica dentro de las muestras y entre ellas.

SALIDA

# A tibble: 1,474 × 23
   gene    GSM2545336 GSM2545337 GSM2545338 GSM2545339 GSM2545340 GSM2545341
   <chr>        <dbl>      <dbl>      <dbl>      <dbl>      <dbl>      <dbl>
 1 Asl           1170        361        400        586        626        988
 2 Apod         36194      10347       9173      10620      13021      29594
 3 Cyp2d22       4060       1616       1603       1901       2171       3349
 4 Klk6           287        629        641        578        448        195
 5 Fcrls           85        233        244        237        180         38
 6 Slc2a4         782        231        248        265        313        786
 7 Exd2          1619       2288       2235       2513       2366       1359
 8 Gjc2           288        595        568        551        310        146
 9 Plp1         43217     101241      96534      58354      53126      27173
10 Gnb4          1071       1791       1867       1430       1355        798
# ℹ 1,464 more rows
# ℹ 16 more variables: GSM2545342 <dbl>, GSM2545343 <dbl>, GSM2545344 <dbl>,
#   GSM2545345 <dbl>, GSM2545346 <dbl>, GSM2545347 <dbl>, GSM2545348 <dbl>,
#   GSM2545349 <dbl>, GSM2545350 <dbl>, GSM2545351 <dbl>, GSM2545352 <dbl>,
#   GSM2545353 <dbl>, GSM2545354 <dbl>, GSM2545362 <dbl>, GSM2545363 <dbl>,
#   GSM2545380 <dbl>

Para convertir los valores de expresión génica de rna a un formato ancho, necesitamos crear una nueva tabla en la que los valores de la columna sample se conviertan en los nombres de las variables de columna.

El punto clave aquí es que seguimos una estructura de datos ordenada, pero hemos reconfigurado los datos según las observaciones de interés: los niveles de expresión por gen, en lugar de registrarlos por gen y por muestra.

La transformación inversa consistiría en convertir los nombres de las columnas en valores de una nueva variable.

Podemos realizar ambas transformaciones con dos funciones tidyr, pivot_longer() y pivot_wider() (consulte el artículo sobre pivots en el sitio web de tidyr para más detalles).

Transformación de los datos a un formato más ancho

Seleccionemos las primeras 3 columnas de rna y utilicemos pivot_wider() para transformar los datos a un formato ancho.

R

rna_exp <- rna |>
  select(gene, sample, expression)
rna_exp

SALIDA

# A tibble: 32,428 × 3
   gene    sample     expression
   <chr>   <chr>           <dbl>
 1 Asl     GSM2545336       1170
 2 Apod    GSM2545336      36194
 3 Cyp2d22 GSM2545336       4060
 4 Klk6    GSM2545336        287
 5 Fcrls   GSM2545336         85
 6 Slc2a4  GSM2545336        782
 7 Exd2    GSM2545336       1619
 8 Gjc2    GSM2545336        288
 9 Plp1    GSM2545336      43217
10 Gnb4    GSM2545336       1071
# ℹ 32,418 more rows

pivot_wider acepta tres argumentos principales:

  1. los datos a transformar;
  2. el names_from : la columna cuyos valores se convertirán en nuevos nombres de columna;
  3. el values_from: la columna cuyos valores llenarán las nuevas columnas.
Figure showing the long table format on the left and wide table format on the right and arrows illustrating how the values in the 'sample' column on the left have become column names on the right and how the values in the 'expression' column on the left have become values on the the right. Below is the call to 'pivot_wider()' with annotations pointing to the 'sample' and 'expression' function arguments.
Wide pivot of the rna data.

R

rna_wide <- rna_exp |>
  pivot_wider(names_from = sample,
              values_from = expression)
rna_wide

SALIDA

# A tibble: 1,474 × 23
   gene    GSM2545336 GSM2545337 GSM2545338 GSM2545339 GSM2545340 GSM2545341
   <chr>        <dbl>      <dbl>      <dbl>      <dbl>      <dbl>      <dbl>
 1 Asl           1170        361        400        586        626        988
 2 Apod         36194      10347       9173      10620      13021      29594
 3 Cyp2d22       4060       1616       1603       1901       2171       3349
 4 Klk6           287        629        641        578        448        195
 5 Fcrls           85        233        244        237        180         38
 6 Slc2a4         782        231        248        265        313        786
 7 Exd2          1619       2288       2235       2513       2366       1359
 8 Gjc2           288        595        568        551        310        146
 9 Plp1         43217     101241      96534      58354      53126      27173
10 Gnb4          1071       1791       1867       1430       1355        798
# ℹ 1,464 more rows
# ℹ 16 more variables: GSM2545342 <dbl>, GSM2545343 <dbl>, GSM2545344 <dbl>,
#   GSM2545345 <dbl>, GSM2545346 <dbl>, GSM2545347 <dbl>, GSM2545348 <dbl>,
#   GSM2545349 <dbl>, GSM2545350 <dbl>, GSM2545351 <dbl>, GSM2545352 <dbl>,
#   GSM2545353 <dbl>, GSM2545354 <dbl>, GSM2545362 <dbl>, GSM2545363 <dbl>,
#   GSM2545380 <dbl>

Tenga en cuenta que, por defecto, la función pivot_wider() añadirá NA para los valores faltantes.

Imaginemos que, por alguna razón, tuviéramos algunos valores de expresión faltantes para ciertos genes en determinadas muestras. En el siguiente ejemplo ficticio, el gen Cyp2d22 tiene un único valor de expresión, en la muestra GSM2545338.

R

rna_with_missing_values <- rna |>
  select(gene, sample, expression) |>
  filter(gene %in% c("Asl", "Apod", "Cyp2d22")) |>
  filter(sample %in% c("GSM2545336", "GSM2545337", "GSM2545338")) |>
  arrange(sample) |>
  filter(!(gene == "Cyp2d22" & sample != "GSM2545338"))
rna_with_missing_values

SALIDA

# A tibble: 7 × 3
  gene    sample     expression
  <chr>   <chr>           <dbl>
1 Asl     GSM2545336       1170
2 Apod    GSM2545336      36194
3 Asl     GSM2545337        361
4 Apod    GSM2545337      10347
5 Asl     GSM2545338        400
6 Apod    GSM2545338       9173
7 Cyp2d22 GSM2545338       1603

Por defecto, la función pivot_wider() añadirá NA para los valores faltantes. Esto puede configurarse mediante el argumento values_fill de la función pivot_wider().

R

rna_with_missing_values |>
  pivot_wider(names_from = sample,
              values_from = expression)

SALIDA

# A tibble: 3 × 4
  gene    GSM2545336 GSM2545337 GSM2545338
  <chr>        <dbl>      <dbl>      <dbl>
1 Asl           1170        361        400
2 Apod         36194      10347       9173
3 Cyp2d22         NA         NA       1603

R

rna_with_missing_values |>
  pivot_wider(names_from = sample,
              values_from = expression,
              values_fill = 0)

SALIDA

# A tibble: 3 × 4
  gene    GSM2545336 GSM2545337 GSM2545338
  <chr>        <dbl>      <dbl>      <dbl>
1 Asl           1170        361        400
2 Apod         36194      10347       9173
3 Cyp2d22          0          0       1603

Conversión de datos a un formato más largo

En el caso opuesto, utilizamos los nombres de las columnas y los convertimos en un par de nuevas variables. Una variable representa los nombres de las columnas como valores, y la otra variable contiene los valores previamente asociados a los nombres de las columnas.

pivot_longer() acepta cuatro argumentos principales:

  1. los datos a transformar;
  2. el names_to: el nombre de la nueva columna que queremos crear y llenar con los nombres de las columnas actuales;
  3. el values_to: el nombre de la nueva columna que queremos crear y llenar con los valores actuales;
  4. los nombres de las columnas que se utilizarán para completar las variables names_to y values_to (o las columnas que se excluirán de la transformación).
Figure showing the long table format on the left and wide table format on the right and arrows illustrating how the column names on the left have become a new column 'sample' on the left and the values in the wide table on the right have become a new column 'expression' on the left.  Below is the call to 'pivot_wider()' with annotations pointing to the 'sample', 'expression' and the '-gene' arguments.
Long pivot of the rna data.

Para recrear rna_long a partir de rna_wide, crearíamos una clave llamada sample y un valor llamado expression, y utilizaríamos todas las columnas excepto gene para la variable clave. Aquí excluimos la columna gene de la transformación con un signo menos.

Observe cómo los nombres de las nuevas variables deben ir entre comillas aquí.

R

rna_long <- rna_wide |>
    pivot_longer(names_to = "sample",
                 values_to = "expression",
                 -gene)
rna_long

SALIDA

# A tibble: 32,428 × 3
   gene  sample     expression
   <chr> <chr>           <dbl>
 1 Asl   GSM2545336       1170
 2 Asl   GSM2545337        361
 3 Asl   GSM2545338        400
 4 Asl   GSM2545339        586
 5 Asl   GSM2545340        626
 6 Asl   GSM2545341        988
 7 Asl   GSM2545342        836
 8 Asl   GSM2545343        535
 9 Asl   GSM2545344        586
10 Asl   GSM2545345        597
# ℹ 32,418 more rows

También podríamos haber utilizado una especificación sobre qué columnas incluir. Esto puede resultar útil si se dispone de un gran número de columnas identificativas, y resulta más sencillo especificar qué recopilar que qué dejar fuera. ¡Aquí la función starts_with() puede ayudar a recuperar los nombres de las muestras sin tener que enumerarlas todas! ¡Otra posibilidad sería utilizar el operador :!

R

rna_wide |>
    pivot_longer(names_to = "sample",
                 values_to = "expression",
                 cols = starts_with("GSM"))

SALIDA

# A tibble: 32,428 × 3
   gene  sample     expression
   <chr> <chr>           <dbl>
 1 Asl   GSM2545336       1170
 2 Asl   GSM2545337        361
 3 Asl   GSM2545338        400
 4 Asl   GSM2545339        586
 5 Asl   GSM2545340        626
 6 Asl   GSM2545341        988
 7 Asl   GSM2545342        836
 8 Asl   GSM2545343        535
 9 Asl   GSM2545344        586
10 Asl   GSM2545345        597
# ℹ 32,418 more rows

R

rna_wide |>
    pivot_longer(names_to = "sample",
                 values_to = "expression",
                 GSM2545336:GSM2545380)

SALIDA

# A tibble: 32,428 × 3
   gene  sample     expression
   <chr> <chr>           <dbl>
 1 Asl   GSM2545336       1170
 2 Asl   GSM2545337        361
 3 Asl   GSM2545338        400
 4 Asl   GSM2545339        586
 5 Asl   GSM2545340        626
 6 Asl   GSM2545341        988
 7 Asl   GSM2545342        836
 8 Asl   GSM2545343        535
 9 Asl   GSM2545344        586
10 Asl   GSM2545345        597
# ℹ 32,418 more rows

Tenga en cuenta que, si tuviéramos valores faltantes en el formato ancho, el NA se incluiría en el nuevo formato largo.

Recuerde nuestro tibble ficticio anterior que contenía valores faltantes:

R

rna_with_missing_values

SALIDA

# A tibble: 7 × 3
  gene    sample     expression
  <chr>   <chr>           <dbl>
1 Asl     GSM2545336       1170
2 Apod    GSM2545336      36194
3 Asl     GSM2545337        361
4 Apod    GSM2545337      10347
5 Asl     GSM2545338        400
6 Apod    GSM2545338       9173
7 Cyp2d22 GSM2545338       1603

R

wide_with_NA <- rna_with_missing_values |>
  pivot_wider(names_from = sample,
              values_from = expression)
wide_with_NA

SALIDA

# A tibble: 3 × 4
  gene    GSM2545336 GSM2545337 GSM2545338
  <chr>        <dbl>      <dbl>      <dbl>
1 Asl           1170        361        400
2 Apod         36194      10347       9173
3 Cyp2d22         NA         NA       1603

R

wide_with_NA |>
    pivot_longer(names_to = "sample",
                 values_to = "expression",
                 -gene)

SALIDA

# A tibble: 9 × 3
  gene    sample     expression
  <chr>   <chr>           <dbl>
1 Asl     GSM2545336       1170
2 Asl     GSM2545337        361
3 Asl     GSM2545338        400
4 Apod    GSM2545336      36194
5 Apod    GSM2545337      10347
6 Apod    GSM2545338       9173
7 Cyp2d22 GSM2545336         NA
8 Cyp2d22 GSM2545337         NA
9 Cyp2d22 GSM2545338       1603

Realizar un pivote a formatos ancho y largo puede ser una forma útil de equilibrar un conjunto de datos para que cada réplica tenga la misma composición.

Desafío

Pregunta

Partiendo de la tabla rna, utilice la función pivot_wider() para crear una tabla en formato ancho que muestre los niveles de expresión génica en cada ratón. A continuación, utilice la función pivot_longer() para restaurar una tabla en formato largo.

R

rna1 <- rna |>
select(gene, mouse, expression) |>
pivot_wider(names_from = mouse, values_from = expression)
rna1

SALIDA

# A tibble: 1,474 × 23
   gene     `14`    `9`  `10`  `15`  `18`   `6`   `5`  `11`  `22`  `13`  `23`
   <chr>   <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1 Asl      1170    361   400   586   626   988   836   535   586   597   938
 2 Apod    36194  10347  9173 10620 13021 29594 24959 13668 13230 15868 27769
 3 Cyp2d22  4060   1616  1603  1901  2171  3349  3122  2008  2254  2277  2985
 4 Klk6      287    629   641   578   448   195   186  1101   537   567   327
 5 Fcrls      85    233   244   237   180    38    68   375   199   177    89
 6 Slc2a4    782    231   248   265   313   786   528   249   266   357   654
 7 Exd2     1619   2288  2235  2513  2366  1359  1474  3126  2379  2173  1531
 8 Gjc2      288    595   568   551   310   146   186   791   454   370   240
 9 Plp1    43217 101241 96534 58354 53126 27173 28728 98658 61356 61647 38019
10 Gnb4     1071   1791  1867  1430  1355   798   806  2437  1394  1554   960
# ℹ 1,464 more rows
# ℹ 11 more variables: `24` <dbl>, `8` <dbl>, `7` <dbl>, `1` <dbl>, `16` <dbl>,
#   `21` <dbl>, `4` <dbl>, `2` <dbl>, `20` <dbl>, `12` <dbl>, `19` <dbl>

R

rna1 |>
pivot_longer(names_to = "mouse_id", values_to = "counts", -gene)

SALIDA

# A tibble: 32,428 × 3
   gene  mouse_id counts
   <chr> <chr>     <dbl>
 1 Asl   14         1170
 2 Asl   9           361
 3 Asl   10          400
 4 Asl   15          586
 5 Asl   18          626
 6 Asl   6           988
 7 Asl   5           836
 8 Asl   11          535
 9 Asl   22          586
10 Asl   13          597
# ℹ 32,418 more rows
Desafío

Pregunta

Extraiga los genes ubicados en los cromosomas X e Y de la tabla de datos rna y despliegue la tabla de datos con sex como columnas, chromosome_name como filas y la expresión media de los genes ubicados en cada cromosoma como valores, como en el siguiente tibble:

A two by two table with  X and Y row names and Female and Male column names showing the total counts for each X/Y and Female/Male combination. The Y/Female combinations shows 3 counts, while all other counts are above 2000 counts.

¡Tendrá que resumir antes de reestructurar!

Primero calculemos el nivel medio de expresión de los genes situados en los cromosomas X e Y en las muestras de ratones machos y hembras…

R

 rna |>
  filter(chromosome_name == "Y" | chromosome_name == "X") |>
  group_by(sex, chromosome_name) |>
  summarise(mean = mean(expression))

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by sex and chromosome_name.
ℹ Output is grouped by sex.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(sex, chromosome_name))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 4 × 3
# Groups:   sex [2]
  sex    chromosome_name  mean
  <chr>  <chr>           <dbl>
1 Female X               3504.
2 Female Y                  3
3 Male   X               2497.
4 Male   Y               2117.

Y pivotar la tabla a formato ancho

R

rna_1 <- rna |>
  filter(chromosome_name == "Y" | chromosome_name == "X") |>
  group_by(sex, chromosome_name) |>
  summarise(mean = mean(expression)) |>
  pivot_wider(names_from = sex,
              values_from = mean)

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by sex and chromosome_name.
ℹ Output is grouped by sex.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(sex, chromosome_name))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

R

rna_1

SALIDA

# A tibble: 2 × 3
  chromosome_name Female  Male
  <chr>            <dbl> <dbl>
1 X                3504. 2497.
2 Y                   3  2117.

Ahora toma esa tabla de datos y transfórmala con pivot_longer() para que cada fila sea una combinación única de chromosome_name por gender.

R

rna_1 |>
  pivot_longer(names_to = "gender",
               values_to = "mean",
               -chromosome_name)

SALIDA

# A tibble: 4 × 3
  chromosome_name gender  mean
  <chr>           <chr>  <dbl>
1 X               Female 3504.
2 X               Male   2497.
3 Y               Female    3
4 Y               Male   2117.
Desafío

Pregunta

Utilice la tabla de datos rna para crear una matriz de expresión en la que cada fila represente los niveles de expresión promedio de los genes y las columnas representen los diferentes puntos temporales.

Primero, calculemos la expresión media por gen y por momento.

R

rna |>
  group_by(gene, time) |>
  summarise(mean_exp = mean(expression))

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene and time.
ℹ Output is grouped by gene.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 4,422 × 3
# Groups:   gene [1,474]
   gene      time mean_exp
   <chr>    <dbl>    <dbl>
 1 AI504432     0    1034.
 2 AI504432     4    1104.
 3 AI504432     8    1014
 4 AW046200     0     155.
 5 AW046200     4     152.
 6 AW046200     8      81
 7 AW551984     0     238
 8 AW551984     4     302.
 9 AW551984     8     342.
10 Aamp         0    4603.
# ℹ 4,412 more rows

antes de utilizar la función pivot_wider()

R

rna_time <- rna |>
  group_by(gene, time) |>
  summarise(mean_exp = mean(expression)) |>
  pivot_wider(names_from = time,
              values_from = mean_exp)

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene and time.
ℹ Output is grouped by gene.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

R

rna_time

SALIDA

# A tibble: 1,474 × 4
# Groups:   gene [1,474]
   gene         `0`     `4`     `8`
   <chr>      <dbl>   <dbl>   <dbl>
 1 AI504432 1034.   1104.   1014
 2 AW046200  155.    152.     81
 3 AW551984  238     302.    342.
 4 Aamp     4603.   4870    4763.
 5 Abca12      5.29    4.25    4.14
 6 Abcc8    2576.   2609.   2292.
 7 Abhd14a   591.    547.    432.
 8 Abi2     4881.   4903.   4945.
 9 Abi3bp   1175.   1061.    762.
10 Abl2     2170.   2078.   2131.
# ℹ 1,464 more rows

Nótese que esto genera un tibble con algunos nombres de columna que comienzan con un número. Si quisiéramos seleccionar la columna correspondiente a los puntos temporales, no podríamos usar los nombres de las columnas directamente… ¿Qué sucede cuando seleccionamos la columna 4?

R

rna |>
  group_by(gene, time) |>
  summarise(mean_exp = mean(expression)) |>
  pivot_wider(names_from = time,
              values_from = mean_exp) |>
  select(gene, 4)

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene and time.
ℹ Output is grouped by gene.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 1,474 × 2
# Groups:   gene [1,474]
   gene         `8`
   <chr>      <dbl>
 1 AI504432 1014
 2 AW046200   81
 3 AW551984  342.
 4 Aamp     4763.
 5 Abca12      4.14
 6 Abcc8    2292.
 7 Abhd14a   432.
 8 Abi2     4945.
 9 Abi3bp    762.
10 Abl2     2131.
# ℹ 1,464 more rows

Para seleccionar el punto temporal 4, tendríamos que encerrar el nombre de la columna entre comillas invertidas (backticks): “`”

R

rna |>
  group_by(gene, time) |>
  summarise(mean_exp = mean(expression)) |>
  pivot_wider(names_from = time,
              values_from = mean_exp) |>
  select(gene, `4`)

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene and time.
ℹ Output is grouped by gene.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 1,474 × 2
# Groups:   gene [1,474]
   gene         `4`
   <chr>      <dbl>
 1 AI504432 1104.
 2 AW046200  152.
 3 AW551984  302.
 4 Aamp     4870
 5 Abca12      4.25
 6 Abcc8    2609.
 7 Abhd14a   547.
 8 Abi2     4903.
 9 Abi3bp   1061.
10 Abl2     2078.
# ℹ 1,464 more rows

Otra posibilidad sería renombrar la columna, eligiendo un nombre que no empiece con un número:

R

rna |>
  group_by(gene, time) |>
  summarise(mean_exp = mean(expression)) |>
  pivot_wider(names_from = time,
              values_from = mean_exp) |>
  rename("time0" = `0`, "time4" = `4`, "time8" = `8`) |>
  select(gene, time4)

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene and time.
ℹ Output is grouped by gene.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

SALIDA

# A tibble: 1,474 × 2
# Groups:   gene [1,474]
   gene       time4
   <chr>      <dbl>
 1 AI504432 1104.
 2 AW046200  152.
 3 AW551984  302.
 4 Aamp     4870
 5 Abca12      4.25
 6 Abcc8    2609.
 7 Abhd14a   547.
 8 Abi2     4903.
 9 Abi3bp   1061.
10 Abl2     2078.
# ℹ 1,464 more rows
Desafío

Pregunta

Utilice la tabla de datos anterior que contiene los niveles medios de expresión por punto temporal y cree una nueva columna con los cocientes de expresión (fold-changes) entre el punto temporal 8 y el punto temporal 0, y entre el punto temporal 8 y el punto temporal 4. Convierta esta tabla en una tabla de formato largo que reúna los cocientes calculados.

Partiendo del tibble rna_time:

R

rna_time

SALIDA

# A tibble: 1,474 × 4
# Groups:   gene [1,474]
   gene         `0`     `4`     `8`
   <chr>      <dbl>   <dbl>   <dbl>
 1 AI504432 1034.   1104.   1014
 2 AW046200  155.    152.     81
 3 AW551984  238     302.    342.
 4 Aamp     4603.   4870    4763.
 5 Abca12      5.29    4.25    4.14
 6 Abcc8    2576.   2609.   2292.
 7 Abhd14a   591.    547.    432.
 8 Abi2     4881.   4903.   4945.
 9 Abi3bp   1175.   1061.    762.
10 Abl2     2170.   2078.   2131.
# ℹ 1,464 more rows

Calcule los cocientes de expresión (fold-changes):

R

rna_time |>
  mutate(time_8_vs_0 = `8` / `0`, time_8_vs_4 = `8` / `4`)

SALIDA

# A tibble: 1,474 × 6
# Groups:   gene [1,474]
   gene         `0`     `4`     `8` time_8_vs_0 time_8_vs_4
   <chr>      <dbl>   <dbl>   <dbl>       <dbl>       <dbl>
 1 AI504432 1034.   1104.   1014          0.981       0.918
 2 AW046200  155.    152.     81          0.522       0.532
 3 AW551984  238     302.    342.         1.44        1.13
 4 Aamp     4603.   4870    4763.         1.03        0.978
 5 Abca12      5.29    4.25    4.14       0.784       0.975
 6 Abcc8    2576.   2609.   2292.         0.889       0.878
 7 Abhd14a   591.    547.    432.         0.731       0.791
 8 Abi2     4881.   4903.   4945.         1.01        1.01
 9 Abi3bp   1175.   1061.    762.         0.649       0.719
10 Abl2     2170.   2078.   2131.         0.982       1.03
# ℹ 1,464 more rows

Y utilice la función pivot_longer():

R

rna_time |>
  mutate(time_8_vs_0 = `8` / `0`, time_8_vs_4 = `8` / `4`) |>
  pivot_longer(names_to = "comparisons",
               values_to = "Fold_changes",
               time_8_vs_0:time_8_vs_4)

SALIDA

# A tibble: 2,948 × 6
# Groups:   gene [1,474]
   gene         `0`     `4`     `8` comparisons Fold_changes
   <chr>      <dbl>   <dbl>   <dbl> <chr>              <dbl>
 1 AI504432 1034.   1104.   1014    time_8_vs_0        0.981
 2 AI504432 1034.   1104.   1014    time_8_vs_4        0.918
 3 AW046200  155.    152.     81    time_8_vs_0        0.522
 4 AW046200  155.    152.     81    time_8_vs_4        0.532
 5 AW551984  238     302.    342.   time_8_vs_0        1.44
 6 AW551984  238     302.    342.   time_8_vs_4        1.13
 7 Aamp     4603.   4870    4763.   time_8_vs_0        1.03
 8 Aamp     4603.   4870    4763.   time_8_vs_4        0.978
 9 Abca12      5.29    4.25    4.14 time_8_vs_0        0.784
10 Abca12      5.29    4.25    4.14 time_8_vs_4        0.975
# ℹ 2,938 more rows

Unión de tablas


En muchas situaciones reales, los datos se distribuyen entre múltiples tablas. Por lo general, esto ocurre porque se recopilan diferentes tipos de información de diversas fuentes.

Para algunos análisis, puede resultar conveniente combinar datos de dos o más tablas en una única tabla de datos basándose en una columna común a todas ellas.

El paquete dplyr proporciona un conjunto de funciones de unión para combinar dos tablas de datos basándose en coincidencias dentro de columnas especificadas. A continuación, presentamos una breve introducción a las uniones. Para lectura adicional, consulte el capítulo sobre uniones de tablas. La Hoja de trucos de transformación de datos también ofrece una breve descripción general de las uniones de tablas.

Vamos a ilustrar la unión utilizando una tabla pequeña, rna_mini, que crearemos mediante un subconjunto de la tabla original rna, manteniendo solo 3 columnas y 10 filas.

R

rna_mini <- rna |>
   select(gene, sample, expression) |>
   head(10)
rna_mini

SALIDA

# A tibble: 10 × 3
   gene    sample     expression
   <chr>   <chr>           <dbl>
 1 Asl     GSM2545336       1170
 2 Apod    GSM2545336      36194
 3 Cyp2d22 GSM2545336       4060
 4 Klk6    GSM2545336        287
 5 Fcrls   GSM2545336         85
 6 Slc2a4  GSM2545336        782
 7 Exd2    GSM2545336       1619
 8 Gjc2    GSM2545336        288
 9 Plp1    GSM2545336      43217
10 Gnb4    GSM2545336       1071

La segunda tabla, annot1, contiene 2 columnas: gene y gene_description. Puede descargar annot1.csv haciendo clic en el enlace y luego moverla a la carpeta data/, o puede utilizar el código de R a continuación para descargarla directamente en la carpeta.

R

download.file(url = "https://carpentries-incubator.github.io/bioc-intro/data/annot1.csv",
              destfile = "data/annot1.csv")
annot1 <- read_csv(file = "data/annot1.csv")
annot1

SALIDA

# A tibble: 10 × 2
   gene    gene_description
   <chr>   <chr>
 1 Cyp2d22 cytochrome P450, family 2, subfamily d, polypeptide 22 [Source:MGI S…
 2 Klk6    kallikrein related-peptidase 6 [Source:MGI Symbol;Acc:MGI:1343166]
 3 Fcrls   Fc receptor-like S, scavenger receptor [Source:MGI Symbol;Acc:MGI:19…
 4 Plp1    proteolipid protein (myelin) 1 [Source:MGI Symbol;Acc:MGI:97623]
 5 Exd2    exonuclease 3'-5' domain containing 2 [Source:MGI Symbol;Acc:MGI:192…
 6 Apod    apolipoprotein D [Source:MGI Symbol;Acc:MGI:88056]
 7 Gnb4    guanine nucleotide binding protein (G protein), beta 4 [Source:MGI S…
 8 Slc2a4  solute carrier family 2 (facilitated glucose transporter), member 4 …
 9 Asl     argininosuccinate lyase [Source:MGI Symbol;Acc:MGI:88084]
10 Gjc2    gap junction protein, gamma 2 [Source:MGI Symbol;Acc:MGI:2153060]    

Ahora queremos unir estas dos tablas en una sola que contenga todas las variables utilizando la función full_join() del paquete dplyr. La función encontrará automáticamente la variable común para emparejar las columnas de la primera y la segunda tabla. En este caso, gene es la variable común. Tales variables se denominan claves. Las claves se utilizan para relacionar observaciones entre diferentes tablas.

R

full_join(rna_mini, annot1)

SALIDA

Joining with `by = join_by(gene)`

SALIDA

# A tibble: 10 × 4
   gene    sample     expression gene_description
   <chr>   <chr>           <dbl> <chr>
 1 Asl     GSM2545336       1170 argininosuccinate lyase [Source:MGI Symbol;Acc…
 2 Apod    GSM2545336      36194 apolipoprotein D [Source:MGI Symbol;Acc:MGI:88…
 3 Cyp2d22 GSM2545336       4060 cytochrome P450, family 2, subfamily d, polype…
 4 Klk6    GSM2545336        287 kallikrein related-peptidase 6 [Source:MGI Sym…
 5 Fcrls   GSM2545336         85 Fc receptor-like S, scavenger receptor [Source…
 6 Slc2a4  GSM2545336        782 solute carrier family 2 (facilitated glucose t…
 7 Exd2    GSM2545336       1619 exonuclease 3'-5' domain containing 2 [Source:…
 8 Gjc2    GSM2545336        288 gap junction protein, gamma 2 [Source:MGI Symb…
 9 Plp1    GSM2545336      43217 proteolipid protein (myelin) 1 [Source:MGI Sym…
10 Gnb4    GSM2545336       1071 guanine nucleotide binding protein (G protein)…

En la práctica, las anotaciones de genes a veces se etiquetan de manera diferente.

La tabla annot2 es exactamente igual que la annot1, excepto que la variable que contiene los nombres de los genes está etiquetada de manera diferente. Nuevamente, puede descargar annot2.csv usted mismo y moverla a data/ o utilizar el código de R a continuación.

R

download.file(url = "https://carpentries-incubator.github.io/bioc-intro/data/annot2.csv",
              destfile = "data/annot2.csv")
annot2 <- read_csv(file = "data/annot2.csv")
annot2

SALIDA

# A tibble: 10 × 2
   external_gene_name description
   <chr>              <chr>
 1 Cyp2d22            cytochrome P450, family 2, subfamily d, polypeptide 22 [S…
 2 Klk6               kallikrein related-peptidase 6 [Source:MGI Symbol;Acc:MGI…
 3 Fcrls              Fc receptor-like S, scavenger receptor [Source:MGI Symbol…
 4 Plp1               proteolipid protein (myelin) 1 [Source:MGI Symbol;Acc:MGI…
 5 Exd2               exonuclease 3'-5' domain containing 2 [Source:MGI Symbol;…
 6 Apod               apolipoprotein D [Source:MGI Symbol;Acc:MGI:88056]
 7 Gnb4               guanine nucleotide binding protein (G protein), beta 4 [S…
 8 Slc2a4             solute carrier family 2 (facilitated glucose transporter)…
 9 Asl                argininosuccinate lyase [Source:MGI Symbol;Acc:MGI:88084]
10 Gjc2               gap junction protein, gamma 2 [Source:MGI Symbol;Acc:MGI:…

En caso de que ninguno de los nombres de las variables coincida, podemos establecer manualmente las variables a utilizar para la coincidencia. Estas variables pueden definirse mediante el argumento by, como se muestra a continuación con las tablas rna_mini y annot2.

R

full_join(rna_mini, annot2, by = c("gene" = "external_gene_name"))

SALIDA

# A tibble: 10 × 4
   gene    sample     expression description
   <chr>   <chr>           <dbl> <chr>
 1 Asl     GSM2545336       1170 argininosuccinate lyase [Source:MGI Symbol;Acc…
 2 Apod    GSM2545336      36194 apolipoprotein D [Source:MGI Symbol;Acc:MGI:88…
 3 Cyp2d22 GSM2545336       4060 cytochrome P450, family 2, subfamily d, polype…
 4 Klk6    GSM2545336        287 kallikrein related-peptidase 6 [Source:MGI Sym…
 5 Fcrls   GSM2545336         85 Fc receptor-like S, scavenger receptor [Source…
 6 Slc2a4  GSM2545336        782 solute carrier family 2 (facilitated glucose t…
 7 Exd2    GSM2545336       1619 exonuclease 3'-5' domain containing 2 [Source:…
 8 Gjc2    GSM2545336        288 gap junction protein, gamma 2 [Source:MGI Symb…
 9 Plp1    GSM2545336      43217 proteolipid protein (myelin) 1 [Source:MGI Sym…
10 Gnb4    GSM2545336       1071 guanine nucleotide binding protein (G protein)…

Como se puede observar arriba, el nombre de la variable de la primera tabla se conserva en la unida.

Desafío

Desafío:

Descargue la tabla annot3 haciendo clic en este enlace y coloque la tabla en su repositorio data/. Utilizando la función full_join(), una las tablas rna_mini y annot3. ¿Qué ha ocurrido con los genes Klk6, mt-Tf, mt-Rnr1, mt-Tv, mt-Rnr2 y mt-Tl1?

R

annot3 <- read_csv("data/annot3.csv")
full_join(rna_mini, annot3)

SALIDA

# A tibble: 15 × 4
   gene    sample     expression gene_description
   <chr>   <chr>           <dbl> <chr>
 1 Asl     GSM2545336       1170 argininosuccinate lyase [Source:MGI Symbol;Acc…
 2 Apod    GSM2545336      36194 apolipoprotein D [Source:MGI Symbol;Acc:MGI:88…
 3 Cyp2d22 GSM2545336       4060 cytochrome P450, family 2, subfamily d, polype…
 4 Klk6    GSM2545336        287 <NA>
 5 Fcrls   GSM2545336         85 Fc receptor-like S, scavenger receptor [Source…
 6 Slc2a4  GSM2545336        782 solute carrier family 2 (facilitated glucose t…
 7 Exd2    GSM2545336       1619 exonuclease 3'-5' domain containing 2 [Source:…
 8 Gjc2    GSM2545336        288 gap junction protein, gamma 2 [Source:MGI Symb…
 9 Plp1    GSM2545336      43217 proteolipid protein (myelin) 1 [Source:MGI Sym…
10 Gnb4    GSM2545336       1071 guanine nucleotide binding protein (G protein)…
11 mt-Tf   <NA>               NA mitochondrially encoded tRNA phenylalanine [So…
12 mt-Rnr1 <NA>               NA mitochondrially encoded 12S rRNA [Source:MGI S…
13 mt-Tv   <NA>               NA mitochondrially encoded tRNA valine [Source:MG…
14 mt-Rnr2 <NA>               NA mitochondrially encoded 16S rRNA [Source:MGI S…
15 mt-Tl1  <NA>               NA mitochondrially encoded tRNA leucine 1 [Source…

El gen Klk6 solo aparece en rna_mini, mientras que los genes mt-Tf, mt-Rnr1, mt-Tv, mt-Rnr2 y mt-Tl1 solo aparecen en la tabla annot3. Sus respectivos valores para las variables de la tabla han sido codificados como faltantes.

Exportación de datos


Ahora que ha aprendido a utilizar dplyr para extraer información de sus datos brutos o resumirlos, es posible que desee exportar estos nuevos conjuntos de datos para compartirlos con sus colaboradores o para su archivo.

Al igual que la función read_csv() utilizada para leer archivos CSV en R, existe una función write_csv() que genera archivos CSV a partir de tablas de datos.

Antes de utilizar write_csv(), vamos a crear una carpeta nueva, data_output, en nuestro directorio de trabajo donde se almacenará este conjunto de datos generado. No queremos escribir los conjuntos de datos generados en el mismo directorio que nuestros datos brutos. Es una buena práctica mantenerlos separados. La carpeta data solo debe contener los datos brutos y sin alterar, y debe dejarse intacta para asegurarnos de no eliminarla ni modificarla. Por el contrario, nuestro script generará el contenido del directorio data_output, por lo que, incluso si se eliminan los archivos que contiene, siempre podremos volver a generarlos.

Utilicemos write_csv() para guardar la tabla rna_wide que creamos anteriormente.

R

write_csv(rna_wide, file = "data_output/rna_wide.csv")
Puntos Clave
  • Datos tabulares en R utilizando el metapaquete tidyverse

Content from Visualización de datos


Última actualización: 2026-11-11 | Mejora esta página

Tiempo estimado: 120 minutos

Hoja de ruta

Preguntas

  • Visualización en R

Objetivos

  • Crear gráficos de dispersión, de cajas, de líneas, etc., utilizando ggplot.
  • Configurar los ajustes universales del gráfico.
  • Describa qué es el faceting y aplique el faceting en ggplot.
  • Modifique la estética de un gráfico ggplot existente (incluyendo las etiquetas de los ejes y el color).
  • Crear gráficos complejos y personalizados a partir de datos de una tabla de datos.

Este episodio se basa en la lección Data Analysis and Visualisation in R for Ecologists de Data Carpentries.

Visualización de datos


Comenzamos cargando los paquetes necesarios. ggplot2 está incluido en el paquete tidyverse.

R

library("tidyverse")

Si aún no se encuentra en el espacio de trabajo, cargue los datos que guardamos en la lección anterior.

R

rna <- read.csv("data/rnaseq.csv")

La Hoja de trucos de visualización de datos cubrirá los conceptos básicos y las funciones más avanzadas de ggplot2, y ayudará, además de servir como recordatorio, a obtener una visión general de las numerosas representaciones de datos disponibles en el paquete. Los siguientes videotutoriales (parte 1 y parte 2) de Thomas Lin Pedersen también son muy instructivos.

Representación gráfica con ggplot2


ggplot2 es un paquete de gráficos que facilita la creación de gráficos complejos a partir de datos de una tabla de datos. Proporciona una interfaz más programática para especificar qué variables representar, cómo mostrarlas y las propiedades visuales generales. La base teórica que sustenta a ggplot2 es la Grammar of Graphics ( Wilkinson (2005) ). Al utilizar este enfoque, solo necesitamos cambios mínimos si los datos subyacentes cambian o si decidimos pasar de un gráfico de barras a un gráfico de dispersión. Esto ayuda a crear gráficos de calidad editorial con la mínima cantidad de ajustes y retoques.

Existe un libro sobre ggplot2 ( Wickham (2016) ) que ofrece una buena visión general, pero está desactualizado. La tercera edición está en preparación y estará disponible gratuitamente en línea. La página web de ggplot2 (https://ggplot2.tidyverse.org) proporciona una documentación amplia.

ggplot2 funciona con datos en formato «largo», es decir, una columna por cada dimensión y una fila por cada observación. Los datos bien estructurados le ahorrarán mucho tiempo al crear figuras con ggplot2.

Los gráficos de ggplot se construyen paso a paso añadiendo nuevos elementos. Añadir capas de esta manera permite una gran flexibilidad y personalización de los gráficos.

La idea detrás de la Gramática de Gráficos es que puedes construir cualquier gráfico a partir de los mismos 3 componentes: (1) un conjunto de datos, (2) un sistema de coordenadas y (3) geoms — es decir, marcas visuales que representan puntos de datos 1

Para crear un ggplot, utilizaremos la siguiente plantilla básica que puede emplearse para diferentes tipos de gráficos:

ggplot(data = <DATA>, mapping = aes(<MAPPINGS>)) +  <GEOM_FUNCTION>()
  • utilice la función ggplot() y vincule el gráfico a una tabla de datos específica mediante el argumento data

R

ggplot(data = rna)
  • defina un mapeo (utilizando la función aesthetic (aes)), seleccionando las variables a representar y especificando cómo presentarlas en el gráfico, por ejemplo, como posiciones x/y o características como el tamaño, la forma, el color, etc.

R

ggplot(data = rna, mapping = aes(x = expression))
  • añada ‘geoms’ - geometrías, o representaciones gráficas de los datos en el gráfico (puntos, líneas, barras). ggplot2 ofrece muchas geometrías diferentes; hoy utilizaremos algunas comunes, entre ellas:

    * `geom_point()` for scatter plots, dot plots, etc.
    * `geom_histogram()` for histograms
    * `geom_boxplot()` for, well, boxplots!
    * `geom_line()` for trend lines, time series, etc.

Para añadir una geometría al gráfico, use el operador +. Empecemos usando geom_histogram():

R

ggplot(data = rna, mapping = aes(x = expression)) +
  geom_histogram()

SALIDA

`stat_bin()` using `bins = 30`. Pick better value `binwidth`.
Default histogram produced by ggplot() and geom_histogram() for the expression data.

El + del paquete ggplot2 es particularmente útil porque permite modificar objetos ggplot existentes. Esto significa que puede configurar fácilmente plantillas de gráficos y explorar cómodamente diferentes tipos de gráficos, por lo que el gráfico anterior también puede generarse con un código como este:

R

# Assign plot to a variable
rna_plot <- ggplot(data = rna,
                   mapping = aes(x = expression))

# Draw the plot
rna_plot + geom_histogram()
Desafío

Desafío

Probablemente hayas notado un mensaje automático que aparece al dibujar el histograma:

SALIDA

`stat_bin()` using `bins = 30`. Pick better value `binwidth`.

Cambie los argumentos bins o binwidth de geom_histogram() para cambiar el número o el ancho de los intervalos.

R

# change bins
ggplot(rna, aes(x = expression)) +
    geom_histogram(bins = 15)
Histograms produced by ggplot() and geom_histogram() for the expression data with bin set of 15 (top) and binwith set to 2000 (bottom).

R

# change binwidth
ggplot(rna, aes(x = expression)) +
    geom_histogram(binwidth = 2000)
Histograms produced by ggplot() and geom_histogram() for the expression data with bin set of 15 (top) and binwith set to 2000 (bottom).

Aquí podemos observar que los datos están sesgados hacia la derecha. Podemos aplicar una transformación logarítmica en base 2 para obtener una distribución más simétrica. Tenga en cuenta que aquí añadimos un pequeño valor constante (+1) para evitar que se devuelvan valores de -Inf para valores de expresión iguales a 0.

R

rna <- rna |>
  mutate(expression_log = log2(expression + 1))

Si ahora trazamos el histograma de las expresiones transformadas logarítmicamente en base 2, la distribución es, en efecto, más cercana a una distribución normal.

R

ggplot(rna, aes(x = expression_log)) + geom_histogram()

SALIDA

`stat_bin()` using `bins = 30`. Pick better value `binwidth`.
Histogram produced by ggplot() and geom_histogram() for the pre-computed log of expression.

A partir de ahora, trabajaremos con los valores de expresión transformados logarítmicamente.

Desafío

Desafío

Otra forma de visualizar esta transformación es considerar la escala de las observaciones. Por ejemplo, puede valer la pena cambiar la escala del eje para distribuir mejor las observaciones en el espacio del gráfico. Cambiar la escala de los ejes se hace de manera similar a añadir o modificar otros componentes (es decir, añadiendo comandos de forma incremental). Intente realizar esta modificación:

  • Represente la expresión sin transformar en la escala log10; consulte scale_x_log10(). Compárela con el gráfico anterior. ¿Por qué ahora aparecen mensajes de advertencia?

R

ggplot(data = rna,mapping = aes(x = expression))+
  geom_histogram() +
  scale_x_log10()

ADVERTENCIA

Warning in scale_x_log10(): log-10 transformation introduced infinite values.

SALIDA

`stat_bin()` using `bins = 30`. Pick better value `binwidth`.

ADVERTENCIA

Warning: Removed 507 rows containing non-finite outside the scale range
(`stat_bin()`).
Histogram produced by ggplot(), geom_histogram() and scale_x_log10() for the log of expression.

Notas

  • Cualquier elemento que incluya en la función ggplot() será visible en cualquier capa geom que añada (es decir, se trata de configuraciones globales del gráfico). Esto incluye el mapeo de los ejes x e y que configure en aes().
  • También puede especificar mapeos para un geom determinado de forma independiente de los mapeos definidos globalmente en la función ggplot().
  • El signo + utilizado para añadir nuevas capas deberá colocarse al final de la línea que contiene la capa anterior. Si, en cambio, se añade el signo + al principio de la línea que contiene la nueva capa, ggplot2 no añadirá la nueva capa y devolverá un mensaje de error.

R

# This is the correct syntax for adding layers
rna_plot +
  geom_histogram()

# This will not add the new layer and will return an error message
rna_plot
  + geom_histogram()

Creación iterativa de gráficos


A continuación, dibujaremos un gráfico de dispersión con dos variables continuas y la función geom_point(). Este gráfico representará los cambios relativos de expresión en escala log2 comparando el tiempo 8 frente al tiempo 0, y el tiempo 4 frente al tiempo 0. Para ello, primero necesitamos calcular los promedios de los valores de expresión transformados logarítmicamente por gen y tiempo, y luego los cambios relativos en escala logarítmica restando el promedio de las expresiones logarítmicas entre el tiempo 8 y el tiempo 0, y entre el tiempo 4 y el tiempo 0. Tenga en cuenta que aquí también incluimos el biotipo del gen que utilizaremos más adelante para representar los genes. Guardaremos los cambios relativos de expresión en una nueva tabla de datos llamada rna_fc..

R

rna_fc <- rna |> select(gene, time,
                         gene_biotype, expression_log) |>
  group_by(gene, time, gene_biotype) |>
  summarize(mean_exp = mean(expression_log)) |>
  pivot_wider(names_from = time,
              values_from = mean_exp) |>
  mutate(time_8_vs_0 = `8` - `0`, time_4_vs_0 = `4` - `0`)

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene, time, and gene_biotype.
ℹ Output is grouped by gene and time.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time, gene_biotype))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

A continuación, podemos crear un ggplot con el conjunto de datos recién creado rna_fc. Crear gráficos con ggplot2 suele ser un proceso iterativo. Comenzamos definiendo el conjunto de datos que utilizaremos, disponiendo los ejes y eligiendo un geom:

R

ggplot(data = rna_fc, mapping = aes(x = time_4_vs_0, y = time_8_vs_0)) +
  geom_point()
Scatter plot produced by ggplot() and geom_point() comparing the log-foldchanges computed above. All dots are black.

A continuación, comenzamos a modificar este gráfico para extraer más información de él. Por ejemplo, podemos añadir transparencia (alpha) para evitar el solapamiento:

R

ggplot(data = rna_fc, mapping = aes(x = time_4_vs_0, y = time_8_vs_0)) +
  geom_point(alpha = 0.3)
Scatter plot produced by ggplot() and geom_point() comparing the log-foldchanges computed above. All dots are semi-transparent black.

También podemos añadir colores a todos los puntos:

R

ggplot(data = rna_fc, mapping = aes(x = time_4_vs_0, y = time_8_vs_0)) +
  geom_point(alpha = 0.3, color = "blue")
Scatter plot produced by ggplot() and geom_point() comparing the log-foldchanges computed above. All dots are semi-transparent blue.

O bien, para colorear cada gen en el gráfico de manera diferente, podría utilizar un vector como entrada para el argumento color. ggplot2 proporcionará un color diferente correspondiente a distintos valores del vector. Aquí tiene un ejemplo donde se colorea con gene_biotype:

R

ggplot(data = rna_fc, mapping = aes(x = time_4_vs_0, y = time_8_vs_0)) +
  geom_point(alpha = 0.3, aes(color = gene_biotype))
Scatter plot produced by ggplot() and geom_point() comparing the log-foldchanges computed above. Dots are colour-coded based on the gene's biotype.

También podemos especificar los colores directamente dentro del mapeo proporcionado en la función ggplot(). Esto será visible en cualquier capa geom y el mapeo se determinará según la configuración de los ejes x e y en aes().

R

ggplot(data = rna_fc, mapping = aes(x = time_4_vs_0, y = time_8_vs_0,
                                color = gene_biotype)) +
  geom_point(alpha = 0.3)
Scatter plot produced by ggplot() and geom_point() comparing the log-foldchanges computed above. Dots are colour-coded based on the gene's biotype.

Por último, también podríamos añadir una línea diagonal con la función geom_abline():

R

ggplot(data = rna_fc, mapping = aes(x = time_4_vs_0, y = time_8_vs_0,
                                color = gene_biotype)) +
  geom_point(alpha = 0.3) +
  geom_abline(intercept = 0)
Scatter plot produced by ggplot() and geom_point() comparing the log-foldchanges computed above. Dots are colour-coded based on the gene's biotype. A black line of slope 1 crossing the origin was added by geom_abline().

Nótese que podemos cambiar la capa geom de geom_point a geom_jitter y los colores seguirán determinados por gene_biotype.

R

ggplot(data = rna_fc, mapping = aes(x = time_4_vs_0, y = time_8_vs_0,
                                color = gene_biotype)) +
  geom_jitter(alpha = 0.3) +
  geom_abline(intercept = 0)
Scatter plot produced by ggplot() and geom_point() comparing the log-foldchanges computed above. Dots are colour-coded based on the gene's biotype. A black line of slope 1 crossing the origin was added by geom_abline().
Desafío

Desafío

Los gráficos de dispersión pueden ser herramientas exploratorias útiles para conjuntos de datos pequeños. Para conjuntos de datos con un gran número de observaciones, como el conjunto de datos rna_fc, la superposición de puntos puede representar una limitación de los gráficos de dispersión. Una estrategia para manejar tales escenarios consiste en utilizar el agrupamiento hexagonal de las observaciones. El espacio del gráfico se tesela en hexágonos. A cada hexágono se le asigna un color basado en el número de observaciones que caen dentro de sus límites.

  • Para utilizar el agrupamiento hexagonal en ggplot2, primero instale el paquete de R hexbin desde CRAN y cárguelo.

  • A continuación, utilice la función geom_hex() para generar la figura de hexbin.

  • ¿Cuáles son las fortalezas y debilidades relativas de un gráfico de binaje hexagonal en comparación con un gráfico de dispersión? Examine el gráfico de dispersión anterior y compárelo con el gráfico de binaje hexagonal que creó.

R

install.packages("hexbin")

R

library("hexbin")

ggplot(data = rna_fc, mapping = aes(x = time_4_vs_0, y = time_8_vs_0)) +
  geom_hex() +
  geom_abline(intercept = 0)
Scatter plot produced by ggplot() and geom_hexbin() comparing the log-foldchanges computed above shows hexagons coloured based on the underlying dot density. A black line of slope 1 crossing the origin was added by geom_abline().
Desafío

Desafío

Utiliza lo que acabas de aprender para crear un gráfico de dispersión de expression_log sobre sample a partir del conjunto de datos rna, con el tiempo mostrado en diferentes colores. ¿Es esta una buena manera de representar este tipo de datos?

R

ggplot(data = rna, mapping = aes(y = expression_log, x = sample)) +
    geom_point(aes(color = time))
Figures showing a stretch of overlapping points indicating the log of expression + 1 for each sample. The points are coloured with different shades of blue for samples collected at different time points.

Gráfico de cajas


Podemos utilizar gráficos de cajas para visualizar la distribución de la expresión génica dentro de cada muestra:

R

ggplot(data = rna,
         mapping = aes(y = expression_log, x = sample)) +
  geom_boxplot()
Boxplot showing the distribution of log expression + 1 values for each sample, as produced by geom_boxlpot(). Each boxplot is filled with white colour.

Al añadir puntos al gráfico de cajas, podemos tener una mejor idea del número de mediciones y de su distribución:

R

ggplot(data = rna,
         mapping = aes(y = expression_log, x = sample)) +
  geom_jitter(alpha = 0.2, color = "tomato") +
  geom_boxplot(alpha = 0)
Boxplot and dots showing the distribution of log expression + 1 values for each sample, as produced by geom_boxlpot(). Each boxplot is transparent and the jittered dots are semi-transparent tomato-coloured and behind the boxpots.
Desafío

Desafío

Observa cómo la capa del gráfico de cajas está por delante de la capa de jitter. ¿Qué debes cambiar en el código para colocar el gráfico de cajas debajo de los puntos?

Deberíamos cambiar el orden de estos dos geoms:

R

ggplot(data = rna,
         mapping = aes(y = expression_log, x = sample)) +
  geom_boxplot(alpha = 0) +
  geom_jitter(alpha = 0.2, color = "tomato")
Boxplot and dots showing the distribution of log expression + 1 values for each sample, as produced by geom_boxlpot(). Each boxplot is transparent and the jittered dots are semi-transparent tomato-coloured. This time, the boxplots are behind the dots.

Es posible que notes que los valores del eje x todavía no son correctamente legibles. Cambiemos la orientación de las etiquetas y ajustémoslas vertical y horizontalmente para que no se solapen. Puedes usar un ángulo de 90 grados, o experimentar para encontrar el ángulo adecuado para las etiquetas orientadas diagonalmente:

R

ggplot(data = rna,
         mapping = aes(y = expression_log, x = sample)) +
  geom_jitter(alpha = 0.2, color = "tomato") +
  geom_boxplot(alpha = 0) +
  theme(axis.text.x = element_text(angle = 90,  hjust = 0.5, vjust = 0.5))
Boxplot and dots showing the distribution of log expression + 1 values for each sample, as produced by geom_boxlpot(). Each boxplot is transparent and the jittered dots are semi-transparent tomato-coloured. The sample labels are displayed vertically and readable.
Desafío

Desafío

Añada color a los puntos de datos de su gráfico de cajas según la duración de la infección (time).

Pista: Revisa la clase de time. Considera cambiar la clase de time de entero a factor directamente en la asignación de ggplot. ¿Por qué esto cambia la forma en que R crea el gráfico?

R

# time as integer
ggplot(data = rna,
         mapping = aes(y = expression_log,
                       x = sample)) +
  geom_jitter(alpha = 0.2, aes(color = time)) +
  geom_boxplot(alpha = 0) +
  theme(axis.text.x = element_text(angle = 90,  hjust = 0.5, vjust = 0.5))
Boxplot and dots showing the distribution of log expression + 1 values for each sample, as produced by geom_boxlpot(). On the first figure, each boxplot is transparent and the jittered dots are semi-transparent and coloured in different shares of blue. On the second figures, each boxplot is transparent and the jittered dots are semi-transparent and coloured red, green and blue.

R

# time as factor
ggplot(data = rna,
         mapping = aes(y = expression_log,
                       x = sample)) +
  geom_jitter(alpha = 0.2, aes(color = as.factor(time))) +
  geom_boxplot(alpha = 0) +
  theme(axis.text.x = element_text(angle = 90,  hjust = 0.5, vjust = 0.5))
Boxplot and dots showing the distribution of log expression + 1 values for each sample, as produced by geom_boxlpot(). On the first figure, each boxplot is transparent and the jittered dots are semi-transparent and coloured in different shares of blue. On the second figures, each boxplot is transparent and the jittered dots are semi-transparent and coloured red, green and blue.
Desafío

Desafío

Los gráficos de cajas son resúmenes útiles, pero ocultan la forma de la distribución. Por ejemplo, si la distribución es bimodal, no se vería en un gráfico de cajas. Una alternativa al gráfico de cajas es el gráfico de violín, donde se dibuja la forma (de la densidad de los puntos).

  • Sustituya el gráfico de cajas por un gráfico de violín; consulte geom_violin(). Rellene los violines según el tiempo con el argumento fill.

R

ggplot(data = rna,
         mapping = aes(y = expression_log, x = sample)) +
  geom_violin(aes(fill = as.factor(time))) +
  theme(axis.text.x = element_text(angle = 90,  hjust = 0.5, vjust = 0.5))
Violin plot showing the distribution of log expression + 1 values for each sample, as produced by geom_violin(). Each boxplot is transparent and the jittered dots are semi-transparent and coloured red, green and blue.
Desafío

Desafío

  • Modifique el gráfico en forma de violín para rellenar los violines mediante sex.

R

ggplot(data = rna,
         mapping = aes(y = expression_log, x = sample)) +
  geom_violin(aes(fill = sex)) +
  theme(axis.text.x = element_text(angle = 90,  hjust = 0.5, vjust = 0.5))
Violin plot showing the distribution of log expression + 1 values for each sample, as produced by geom_violon(). Each violin plot is coloured in red or blue depending on the sex variable.

Gráficos de líneas


Calculemos la expresión media por duración de la infección para los 10 genes con los mayores cambios relativos de expresión en escala logarítmica al comparar el tiempo 8 con el tiempo 0. Primero, debemos seleccionar los genes y crear un subconjunto de rna llamado sub_rna que contenga los 10 genes seleccionados; luego, debemos agrupar los datos y calcular la expresión génica media dentro de cada grupo:

R

rna_fc <- rna_fc |> arrange(desc(time_8_vs_0))

genes_selected <- rna_fc$gene[1:10]

sub_rna <- rna |>
    filter(gene %in% genes_selected)

mean_exp_by_time <- sub_rna |>
  group_by(gene,time) |>
    summarize(mean_exp = mean(expression_log))

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene and time.
ℹ Output is grouped by gene.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

R

mean_exp_by_time

SALIDA

# A tibble: 30 × 3
# Groups:   gene [10]
   gene   time mean_exp
   <chr> <int>    <dbl>
 1 Acr       0     5.07
 2 Acr       4     5.54
 3 Acr       8     7.31
 4 Aipl1     0     3.70
 5 Aipl1     4     3.89
 6 Aipl1     8     6.56
 7 Bst1      0     3.20
 8 Bst1      4     3.77
 9 Bst1      8     5.22
10 Chil3     0     4.00
# ℹ 20 more rows

Podemos crear el gráfico de líneas con la duración de la infección en el eje x y la expresión media en el eje y:

R

ggplot(data = mean_exp_by_time, mapping = aes(x = time, y = mean_exp)) +
  geom_line()
Line plot, as produced by geom_line(), but the lines are clearly not show what we expect.

Lamentablemente, esto no funciona porque representamos los datos de todos los genes juntos. Necesitamos indicar a ggplot que dibuje una línea para cada gen modificando la función estética para incluir group = gene:

R

ggplot(data = mean_exp_by_time,
       mapping = aes(x = time, y = mean_exp, group = gene)) +
  geom_line()
Line plot, as produced by geom_line(), with 10 lines showing increasing expression values over time.

Podremos distinguir los genes en el gráfico si añadimos colores (el uso de color también agrupa automáticamente los datos):

R

ggplot(data = mean_exp_by_time,
       mapping = aes(x = time, y = mean_exp, color = gene)) +
  geom_line()
Line plot, as produced by geom_line(), with 10 colour-coded lines showing increasing expression values over time.

Faceting


ggplot2 cuenta con una técnica especial llamada faceting que permite al usuario dividir un gráfico en múltiples gráficos (subgráficos) basándose en un factor incluido en el conjunto de datos. Estos diferentes subgráficos heredan las mismas propiedades (límites de los ejes, marcas, …) para facilitar su comparación directa. Lo utilizaremos para crear un gráfico de líneas a lo largo del tiempo para cada gen:

R

ggplot(data = mean_exp_by_time,
       mapping = aes(x = time, y = mean_exp)) + geom_line() +
  facet_wrap(~ gene)
Line plot, as produced by geom_line(), with 10 sub-plots/facets, each showing one line with increasing expression values over time. All y-scales are identical.

Aquí, tanto el eje x como el y tienen la misma escala en todos los subgráficos. Puede cambiar este comportamiento predeterminado modificando scales para permitir una escala libre para el eje y:

R

ggplot(data = mean_exp_by_time,
       mapping = aes(x = time, y = mean_exp)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y")
Line plot, as produced by geom_line(), with 10 sub-plots/facets, each showing one line with increasing expression values over time. y-scales are now adapted to the expression range for each facet/gene.

Ahora queremos dividir la línea en cada gráfico según el sexo de los ratones. Para ello, necesitamos calcular la expresión media en la tabla de datos agrupada por gene, time y sex:

R

mean_exp_by_time_sex <- sub_rna |>
  group_by(gene, time, sex) |>
    summarize(mean_exp = mean(expression_log))

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by gene, time, and sex.
ℹ Output is grouped by gene and time.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(gene, time, sex))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

R

mean_exp_by_time_sex

SALIDA

# A tibble: 60 × 4
# Groups:   gene, time [30]
   gene   time sex    mean_exp
   <chr> <int> <chr>     <dbl>
 1 Acr       0 Female     5.13
 2 Acr       0 Male       5.00
 3 Acr       4 Female     5.93
 4 Acr       4 Male       5.15
 5 Acr       8 Female     7.27
 6 Acr       8 Male       7.36
 7 Aipl1     0 Female     3.67
 8 Aipl1     0 Male       3.73
 9 Aipl1     4 Female     4.07
10 Aipl1     4 Male       3.72
# ℹ 50 more rows

Ahora podemos crear el gráfico facetado dividiendo aún más por sexo utilizando color (dentro de un único gráfico):

R

ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y")
Line plot, as produced by geom_line(), with 10 sub-plots/facets, each showing two coloured lines (red for Female and blue for Make) with increasing expression values over time.

Por lo general, los gráficos con fondo blanco resultan más legibles al imprimirlos. Podemos establecer el fondo en blanco utilizando la función theme_bw(). Además, podemos eliminar la cuadrícula:

R

ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank())
Line plot, as produced by geom_line(), with 10 sub-plots/facets, each showing two coloured lines (red for Female and blue for Make) with increasing expression values over time. The figure background is now white.
Desafío

Desafío

Utiliza lo que acabas de aprender para crear un gráfico que represente cómo varía la expresión promedio de cada cromosoma a lo largo de la duración de la infección.

R

mean_exp_by_chromosome <- rna |>
  group_by(chromosome_name, time) |>
  summarize(mean_exp = mean(expression_log))

SALIDA

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by chromosome_name and time.
ℹ Output is grouped by chromosome_name.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(chromosome_name, time))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

R

ggplot(data = mean_exp_by_chromosome, mapping = aes(x = time,
                                y = mean_exp)) +
  geom_line() +
  facet_wrap(~ chromosome_name, scales = "free_y")
Line plot, as produced by geom_line(), with 21 sub-plots/facets, each showing one line with expression values over time for each chromosome.

La geometría facet_wrap extrae los gráficos en un número arbitrario de dimensiones para que quepan correctamente en una sola página. Por otro lado, la geometría facet_grid permite especificar explícitamente cómo se desea que estén dispuestos los gráficos mediante la notación de fórmula (rows ~ columns; un . puede utilizarse como marcador que indique una sola fila o columna).

Modifiquemos el gráfico anterior para comparar cómo ha cambiado la expresión génica media de los machos y las hembras a lo largo del tiempo:

R

# One column, facet by rows
ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = gene)) +
  geom_line() +
  facet_grid(sex ~ .)
Two line plots above each other, each showing 10 lines coloured by gene. The top facet shows the expression values for Female sample, the bottom one for Male samples.

R

# One row, facet by column
ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = gene)) +
  geom_line() +
  facet_grid(. ~ sex)
Two line plots next to each other, each showing 10 lines coloured by gene. The left facet shows the expression values for Female sample, the right one for Male samples.

Temas de ggplot2


Además de theme_bw(), que cambia el fondo del gráfico a blanco, ggplot2 incluye varios otros temas que pueden resultar útiles para cambiar rápidamente la apariencia de su visualización. La lista completa de temas está disponible en https://ggplot2.tidyverse.org/reference/ggtheme.html. theme_minimal() y theme_light() son populares, y theme_void() puede resultar útil como punto de partida para crear un nuevo tema diseñado a mano.

El paquete ggthemes ofrece una amplia variedad de opciones (incluido un tema de Excel 2003). El sitio web de extensiones ggplot2 proporciona una lista de paquetes que amplían las capacidades de ggplot2, incluidos temas adicionales.

Personalización


Volvamos al gráfico facetado de la expresión media según el tiempo y el gen, coloreado por sexo.

Echa un vistazo a la hoja de trucos ggplot2 y piensa en formas de mejorar el gráfico.

Ahora, podemos cambiar los nombres de los ejes por algo más informativo que ‘time’ y ‘mean_exp’, y añadir un título a la figura:

R

ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank()) +
  labs(title = "Mean gene expression by duration of the infection",
       x = "Duration of the infection (in days)",
       y = "Mean gene expression")
Line plot with white background and custom title and axis legends.

Los ejes tienen nombres más informativos, pero su legibilidad puede mejorarse aumentando el tamaño de la fuente:

R

ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank()) +
  labs(title = "Mean gene expression by duration of the infection",
       x = "Duration of the infection (in days)",
       y = "Mean gene expression")  +
  theme(text = element_text(size = 16))
Line plot with white background and larger custom title and axis legends.

Tenga en cuenta que también es posible cambiar las fuentes de sus gráficos. Si utiliza Windows, es posible que tenga que instalar el paquete extrafont.

Podemos personalizar aún más el color del texto de los ejes x e y, el color de la cuadrícula, etc. También podemos, por ejemplo, mover la leyenda a la parte superior estableciendo legend.position en "top".

R

ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank()) +
  labs(title = "Mean gene expression by duration of the infection",
       x = "Duration of the infection (in days)",
       y = "Mean gene expression")  +
  theme(text = element_text(size = 16),
        axis.text.x = element_text(colour = "royalblue4", size = 12),
        axis.text.y = element_text(colour = "royalblue4", size = 12),
        panel.grid = element_line(colour="lightsteelblue1"),
        legend.position = "top")
Line plot with white background and larger custom title and axis legends and blue grid.

Si te gustan más los cambios que has realizado que el tema predeterminado, puedes guardarlos como un objeto para poder aplicarlos fácilmente a otros gráficos que crees. Aquí tienes un ejemplo con el histograma que creamos anteriormente.

R

blue_theme <- theme(axis.text.x = element_text(colour = "royalblue4",
                                               size = 12),
                    axis.text.y = element_text(colour = "royalblue4",
                                               size = 12),
                    text = element_text(size = 16),
                    panel.grid = element_line(colour="lightsteelblue1"))

ggplot(rna, aes(x = expression_log)) +
  geom_histogram(bins = 20) +
    blue_theme
Custrom 'blue_theme() showing a blue grid and blue axis legends.
Desafío

Desafío

Con toda esta información a mano, tómese otros cinco minutos para mejorar uno de los gráficos generados en este ejercicio o crear su propio gráfico impecable. Utilice la hoja de trucos ggplot2 de RStudio para inspirarse. Aquí tiene algunas ideas:

  • Mira si puedes cambiar el grosor de las líneas.
  • ¿Puedes encontrar una forma de cambiar el nombre de la leyenda? ¿Y sus etiquetas? (pista: busca una función de ggplot que empiece por scale_)
  • Prueba a utilizar una paleta de colores diferente o a especificar manualmente los colores de las líneas (consulta https://www.cookbook-r.com/Graphs/Colors_(ggplot2)/).

Por ejemplo, basándose en este gráfico:

R

ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank())
Plain facetted line plot, as produced by theme_bw() and a blank grid above.

Podemos personalizarlo de las siguientes maneras:

R

# change the thickness of the lines
ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line(size=1.5) +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank())

ADVERTENCIA

Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
This warning is displayed once per session.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
generated.
Plain facetted line plot, as produced by theme_bw() and a blank grid with wide lines.

R

# change the name of the legend and the labels
ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank()) +
  scale_color_discrete(name = "Gender", labels = c("F", "M"))
Plain facetted line plot, as produced by theme_bw() and a blank grid with renamed colour labels.

R

# using a different color palette
ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank()) +
  scale_color_brewer(name = "Gender", labels = c("F", "M"), palette = "Dark2")
Plain facetted line plot, as produced by theme_bw() and a blank grid with a different color palette and renamed colour labels.

R

# manually specifying the colors
ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  theme_bw() +
  theme(panel.grid = element_blank()) +
  scale_color_manual(name = "Gender",  labels = c("F", "M"),
                     values = c("royalblue", "deeppink"))
Plain facetted line plot, as produced by theme_bw() and a blank grid with a manually-set colors and renamed colour labels..

Composición de gráficos


El faceting es una herramienta excelente para dividir un gráfico en múltiples subgráficos, pero a veces es posible que desee crear una sola figura que contenga varios gráficos independientes, es decir, gráficos basados en variables diferentes o incluso en tablas de datos distintas.

Comencemos creando los dos gráficos que queremos disponer uno junto al otro:

El primer gráfico cuenta el número de genes únicos por cromosoma. Primero debemos reordenar los niveles de chromosome_name y filtrar los genes únicos por cromosoma. También cambiamos la escala del eje y a una escala logarítmica en base 10 para mejorar la legibilidad.

R

rna$chromosome_name <- factor(rna$chromosome_name,
                               levels = c(1:19,"X","Y"))

count_gene_chromosome <- rna |>
  select(chromosome_name, gene) |>
  distinct() |> ggplot() +
  geom_bar(aes(x = chromosome_name), fill = "seagreen",
           position = "dodge", stat = "count") +
  labs(y = "log10(n genes)", x = "chromosome") +
  scale_y_log10()

count_gene_chromosome
A simple histogram with greed bars showing the log10 number of genes per chromosome.

A continuación, también eliminamos la leyenda por completo estableciendo legend.position en "none".

R

exp_boxplot_sex <- ggplot(rna, aes(y=expression_log, x = as.factor(time),
                 color=sex)) +
   geom_boxplot(alpha = 0) +
  labs(y = "Mean gene exp",
       x = "time") + theme(legend.position = "none")

exp_boxplot_sex
Transparently-filled red and blue boxplots for Female and Male expression values for different time points.

El paquete patchwork ofrece un enfoque elegante para combinar figuras utilizando + para organizarlas (normalmente una al lado de la otra). Más concretamente, | las organiza explícitamente una al lado de la otra y / las apila una sobre otra.

R

install.packages("patchwork")

R

library("patchwork")
count_gene_chromosome + exp_boxplot_sex
A composed figure show the histogram (left) and boxplots (right) next to each other.

R

## or count_gene_chromosome | exp_boxplot_sex

R

count_gene_chromosome / exp_boxplot_sex
A composed figure show the histogram (top) and boxplots (bottom) on top or each other.

Podemos controlar aún más la disposición de la composición final con plot_layout para crear diseños más complejos:

R

count_gene_chromosome + exp_boxplot_sex + plot_layout(ncol = 1)
A composed figure show the histogram (top) and boxplots (bottom) on top or each other.

R

count_gene_chromosome +
 (count_gene_chromosome + exp_boxplot_sex) +
 exp_boxplot_sex +
 plot_layout(ncol = 1)
A figure composed of 4 plots with on top the histogram, in the middle the histogram and the boxpot, side-by-side, and at the bottom, the boxplot.

El último gráfico también puede crearse utilizando los compositores | y /:

R

count_gene_chromosome /
 (count_gene_chromosome | exp_boxplot_sex) /
 exp_boxplot_sex
As above, a figure composed of 4 plots with on top the histogram, in the middle the histogram and the boxpot, side-by-side, and at the bottom, the boxplot.

Obtenga más información sobre patchwork en su página web o en este video.

Otra opción es el paquete gridExtra, que permite combinar ggplots independientes en una sola figura mediante grid.arrange():

R

install.packages("gridExtra")

R

library("gridExtra")
grid.arrange(count_gene_chromosome, exp_boxplot_sex, ncol = 2)
A composed figure with the histogram (left) and boxplot (right) side-by-side.

Además de los argumentos ncol y nrow, utilizados para realizar diseños sencillos, existen herramientas para construir diseños más complejos.

Exportación de gráficos


Tras crear su gráfico, puede guardarlo en un archivo en su formato preferido. La pestaña Exportar en el panel Plot de RStudio guardará sus gráficos a baja resolución, la cual no será aceptada por muchas revistas y no escalará bien para pósteres.

En su lugar, utilice la función ggsave(), que le permite cambiar fácilmente la dimensión y la resolución de su gráfico ajustando los argumentos correspondientes (width, height y dpi).

Asegúrese de tener la carpeta fig_output/ en su directorio de trabajo.

R

my_plot <- ggplot(data = mean_exp_by_time_sex,
       mapping = aes(x = time, y = mean_exp, color = sex)) +
  geom_line() +
  facet_wrap(~ gene, scales = "free_y") +
  labs(title = "Mean gene expression by duration of the infection",
         x = "Duration of the infection (in days)",
         y = "Mean gene expression") +
  guides(color=guide_legend(title="Gender")) +
  theme_bw() +
  theme(axis.text.x = element_text(colour = "royalblue4", size = 12),
        axis.text.y = element_text(colour = "royalblue4", size = 12),
        text = element_text(size = 16),
        panel.grid = element_line(colour="lightsteelblue1"),
        legend.position = "top")
ggsave("fig_output/mean_exp_by_time_sex.png", my_plot, width = 15,
       height = 10)

# This also works for grid.arrange() plots
combo_plot <- grid.arrange(count_gene_chromosome, exp_boxplot_sex,
                           ncol = 2, widths = c(4, 6))
ggsave("fig_output/combo_plot_chromosome_sex.png", combo_plot,
       width = 10, dpi = 300)

Nota: Los parámetros width y height también determinan el tamaño de la fuente en el gráfico guardado.

Otros paquetes para la visualización


ggplot2 es un paquete muy potente que encaja a la perfección en nuestro pipeline de datos ordenados y herramientas ordenadas. Existen otros paquetes de visualización en R que no deben pasarse por alto.

Gráficos básicos

El sistema gráfico predeterminado que incluye R, a menudo llamado gráficos básicos de R, es sencillo y rápido. Se basa en el modelo del pintor o del lienzo, donde diferentes resultados se superponen directamente entre sí (véase la figura siguiente ). Esta es una diferencia fundamental con ggplot2 (y con lattice, descrito más adelante), que devuelve objetos dedicados que se renderizan en pantalla o en un archivo, e incluso pueden actualizarse.

R

par(mfrow = c(1, 3))
plot(1:20, main = "First layer, produced with plot(1:20)")

plot(1:20, main = "A horizontal red line, added with abline(h = 10)")
abline(h = 10, col = "red")

plot(1:20, main = "A rectangle, added with rect(5, 5, 15, 15)")
abline(h = 10, col = "red")
rect(5, 5, 15, 15, lwd = 3)
The 'base' figures, side-by-side, showing (from left to right) 20 empty dots along the diagonal, same with a vertical red line, and same as the second one with a rectangle overlaid in the middle of the plot.
Successive layers added on top of each other.

Otra diferencia principal es que las funciones de trazado de los gráficos básicos intentan hacer lo correcto según el tipo de entrada; es decir, adaptan su comportamiento en función de la clase de su entrada. Esto es, nuevamente, muy diferente a lo que tenemos en ggplot2, que solo acepta tablas de datos como entrada y requiere que los gráficos se construyan poco a poco.

R

par(mfrow = c(2, 2))
boxplot(rnorm(100),
        main = "Boxplot of rnorm(100)")
boxplot(matrix(rnorm(100), ncol = 10),
        main = "Boxplot of matrix(rnorm(100), ncol = 10)")
hist(rnorm(100))
hist(matrix(rnorm(100), ncol = 10))
A two-by-two composition of 2 boxplots at the top, and 2 histograms at the bottom.
Plotting boxplots (top) and histograms (bottom) vectors (left) or a matrices (right).

El enfoque estándar de los gráficos básicos puede ser muy eficiente para figuras sencillas y estándar, que pueden producirse muy rápidamente con una sola línea de código y una sola función como plot, o hist, o boxplot, … Sin embargo, los valores predeterminados no siempre son los más atractivos, y el ajuste de las figuras, especialmente cuando estas se vuelven más complejas (por ejemplo, para producir facetas), puede resultar largo y engorroso.

El paquete lattice

El paquete lattice es similar a ggplot2 en que utiliza tablas de datos como entrada, devuelve objetos gráficos y admite el faceting. lattice, sin embargo, no se basa en la gramática de gráficos y tiene una interfaz más compleja.

Una buena referencia para el paquete lattice es Deepayan (2008).

Puntos Clave
  • Visualización en R
Deepayan, Sarkar. 2008. Lattice: Multivariate Data Visualization with r. Springer. http://lmdvr.r-forge.r-project.org.
Wickham, Hadley. 2016. ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York. http://ggplot2.org.
Wilkinson, Leland. 2005. The Grammar of Graphics (Statistics and Computing). Springer-Verlag.

Content from Próximos pasos


Última actualización: 2026-11-11 | Mejora esta página

Tiempo estimado: 90 minutos

Hoja de ruta

Preguntas

  • ¿Qué es un SummarizedExperiment?
  • ¿Qué es Bioconductor?

Objetivos

  • Presentar el proyecto Bioconductor.
  • Presentar el concepto de contenedores de datos.
  • Proporcione una visión general del SummarizedExperiment, ampliamente utilizado en análisis ómicos.

Próximos pasos


Los datos en bioinformática suelen ser complejos. Para abordar esto, los desarrolladores definen contenedores de datos especializados (denominados clases) que coinciden con las propiedades de los datos que necesitan manejar.

Este aspecto es fundamental para el proyecto Bioconductor1, que utiliza la misma infraestructura de datos central en todos sus paquetes. Esto ciertamente contribuyó al éxito de Bioconductor. Se recomienda a los desarrolladores de paquetes de Bioconductor que hagan uso de la infraestructura existente para proporcionar coherencia, interoperabilidad y estabilidad al proyecto en su conjunto.

Para ilustrar dicho contenedor de datos ómicos, presentaremos la clase SummarizedExperiment.

SummarizedExperiment


La siguiente figura representa la estructura de la clase SummarizedExperiment.

Schematic representation of the SummarizedExperiment class illustrating the following slots: fowData and rowRanges, one assay or multiple assays, colData and metadata.

Los objetos de la clase SummarizedExperiment contienen:

  • Uno o varios ensayos que contienen los datos ómicos cuantitativos (datos de expresión), cada uno almacenado como un objeto similar a una matriz. Las características (genes, transcritos, proteínas, …) se definen a lo largo de las filas y las muestras a lo largo de las columnas.

  • Una ranura de metadatos de muestras que contiene covariables de las muestras, almacenada como una tabla de datos. Las filas de esta tabla representan las muestras (las filas coinciden exactamente con las columnas de los datos de expresión).

  • Una ranura de metadatos de características que contiene covariables de características, almacenada como una tabla de datos. Las filas de esta tabla de datos coinciden exactamente con las filas de los datos de expresión.

La naturaleza coordinada de SummarizedExperiment garantiza que las dimensiones de los distintos componentes coincidan siempre durante la manipulación de datos (es decir, las columnas de los datos de expresión con las filas de los metadatos de las muestras, y las filas de los datos de expresión con las filas de los metadatos de las características). Por ejemplo, si tuviéramos que excluir una muestra del ensayo, esta se eliminaría automáticamente de los metadatos de las muestras en la misma operación.

Los slots de metadatos pueden ampliarse con covariables (columnas) adicionales sin afectar a las otras estructuras.

Creación de un SummarizedExperiment

Para crear un SummarizedExperiment, crearemos los componentes individuales, es decir, la matriz de conteos y los metadatos de muestras y genes a partir de archivos csv. Por lo general, así es como se proporcionan los datos de RNA-Seq (una vez procesados los datos brutos).

  • Una matriz de expresión: cargamos la matriz de conteos, especificando que la primera columna contiene los nombres de las filas/genes, y convertimos el data.frame en una matrix. Puede descargarla haciendo clic en este enlace.

R

count_matrix <- read.csv("data/count_matrix.csv",
                         row.names = 1) |>
    as.matrix()

count_matrix[1:5, ]

SALIDA

        GSM2545336 GSM2545337 GSM2545338 GSM2545339 GSM2545340 GSM2545341
Asl           1170        361        400        586        626        988
Apod         36194      10347       9173      10620      13021      29594
Cyp2d22       4060       1616       1603       1901       2171       3349
Klk6           287        629        641        578        448        195
Fcrls           85        233        244        237        180         38
        GSM2545342 GSM2545343 GSM2545344 GSM2545345 GSM2545346 GSM2545347
Asl            836        535        586        597        938       1035
Apod         24959      13668      13230      15868      27769      34301
Cyp2d22       3122       2008       2254       2277       2985       3452
Klk6           186       1101        537        567        327        233
Fcrls           68        375        199        177         89         67
        GSM2545348 GSM2545349 GSM2545350 GSM2545351 GSM2545352 GSM2545353
Asl            494        481        666        937        803        541
Apod         11258      11812      15816      29242      20415      13682
Cyp2d22       1883       2014       2417       3678       2920       2216
Klk6           742        881        828        250        798        710
Fcrls          300        233        231         81        303        285
        GSM2545354 GSM2545362 GSM2545363 GSM2545380
Asl            473        748        576       1192
Apod         11088      15916      11166      38148
Cyp2d22       1821       2842       2011       4019
Klk6           894        501        598        259
Fcrls          248        179        184         68

R

dim(count_matrix)

SALIDA

[1] 1474   22

R

sample_metadata <- read.csv("data/sample_metadata.csv")
sample_metadata

SALIDA

       sample     organism age    sex   infection  strain time     tissue mouse
1  GSM2545336 Mus musculus   8 Female  InfluenzaA C57BL/6    8 Cerebellum    14
2  GSM2545337 Mus musculus   8 Female NonInfected C57BL/6    0 Cerebellum     9
3  GSM2545338 Mus musculus   8 Female NonInfected C57BL/6    0 Cerebellum    10
4  GSM2545339 Mus musculus   8 Female  InfluenzaA C57BL/6    4 Cerebellum    15
5  GSM2545340 Mus musculus   8   Male  InfluenzaA C57BL/6    4 Cerebellum    18
6  GSM2545341 Mus musculus   8   Male  InfluenzaA C57BL/6    8 Cerebellum     6
7  GSM2545342 Mus musculus   8 Female  InfluenzaA C57BL/6    8 Cerebellum     5
8  GSM2545343 Mus musculus   8   Male NonInfected C57BL/6    0 Cerebellum    11
9  GSM2545344 Mus musculus   8 Female  InfluenzaA C57BL/6    4 Cerebellum    22
10 GSM2545345 Mus musculus   8   Male  InfluenzaA C57BL/6    4 Cerebellum    13
11 GSM2545346 Mus musculus   8   Male  InfluenzaA C57BL/6    8 Cerebellum    23
12 GSM2545347 Mus musculus   8   Male  InfluenzaA C57BL/6    8 Cerebellum    24
13 GSM2545348 Mus musculus   8 Female NonInfected C57BL/6    0 Cerebellum     8
14 GSM2545349 Mus musculus   8   Male NonInfected C57BL/6    0 Cerebellum     7
15 GSM2545350 Mus musculus   8   Male  InfluenzaA C57BL/6    4 Cerebellum     1
16 GSM2545351 Mus musculus   8 Female  InfluenzaA C57BL/6    8 Cerebellum    16
17 GSM2545352 Mus musculus   8 Female  InfluenzaA C57BL/6    4 Cerebellum    21
18 GSM2545353 Mus musculus   8 Female NonInfected C57BL/6    0 Cerebellum     4
19 GSM2545354 Mus musculus   8   Male NonInfected C57BL/6    0 Cerebellum     2
20 GSM2545362 Mus musculus   8 Female  InfluenzaA C57BL/6    4 Cerebellum    20
21 GSM2545363 Mus musculus   8   Male  InfluenzaA C57BL/6    4 Cerebellum    12
22 GSM2545380 Mus musculus   8 Female  InfluenzaA C57BL/6    8 Cerebellum    19

R

dim(sample_metadata)

SALIDA

[1] 22  9

R

gene_metadata <- read.csv("data/gene_metadata.csv")
gene_metadata[1:10, 1:4]

SALIDA

      gene ENTREZID
1      Asl   109900
2     Apod    11815
3  Cyp2d22    56448
4     Klk6    19144
5    Fcrls    80891
6   Slc2a4    20528
7     Exd2    97827
8     Gjc2   118454
9     Plp1    18823
10    Gnb4    14696
                                                                         product
1                                 argininosuccinate lyase, transcript variant X1
2                                         apolipoprotein D, transcript variant 3
3   cytochrome P450, family 2, subfamily d, polypeptide 22, transcript variant 2
4                           kallikrein related-peptidase 6, transcript variant 2
5                  Fc receptor-like S, scavenger receptor, transcript variant X1
6            solute carrier family 2 (facilitated glucose transporter), member 4
7                                          exonuclease 3'-5' domain containing 2
8                            gap junction protein, gamma 2, transcript variant 1
9                           proteolipid protein (myelin) 1, transcript variant 1
10 guanine nucleotide binding protein (G protein), beta 4, transcript variant X2
      ensembl_gene_id
1  ENSMUSG00000025533
2  ENSMUSG00000022548
3  ENSMUSG00000061740
4  ENSMUSG00000050063
5  ENSMUSG00000015852
6  ENSMUSG00000018566
7  ENSMUSG00000032705
8  ENSMUSG00000043448
9  ENSMUSG00000031425
10 ENSMUSG00000027669

R

dim(gene_metadata)

SALIDA

[1] 1474    9

Crearemos un SummarizedExperiment a partir de estas tablas:

  • La matriz de conteos que se utilizará como el assay

  • La tabla que describe las muestras se utilizará como la ranura metadatos de la muestra

  • La tabla que describe los genes se utilizará como la ranura de metadatos de características

Para ello, podemos unir las diferentes partes utilizando el constructor SummarizedExperiment:

R

## BiocManager::install("SummarizedExperiment")
library("SummarizedExperiment")

Primero, nos aseguramos de que las muestras estén en el mismo orden en la matriz de conteos y en la anotación de muestras, y lo mismo ocurra con los genes en la matriz de conteos y en la anotación de genes.

R

stopifnot(rownames(count_matrix) == gene_metadata$gene)
stopifnot(colnames(count_matrix) == sample_metadata$sample)

R

se <- SummarizedExperiment(assays = list(counts = count_matrix),
                           colData = sample_metadata,
                           rowData = gene_metadata)
se

SALIDA

class: SummarizedExperiment
dim: 1474 22
metadata(0):
assays(1): counts
rownames(1474): Asl Apod ... Lmx1a Pbx1
rowData names(9): gene ENTREZID ... phenotype_description
  hsapiens_homolog_associated_gene_name
colnames(22): GSM2545336 GSM2545337 ... GSM2545363 GSM2545380
colData names(9): sample organism ... tissue mouse

Guardar datos

Exportar datos a una hoja de cálculo, como hicimos en un episodio anterior, presenta varias limitaciones, tales como las descritas en el primer capítulo (posibles inconsistencias con , y . como separadores decimales y la falta de definiciones de tipos de variables). Además, exportar datos a una hoja de cálculo solo es relevante para datos rectangulares, como las tablas de datos y las matrices.

Una forma más general de guardar datos, específica de R y que garantiza que funcione en cualquier sistema operativo, es utilizar la función saveRDS. Guardar objetos de este modo generará una representación binaria en el disco (utilizando aquí la extensión de archivo rds), que puede cargarse de nuevo en R mediante la función readRDS.

R

saveRDS(se, file = "data_output/se.rds")
rm(se)
se <- readRDS("data_output/se.rds")
head(se)

Para concluir, cuando se trata de guardar datos desde R que serán cargados nuevamente en R, el método preferido es guardar y cargar con saveRDS y readRDS. Si los datos en formato tabular deben compartirse con alguien que no utilice R, exportarlos a una hoja de cálculo basada en texto es una buena alternativa.

Utilizando esta estructura de datos, podemos acceder a la matriz de expresión con la función assay:

R

head(assay(se))

SALIDA

        GSM2545336 GSM2545337 GSM2545338 GSM2545339 GSM2545340 GSM2545341
Asl           1170        361        400        586        626        988
Apod         36194      10347       9173      10620      13021      29594
Cyp2d22       4060       1616       1603       1901       2171       3349
Klk6           287        629        641        578        448        195
Fcrls           85        233        244        237        180         38
Slc2a4         782        231        248        265        313        786
        GSM2545342 GSM2545343 GSM2545344 GSM2545345 GSM2545346 GSM2545347
Asl            836        535        586        597        938       1035
Apod         24959      13668      13230      15868      27769      34301
Cyp2d22       3122       2008       2254       2277       2985       3452
Klk6           186       1101        537        567        327        233
Fcrls           68        375        199        177         89         67
Slc2a4         528        249        266        357        654        693
        GSM2545348 GSM2545349 GSM2545350 GSM2545351 GSM2545352 GSM2545353
Asl            494        481        666        937        803        541
Apod         11258      11812      15816      29242      20415      13682
Cyp2d22       1883       2014       2417       3678       2920       2216
Klk6           742        881        828        250        798        710
Fcrls          300        233        231         81        303        285
Slc2a4         271        304        349        715        513        320
        GSM2545354 GSM2545362 GSM2545363 GSM2545380
Asl            473        748        576       1192
Apod         11088      15916      11166      38148
Cyp2d22       1821       2842       2011       4019
Klk6           894        501        598        259
Fcrls          248        179        184         68
Slc2a4         248        350        317        796

R

dim(assay(se))

SALIDA

[1] 1474   22

Podemos acceder a los metadatos de la muestra utilizando la función colData:

R

colData(se)

SALIDA

DataFrame with 22 rows and 9 columns
                sample     organism       age         sex   infection
           <character>  <character> <integer> <character> <character>
GSM2545336  GSM2545336 Mus musculus         8      Female  InfluenzaA
GSM2545337  GSM2545337 Mus musculus         8      Female NonInfected
GSM2545338  GSM2545338 Mus musculus         8      Female NonInfected
GSM2545339  GSM2545339 Mus musculus         8      Female  InfluenzaA
GSM2545340  GSM2545340 Mus musculus         8        Male  InfluenzaA
...                ...          ...       ...         ...         ...
GSM2545353  GSM2545353 Mus musculus         8      Female NonInfected
GSM2545354  GSM2545354 Mus musculus         8        Male NonInfected
GSM2545362  GSM2545362 Mus musculus         8      Female  InfluenzaA
GSM2545363  GSM2545363 Mus musculus         8        Male  InfluenzaA
GSM2545380  GSM2545380 Mus musculus         8      Female  InfluenzaA
                strain      time      tissue     mouse
           <character> <integer> <character> <integer>
GSM2545336     C57BL/6         8  Cerebellum        14
GSM2545337     C57BL/6         0  Cerebellum         9
GSM2545338     C57BL/6         0  Cerebellum        10
GSM2545339     C57BL/6         4  Cerebellum        15
GSM2545340     C57BL/6         4  Cerebellum        18
...                ...       ...         ...       ...
GSM2545353     C57BL/6         0  Cerebellum         4
GSM2545354     C57BL/6         0  Cerebellum         2
GSM2545362     C57BL/6         4  Cerebellum        20
GSM2545363     C57BL/6         4  Cerebellum        12
GSM2545380     C57BL/6         8  Cerebellum        19

R

dim(colData(se))

SALIDA

[1] 22  9

También podemos acceder a los metadatos de las características utilizando la función rowData:

R

head(rowData(se))

SALIDA

DataFrame with 6 rows and 9 columns
               gene  ENTREZID                product    ensembl_gene_id
        <character> <integer>            <character>        <character>
Asl             Asl    109900 argininosuccinate ly.. ENSMUSG00000025533
Apod           Apod     11815 apolipoprotein D, tr.. ENSMUSG00000022548
Cyp2d22     Cyp2d22     56448 cytochrome P450, fam.. ENSMUSG00000061740
Klk6           Klk6     19144 kallikrein related-p.. ENSMUSG00000050063
Fcrls         Fcrls     80891 Fc receptor-like S, .. ENSMUSG00000015852
Slc2a4       Slc2a4     20528 solute carrier famil.. ENSMUSG00000018566
        external_synonym chromosome_name   gene_biotype  phenotype_description
             <character>     <character>    <character>            <character>
Asl        2510006M18Rik               5 protein_coding abnormal circulating..
Apod                  NA              16 protein_coding abnormal lipid homeo..
Cyp2d22             2D22              15 protein_coding abnormal skin morpho..
Klk6                Bssp               7 protein_coding abnormal cytokine le..
Fcrls      2810439C17Rik               3 protein_coding decreased CD8-positi..
Slc2a4            Glut-4              11 protein_coding abnormal circulating..
        hsapiens_homolog_associated_gene_name
                                  <character>
Asl                                       ASL
Apod                                     APOD
Cyp2d22                                CYP2D6
Klk6                                     KLK6
Fcrls                                   FCRL2
Slc2a4                                 SLC2A4

R

dim(rowData(se))

SALIDA

[1] 1474    9

Selección de subconjuntos de un SummarizedExperiment

SummarizedExperiment puede utilizarse para obtener subconjuntos al igual que con las tablas de datos, mediante valores numéricos, cadenas de caracteres o valores lógicos.

A continuación, creamos una nueva instancia de la clase SummarizedExperiment que contiene únicamente las 5 primeras características de las 3 primeras muestras.

R

se1 <- se[1:5, 1:3]
se1

SALIDA

class: SummarizedExperiment
dim: 5 3
metadata(0):
assays(1): counts
rownames(5): Asl Apod Cyp2d22 Klk6 Fcrls
rowData names(9): gene ENTREZID ... phenotype_description
  hsapiens_homolog_associated_gene_name
colnames(3): GSM2545336 GSM2545337 GSM2545338
colData names(9): sample organism ... tissue mouse

R

colData(se1)

SALIDA

DataFrame with 3 rows and 9 columns
                sample     organism       age         sex   infection
           <character>  <character> <integer> <character> <character>
GSM2545336  GSM2545336 Mus musculus         8      Female  InfluenzaA
GSM2545337  GSM2545337 Mus musculus         8      Female NonInfected
GSM2545338  GSM2545338 Mus musculus         8      Female NonInfected
                strain      time      tissue     mouse
           <character> <integer> <character> <integer>
GSM2545336     C57BL/6         8  Cerebellum        14
GSM2545337     C57BL/6         0  Cerebellum         9
GSM2545338     C57BL/6         0  Cerebellum        10

R

rowData(se1)

SALIDA

DataFrame with 5 rows and 9 columns
               gene  ENTREZID                product    ensembl_gene_id
        <character> <integer>            <character>        <character>
Asl             Asl    109900 argininosuccinate ly.. ENSMUSG00000025533
Apod           Apod     11815 apolipoprotein D, tr.. ENSMUSG00000022548
Cyp2d22     Cyp2d22     56448 cytochrome P450, fam.. ENSMUSG00000061740
Klk6           Klk6     19144 kallikrein related-p.. ENSMUSG00000050063
Fcrls         Fcrls     80891 Fc receptor-like S, .. ENSMUSG00000015852
        external_synonym chromosome_name   gene_biotype  phenotype_description
             <character>     <character>    <character>            <character>
Asl        2510006M18Rik               5 protein_coding abnormal circulating..
Apod                  NA              16 protein_coding abnormal lipid homeo..
Cyp2d22             2D22              15 protein_coding abnormal skin morpho..
Klk6                Bssp               7 protein_coding abnormal cytokine le..
Fcrls      2810439C17Rik               3 protein_coding decreased CD8-positi..
        hsapiens_homolog_associated_gene_name
                                  <character>
Asl                                       ASL
Apod                                     APOD
Cyp2d22                                CYP2D6
Klk6                                     KLK6
Fcrls                                   FCRL2

También podemos utilizar la función colData() para filtrar por algún elemento de los metadatos de las muestras o la rowData() para filtrar por algún elemento de los metadatos de las características. Por ejemplo, aquí solo conservamos los miRNAs y las muestras no infectadas:

R

se1 <- se[rowData(se)$gene_biotype == "miRNA",
          colData(se)$infection == "NonInfected"]
se1

SALIDA

class: SummarizedExperiment
dim: 7 7
metadata(0):
assays(1): counts
rownames(7): Mir1901 Mir378a ... Mir128-1 Mir7682
rowData names(9): gene ENTREZID ... phenotype_description
  hsapiens_homolog_associated_gene_name
colnames(7): GSM2545337 GSM2545338 ... GSM2545353 GSM2545354
colData names(9): sample organism ... tissue mouse

R

assay(se1)

SALIDA

         GSM2545337 GSM2545338 GSM2545343 GSM2545348 GSM2545349 GSM2545353
Mir1901          45         44         74         55         68         33
Mir378a          11          7          9          4         12          4
Mir133b           4          6          5          4          6          7
Mir30c-2         10          6         16         12          8         17
Mir149            1          2          0          0          0          0
Mir128-1          4          1          2          2          1          2
Mir7682           2          0          4          1          3          5
         GSM2545354
Mir1901          60
Mir378a           8
Mir133b           3
Mir30c-2         15
Mir149            2
Mir128-1          1
Mir7682           5

R

colData(se1)

SALIDA

DataFrame with 7 rows and 9 columns
                sample     organism       age         sex   infection
           <character>  <character> <integer> <character> <character>
GSM2545337  GSM2545337 Mus musculus         8      Female NonInfected
GSM2545338  GSM2545338 Mus musculus         8      Female NonInfected
GSM2545343  GSM2545343 Mus musculus         8        Male NonInfected
GSM2545348  GSM2545348 Mus musculus         8      Female NonInfected
GSM2545349  GSM2545349 Mus musculus         8        Male NonInfected
GSM2545353  GSM2545353 Mus musculus         8      Female NonInfected
GSM2545354  GSM2545354 Mus musculus         8        Male NonInfected
                strain      time      tissue     mouse
           <character> <integer> <character> <integer>
GSM2545337     C57BL/6         0  Cerebellum         9
GSM2545338     C57BL/6         0  Cerebellum        10
GSM2545343     C57BL/6         0  Cerebellum        11
GSM2545348     C57BL/6         0  Cerebellum         8
GSM2545349     C57BL/6         0  Cerebellum         7
GSM2545353     C57BL/6         0  Cerebellum         4
GSM2545354     C57BL/6         0  Cerebellum         2

R

rowData(se1)

SALIDA

DataFrame with 7 rows and 9 columns
                gene  ENTREZID        product    ensembl_gene_id
         <character> <integer>    <character>        <character>
Mir1901      Mir1901 100316686  microRNA 1901 ENSMUSG00000084565
Mir378a      Mir378a    723889  microRNA 378a ENSMUSG00000105200
Mir133b      Mir133b    723817  microRNA 133b ENSMUSG00000065480
Mir30c-2    Mir30c-2    723964 microRNA 30c-2 ENSMUSG00000065567
Mir149        Mir149    387167   microRNA 149 ENSMUSG00000065470
Mir128-1    Mir128-1    387147 microRNA 128-1 ENSMUSG00000065520
Mir7682      Mir7682 102466847  microRNA 7682 ENSMUSG00000106406
         external_synonym chromosome_name gene_biotype  phenotype_description
              <character>     <character>  <character>            <character>
Mir1901          Mirn1901              18        miRNA                     NA
Mir378a           Mirn378              18        miRNA abnormal mitochondri..
Mir133b          mir 133b               1        miRNA no abnormal phenotyp..
Mir30c-2        mir 30c-2               1        miRNA                     NA
Mir149            Mirn149               1        miRNA increased circulatin..
Mir128-1          Mirn128               1        miRNA no abnormal phenotyp..
Mir7682      mmu-mir-7682               1        miRNA                     NA
         hsapiens_homolog_associated_gene_name
                                   <character>
Mir1901                                     NA
Mir378a                                MIR378A
Mir133b                                MIR133B
Mir30c-2                               MIR30C2
Mir149                                      NA
Mir128-1                              MIR128-1
Mir7682                                     NA
Desafío

Desafío

Extraiga los niveles de expresión génica de los tres primeros genes en las muestras al tiempo 0 y al tiempo 8.

R

assay(se)[1:3, colData(se)$time != 4]

SALIDA

        GSM2545336 GSM2545337 GSM2545338 GSM2545341 GSM2545342 GSM2545343
Asl           1170        361        400        988        836        535
Apod         36194      10347       9173      29594      24959      13668
Cyp2d22       4060       1616       1603       3349       3122       2008
        GSM2545346 GSM2545347 GSM2545348 GSM2545349 GSM2545351 GSM2545353
Asl            938       1035        494        481        937        541
Apod         27769      34301      11258      11812      29242      13682
Cyp2d22       2985       3452       1883       2014       3678       2216
        GSM2545354 GSM2545380
Asl            473       1192
Apod         11088      38148
Cyp2d22       1821       4019

R

# Equivalent to
assay(se)[1:3, colData(se)$time == 0 | colData(se)$time == 8]

SALIDA

        GSM2545336 GSM2545337 GSM2545338 GSM2545341 GSM2545342 GSM2545343
Asl           1170        361        400        988        836        535
Apod         36194      10347       9173      29594      24959      13668
Cyp2d22       4060       1616       1603       3349       3122       2008
        GSM2545346 GSM2545347 GSM2545348 GSM2545349 GSM2545351 GSM2545353
Asl            938       1035        494        481        937        541
Apod         27769      34301      11258      11812      29242      13682
Cyp2d22       2985       3452       1883       2014       3678       2216
        GSM2545354 GSM2545380
Asl            473       1192
Apod         11088      38148
Cyp2d22       1821       4019
Desafío

Desafío

Verifique que obtenga los mismos valores utilizando la tabla rna en formato largo.

R

rna |>
    filter(gene %in% c("Asl", "Apod", "Cyd2d22")) |>
    filter(time != 4) |>
    select(expression)

SALIDA

# A tibble: 28 × 1
   expression
        <dbl>
 1       1170
 2      36194
 3        361
 4      10347
 5        400
 6       9173
 7        988
 8      29594
 9        836
10      24959
# ℹ 18 more rows

La tabla en formato largo y el SummarizedExperiment contienen la misma información, pero están simplemente estructurados de manera diferente. Cada enfoque tiene sus propias ventajas: el primero es adecuado para los paquetes tidyverse, mientras que el segundo es la estructura preferida para muchos pasos de bioinformática y procesamiento estadístico. Por ejemplo, un análisis típico de RNA-Seq utilizando el paquete DESeq2.

Añadir variables a los metadatos

También podemos añadir información a los metadatos. Supongamos que quieres añadir el centro donde se recolectaron las muestras…

R

colData(se)$center <- rep("University of Illinois", nrow(colData(se)))
colData(se)

SALIDA

DataFrame with 22 rows and 10 columns
                sample     organism       age         sex   infection
           <character>  <character> <integer> <character> <character>
GSM2545336  GSM2545336 Mus musculus         8      Female  InfluenzaA
GSM2545337  GSM2545337 Mus musculus         8      Female NonInfected
GSM2545338  GSM2545338 Mus musculus         8      Female NonInfected
GSM2545339  GSM2545339 Mus musculus         8      Female  InfluenzaA
GSM2545340  GSM2545340 Mus musculus         8        Male  InfluenzaA
...                ...          ...       ...         ...         ...
GSM2545353  GSM2545353 Mus musculus         8      Female NonInfected
GSM2545354  GSM2545354 Mus musculus         8        Male NonInfected
GSM2545362  GSM2545362 Mus musculus         8      Female  InfluenzaA
GSM2545363  GSM2545363 Mus musculus         8        Male  InfluenzaA
GSM2545380  GSM2545380 Mus musculus         8      Female  InfluenzaA
                strain      time      tissue     mouse                 center
           <character> <integer> <character> <integer>            <character>
GSM2545336     C57BL/6         8  Cerebellum        14 University of Illinois
GSM2545337     C57BL/6         0  Cerebellum         9 University of Illinois
GSM2545338     C57BL/6         0  Cerebellum        10 University of Illinois
GSM2545339     C57BL/6         4  Cerebellum        15 University of Illinois
GSM2545340     C57BL/6         4  Cerebellum        18 University of Illinois
...                ...       ...         ...       ...                    ...
GSM2545353     C57BL/6         0  Cerebellum         4 University of Illinois
GSM2545354     C57BL/6         0  Cerebellum         2 University of Illinois
GSM2545362     C57BL/6         4  Cerebellum        20 University of Illinois
GSM2545363     C57BL/6         4  Cerebellum        12 University of Illinois
GSM2545380     C57BL/6         8  Cerebellum        19 University of Illinois

¡Esto ilustra que los slots de los metadatos pueden crecer indefinidamente sin afectar a las otras estructuras!

tidySummarizedExperiment

Quizás se pregunte si podemos usar comandos de tidyverse para interactuar con los objetos SummarizedExperiment. La respuesta es sí, podemos hacerlo con el paquete tidySummarizedExperiment.

Recuerde cómo es nuestro objeto SummarizedExperiment:

R

se

SALIDA

class: SummarizedExperiment
dim: 1474 22
metadata(0):
assays(1): counts
rownames(1474): Asl Apod ... Lmx1a Pbx1
rowData names(9): gene ENTREZID ... phenotype_description
  hsapiens_homolog_associated_gene_name
colnames(22): GSM2545336 GSM2545337 ... GSM2545363 GSM2545380
colData names(10): sample organism ... mouse center

Carga tidySummarizedExperiment y luego mira de nuevo el objeto se.

R

## BiocManager::install("tidySummarizedExperiment")
library("tidySummarizedExperiment")

se

SALIDA

class: SummarizedExperiment
dim: 1474 22
metadata(0):
assays(1): counts
rownames(1474): Asl Apod ... Lmx1a Pbx1
rowData names(9): gene ENTREZID ... phenotype_description
  hsapiens_homolog_associated_gene_name
colnames(22): GSM2545336 GSM2545337 ... GSM2545363 GSM2545380
colData names(10): sample organism ... mouse center

Sigue siendo un objeto SummarizedExperiment, por lo que mantiene la estructura eficiente, pero ahora podemos verlo como un tibble. Observa que la primera línea de la salida indica esto: es una abstracción SummarizedExperiment-tibble. También podemos ver en la segunda línea de la salida el número de transcritos y muestras.

Ahora podemos utilizar comandos de tidyverse para interactuar con el objeto SummarizedExperiment.

Podemos usar filter para filtrar filas según una condición, por ejemplo, para ver todas las filas de una muestra.

R

se |> filter(sample == "GSM2545336")

SALIDA

class: SummarizedExperiment
dim: 1474 1
metadata(1): latest_filter_scope_report
assays(1): counts
rownames(1474): Asl Apod ... Lmx1a Pbx1
rowData names(9): gene ENTREZID ... phenotype_description
  hsapiens_homolog_associated_gene_name
colnames(1): GSM2545336
colData names(10): sample organism ... mouse center

R

se |> filter(sex == "Female")

SALIDA

class: SummarizedExperiment
dim: 1474 12
metadata(1): latest_filter_scope_report
assays(1): counts
rownames(1474): Asl Apod ... Lmx1a Pbx1
rowData names(9): gene ENTREZID ... phenotype_description
  hsapiens_homolog_associated_gene_name
colnames(12): GSM2545336 GSM2545337 ... GSM2545362 GSM2545380
colData names(10): sample organism ... mouse center

Podemos usar select para especificar las columnas que queremos ver.

R

se |> select(sample, sex)

ADVERTENCIA

Warning: `when()` was deprecated in purrr 1.0.0.
ℹ Please use `if` instead.
ℹ The deprecated feature was likely used in the tidySummarizedExperiment
  package.
  Please report the issue at
  <https://github.com/stemangiola/tidySummarizedExperiment/issues>.
This warning is displayed once per session.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
generated.

SALIDA

tidySummarizedExperiment says: Key columns are missing. A data frame is returned for independent data analysis.

SALIDA

# A tibble: 32,428 × 2
   sample     sex
   <chr>      <chr>
 1 GSM2545336 Female
 2 GSM2545336 Female
 3 GSM2545336 Female
 4 GSM2545336 Female
 5 GSM2545336 Female
 6 GSM2545336 Female
 7 GSM2545336 Female
 8 GSM2545336 Female
 9 GSM2545336 Female
10 GSM2545336 Female
# ℹ 32,418 more rows

Podemos usar mutate para añadir información de metadatos.

R

se |> mutate(center = "Heidelberg University")

SALIDA

class: SummarizedExperiment
dim: 1474 22
metadata(1): latest_mutate_scope_report
assays(1): counts
rownames(1474): Asl Apod ... Lmx1a Pbx1
rowData names(9): gene ENTREZID ... phenotype_description
  hsapiens_homolog_associated_gene_name
colnames(22): GSM2545336 GSM2545337 ... GSM2545363 GSM2545380
colData names(10): sample organism ... mouse center

También podemos combinar comandos con el pipe de tidyverse |>. Por ejemplo, podríamos combinar group_by y summarise para obtener los recuentos totales de cada muestra.

R

se |>
    group_by(.sample) |>
    summarise(total_counts=sum(counts))

SALIDA

tidySummarizedExperiment says: A data frame is returned for independent data analysis.

SALIDA

# A tibble: 22 × 2
   .sample    total_counts
   <chr>             <int>
 1 GSM2545336      3039671
 2 GSM2545337      2602360
 3 GSM2545338      2458618
 4 GSM2545339      2500082
 5 GSM2545340      2479024
 6 GSM2545341      2413723
 7 GSM2545342      2349728
 8 GSM2545343      3105652
 9 GSM2545344      2524137
10 GSM2545345      2506038
# ℹ 12 more rows

Podemos tratar el objeto tidy SummarizedExperiment como un tibble normal para realizar gráficos.

Aquí representamos la distribución de los recuentos por muestra.

R

se |>
    ggplot(aes(counts + 1, group=.sample, color=infection)) +
    geom_density() +
    scale_x_log10() +
    theme_bw()
Density plot showing log of expression counts + 1 density lines, one per samples, coloured based on the infection status.

Para obtener más información sobre tidySummarizedExperiment, consulte el sitio web del paquete.

Mensaje clave

  • SummarizedExperiment representa una forma eficiente de almacenar y manejar datos ómicos.

  • Se utilizan en muchos paquetes de Bioconductor.

Si sigue el siguiente entrenamiento centrado en el análisis de secuenciación de ARN, aprenderá a utilizar el paquete DESeq2 de Bioconductor para realizar algunos análisis de expresión diferencial. Todo el análisis del paquete DESeq2 se gestiona en un SummarizedExperiment.

Puntos Clave
  • Bioconductor es un proyecto que proporciona soporte y paquetes para la comprensión de datos biológicos de alto rendimiento.
  • Un SummarizedExperiment es un tipo de objeto útil para almacenar y gestionar datos ómicos de alto rendimiento.

  1. Bioconductor fue iniciado por Robert Gentleman, uno de los dos creadores del lenguaje R. Bioconductor proporciona herramientas dedicadas al análisis de datos ómicos. Bioconductor utiliza el lenguaje de programación estadístico R y es de código abierto y desarrollo abierto.↩︎