Análisis de datos en R e introducción a Stata
¡Bienvenidos a R!
Dejad vuestras hojas de cálculo, Anacondas y SAS a un lado
Correo: javalv09@ucm.es. Despacho: 722 (3ª planta). Tutorías (curso 2024-2025): …
Javier Álvarez Liébana, de Carabanchel (Bajo).
Licenciado en Matemáticas (UCM). Doctorado en estadística (UGR).
Encargado de la visualización y análisis de datos covid del Principado de Asturias (2021-2022).
Miembro de la Sociedad Española de Estadística e IO y la Real Sociedad Matemática Española.
Actualmente, investigador y docente en la Facultad de Estadística de la UCM. Divulgando por Twitter e Instagram
Quitarnos el miedo a los errores en programación → a programar se aprende programando
Entender los conceptos básicos de R desde cero → aprender a abstraer ideas y algoritmos
Utilidad de programar → flujos de trabajo reproducibles, transparentes y mantenibles
Introducción al análisis y preprocesamiento de datos → {tidyverse}
Adquirir habilidades en la visualización de datos → {ggplot2}
Entrega I (5%): 25 de septiembre (60 minutos).
Entrega II (15%): 9 de octubre (120 minutos).
Entrega III (25%): 30 de octubre (120 minutos).
Entrega IV (30%): 16 de diciembre (120 minutos).
Se podrán modificar las fechas por saturación con otras asignaturas siempre y cuando el/la delegado/a lo solicite con más de 7 días de antelación.
CLASE | SEMANA | FECHAS | TOPIC | EJ. | WORKBOOK | ENTREGA | |||
---|---|---|---|---|---|---|---|---|---|
1 | S1 | 11 sep | Primeros pasos. Tipos básicos de datos | 💻 💻 | |||||
2 | S2 | 16 sep | Vectores | 💻 💻 💻 | 🐣 🐣 | ||||
3 | S2 | 18 sep | Primeras bases de datos | 💻 | 🐣 | ||||
4 | S3 | 25 sep | Comunicar: apuntes. Entrega I | 🐣 | 🎯 5% | ||||
5 | S4 | 30 sep | Estructuras de control | 💻 💻 | 🐣 | ||||
6 | S4 | 2 oct | Funciones | 💻 | 🐣 | ||||
7 | S5 | 7 oct | Tidy data | 💻 | 🐣 | ||||
8 | S5 | 9 oct | Entrega II | 🎯 15% | |||||
9 | S6 | 14 oct | Tidyverse: filas | 💻 💻 | 🐣 | ||||
10 | S6 | 15 oct | Tidyverse: columnas | 💻 💻 | 🐣 🐣 🐣 | ||||
11 | S6 | 16 oct | Tidyverse: resúmenes | 💻 | 🐣 🐣 | ||||
12 | S7 | 21 oct | Repaso | 🐣 🐣 🐣 🐣 🐣 🐣 | |||||
13 | S7 | 23 oct | Joins e importar | 💻 💻 | 🐣 🐣 | ||||
14 | S7 | 28 oct | Listas | … | … | ||||
15 | S7 | 30 oct | Entrega III | 🎯 25% |
Quarto
disponibles y actualizadas en https://javieralvarezliebana.es/docencia/R-biostats. En el menú de las diapositivas (abajo a la izquierda) tienes una opción para descargarlas en pdf en Tools
Material: cuadernos de trabajo y materiales extras y resúmenes de paquetes
🗃 Datos: datasets que usaremos a lo largo de la asignatura, disponibles en https://javieralvarezliebana.es/docencia/R-biostats/material y en drive https://drive.google.com/drive/folders/1lebT2L2zdrC0TeDzo1c613qavE1umRWw?usp=sharing
📚 Recursos de apoyo: en inglés https://r4ds.had.co.nz/ y en castellano https://cdr-book.github.io/, y https://ivelasq.quarto.pub/intro-to-quarto/ para Quarto.
Instalando R y RStudio. Primeros pasos: ¿qué tipos de celdas (datos) existen?
Para el curso los únicos requisitos serán:
Programaremos como escribimos (castellano, por ejemplo) → R
es lenguaje
R
)RStudio
), para escribirloEl lenguaje R
será nuestra gramática y ortografía (nuestras reglas de juego)
Paso 1: entra en https://cran.r-project.org/ y selecciona tu sistema operativo.
Paso 2: para Mac basta con que hacer click en el archivo .pkg, y abrirlo una vez descargado. Para sistemas Windows, debemos clickar en install R for the first time y después en Download R for Windows. Una vez descargado, abrirlo como cualquier archivo de instalación.
Paso 3: abrir el ejecutable de instalación.
Warning
Siempre que tengas que descargar algo de CRAN (ya sea el propio R o un paquete), asegúrate de tener conexión a internet.
Para comprobar la instalación, tras abrir R
, deberías ver el R GUI
(Graphical User Interface) con una pantalla blanca similar a esta (consola).
Para comprobar la instalación, tras abrir R
, deberías ver el R GUI
(Graphical User Interface) con una pantalla blanca similar a esta (consola).
Primer código: a una variable llamada a
le asignaremos el valor 1
(escribiremos el código en la consola y daremos «enter»). Tras ello haremos la suma a + b
.
Para comprobar la instalación, tras abrir R
, deberías ver el R GUI
(Graphical User Interface) con una pantalla blanca similar a esta (consola).
Primer código: a una variable llamada a
le asignaremos el valor 1
(escribiremos el código en la consola y daremos «enter»). Tras ello haremos la suma a + b
.
Fíjate que…
En la consola aparece un número [1]
: simplemente es un contador de elementos (como contar filas en un Word)
RStudio
será el Word que usaremos para escribir (lo que se conoce como un IDE: entorno integrado de desarrollo).
Paso 1: entra la web oficial de RStudio (ahora llamado Posit) y selecciona la descarga gratuita.
Paso 2: selecciona el ejecutable que te aparezca acorde a tu sistema operativo.
Paso 3: tras descargar el ejecutable, hay que abrirlo como otro cualquier otro y dejar que termine la instalación.
Al abrir RStudio
seguramente tengas tres ventanas:
Al abrir RStudio
seguramente tengas tres ventanas:
Al abrir RStudio
seguramente tengas tres ventanas:
R
es la evolución del trabajo de los laboratorios Bell con el lenguaje S
, que fue llevado al mundo del software libre por Ross Ihaka y Robert Gentleman en los años 90. La version R 1.0.0
se publicó el 29 de febrero de 2000.
R
es el lenguaje estadístico por excelencia, creado por y para estadísticos/as, con 6 ventajas fundamentales frente a Excel, SAS, Stata o SPSS:
R
es el compartir código bajo copyleft → uso ético de dinero y algoritmosR
es el lenguaje estadístico por excelencia, creado por y para estadísticos/as, con 6 ventajas fundamentales frente a Excel, SAS, Stata o SPSS:
Automatizar → te permitirá automatizar tareas recurrentes.
Replicabilidad → podrás replicar tu análisis siempre de la misma manera.
Flexibilidad → podrás adaptar el software a tus necesidades.
Transparencia → ser auditado por la comunidad.
Una de las ideas claves de R
es el uso de paquetes: códigos que otras personas han implementado para resolver un problema
Una vez instalado, hay dos manera de usar un paquete (traerlo de la estantería)
library()
, usando el nombre del paquete sin comillas, cargamos en la sesión todo el libroDurante tu aprendizaje va a ser muy habitual que las cosas no salgan a la primera → te vas equivocar. No solo será importante asumirlo sino que es importante leer los mensajes de error para aprender de ellos.
Un script será el documento en el que programamos, nuestro archivo .doc
(aquí con extensión .R
) donde escribiremos las órdenes. Para abrir nuestro primero script, haz click en el menú en File < New File < R Script
.
Cuidado
Es importante no abusar de la consola: todo lo que no escribas en un script, cuando cierres, lo habrás perdido.
Cuidado
R
es case-sensitive: es sensible a mayúsculas y minúsculas por lo que x
y X
representa variables distintas.
Ahora tenemos una cuarta ventana: la ventana donde escribiremos nuestros códigos. ¿Cómo ejecutarlo?
Save current document
.Ctrl+Enter
De la misma manera que en el ordenador solemos trabajar de manera ordenada por carpetas, en RStudio
podemos hacer lo mismo para trabajar de manera eficaz creando proyectos.
Un proyecto será una «carpeta» dentro de RStudio
, de manera que nuestro directorio raíz automáticamente será la propia carpeta de proyecto (pudiendo pasar de un proyecto a otro con el menu superior derecho).
Podemos crear uno en una carpeta nueva o en una carpeta ya existente.
📝 Crea en tu ordenador una carpeta de la asignatura y crea dentro de ella el proyecto de RStudio
: es ahí donde vas a guardar todo lo que hagamos a lo largo de este curso.Tras crear el proyecto tendrás un archivo R Project
. A continuación crea en dicha carpeta dos subcarpetas: datos
(es ahí donde irás guardando los distintos datasets que usaremos) y scripts
(es ahí donde irás guardando los archivos .R
de cada clase)
📝 Dentro del proyecto crea un script ejercicios-clase1.R
(dentro de la carpeta scripts
). Una vez creado define en él una variable de nombre a
y cuyo valor sea -1. Ejecuta el código de las 3 maneras explicadas.
📝 Añade debajo otra línea para definir una variable b
con el valor 5. Tras ello guarda la multiplicación de ambas variables. Ejecuta el código como consideres.
📝 Modifica el código inferior para definir dos variables c y d, con valores 3 y -1. Tras ello divide las variables y guarda el resultado.
📝 Asigna un valor positivo a x
y calcula su raíz cuadrada; asigna otro negativo y
y calcula su valor absoluto con la función abs()
.
Toma nota
Comandos como sqrt()
, abs()
o max()
son lo que llamamos funciones: líneas de código que hemos «encapsulado» bajo un nombre, y dado unos argumentos de entrada, ejecuta las órdenes (una especie de atajo). En las funciones los argumentos irán SIEMPRE entre paréntesis
📝 Usando la variable x
ya definida, completa/modifica el código inferior para guardar en una nueva variable z
el resultado guardado en x
menos 5.
📝 Define una variable x
y asígnale el valor -1
. Define otra y
y asígnale el valor 0
. Tras ello realiza las operaciones a) x
entre y
; b) raíz cuadrada de x
. ¿Qué obtienes?
¿Qué tipo de dato podemos tener en cada celda de una tabla?
Antes de seguir, es importante que sepas algo cuánto antes: empezar en la programación puede ser frustrante
Al igual que cuando aprendes un idioma nuevo, el primer obstáculo a solventar no es tanto qué decir sino cómo decirlo de manera correcta. Y en R
pasa lo mismo, así que vamos a normalizar nuestra forma de programar lo máximo posible para evitar errores futuros.
R
no procesa los espacios)snake_case
Tools < Global Options
puedes personalizar algunas opciones de RStudio
. En Code < Display
podemos indicarle en Show margin
que los scripts nos muestren un margen “imaginario” (no interacciona con el código) para “forzarnos” a realizar un salto de línea.RStudio
tenemos una herramienta maravillosa: si escribes parte del nombre de una variable o función y tabulas, RStudio
te autocompletaTools < Global Options < Code < Display
y activa la opción Rainbow parentheses
RStudio
te avisará.clase2.R
en el proyecto que creamos en la anterior clase)
Ver más tips en https://r4ds.had.co.nz/workflow-basics.html#whats-in-a-name
¿Existen variables más allá de los números en la ciencia de datos? Piensa por ejemplo en los datos que podrías guardar de una persona:
TRUE
si está matriculado o FALSE
en otro caso).El dato más sencillo (ya lo hemos usado) serán las variables numéricas. Para saber la clase de dato en R de una variable tenemos la función class()
El dato más sencillo (ya lo hemos usado) serán las variables numéricas. Para saber la clase de dato en R de una variable tenemos la función class()
Para saber su tipología (naturaleza o formato) variable tenemos typeof()
[1] "double"
[1] "integer"
Fíjate que…
En R
tenemos una colección de funciones que empiezan por as.x()
y que sirven como funciones de conversión: un dato que era de un tipo, lo convertimos a tipo x
.
Además de los números «normales» tendremos el valor más/menos infinito codificado como Inf
o -Inf
Con las variables numéricas podemos realizar las operaciones aritméticas de una calculadora: sumar (+
)…
Imagina que además de la edad de una persona queremos guardar su nombre: ahora la variable será de tipo character
Las cadenas de texto son un tipo con el que obviamente no podremos hacer operaciones aritméticas (sí otras operaciones como pegar o localizar patrones).
Recuerda que…
Las variables de tipo texto (character o string) van SIEMPRE entre comillas: no es lo mismo TRUE
(valor lógico, binario) que "TRUE"
(texto).
Como hemos comentado R
llamaremos función a un trozo de código encapsulado bajo un nombre, y que depende de unos argumentos de entrada. Nuestra primera función será paste()
: dadas dos cadenas de texto nos permite pegarlas.
¿Cómo saber qué argumentos necesita una función? Escribiendo en consola ? paste
te aparecerá una ayuda en el panel multiusos.
En dicha ayuda podrás ver en su cabecera que argumentos ya tiene asignados por defecto la función
Los argumentos (y su detalle) también pueden ser consultado tabulando (detras una coma).
Es muy importante entender el concepto de argumento por defecto de una función en R
: es un valor que la función usa pero a veces podemos no ver porque ya tiene un valor asignado.
[1] "Javier Álvarez"
[1] "Javier Álvarez"
Toma nota
El operador =
lo reservaremos para asignar argumentos dentro de funciones. Para todas las demás asignaciones usaremos <-
Una forma más intuitiva de trabajar con textos es usar el paquete {glue}
: lo primero que haremos será «comprar el libro» (si nunca lo hemos hecho). Tras ello cargamos el paquete
Con la función glue()
de dicho paquete podemos usar variables dentro de cadenas de texto. Por ejemplo, «la edad es de … años», donde la edad está guardada en una variable.
Otro tipo fundamental serán las variables lógicas o binarias (dos valores):
TRUE
: verdadero guardado internamente como un 1.FALSE
: falso guardado internamente como un 0.Como veremos en breve, las variables lógicas en realidad puede tomar un tercer valor: NA
o dato ausente, representando las siglas de not available, y será muy habitual encontrarlo dentro de una base de datos.
Los valores lógicos suelen ser resultado de evaluar condiciones lógicas. Por ejemplo, imaginemos que queremos comprobar si una persona se llama Javi.
Con el operador lógico ==
preguntamos sí lo que tenemos guardado a la izquierda es igual que lo que tenemos a la derecha: es una pregunta
Fíjate que…
No es lo mismo <-
(asignación) que ==
(estamos preguntando, es una comparación lógica).
Además de las comparaciones «igual a» frente «distinto», también comparaciones de orden como menor que <
, mayor que >
, <=
o >=
.
¿Tiene la persona menos de 32 años?
Un tipo de datos muy especial: los datos de tipo fecha.
Parece una simple cadena de texto pero debería representar un instante en el tiempo. ¿Qué debería suceder si sumamos un 1 a una fecha?
Las fechas NO pueden ser texto: debemos convertir la cadena de texto a fecha.
Una vez instalado, de todos los paquetes (libros) que tenemos, le indicaremos que nos cargue ese concretamente.
Para convertir a tipo fecha usaremos la función as_date()
del paquete {lubridate}
(por defecto en formato yyyy-mm-dd
)
En as_date()
el formato de fecha por defecto es yyyy-mm-dd
así si la cadena de texto no se introduce de manera adecuada…
Para cualquier otro formato debemos especificarlo en el argumento opcional format = ...
tal que %d
representa días, %m
meses, %Y
en formato de 4 años y %y
en formato de 2 años.
En dicho paquete tenemos funciones muy útiles para manejar fechas:
today()
podemos obtener directamente la fecha actual.Amplia contenido
Tienes un resumen en pdf de los paquetes más importantes en la carpeta correspondiente en el campus
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Define una variable que guarde tu edad (llamada edad
) y otra con tu nombre (llamada nombre
)
📝 Comprueba con dicha variable edad
si NO tiene 60 años o si se llama "Ornitorrinco"
(debes obtener variables lógicas como resultado)
📝 ¿Por qué el código inferior da error?
📝 Define otra variable llamada hermanos
que responda la pregunta «¿tienes hermanos?» y otra variable que almacene tu fecha de nacimiento (llamada fecha_nacimiento
).
📝 Define otra variable con tus apellidos (llamada apellidos
) y usa glue()
para tener, en una sola variable llamada nombre_completo
, tu nombre y apellidos separando nombre y apellido por una coma
Concatenando celdas: vectores. Primera base de datos: data.frame y tibble
Cuando trabajamos con datos normalmente tendremos columnas que representan variables: llamaremos vectores a una concatenación de celdas (valores) del mismo tipo (lo que sería una columna de una tabla).
La forma más sencilla es con el comando c()
(c de concatenar), y basta con introducir sus elementos entre paréntesis y separados por comas
Tip
Un número individual x <- 1
(o bien x <- c(1)
) es en realidad un vector de longitud uno –> todo lo que sepamos hacer con un número podemos hacerlo con un vector de ellos.
El vector más famoso será el de tipo numérico, y en concreto, las conocidas como secuencias numéricas (por ejemplo, los días del mes), usadas para, entre otras cosas, indexar bucles.
El comando seq(inicio, fin)
nos permite crear una secuencia numérica desde un elemento inicial hasta uno final, avanzando de uno en uno.
Un atajo es el comando 1:n
, que nos devuelve lo mismo que seq(1, n)
Si el elemento inicial es mayor que el final, entenderá que la secuencia es en orden decreciente.
Otras veces nos interesará definir una secuencia con una longitud concreta
[1] 1.000000 9.166667 17.333333 25.500000 33.666667 41.833333 50.000000
Incluso puede nos interese generar un vector de n elementos repetidos
Un vector es una concatenación de elementos del mismo tipo, pero no tienen porque ser necesariamente números. Vamos a crear una frase de ejemplo.
¿Qué sucederá si concatenamos elementos de diferente tipo?
Fíjate que como todos tienen que ser del mismo tipo, lo que hace R
es convertir todo a texto, violando la integridad del dato
Con los vectores numéricos podemos hacer las mismas operaciones aritméticas que con los números → un número es un vector (de longitud uno)
¿Qué sucederá si sumamos o restamos un valor a un vector?
Los vectores también pueden interactuar entre ellos, así que podemos definir, por ejemplo, sumas de vectores (elemento a elemento)
Dado que la operación (por ejemplo, una suma) se realiza elemento a elemento, ¿qué sucederá si sumamos dos vectores de distinta longitud?
Una operación muy habitual es preguntar a los datos mediante el uso de condiciones lógicas. Por ejemplo, si definimos un vector de temperaturas…
¿Qué días hizo menos de 22 grados?
Nos devolverá un vector lógico, en función de si cada elemento cumple o no la condición pedida (de igual longitud que el vector preguntado)
Las condiciones lógicas pueden ser combinadas de dos maneras:
&
) para devolver un TRUE
|
)Otra operación muy habitual es la de acceder a elementos. La forma más sencilla es usar el operador [i]
(acceder al elemento i-ésimo)
Dado que un número no es más que un vector de longitud uno, esta operación también la podemos aplicar usando un vector de índices a seleccionar
Tip
Para acceder al último, sin preocuparnos de cuál es, podemos pasarle como índice la propia longitud x[length(x)]
Otras veces no querremos seleccionar sino eliminar algunos elementos. Deberemos repetir la misma operación pero con el signo - delante: el operador [-i]
no selecciona el elemento i-ésimo del vector sino que lo «des-selecciona»
En muchas ocasiones los queremos seleccionar o eliminar en base a condiciones lógicas, en función de los valores, así que pasaremos como índice la propia condición (recuerda, x < 2
nos devuelve un vector lógico)
También podemos hacer uso de operaciones estadísticas como por ejemplo sum()
que, dado un vector, nos devuelve la suma de todos sus elementos.
¿Qué sucede cuando falta un dato (ausente)?
Como hemos comentado que los valores lógicos son guardados internamente como 0 y 1, podremos usarlos en operaciones aritméticas.
Por ejemplo, si queremos averiguar el número de elementos que cumplen una condición (por ejemplo, menores que 3), los que lo hagan tendrán asignado un 1 (TRUE
) y los que no un 0 (FALSE
) , por lo que basta con sumar dicho vector lógico para obtener el número de elementos que cumplen
Otra operación habitual que puede sernos útil es la suma acumulada con cumsum()
que, dado un vector, nos devuelve un vector a su vez con el primero, el primero más el segundo, el primero más el segundo más el tercero…y así sucesivamente.
¿Qué sucede cuando falta un dato (ausente)?
En el caso de la suma acumulada lo que sucede es que a partir de ese valor, todo lo acumulado posterior será ausente.
Otra operación habitual que puede sernos útil es la diferencia (con retardo) con diff()
que, dado un vector, nos devuelve un vector con el segundo menos el primero, el tercero menos el segundo, el cuarto menos el tercero…y así sucesivamente.
Otras operaciones habituales son la media, mediana, percentiles, etc.
Otras operaciones habituales son la media, mediana, percentiles, etc.
Por último, una acción habitual es saber ordenar valores:
sort()
: devuelve el vector ordenado. Por defecto de menor a mayor pero con decreasing = TRUE
podemos cambiarlo[1] 7 20 23 25 33 41 65 77 81
[1] 81 77 65 41 33 25 23 20 7
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Define el vector x
como la concatenación de los 5 primeros números impares. Calcula la longitud del vector
📝 Accede al tercer elemento de x
. Accede al último elemento (sin importar la longitud, un código que pueda ejecutarse siempre). Elimina el primer elemento.
📝 Obtén los elementos de x
mayores que 4. Calcula el vector 1/x
y guárdalo en una variable.
📝 Crea un vector que represente los nombres de 5 personas, de los cuales uno es desconocido.
📝 Encuentra del vector x
de ejercicios anteriores los elementos mayores (estrictos) que 1 Y ADEMÁS menores (estrictos) que 7. Encuentra una forma de averiguar si todos los elementos son o no positivos.
📝 Dado el vector x <- c(1, -5, 8, NA, 10, -3, 9)
, ¿por qué su media no devuelve un número sino lo que se muestra en el código inferior?
📝 Dado el vector x <- c(1, -5, 8, NA, 10, -3, 9)
, extrae los elementos que ocupan los lugares 1, 2, 5, 6.
📝 Dado el vector x
del ejercicio anterior, ¿cuales tienen un dato ausente? Pista: las funciones is.algo()
comprueban si el elemento es tipo algo
(tabula)
📝 Define el vector x
como la concatenación de los 4 primeros números pares. Calcula el número de elementos de x
menores estrictamente que 5.
📝 Calcula el vector 1/x
y obtén la versión ordenada (de menor a mayor) de las dos formas posibles
En el paquete {datasets}
(ya instalado por defecto) tenemos diversos conjuntos de datos y uno de ellos es airquality
. Debajo te he extraído 3 variables de dicho dataset (fíjate que se hace con datos$variable
, ese dolar será importante en el futuro).
Los datos capturan medidas diarias (n = 153 observaciones) de la calidad del aire en Nueva York, de mayo a septiembre de 1973. Se midieron 6 variables: niveles de ozono, radiación solar, viento, temperatura, mes y día.
Intenta responder a las preguntas planteadas en el workbook
Cuando analizamos datos solemos tener varias variables de cada individuo: necesitamos una «tabla» que las recopile. La opción más inmediata son las matrices: concatenación de variables del mismo tipo e igual longitud.
Imagina que tenemos estaturas y pesos de 4 personas. ¿Cómo crear un dataset con las dos variables?
También podemos construir la matriz por filas con la función rbind()
(concatenar - bind - por filas - rows), aunque lo recomendable es tener cada variable en columna e individuo en fila como luego veremos.
[,1] [,2] [,3] [,4]
estaturas 150 160 170 180
pesos 63 70 85 95
View(matriz)
.También podemos «darle vuelta» (matriz transpuesta) con t()
.
Dado que ahora tenemos dos dimensiones en nuestros datos, para acceder a elementos con []
deberemos proporcionar dos índices separados por comas: índice de la fila y de la columna
En algunas casos querremos obtener los datos totales de un individuo (una fila concreta pero todas las columnas) o los valores de toda una variable para todos los individuos (una columna concreta pero todas las filas). Para ello dejaremos sin rellenar uno de los índices
estaturas pesos
160 70
[1] 150 160 170 180
También podemos definir una matriz a partir de un vector numérico, reorganizando los valores en forma de matriz (sabiendo que los elementos se van colocando por columnas).
Con las matrices sucede como con los vectores: cuando aplicamos una operación aritmética lo hacemos elemento a elemento
También podemos realizar operaciones por columnas/filas sin recurrir a bucles con la función apply()
, y le indicaremos como argumentos
MARGIN = 1
por filas, MARGIN = 2
por columnas)Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Modifica el código inferior para definir una matriz x
de unos, de 3 filas y 7 columnas.
📝 A la matriz anterior, suma un 1 a cada número de la matriz y divide el resultado entre 5. Tras ello calcula su transpuesta
📝 ¿Por qué el código inferior nos devuelve dicho mensaje de aviso?
📝 Define la matriz x <- matrix(1:12, nrow = 4)
. Tras ello obtén los datos del primer individuo, los datos de la tercera variable, y el elemento (4, 1)
.
📝 Define una matriz de 2 variables y 3 individuos tal que cada variable capture la estatura y la edad 3 personas, de manera que la edad de la segunda persona sea desconocida (ausente). Tras ello calcula la media de cada variable (¡nos debe de volver un número!)
Primera base de datos: data.frame y tibble
Las matrices tienen el mismo problema que los vectores: si juntamos datos de distinto tipo, se perturba la integridad del dato ya que los convierte (fíjate en el código inferior: las edades y los TRUE/FALSE
los ha convertido a texto)
Para poder trabajar con variables de distinto tipo tenemos en R
lo que se conoce como data.frame: concatenación de variables de igual longitud pero que pueden ser de tipo distinto.
Dado que un data.frame
es ya un intento de «base de datos» las variables no son meros vectores matemáticos: tienen un significado y podemos (debemos) ponerles nombres que describan su significado
¡TENEMOS NUESTRO PRIMER CONJUNTO DE DATOS! (estrictamente no podemos hablar de base de datos pero de momento como lo si fuesen). Puedes visualizarlo escribiendo su nombre en consola o con View(tabla)
Si queremos acceder a sus elementos, al ser de nuevo datos tabulados, podemos acceder como en las matrices (no recomendable): de nuevo tenemos dos índices (filas y columnas, dejando libre la que no usemos)
edad estado nombre f_nacimiento
2 24 NA laura 1992-04-01
[1] "javi" "laura" "lucía"
[1] 24
Pero también tiene las ventajas de una «base» de datos : podemos aceder a las variables por su nombre (lo recomendable ya que las variables pueden cambiar de posición y ahora sí tienen un significado), poniendo el nombre de la tabla seguido del símbolo $
(con el tabulador, nos aparecerá un menú de columnas a elegir)
names()
: nos muestra los nombres de las variablesSi tenemos uno ya creado y queremos añadir una columna es tan simple como usar la función data.frame()
que ya hemos visto para concatenar la columna. Vamos añadir por ejemplo una nueva variable, el número de hermanos de cada individuo.
Las tablas en formato data.frame
tienen algunas limitaciones. La principal es que no permite la recursividad: imagina que definimos una base de datos con estaturas y pesos, y queremos una tercera variable con el IMC
Error in data.frame(estatura = c(1.7, 1.8, 1.6), peso = c(80, 75, 70), : object 'peso' not found
En adelante usaremos el formato tibble
(data.frame mejorado) del paquete {tibble}
datos_tb <-
tibble("estatura" = c(1.7, 1.8, 1.6), "peso" = c(80, 75, 70), "IMC" = peso / (estatura^2))
datos_tb
# A tibble: 3 × 3
estatura peso IMC
<dbl> <dbl> <dbl>
1 1.7 80 27.7
2 1.8 75 23.1
3 1.6 70 27.3
Las tablas en formato tibble
nos permitirá una gestión más ágil, eficiente y coherente de los datos, con 4 ventajas principales:
tribble()
Tip
El paquete {datapasta}
nos permite copiar y pegar tablas de páginas web y documentos sencillos
R
por defecto las operaciones se hacen elemento a elementoIntenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Carga del paquete {datasets}
el conjunto de datos airquality
(variables de la calidad del aire de Nueva York desde mayo hasta septiembre de 1973). ¿Es el conjunto de datos airquality de tipo tibble? En caso negativo, conviértelo a tibble (busca en la documentación del paquete en https://tibble.tidyverse.org/index.html).
📝 Una vez convertido a tibble
obtén el nombre de las variables y las dimensiones del conjunto de datos. ¿Cuántas variables hay? ¿Cuántos días se han medido?
📝 Modifica el código inferior para filtrar solo los datos del mes de agosto. ¿Cómo indicarle que queremos solo las filas que cumplan una condición concreta? (pista: en realidad todo son vectores “formateados”)
📝 Selecciona aquellos datos que no sean ni de julio ni de agosto.
📝 Modifica el siguiente código para quedarte solo con las variable de ozono y temperatura (sin importar qué posición ocupen)
📝 Selecciona los datos de temperatura y viento de agosto.
Del paquete {Biostatistics}
usaremos el conunto de datos pinniped
, que guarda los datos de peso de cuerpo y cerebro (desagregado por sexo y mono/poligamia) de 33 especies de mamíferos marinos.
Species Male_brain_g Female_brain_g Male_mass_Kg
1 Monachus schauinslandi 370.0 NA 173.0
2 Monachus monachus 480.0 480.0 260.0
3 Mirounga angustirostris 700.0 640.0 2275.0
4 Mirounga leonina 1431.3 898.8 3510.0
5 Leptonychotes weddelli 535.0 637.5 450.0
6 Ommatophoca rossi 425.0 530.0 153.8
7 Lobodon carcinophagus 578.2 538.8 220.5
8 Hydrurga leptonyx 765.0 660.0 324.0
9 Cystophora cristata 480.0 430.0 343.2
10 Erignathus barbatus NA 460.0 312.5
11 Halichoerus grypus 342.5 272.5 233.0
12 Phoca groenlandica 297.5 252.5 145.0
13 Phoca fasciata 257.5 240.0 94.8
14 Phoca largha 257.5 250.0 97.0
15 Phoca caspica 165.0 160.0 70.5
16 Phoca sibirica 185.0 190.0 89.5
17 Phoca hispida 229.3 220.0 84.0
18 Phoca vitulina 362.3 265.0 97.1
19 Zalophus californianus 405.0 361.5 244.5
20 Eumetopias jubatus 747.5 575.0 1000.0
21 Otaria byronia 546.3 470.0 300.0
22 Neophoca cinerea 440.0 337.5 300.0
23 Phocarctos hookeri 417.5 370.0 364.0
24 Callorhinus ursinus 355.0 302.5 140.0
25 Arctocephalus townsendi NA NA 112.0
26 Arctocephalus philippii 415.0 NA 140.0
27 Arctocephalus galapagoensis 302.5 280.0 64.5
28 Arctocephalus australis 350.0 265.0 91.0
29 Arctocephalus forsteri 340.0 300.0 125.0
30 Arctocephalus gazella 360.0 320.0 155.0
31 Arctocephalus tropicalis 322.5 330.0 152.5
32 Arctocephalus pusillus 401.3 337.5 263.0
33 Odobenus rosmarus 1303.0 1340.5 1233.0
Female_mass_Kg Mate_type
1 272.2 mono
2 275.0 mono
3 488.0 poly
4 565.7 poly
5 447.0 poly
6 164.0 mono
7 224.0 mono
8 367.0 mono
9 222.5 mono
10 326.0 mono
11 205.8 poly
12 139.0 mono
13 80.4 mono
14 71.3 mono
15 55.0 mono
16 85.0 mono
17 81.2 mono
18 85.2 mono
19 81.0 poly
20 287.5 poly
21 144.0 poly
22 78.6 poly
23 114.7 poly
24 33.3 poly
25 49.6 poly
26 48.1 poly
27 27.4 poly
28 48.5 poly
29 38.1 poly
30 45.0 poly
31 50.0 poly
32 64.1 poly
33 811.5 poly
Intenta responder a las preguntas planteadas en el workbook
Comunicar: elaborar apuntes, diapositivas, etc. Entrega
Una de las principales fortalezas de R
es la facilidad para generar informes, libros, webs, apuntes y hasta diapositivas (este mismo material por ejemplo). Para ello instalaremos antes
{rmarkdown}
(para generar archivos .rmd
)R
, el «nuevo» .rmd
ahora como .qmd
)Hasta ahora solo hemos programado en scripts (archivos .R
) dentro de proyectos, pero en muchas ocasiones no trabajaremos solos y necesitaremos comunicar los resultados en diferentes formatos:
Para todo ello usaremos Quarto (ver más en https://ivelasq.quarto.pub/intro-to-quarto/)
Los archivos de extensión .qmd
(o .rmd
antes) nos permitirán fácilmente combinar:
R
, Python
, C++
, Julia
, …), con cajitas de código llamadas CHUNKS.La principal ventaja de realizar este tipo de material en Quarto/Rmarkdown es que, al hacerlo desde RStudio
, puedes generar un informe o una presentación sin salirte del entorno de programación en el que estás trabajando
De esta forma podrás analizar los datos, resumirlos y a la vez comunicarlos con la misma herramienta.
Recientemente el equipo de RStudio
desarrolló Quarto, una versión mejorada de Rmarkdown (archivos .qmd
), con un formato un poco más estético y simple. Tienes toda la documentación y ejemplos en https://quarto.org/
Imágenes obtenidas de https://ivelasq.quarto.pub/intro-to-quarto/#/working-with-the-rstudio-visual-editor
Vamos a crear el primer fichero rmarkdown con Quarto con extensión .qmd
. Para ello solo necesitaremos hacer click en
File << New File << Quarto Document
Tras hacerlo nos aparecerán varias opciones de formatos de salida:
.pdf
.html
(recomendable): documento dinámico, permite la interacción con el usuario, como una «página web»..doc
(nada recomendable)De momento dejaremos marcado el formato HTML que viene por defecto, y escribiremos el título de nuestro documento. Tras ello tendremos nuestro archivo .qmd (ya no es un script .R como los que hemos abierto hasta ahora).
Deberías tener algo similar a la captura de la imagen con dos modos de edición: Source
(con código, la opción recomendada hasta que lo domines) y Visual
(más parecido a un blog)
Para ejecutar TODO el documento debes clickar Render on Save
y darle a guardar.
Deberías haber obtenido una salida en html similar a esta (y se te ha generado en tu ordenador un archivo html)
Como se indicaba, tienes dos formas de trabajar: con código puro y algo parecido a un Notion (blog)
Imagen obtenida de https://ivelasq.quarto.pub/intro-to-quarto/#/working-with-the-rstudio-visual-editor
Un fichero .qmd
se divide básicamente en tres partes:
Cabecera: la parte que tienes al inicio entre ---
.
Texto: que podremos formatear y mejorar con negritas (escrito como negritas, con doble astérisco al inicio y final), cursivas (cursivas, con barra baja al inicio y final) o destacar nombres de funciones o variables de R. Puedes añadir ecuaciones como \(x^2\) (he escrito $x^2$
, entre dólares).
Código R
La cabecera están en formato YAML y contiene los metadatos del documento
title
y subtitle
: el título/subtítulo del documentoauthor
: autor del mismoformat
: formato de salida (podremos personalizar)
theme
: si tienes algún archivo de estilostoc
: si quieres índice o notoc-location
: posición del índicetoc-title
: título del índiceeditor
: si estás en modo visual o source.La cabecera están en formato YAML y contiene los metadatos del documento
title
y subtitle
: el título/subtítulo del documentoauthor
: autor del mismoformat
: formato de salida (podremos personalizar)
theme
: si tienes algún archivo de estilostoc
: si quieres índice o notoc-location
: posición del índicetoc-title
: título del índiceeditor
: si estás en modo visual o source.La cabecera están en formato YAML y contiene los metadatos del documento
title
y subtitle
: el título/subtítulo del documentoauthor
: autor del mismoformat
: formato de salida (podremos personalizar)
theme
: si tienes algún archivo de estilostoc
: si quieres índice o notoc-location
: posición del índicetoc-title
: título del índiceeditor
: si estás en modo visual o source.La cabecera están en formato YAML y contiene los metadatos del documento
title
y subtitle
: el título/subtítulo del documentoauthor
: autor del mismoformat
: formato de salida (podremos personalizar)
theme
: si tienes algún archivo de estilostoc
: si quieres índice o notoc-location
: posición del índicetoc-title
: título del índiceeditor
: si estás en modo visual o source.La cabecera están en formato YAML y contiene los metadatos del documento
title
y subtitle
: el título/subtítulo del documentoauthor
: autor del mismoformat
: formato de salida (podremos personalizar)
theme
: si tienes algún archivo de estilostoc
: si quieres índice o notoc-location
: posición del índicetoc-title
: título del índiceeditor
: si estás en modo visual o source.Respecto a la escritura solo hay una cosa importante: salvo que indiquemos lo contrario, TODO lo que vamos a escribir es texto (normal). No código R.
Vamos a empezar escribiendo una sección al inicio (# Intro
y detrás por ej. la frase
Este material ha sido diseñado por el profesor Javier Álvarez Liébana, docente en la Universidad Complutense de Madrid
Además al Running Code
le añadiremos una almohadilla #
: las almohadillas FUERA DE CHUNKS nos servirán para crear epígrafes (secciones) en el documento
Para que el índice capture dichas secciones modificaremos la cabecera del archivo como se observa en la imagen (puedes cambiar la localización del índice y el título si quieres para probar).
Vamos a personalizar un poco el texto haciendo lo siguiente:
Vamos a añadir negrita al nombre (poniendo ** al inicio y al final).
Vamos añadir cursiva a la palabra material (poniendo _ al inicio y al final).
Vamos añadir un enlace https://www.ucm.es, asociándolo al nombre de la Universidad. Para ello el título lo ponemos entre corchetes y justo detrás el enlace entre paréntesis [«Universidad Complutense de Madrid»](https://www.ucm.es)
Para añadir código R debemos crear nuestras cajas de código llamadas chunks: altos en el camino en nuestro texto markdown donde podremos incluir código de casi cualquier lenguaje (y sus salidas).
Para incluir uno deberá de ir encabezado de la siguiente forma tienes un atajo Command + Option + I
(Mac) o Ctrl + Shift + I
(Windows)
Dentro de dicha cajita (que tiene ahora otro color en el documento) escribiremos código R como lo veníamos haciendo hasta ahora en los scripts.
Los chunks pueden tener un nombre o etiqueta, de forma que podamos referenciarlos de nuevo para no repetir código.
En cada chunk aparecen dos botones:
botón de play: activa la ejecución y salida de ese chunk particular (lo puedes visualizar dentro de tu propio RStudio
)
botón de rebobinar: activa la ejecución y salida de todos los chunk hasta ese (sin llegar a él)
Además podemos incluir código R dentro de la línea de texto (en lugar de mostrar el texto x ejecuta el código R mostrando la variable).
Los chunks podemos personalizarlos con opciones al inicio del chunk precedido de #|
:
#| echo: false
: ejecuta código y se muestra resultado pero no visualiza código en la salida.
#| include: false
: ejecuta código pero no muestra resultado y no visualiza código en la salida.
#| eval: false
: no ejecuta código, no muestra resultado pero sí visualiza código en la salida.
#| message: false
: ejecuta código pero no muestra mensajes de salida.
#| warning: false
: ejecuta código pero no muestra mensajes de warning.
#| error: true
: ejecuta código y permite que haya errores mostrando el mensaje de error en la salida.
Estas opciones podemos aplicarlas chunk a chunk o fijar los parámetros de forma global con knitr::opts_chunk$set()
al inicio del documento (dentro de un chunk).
Si queremos que aplique la opción a todos los chunks por defecto debemos incluirlo al final de la cabecera, como opciones de ejecución
Además de texto y código podemos introducir lo siguiente:
Ecuaciones: puedes añadir además ecuaciones como \(x^2\) (he escrito $x^2$
, la ecuación entre dólares).
Listas: puedes itemizar elementos poniendo *
* Paso 1: ...
* Paso 2: ...
{#nombre-seccion}
) y llamarlas luego con [Sección](@nombre-seccion)
Por último, también podemos añadir pies de gráficas o imágenes añadiendo #| fig-cap: "..."
Fíjate que el caption está en el margen (por ejemplo). Puedes cambiarlo introduciendo ajustes en la cabecera (todo lo relativo a figuras empieza por fig-
, y puedes ver las opciones tabulando). Tienes más información en https://quarto.org/
Por último puedes añadir un tema personalizado incluyendo un archivo de estilos (archivo en formato .scss
o .css
). Te he dejado uno en https://github.com/dadosdelaplace/docencia-R-master-bio-2324/tree/main/material.
Importante
El archivo de estilos debe estar en la misma carpeta que el archivo .qmd
También puedes hacerlo de manera sencilla añadiendo a los textos un poco de HTML. Por ejemplo, para personalizar el color de un texto va entre corchetes y justo tras el texto, entre llaves, las opciones de estilo
Esta palabra es roja …
… y esta verde y en negrita
Puedes añadir algunas «animaciones» usando lo que se conoce como Revealjs (javascript), especifcándolo en la cabecera y usando bloques de dicho lenguaje delimitados por :::
al inicio y final, y la palabra de la «herramienta» a usar. Por ejemplo {.incremental}
hace una transición de los elementos.
También puedes usar los bloques de llamada que por defecto son note
, tip
, warning
, caution
e important
(aunque los puedes crear y personalizar). Para ello basta con usar :::{.callout-tipo}
y el tipo que quieras
:::{.callout-tip}
Note that there are five types of callouts, including:
`note`, `tip`, `warning`, `caution`, and `important`.
:::
Tip
Recuerda que los 5 tipos son note
, tip
, warning
, caution
e important
.
Caution
Úsalos con cabeza, a veces mucho recursos estético puede marear.
Con :::: columns
podemos definir una disposición de múltiples columnas donde cada una viene definida por ::: {.column width="65%"} cosa :::
, indicando al lado del porcentaje cuanto quieres que ocupe cada columna (¡cuidado, no dejar espacios!)
:::: columns
::: {.column width="65%"}
Así se define un vector
:::
::: {.column width="35%"}
x <- c(1, 2, 3)
x
:::
::::
Con ::: panel-tabset :::
podemos también definir un panel de pestañas
### Ejercicios
Resuelve los siguientes ejercicios
::: panel-tabset
#### Ejercicio 1
Define un vector x con los primeros 3 números naturales
#### Ejercicio 2
Haz la suma del vector anterior
:::
Resuelve los siguientes ejercicios
Define un vector x con los primeros 3 números naturales
Haz la suma del vector anterior
Además {reticulate}
nos permite crear chunks de python
dentro de un Quarto en R
(ver https://quarto.org/docs/computations/python.html para crear jupyter notebooks directamente desde Quarto)
Vamos a realizar un pequeño simulacro antes de la entrega usando el dataset starwars
del paquete {dplyr}
# A tibble: 87 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Leia Or… 150 49 brown light brown 19 fema… femin…
6 Owen La… 178 120 brown, gr… light blue 52 male mascu…
7 Beru Wh… 165 75 brown light blue 47 fema… femin…
8 R5-D4 97 32 <NA> white, red red NA none mascu…
9 Biggs D… 183 84 black light brown 24 male mascu…
10 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 77 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
En él tenemos diferentes variables de los personajes de Star Wars, con características de su pelo, piel, altura, nombre, etc.
Crea un documento
.qmd
con nombre, título, formato e índice. Cada ejercicio posterior será una subsección del documento. Ejecuta los chunks que consideres y comenta las salidas para responder a cada pregunta
Ejercicio 1. ¿Cuántos personajes hay guardados en la base de datos? ¿Cuántas características se han medido de cada uno?
Ejercicio 2. Extrae en dos variables distintas
nombres
yedades
las variables correspondientes de la tabla. ¿De qué tipo es la variable nombre? ¿Y la variable birth_year?
Ejercicio 3. Obtén el vector de nombres de los personajes ordenados de mayores a jóvenes.
Ejercicio 4. Busca ayuda de la función unique(). Úsala para saber que modalidades tiene la variable cualitativa correspondiente al color de ojos. ¿Cuántos distintos hay?
Ejercicio 5. ¿Existe ALGÚN valor ausente en la variable de color ojos?
Ejercicio 6. Calcula la media y desviación típica de las variables de estatura y peso (cuidado con los ausentes). Define un nuevo tibble con esas dos variables e incorpora una tercera variable que se llame “IMC” que calcule el índice de masa corporal. Incorpora con
$ $
la fórmula usada para el IMC.
.qmd
en el que al menos la cabecera contengaTras ello vuelve a ejercicios del tema 2 y estructura un documento donde cada ejercicio sea una subsección. En cada subsección pon el enunciado.
Debajo de cada enunciado pon el chunk con el código correspondiente, así como comentarios de texto de la salida (con negritas y cursivas)
Añade un último chunk en el que, dado un vector x <- 1:5
, calcules su media, e incluye con $ $
la fórmula de la media aritmética e incrusta además una foto de la fórmula que encuentres por google.
Renderiza el documento para obtener el html
El día de la entrega tendrás subido una plantilla de entrega en formato .qmd
en el campus.
Descomprime la carpeta (¡importante! si no descomprimes, aunque puedas editar el .qmd
, no podrás generar el .html
)
Edita la cabecera con tu nombre y DNI
Deberás rellenar cada chunk con el código que consideres (en algunos te he dejado pistas) y cambiar de #| eval: false
a #| eval: true
(si los quitas directamente, por defecto ya es true)
Deberás de comentar con texto normal lo que consideres para responder a las preguntas.
Será OBLIGATORIO subir el archivo .html
generado (solo se corregirá dicho archivo) así que ve renderizando según rellenas el documento, no lo dejes para el final.
Estructuras condicionales y bucles
Una estructura de control se compone de una serie de comandos orientados a decidir el camino que tu código debe recorrer
Si se cumple la condición A, ¿qué sucede?
¿Y si sucede B?
¿Cómo puedo repetir una misma expresión (dependiendo de una variable)?
Si has programado antes, quizás te sea familiar las conocidas como estructuras condicionales tales como if (blabla) {...} else {...}
o bucles for/while
(a evitar siempre que podamos).
Una de las estructuras de control más famosas son las conocidas como estructuras condicionales if
.
SI (IF) un conjunto de condiciones se cumple (TRUE), entonces ejecuta lo que haya dentro de las llaves
Por ejemplo, la estructura if (x == 1) { código A }
lo que hará será ejecutar el código A entre llaves pero SOLO SI la condición entre paréntesis es cierta (solo si x
es 1). En cualquier otro caso, no hará nada.
Nuestra estructura condicional hará lo siguiente: si existe algún menor de edad, imprimirá por pantalla un mensaje.
En caso de que las condiciones no sean ciertas dentro de if()
(FALSE
), no sucede nada
No obtenemos ningún mensaje porque la condición all(edad >= 18)
no es TRUE
, así que no ejecuta nada.
La estructura if (condicion) { código A }
puede combinarse con un else { código B }
: cuando la condición no está verificada, se ejecutará el código alternativo B dentro de else { }
, permitiéndonos decidir que sucede cuando se cumple y cuando no.
Esta estructura if - else
puede ser anidada: imagina que queremos ejecutar un código si todos son menores; si no sucede, pero todos son mayores de 16, hacer otra cosa; en cualquier otra cosa, otra acción.
if (all(edad >= 18)) {
print("Todos son mayores de edad")
} else if (all(edad >= 16)) {
print("Hay algún menor de edad pero todos con 16 años o más")
} else { print("Hay alguna persona con menos de 16 años") }
[1] "Hay alguna persona con menos de 16 años"
Truco
Puedes colapsar las estructuras haciendo click en la flecha a la izquierda que aparece en tu script.
Esta estructura condicional se puede vectorizar (en una sola línea) con if_else()
(del paquete {dplyr}
), cuyos argumentos son
NA
Vamos a etiquetar sin son mayores/menores y un “desconocido” cuando no conocemos
[1] "desconocido" "menor" "menor" "mayor" "mayor"
[6] "mayor" "mayor" "mayor" "mayor"
En R
base existe ifelse()
: no deja especificar que hacer con los ausentes pero permite especificar distintos tipos de datos en TRUE
y en FALSE
.
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 ¿Cuál es la salida del siguiente código?
📝 ¿Cuál es la salida del siguiente código?
📝 Modifica el código inferior para que, cuando no se pueda verificar si la raíz cuadrada de un número es menor que 2, devuelva -1
📝 ¿Cuál es son los valores de x
e y
del código inferior para z <- 1
, z <- -1
y z <- -5
?
📝 ¿Qué pasaría si ejecutamos el siguiente código? Spoiler: da error. ¿Por qué? ¿Cómo solucionarlo?
📝 ¿Qué sucederá si ejecutamos el código inferior?
📝 Del paquete {lubridate}
, la función hour()
nos devuelve la hora de una fecha dada, y la función now()
nos devuelve fecha y hora del momento actual. Con ambas funciones haz que se imprima por pantalla (cat()
) “buenas noches” solo a partir de las 21 horas.
Aunque en la mayoría de ocasiones se pueden reemplazar por otras estructuras más eficientes y legibles, es importante conocer una de las expresiones de control más famosas: los bucles.
for { }
: permite repetir el mismo código en un número prefijado y conocido de veces.
while { }
: permite repetir el mismo código pero en un número indeterminado de veces (hasta que una condición deje de cumplirse).
Un bucle for es una estructura que permite repetir un conjunto de órdenes un número finito, prefijado y conocido de veces dado un conjunto de índices.
Vamos a definir un vector x <- c(0, -7, 1, 4)
y otra variable vacía y
. Tras ello definiremos un bucle for con for () { }
: dentro de los paréntesis indicaremos un índice y unos valores a recorrer, dentro de las llaves el código a ejecutar en cada iteración (en este caso, rellenar y
como x + 1
)
Un bucle for es una estructura que permite repetir un conjunto de órdenes un número finito, prefijado y conocido de veces dado un conjunto de índices.
Vamos a definir un vector x <- c(0, -7, 1, 4)
y otra variable vacía y
. Tras ello definiremos un bucle for con for () { }
: dentro de los paréntesis indicaremos un índice y unos valores a recorrer, dentro de las llaves el código a ejecutar en cada iteración (en este caso, rellenar y
como x + 1
)
Un bucle for es una estructura que permite repetir un conjunto de órdenes un número finito, prefijado y conocido de veces dado un conjunto de índices.
Vamos a definir un vector x <- c(0, -7, 1, 4)
y otra variable vacía y
. Tras ello definiremos un bucle for con for () { }
: dentro de los paréntesis indicaremos un índice y unos valores a recorrer, dentro de las llaves el código a ejecutar en cada iteración (en este caso, rellenar y
como x + 1
)
Fíjate que debido a que R
funciona de manera vectorial por defecto, el bucle es lo mismo que hacer x + 1
directamente.
Otra opción habitual es indicar los índices de manera «automática»: desde el primero 1
hasta el último (que corresponde con la longitud de x length(x)
)
Así la estructura general de un bucle for será siempre la siguiente
SIEMPRE sabemos cuántas iteraciones tenemos (tantas como elementos haya en el conjunto a indexar)
Como ya hemos aprendido con el paquete{microbenchmark}
podemos chequear como los bucles suelen ser muy ineficientes (de ahí que debamos evitarlos en la mayoría de ocasiones
library(microbenchmark)
x <- 1:1000
microbenchmark(y <- x^2,
for (i in 1:100) { y[i] <- x[i]^2 },
times = 500)
Unit: microseconds
expr min lq mean median
y <- x^2 1.722 1.968 2.699194 2.132
for (i in 1:100) { y[i] <- x[i]^2 } 1263.251 1287.092 1374.566164 1301.688
uq max neval
3.116 59.327 500
1370.938 4121.894 500
Podemos ver otro ejemplo de bucle combinando números y textos: definimos un vector de edades y de nombres, e imprimimos el nombre y edad i-ésima.
Aunque normalmente se suelen indexar con vectors numéricos, los bucles pueden ser indexados sobre cualquier estructura vectorial, da igual de que tipo sea el conjunto
Vamos a combinar las estructuras condicionales y los bucles: usando el conjunto swiss
del paquete {datasets}
, vamos a asignar NA
si los valores de fertilidad son mayores de 80.
Otra forma de crear un bucle es con la estructura while { }
, que nos ejecutará un bucle un número desconocido de veces, hasta que una condición deje de cumplirse (de hecho puede que nunca termine). Por ejemplo, vamos a inializar una variable ciclos <- 1
, que incrementaremos en cada paso, y no saldremos del bucle hasta que ciclos > 4
.
Un bucle while
será siempre como sigue
¿Qué sucede cuando la condición nunca es FALSE? Pruébalo tu mismo
Cuidado
Un bucle while { }
puede ser bastante «peligroso» sino controlamos bien cómo pararlo.
Contamos con dos palabras reservadas para abortar un bucle o forzar su avance:
break
: permite abortar un bucle incluso si no se ha llegado a su finalContamos con dos palabras reservadas para abortar un bucle o forzar su avance:
next
: fuerza un bucle a avanzar a la siguiente iteraciónAunque no es tan usado como las opciones anteriores, también contamos con repeat { }
que ejecuta un bucle de manera infinita hasta que se indique abortar con un break
Aunque no es formalmente un bucle, otra forma de repetir código un número de veces es hacer uso de replicate()
: simplemente permite repetir lo mismo n veces
La función replicate()
se suele usar para generar distintas repeticiones de elementos aleatorios. Por ejemplo, imaginemos que queremos generar 3 muestras de distribuciones normales, en la que cada muestra tendrá 7 elementos. Para generar una se usa rnorm(n = 7)
(r de resample, norm de normal, y si no se dice nada es media 0 y desv 1).
[,1] [,2] [,3]
[1,] 0.5931257 0.2548865 -0.4601346
[2,] 1.9093657 -2.4075243 -0.4526144
[3,] 0.9902096 0.2757109 -1.2910456
[4,] -1.7054437 1.8615060 1.4108765
[5,] 0.5409032 -0.5505094 -1.4473065
[6,] 1.5770271 -0.7582591 0.7892509
[7,] 1.0449203 -1.0664975 -1.0188459
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Modifica el código inferior para que se imprima un mensaje por pantalla si y solo si todos los datos de airquality
son con mes distinto a enero
📝 Modifica el código inferior para guardar en una variable llamada temp_alta
un TRUE
si alguno de los registros tiene una temperatura superior a 90 grados Farenheit y FALSE
en cualquier otro caso
📝 Modifica el código inferior para diseñar un bucle for
de 5 iteraciones que solo recorra los primeros 5 impares (y en cada paso del bucle los imprima)
Intenta responder a las preguntas planteadas en el workbook donde tendrás que diseñar algunos estudios de simulación haciendo uso de bucles y estructuras condicionales
¿Qué es una función? ¿Cómo se definen? Variables locales vs globales
No solo podemos usar funciones predeterminadas que vienen ya cargadas en paquetes, además podemos crear nuestras propias funciones para automatizar tareas. ¿Cómo crear nuestra propia función? Veamos su esquema básico:
Nombre: por ejemplo name_fun
(sin espacios ni caracteres extraños). Al nombre le asignamos la palabra reservada function()
.
Definir argumentos de entrada (dentro de function()
).
Cuerpo de la función dentro de { }
.
Finalizamos la función con los argumentos de salida con return()
.
No solo podemos usar funciones predeterminadas que vienen ya cargadas en paquetes, además podemos crear nuestras propias funciones para automatizar tareas. ¿Cómo crear nuestra propia función? Veamos su esquema básico:
Nombre: por ejemplo name_fun
(sin espacios ni caracteres extraños). Al nombre le asignamos la palabra reservada function()
.
Definir argumentos de entrada (dentro de function()
).
Cuerpo de la función dentro de { }
.
Finalizamos la función con los argumentos de salida con return()
.
No solo podemos usar funciones predeterminadas que vienen ya cargadas en paquetes, además podemos crear nuestras propias funciones para automatizar tareas. ¿Cómo crear nuestra propia función? Veamos su esquema básico:
Nombre: por ejemplo name_fun
(sin espacios ni caracteres extraños). Al nombre le asignamos la palabra reservada function()
.
Definir argumentos de entrada (dentro de function()
).
Cuerpo de la función dentro de { }
.
Finalizamos la función con los argumentos de salida con return()
.
No solo podemos usar funciones predeterminadas que vienen ya cargadas en paquetes, además podemos crear nuestras propias funciones para automatizar tareas. ¿Cómo crear nuestra propia función? Veamos su esquema básico:
Nombre: por ejemplo name_fun
(sin espacios ni caracteres extraños). Al nombre le asignamos la palabra reservada function()
.
Definir argumentos de entrada (dentro de function()
).
Cuerpo de la función dentro de { }
.
Finalizamos la función con los argumentos de salida con return()
.
arg1, arg2, ...
: serán los argumentos de entrada, los argumentos que toma la función para ejecutar el código que tiene dentro
código
: líneas de código que queramos que ejecute la función.
return(var_salida)
: se introducirán los argumentos de salida.
Importante
Todas las variables que definamos dentro de la función son variables LOCALES: solo existirán dentro de la función salvo que especifiquemos lo contrario.
Veamos un ejemplo muy simple de función para calcular el área de un rectángulo.
Dado que el área de un rectángulo se calcula como el producto de sus lados, necesitaremos precisamente eso, sus lados: esos serán los argumentos de entrada y el valor a devolver será justo su área (\(lado_1 * lado_2\)).
Veamos un ejemplo muy simple de función para calcular el área de un rectángulo.
Dado que el área de un rectángulo se calcula como el producto de sus lados, necesitaremos precisamente eso, sus lados: esos serán los argumentos de entrada y el valor a devolver será justo su área (\(lado_1 * lado_2\)).
Veamos un ejemplo muy simple de función para calcular el área de un rectángulo.
Dado que el área de un rectángulo se calcula como el producto de sus lados, necesitaremos precisamente eso, sus lados: esos serán los argumentos de entrada y el valor a devolver será justo su área (\(lado_1 * lado_2\)).
También podemos hacer una definición directa de las variables sin almacenar por el camino.
Tip
Aunque no sea necesario, es recomendable hacer explícita la llamada de los argumentos, especificando en el código qué valor es para cada argumento para que no dependa de su orden, haciendo el código más legible
Imagina ahora que nos damos cuenta que el 90% de las veces usamos dicha función para calcular por defecto el área de un cuadrado (es decir, solo necesitamos un lado). Para ello, podemos definir argumentos por defecto en la función: tomarán dicho valor salvo que le asignemos otro.
¿Por qué no asignar lado_2 = lado_1
por defecto, para ahorrar líneas de código y tiempo?
Ahora por defecto el segundo lado será igual al primero (si se lo añadimos usará ambos).
Compliquemos un poco la función y añadamos en la salida los valores de cada lado, etiquetados como lado_1
y lado_2
, empaquetando la salida en una vector.
Podemos complicar un poco más la salida añadiendo una cuarta variable que nos diga, en función de los argumentos, si rectángulo o cuadrado, teniendo que añadir en la salida una variable que de tipo caracter (o lógica).
# Definición del nombre de función y argumentos de entrada
calcular_area <- function(lado_1, lado_2 = lado_1) {
# Cuerpo de la función
area <- lado_1 * lado_2
# Resultado
return(c("area" = area, "lado_1" = lado_1, "lado_2" = lado_2,
"tipo" = if_else(lado_1 == lado_2, "cuadrado", "rectángulo")))
}
calcular_area(5, 3)
area lado_1 lado_2 tipo
"15" "5" "3" "rectángulo"
Problema: al intentar juntar números y texto, lo convierte todo a números. Podríamos guardarlo todo en un tibble()
como hemos aprendido o en un objeto conocido en R
como listas
Veamos un pequeño resumen de los datos que ya conocemos:
vectores: colección de elementos de igual tipo. Pueden ser números, caracteres o valores lógicos, entre otros.
matrices: colección BIDIMENSIONAL de elementos de igual tipo e igual longitud.
data.frame / tibble: colección BIDIMENSIONAL de elementos de igual longitud pero de cualquier tipo.
Las listas serán colecciones de variables de diferente tipo y diferente longitud, con estructuras totalmente heterógeneas (incluso una lista puede tener dentro a su vez otra lista).
Vamos a crear nuestra primera lista con list()
con tres elementos: el nombre de nuestros padres/madres, nuestro lugar de nacimiento y edades de nuestros hermanos.
Si observas el objeto que hemos definido como lista, su longitud del es de 3 ya que tenemos guardados tres elementos: un vector de caracteres (de longitud 2), un caracter (vector de longitud 1), y un vector de números (de longitud 3)
Si los juntásemos con un tibble()
, al tener distinta longitud, obtendríamos un error.
[[i]]
accedemos al elemento i-ésimo de la lista.$nombre_elemento
accedemos por su nombre.# Definición del nombre de función y argumentos de entrada
calcular_area <- function(lado_1, lado_2 = lado_1) {
# Cuerpo de la función
area <- lado_1 * lado_2
# Resultado
return(list("area" = area, "lado_1" = lado_1, "lado_2" = lado_2,
"tipo" = if_else(lado_1 == lado_2, "cuadrado", "rectángulo")))
}
calcular_area(5, 3)
$area
[1] 15
$lado_1
[1] 5
$lado_2
[1] 3
$tipo
[1] "rectángulo"
Antes nos daba igual el orden de los argumentos pero ahora el orden de los argumentos de entrada importa, ya que en la salida incluimos lado_1
y lado_2
.
Recomendación
Parece una tontería lo que hemos hecho pero hemos cruzado una frontera importante: hemos pasado de consumir conocimiento (código de otros paquetes, elaborado por otros/as), a generar conocimiento, creando nuestras propias funciones.
Las funciones van a ser claves en tu día a día ya que te permitirá automatizar código que vas a repetir una y otra vez: empaquetando ese código bajo un alias (nombre de la función) vas a poder usarlo una y otra vez sin necesidad de programarlo (por lo que hacer el doble de trabajo no implicará trabajar el doble)
Un aspecto importante sobre el que reflexionar con las funciones: ¿qué sucede si nombramos a una variable dentro de una función a la que se nos ha olvidado asignar un valor dentro de la misma?
Debemos ser cautos al usar funciones en R
, ya que debido a la «regla lexicográfica», si una variable no se define dentro de la función, R
buscará dicha variable en el entorno de variables.
Si una variable ya está definida fuera de la función (entorno global), y además es usada dentro de cambiando su valor, el valor solo cambia dentro pero no en el entorno global.
Si queremos que además de cambiar localmente lo haga globalmente deberemos usar la doble asignación (<<-
).
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Modifica el código inferior para definir una función llamada funcion_suma
, de forma que dados dos elementos, devuelve su suma.
📝 Modifica el código inferior para definir una función llamada funcion_producto
, de forma que dados dos elementos, devuelve su producto, pero que por defecto calcule el cuadrado
📝 Define una función llamada igualdad_nombres
que, dados dos nombres, nos diga si son iguales o no. Hazlo considerando importantes las mayúsculas, y sin que importen las mayúsculas. Usa el paquete {stringr}
.
# Distinguiendo mayúsculas
igualdad_nombres <- function(persona_1, persona_2) {
return(persona_1 == persona_2)
}
igualdad_nombres("Javi", "javi")
igualdad_nombres("Javi", "Lucía")
# Sin importar mayúsculas (ver también str_equal)
igualdad_nombres <- function(persona_1, persona_2) {
return(str_to_upper(persona_1) == str_to_upper(persona_2))
}
igualdad_nombres("Javi", "javi")
igualdad_nombres("Javi", "Lucía")
📝 Crea una función llamada calculo_IMC
que, dados dos argumentos (peso y estatura en metros) y un nombre, devuelva una lista con el IMC (\(peso/(estatura_m^2)\)) y el nombre.
📝 Repite el ejercicio anterior pero con otro argumento opcional que se llame unidades (por defecto, unidades = "metros"
). Desarrolla la función de forma que haga lo correcto si unidades = "metros"
y si unidades = "centímetros"
.
📝 Crea un tibble ficticio de 7 personas, con tres variables (inventa nombre, y simula peso, estatura en centímetros), y adapta la función calculo_IMC()
de forma que obtengamos una cuarta columna con su IMC.
datos <- tibble("nombres" = c("javi", "sandra", "eva", "ana", "carlos", "leo", NA),
"peso" = rnorm(n = 7, mean = 70, sd = 1),
"estatura" = rnorm(n = 7, mean = 168, sd = 5))
# IMPORTANTE. if_else trabaja de manera vectorial, elemento a elemento: la longitud de la
# condición (unidades == "metros" es un vector lógico de longitud uno) debe ser igual que
# lo que le decimos que haga cuando es TRUE y FALSE (en este caso, condicion tiene longitud 1
# pero al aplicarla lo que devuelve es vector de tamaño 7, por eso hay que usar ifelse (sin _))
calculo_IMC <- function(nombre, peso, estatura, unidades = "metros") {
return(list("nombre" = nombre,
"IMC" = peso/(ifelse(unidades == "metros", estatura, estatura/100)^2)))
}
datos |> mutate(IMC = calculo_IMC(nombres, peso, estatura, unidades = "centímetros")$IMC)
📝 Crea una función llamada atajo
que tenga dos argumentos numéricos x
e y
. Si ambos son iguales, debes devolver "iguales"
y hacer que la función acaba automáticamente (piensa cuándo una función sale). OJO: x
e y
podrían ser vectores. Si son distintos (de igual de longitud) calcula la proporción de elementos diferentes. Si son distintos (por ser distinta longitud), devuelve los elementos que no sean comunes.
Para practicar con funciones vamos a crear un completo conversor de temperaturas que, dada una temperatura en Fahrenheit, Celsius o Kelvin, la convierta a cualquiera de las otras
Intenta responder a las preguntas planteadas en el workbook hasta construirlo.
Nuestra base de datos: tibble. Tidydata: un multiverso de datos limpios
Nuestro formato final de base de datos será el objeto de tipo tibble
, un data.frame
mejorado
library(tibble)
tibble("estatura" = c(1.7, 1.8, 1.6), "peso" = c(80, 75, 70), "IMC" = peso / (estatura^2))
# A tibble: 3 × 3
estatura peso IMC
<dbl> <dbl> <dbl>
1 1.7 80 27.7
2 1.8 75 23.1
3 1.6 70 27.3
Metainformación: en la cabecera nos dice ya automáticamente el número de filas y columnas, y el tipo de cada variable
Recursividad: permite definir las variables secuencialmente (como hemos visto)
Consistencia: si accedes a una columna que no existe avisa con un warning
Por filas: permite crear por filas con tribble()
Para definir un tibble()
nosotro mismos tenemos dos opciones:
tibble()
del paquete {tibble}
(ya incluido en {tidyverse}
)Hasta ahora todo lo que hemos hecho en R
lo hemos realizado en el paradigma de programación conocido como R base. Y es que cuando R
nació como lenguaje, muchos de los que programaban en él imitaron formas y metodologías heredadas de otros lenguajes, basado en el uso de
Bucles for
Bucles while
Estructuras if-else
Y aunque conocer dichas estructuras puede sernos en algunos casos interesantes, en la mayoría de ocasiones han quedado caducas y vamos a poder evitarlas (en especial los bucles) ya que R
está especialmente diseñado para trabajar de manera funcional (en lugar de elemento a elemento).
En ese contexto de programación funcional, hace una década nacía {tidyverse}
, un «universo» de paquetes para garantizar un flujo de trabajo eficiente, coherente y lexicográficamente sencillo de entender, basado en la idea de que nuestros datos están limpios y ordenados (tidy)
{lubridate}
manejo de fechas{rvest}
: web scraping{tidymodels}
: modelización/predicción{tibble}
: optimizando data.frame{tidyr}
: limpieza de datos{readr}
: carga datos rectangulares (.csv), {readxl}
para importar archivos .xls y .xlsx{dplyr}
: gramática para depurar{stringr}
: manejo de textos{purrr}
: manejo de listas{forcats}
: manejo de cualitativas{ggplot2}
: visualización de datos{lubridate}
manejo de fechas{rvest}
: web scraping{tidymodels}
: modelización/predicción{tibble}
: optimizando data.frame{tidyr}
: limpieza de datos{readr}
: carga datos rectangulares (.csv), {readxl}
para importar archivos .xls y .xlsx{dplyr}
: gramática para depurar{stringr}
: manejo de textos{purrr}
: manejo de listas{forcats}
: manejo de cualitativas{ggplot2}
: visualización de datosTidy datasets are all alike, but every messy dataset is messy in its own way (Hadley Wickham, Chief Scientist en RStudio)
TIDYVERSE
El universo de paquetes {tidyverse}
se basa en la idea introducida por Hadley Wickham (el Dios al que rezamos) de estandarizar el formato de los datos para
Lo primero por tanto será entender qué son los conjuntos tidydata ya que todo {tidyverse}
se basa en que los datos están estandarizados.
En {tidyverse}
será clave el operador pipe (tubería) definido como |>
(ctrl+shift+M): será una tubería que recorre los datos y los transforma.
Apunte importante
Desde la versión 4.1.0 de R
disponemos de |>
, un pipe nativo disponible fuera de tidyverse, sustituyendo al antiguo pipe %>%
que dependía del paquete {magrittr}
(bastante problemático).
La principal ventaja es que el código sea muy legible (casi literal) pudiendo hacer grandes operaciones con los datos con apenas código.
¿Pero qué aspecto tienen los datos no tidy? Vamos a cargar la tabla table4a
del paquete {tidyr}
(ya lo tenemos cargado del entorno tidyverse).
# A tibble: 3 × 3
country `1999` `2000`
<chr> <dbl> <dbl>
1 Afghanistan 745 2666
2 Brazil 37737 80488
3 China 212258 213766
¿Qué puede estar fallando?
❎ Cada fila representa dos observaciones (1999 y 2000) → las columnas 1999
y 2000
en realidad deberían ser en sí valores de una variable y no nombres de columnas.
Incluiremos una nueva columna que nos guarde el año y otra que guarde el valor de la variable de interés en cada uno de esos años. Y lo haremos con la función pivot_longer()
: pivotaremos la tabla a formato long:
cols
: nombre de las variables a pivotarnames_to
: nombre de la nueva variable a la quemandamos la cabecera de la tabla (los nombres).values_to
: nombre de la nueva variable a la que vamos a mandar los datos.Veamos otro ejemplo con la tabla table2
# A tibble: 12 × 4
country year type count
<chr> <dbl> <chr> <dbl>
1 Afghanistan 1999 cases 745
2 Afghanistan 1999 population 19987071
3 Afghanistan 2000 cases 2666
4 Afghanistan 2000 population 20595360
5 Brazil 1999 cases 37737
6 Brazil 1999 population 172006362
7 Brazil 2000 cases 80488
8 Brazil 2000 population 174504898
9 China 1999 cases 212258
10 China 1999 population 1272915272
11 China 2000 cases 213766
12 China 2000 population 1280428583
¿Qué puede estar fallando?
# A tibble: 12 × 4
country year type count
<chr> <dbl> <chr> <dbl>
1 Afghanistan 1999 cases 745
2 Afghanistan 1999 population 19987071
3 Afghanistan 2000 cases 2666
4 Afghanistan 2000 population 20595360
5 Brazil 1999 cases 37737
6 Brazil 1999 population 172006362
7 Brazil 2000 cases 80488
8 Brazil 2000 population 174504898
9 China 1999 cases 212258
10 China 1999 population 1272915272
11 China 2000 cases 213766
12 China 2000 population 1280428583
❎ Cada observación está dividido en dos filas → los registros con el mismo año deberían ser el mismo
Lo que haremos será lo opuesto: con pivot_wider()
ensancharemos la tabla
# A tibble: 6 × 4
country year cases population
<chr> <dbl> <dbl> <dbl>
1 Afghanistan 1999 745 19987071
2 Afghanistan 2000 2666 20595360
3 Brazil 1999 37737 172006362
4 Brazil 2000 80488 174504898
5 China 1999 212258 1272915272
6 China 2000 213766 1280428583
Veamos otro ejemplo con la tabla table3
# A tibble: 6 × 3
country year rate
<chr> <dbl> <chr>
1 Afghanistan 1999 745/19987071
2 Afghanistan 2000 2666/20595360
3 Brazil 1999 37737/172006362
4 Brazil 2000 80488/174504898
5 China 1999 212258/1272915272
6 China 2000 213766/1280428583
¿Qué puede estar fallando?
❎ Cada celda contiene varios valores
Lo que haremos será hacer uso de la función separate()
para mandar separar cada valor a una columna diferente.
# A tibble: 6 × 4
country year cases pop
<chr> <dbl> <chr> <chr>
1 Afghanistan 1999 745 19987071
2 Afghanistan 2000 2666 20595360
3 Brazil 1999 37737 172006362
4 Brazil 2000 80488 174504898
5 China 1999 212258 1272915272
6 China 2000 213766 1280428583
# A tibble: 6 × 4
country year cases pop
<chr> <dbl> <chr> <chr>
1 Afghanistan 1999 745 19987071
2 Afghanistan 2000 2666 20595360
3 Brazil 1999 37737 172006362
4 Brazil 2000 80488 174504898
5 China 1999 212258 1272915272
6 China 2000 213766 1280428583
Fíjate que los datos, aunque los ha separado, los ha mantenido como texto cuando en realidad deberían ser variables numéricas. Para ello podemos añadir el argumento opcional convert = TRUE
# A tibble: 6 × 4
country year cases pop
<chr> <dbl> <int> <int>
1 Afghanistan 1999 745 19987071
2 Afghanistan 2000 2666 20595360
3 Brazil 1999 37737 172006362
4 Brazil 2000 80488 174504898
5 China 1999 212258 1272915272
6 China 2000 213766 1280428583
Veamos el último ejemplo con la tabla table5
# A tibble: 6 × 4
country century year rate
<chr> <chr> <chr> <chr>
1 Afghanistan 19 99 745/19987071
2 Afghanistan 20 00 2666/20595360
3 Brazil 19 99 37737/172006362
4 Brazil 20 00 80488/174504898
5 China 19 99 212258/1272915272
6 China 20 00 213766/1280428583
¿Qué puede estar fallando?
❎ Tenemos mismos valores divididos en dos columnas
Usaremos unite()
para unir los valores de siglo y año en una misma columna
# A tibble: 6 × 3
country year_completo rate
<chr> <chr> <chr>
1 Afghanistan 1999 745/19987071
2 Afghanistan 2000 2666/20595360
3 Brazil 1999 37737/172006362
4 Brazil 2000 80488/174504898
5 China 1999 212258/1272915272
6 China 2000 213766/1280428583
Vamos a realizar un ejemplo juntos con la tabla relig_income
del paquete {tidyr}
. Como se indica en la ayuda ? relig_income
, la tabla representa la cantidad de personas que hay en cada tramo de ingresos anuales (20k = 20 000$) y en cada religión.
# A tibble: 18 × 11
religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Agnostic 27 34 60 81 76 137 122
2 Atheist 12 27 37 52 35 70 73
3 Buddhist 27 21 30 34 33 58 62
4 Catholic 418 617 732 670 638 1116 949
5 Don’t k… 15 14 15 11 10 35 21
6 Evangel… 575 869 1064 982 881 1486 949
7 Hindu 1 9 7 9 11 34 47
8 Histori… 228 244 236 238 197 223 131
9 Jehovah… 20 27 24 24 21 30 15
10 Jewish 19 19 25 25 30 95 69
11 Mainlin… 289 495 619 655 651 1107 939
12 Mormon 29 40 48 51 56 112 85
13 Muslim 6 7 9 10 9 23 16
14 Orthodox 13 17 23 32 32 47 38
15 Other C… 9 7 11 13 13 14 18
16 Other F… 20 33 40 46 49 63 46
17 Other W… 5 2 3 4 2 7 3
18 Unaffil… 217 299 374 365 341 528 407
# ℹ 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
# `Don't know/refused` <dbl>
# A tibble: 18 × 11
religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Agnostic 27 34 60 81 76 137 122
2 Atheist 12 27 37 52 35 70 73
3 Buddhist 27 21 30 34 33 58 62
4 Catholic 418 617 732 670 638 1116 949
5 Don’t k… 15 14 15 11 10 35 21
6 Evangel… 575 869 1064 982 881 1486 949
7 Hindu 1 9 7 9 11 34 47
8 Histori… 228 244 236 238 197 223 131
9 Jehovah… 20 27 24 24 21 30 15
10 Jewish 19 19 25 25 30 95 69
11 Mainlin… 289 495 619 655 651 1107 939
12 Mormon 29 40 48 51 56 112 85
13 Muslim 6 7 9 10 9 23 16
14 Orthodox 13 17 23 32 32 47 38
15 Other C… 9 7 11 13 13 14 18
16 Other F… 20 33 40 46 49 63 46
17 Other W… 5 2 3 4 2 7 3
18 Unaffil… 217 299 374 365 341 528 407
# ℹ 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
# `Don't know/refused` <dbl>
¿Es tidydata?
# A tibble: 18 × 11
religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Agnostic 27 34 60 81 76 137 122
2 Atheist 12 27 37 52 35 70 73
3 Buddhist 27 21 30 34 33 58 62
4 Catholic 418 617 732 670 638 1116 949
5 Don’t k… 15 14 15 11 10 35 21
6 Evangel… 575 869 1064 982 881 1486 949
7 Hindu 1 9 7 9 11 34 47
8 Histori… 228 244 236 238 197 223 131
9 Jehovah… 20 27 24 24 21 30 15
10 Jewish 19 19 25 25 30 95 69
11 Mainlin… 289 495 619 655 651 1107 939
12 Mormon 29 40 48 51 56 112 85
13 Muslim 6 7 9 10 9 23 16
14 Orthodox 13 17 23 32 32 47 38
15 Other C… 9 7 11 13 13 14 18
16 Other F… 20 33 40 46 49 63 46
17 Other W… 5 2 3 4 2 7 3
18 Unaffil… 217 299 374 365 341 528 407
# ℹ 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
# `Don't know/refused` <dbl>
No lo es ya que en realidad solo deberíamos tener una variable de ingresos y la tenemos dividida en 11: todas ellas es la misma variable solo que adopta un valor diferente. ¿Cómo convertirla a tidy data?
La idea es pivotar todas las columnas de ingresos para que acaben en una sola columna llamada income
, y los valores (el número de personas) en otra llamada people
(por ejemplo). La tabla la haremos más larga y menos ancha así que…
relig_tidy <-
relig_income |>
pivot_longer(cols = "<$10k":"Don't know/refused", names_to = "income",
values_to = "people")
relig_tidy
# A tibble: 180 × 3
religion income people
<chr> <chr> <dbl>
1 Agnostic <$10k 27
2 Agnostic $10-20k 34
3 Agnostic $20-30k 60
4 Agnostic $30-40k 81
5 Agnostic $40-50k 76
6 Agnostic $50-75k 137
7 Agnostic $75-100k 122
8 Agnostic $100-150k 109
9 Agnostic >150k 84
10 Agnostic Don't know/refused 96
# ℹ 170 more rows
Vamos a hilar más fino: ahora mismo en la variable income
en realidad tenemos dos valores, el límite inferior y el superior de la renta. Vamos a separar dicha variable e ingresos en dos, llamadas income_inf
y income_sup
# A tibble: 180 × 3
religion income people
<chr> <chr> <dbl>
1 Agnostic <$10k 27
2 Agnostic $10-20k 34
3 Agnostic $20-30k 60
4 Agnostic $30-40k 81
5 Agnostic $40-50k 76
6 Agnostic $50-75k 137
7 Agnostic $75-100k 122
8 Agnostic $100-150k 109
9 Agnostic >150k 84
10 Agnostic Don't know/refused 96
# ℹ 170 more rows
Vamos a hilar más fino: ahora mismo en la variable income
en realidad tenemos dos valores, el límite inferior y el superior de la renta. Vamos a separar dicha variable e ingresos en dos, llamadas income_inf
y income_sup
# A tibble: 180 × 4
religion income_inf income_sup people
<chr> <chr> <chr> <dbl>
1 Agnostic <$10k <NA> 27
2 Agnostic $10 20k 34
3 Agnostic $20 30k 60
4 Agnostic $30 40k 81
5 Agnostic $40 50k 76
6 Agnostic $50 75k 137
7 Agnostic $75 100k 122
8 Agnostic $100 150k 109
9 Agnostic >150k <NA> 84
10 Agnostic Don't know/refused <NA> 96
# ℹ 170 more rows
¿Está ya bien? Fíjate bien…
# A tibble: 180 × 4
religion income_inf income_sup people
<chr> <chr> <chr> <dbl>
1 Agnostic <$10k <NA> 27
2 Agnostic $10 20k 34
3 Agnostic $20 30k 60
4 Agnostic $30 40k 81
5 Agnostic $40 50k 76
6 Agnostic $50 75k 137
7 Agnostic $75 100k 122
8 Agnostic $100 150k 109
9 Agnostic >150k <NA> 84
10 Agnostic Don't know/refused <NA> 96
# ℹ 170 more rows
Si te fijas la primera columna el "$10k"
debería ser una cota superior, no inferior. ¿Cómo indicarle que separe bien ese caso?
Le indicaremos que separe si encuentra "-"
o "<"
(usamos |
para separar ambas opciones)
# A tibble: 180 × 4
religion income_inf income_sup people
<chr> <chr> <chr> <dbl>
1 Agnostic "" $10k 27
2 Agnostic "$10" 20k 34
3 Agnostic "$20" 30k 60
4 Agnostic "$30" 40k 81
5 Agnostic "$40" 50k 76
6 Agnostic "$50" 75k 137
7 Agnostic "$75" 100k 122
8 Agnostic "$100" 150k 109
9 Agnostic ">150k" <NA> 84
10 Agnostic "Don't know/refused" <NA> 96
# ℹ 170 more rows
relig_tidy <-
relig_tidy |>
# Separamos por -
separate(income, into = c("income_inf", "income_sup"), sep = "-|<")
relig_tidy
# A tibble: 180 × 4
religion income_inf income_sup people
<chr> <chr> <chr> <dbl>
1 Agnostic "" $10k 27
2 Agnostic "$10" 20k 34
3 Agnostic "$20" 30k 60
4 Agnostic "$30" 40k 81
5 Agnostic "$40" 50k 76
6 Agnostic "$50" 75k 137
7 Agnostic "$75" 100k 122
8 Agnostic "$100" 150k 109
9 Agnostic ">150k" <NA> 84
10 Agnostic "Don't know/refused" <NA> 96
# ℹ 170 more rows
Piensa ahora como podemos convertir los límites de ingresos a numéricas (eliminando símbolos, letras, etc)
Para ello usaremos el paquete {stringr}
que hemos visto antes, en concreto la función str_remove_all()
a la que le podemos pasar los caracteres que queremos eliminar (fíjate que $
al ser un caracter reservado en R
hay que indicárselo con \\$
)
relig_tidy$income_inf <-
str_remove_all(relig_tidy$income_inf, "\\$|>|k")
relig_tidy$income_sup <-
str_remove_all(relig_tidy$income_sup, "\\$|>|k")
relig_tidy
# A tibble: 180 × 4
religion income_inf income_sup people
<chr> <chr> <chr> <dbl>
1 Agnostic "" 10 27
2 Agnostic "10" 20 34
3 Agnostic "20" 30 60
4 Agnostic "30" 40 81
5 Agnostic "40" 50 76
6 Agnostic "50" 75 137
7 Agnostic "75" 100 122
8 Agnostic "100" 150 109
9 Agnostic "150" <NA> 84
10 Agnostic "Don't now/refused" <NA> 96
# ℹ 170 more rows
Fíjate que tenemos "Don't now/refused"
. ¿Qué deberíamos tener?
Debería ser un dato ausente así que usaremos if_else()
: si contiene dicha frase, NA
, en caso contrario su valor (consejo: str_detect()
para detectar patrones en textos, y evitar tener que escribir toda la palabra sin errores)
relig_tidy$income_inf <-
if_else(str_detect(relig_tidy$income_inf, "refused"), NA, relig_tidy$income_inf)
relig_tidy$income_sup <-
if_else(str_detect(relig_tidy$income_sup, "refused"), NA, relig_tidy$income_sup)
relig_tidy
# A tibble: 180 × 4
religion income_inf income_sup people
<chr> <chr> <chr> <dbl>
1 Agnostic "" 10 27
2 Agnostic "10" 20 34
3 Agnostic "20" 30 60
4 Agnostic "30" 40 81
5 Agnostic "40" 50 76
6 Agnostic "50" 75 137
7 Agnostic "75" 100 122
8 Agnostic "100" 150 109
9 Agnostic "150" <NA> 84
10 Agnostic <NA> <NA> 96
# ℹ 170 more rows
# A tibble: 180 × 4
religion income_inf income_sup people
<chr> <chr> <chr> <dbl>
1 Agnostic "" 10 27
2 Agnostic "10" 20 34
3 Agnostic "20" 30 60
4 Agnostic "30" 40 81
5 Agnostic "40" 50 76
6 Agnostic "50" 75 137
7 Agnostic "75" 100 122
8 Agnostic "100" 150 109
9 Agnostic "150" <NA> 84
10 Agnostic <NA> <NA> 96
# ℹ 170 more rows
En la primera línea, ese ""
también debería ser `NA``
# A tibble: 180 × 5
religion income_inf income_sup people income_suop
<chr> <chr> <chr> <dbl> <chr>
1 Agnostic <NA> 10 27 10
2 Agnostic 10 20 34 20
3 Agnostic 20 30 60 30
4 Agnostic 30 40 81 40
5 Agnostic 40 50 76 50
6 Agnostic 50 75 137 75
7 Agnostic 75 100 122 100
8 Agnostic 100 150 109 150
9 Agnostic 150 <NA> 84 <NA>
10 Agnostic <NA> <NA> 96 <NA>
# ℹ 170 more rows
Además si te fijas los números son en realidad caracteres, así que vamos a convertirlos a números
Además si te fijas los números son en realidad caracteres, así que vamos a convertirlos a números
relig_tidy$income_inf <- as.numeric(relig_tidy$income_inf)
relig_tidy$income_sup <- as.numeric(relig_tidy$income_sup)
relig_tidy
# A tibble: 180 × 5
religion income_inf income_sup people income_suop
<chr> <dbl> <dbl> <dbl> <chr>
1 Agnostic NA 10 27 10
2 Agnostic 10 20 34 20
3 Agnostic 20 30 60 30
4 Agnostic 30 40 81 40
5 Agnostic 40 50 76 50
6 Agnostic 50 75 137 75
7 Agnostic 75 100 122 100
8 Agnostic 100 150 109 150
9 Agnostic 150 NA 84 <NA>
10 Agnostic NA NA 96 <NA>
# ℹ 170 more rows
¿Se te ocurre alguna forma de «cuantificar numéricamente» los valores ausentes que tenemos en este caso?
Si te fijas en realidad cuando hay ausente en el límite inferior en realidad podríamos poner un 0 (nadie puede ganar menos de eso) y cuando lo tenemos en el límite superior sería Inf
relig_tidy$income_inf <-
if_else(is.na(relig_tidy$income_inf), 0, relig_tidy$income_inf)
relig_tidy$income_sup <-
if_else(is.na(relig_tidy$income_sup), Inf, relig_tidy$income_sup)
relig_tidy
# A tibble: 180 × 5
religion income_inf income_sup people income_suop
<chr> <dbl> <dbl> <dbl> <chr>
1 Agnostic 0 10 27 10
2 Agnostic 10 20 34 20
3 Agnostic 20 30 60 30
4 Agnostic 30 40 81 40
5 Agnostic 40 50 76 50
6 Agnostic 50 75 137 75
7 Agnostic 75 100 122 100
8 Agnostic 100 150 109 150
9 Agnostic 150 Inf 84 <NA>
10 Agnostic 0 Inf 96 <NA>
# ℹ 170 more rows
Aunque nos haya llevado un rato este es el código completo resumido
relig_tidy <-
relig_income |>
pivot_longer(cols = "<$10k":"Don't know/refused", names_to = "income",
values_to = "people") |>
separate(income, into = c("income_inf", "income_sup"), sep = "-|<")
relig_tidy$income_inf <- str_remove_all(relig_tidy$income_inf, "\\$|>|k")
relig_tidy$income_sup <- str_remove_all(relig_tidy$income_sup, "\\$|>|k")
relig_tidy$income_inf <-
if_else(str_detect(relig_tidy$income_inf, "refused") |
relig_tidy$income_inf == "", 0, as.numeric(relig_tidy$income_inf))
relig_tidy$income_sup <-
if_else(str_detect(relig_tidy$income_sup, "refused") |
relig_tidy$income_sup == "", Inf, as.numeric(relig_tidy$income_sup))
¿Por qué era importante tenerlo en tidydata? Lo veremos más adelante al visualizar los datos pero esto ya nos permite realizar filtros muy rápidos con muy poco código.
Por ejemplo: ¿cuántas personas agnósticas con ingresos superiores (o iguales) a 30 tenemos?
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Usa el dataset original relig_income
y trata de responder a la última pregunta: ¿cuántas personas agnósticas con ingresos superiores (o iguales) a 30 tenemos? Compara el código a realizar cuando tenemos tidydata a cuando no. ¿Cuál es más legible si no supieses R
? ¿Cuál tiene mayor probabilidad de error?
📝 Usando relig_tidy
determina quién tiene más ingresos medios, ¿católicos (Catholic
) o agnósticos (Agnostic
)? Crea antes una variable avg_income
(ingresos medios por intervalo): si hay 5 personas entre 20 y 30, y 3 personas entre 30 y 50, la media sería \((25*5 + 40*3)/8\) (si es Inf
por arriba, NA
)
relig_tidy$avg_income <-
if_else(is.infinite(relig_tidy$income_sup), NA, (relig_tidy$income_sup + relig_tidy$income_inf)/2)
# Agnosticos vs catolicos
sum((relig_tidy$avg_income[relig_tidy$religion == "Agnostic"] * relig_tidy$people[relig_tidy$religion == "Agnostic"]), na.rm = TRUE) /
sum(relig_tidy$people[relig_tidy$religion == "Agnostic"], na.rm = TRUE)
sum((relig_tidy$avg_income[relig_tidy$religion == "Catholic"] * relig_tidy$people[relig_tidy$religion == "Catholic"]), na.rm = TRUE) /
sum(relig_tidy$people[relig_tidy$religion == "Catholic"], na.rm = TRUE)
📝 Si debemos elegir budismo (Buddhist
) e hinduismo (Hindu
), ¿cuál de las dos es la religión mayoritaria entre los que ganan más de 50 000$ anuales?
📝 Echa un vistazo a la tabla table4b
del paquete {tidyr}
. ¿Es tidydata? En caso negativo, ¿qué falla? ¿Cómo convertirla a tidy data en caso de que no lo sea ya?
📝 Echa un vistazo a la tabla billboard
del paquete {tidyr}
. ¿Es tidydata? En caso negativo, ¿qué falla? ¿Cómo convertirla a tidy data en caso de que no lo sea ya?
En el paquete {tidyr}
contamos con el dataset who2
(dataset de la Organización Mundial de la Salud). Intenta responder a las preguntas planteadas en el workbook.
# A tibble: 7,240 × 58
country year sp_m_014 sp_m_1524 sp_m_2534 sp_m_3544 sp_m_4554 sp_m_5564
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Afghanistan 1980 NA NA NA NA NA NA
2 Afghanistan 1981 NA NA NA NA NA NA
3 Afghanistan 1982 NA NA NA NA NA NA
4 Afghanistan 1983 NA NA NA NA NA NA
5 Afghanistan 1984 NA NA NA NA NA NA
6 Afghanistan 1985 NA NA NA NA NA NA
7 Afghanistan 1986 NA NA NA NA NA NA
8 Afghanistan 1987 NA NA NA NA NA NA
9 Afghanistan 1988 NA NA NA NA NA NA
10 Afghanistan 1989 NA NA NA NA NA NA
# ℹ 7,230 more rows
# ℹ 50 more variables: sp_m_65 <dbl>, sp_f_014 <dbl>, sp_f_1524 <dbl>,
# sp_f_2534 <dbl>, sp_f_3544 <dbl>, sp_f_4554 <dbl>, sp_f_5564 <dbl>,
# sp_f_65 <dbl>, sn_m_014 <dbl>, sn_m_1524 <dbl>, sn_m_2534 <dbl>,
# sn_m_3544 <dbl>, sn_m_4554 <dbl>, sn_m_5564 <dbl>, sn_m_65 <dbl>,
# sn_f_014 <dbl>, sn_f_1524 <dbl>, sn_f_2534 <dbl>, sn_f_3544 <dbl>,
# sn_f_4554 <dbl>, sn_f_5564 <dbl>, sn_f_65 <dbl>, ep_m_014 <dbl>, …
Ponderación: 15%
Tidyverse: operaciones por filas
El nombre de las columnas codifica el sexo (H hombre, M mujer, NC no consta) y el grupo etario (0-9, 10-19, 20-29, 30-39, 40-49, 50-59, 60-69, 70-79, ≥80 años y NC no consta).
Diseña un bucle que recorra todas las filas y, salvo en las dos primeras columnas, convierta cada 0 casos que encuentre en la fila por un NA
Necesitamos 2 bucles: uno que recorra filas (desde la primera hasta la última) y otro que recorra columnas (desde la tercera hasta la última), y dentro del bucle: si el elemento (i, j) es 0 (o ausente), lo pasamos a ausente; en caso contrario, lo que había guardado.
Razona por qué no es tidydata y conviérte a tidy data. Si te fijas hay muchísimas filas que nos sobran (ausentes) así que haz que al pivotar las elimine.
Es un pivot_longer()
sin más: le indicamos que columnas no queremos pivotar, la variable a donde irán los nombres y cómo se llamará la variable de casos; le incluimos values_drop_na = TRUE
para que elimine los ausentes (de ahí que hayamos convertido los 0 para hacerlo más sencillo aquí)
Una de las columnas la tenemos codificada a veces como cosa-cosa_cosa
, otras como 80-Inf_cosa
, otras como NC-NC_cosa
. Intenta separar dicha columna para generar tres columnas nuevas edad_inf
, edad_sup
y sexo
de manera adecuada. Recuerda que NC
es un ausente.
Si lo intentamos hacer sin procesar nada antes, vemos que los Inf
y los NC
figuran como tal, como cadena texto, lo que nos impide convertir a numérica el resto de la columna.
Por eso antes vamos a reemplazar los "Inf"
y "NC"
por "NA"
, para que luego al convertirlos pueda pasarlos a NA
numéricos
Incorpora una nueva variable a la tabla que codifice el mes y el año (por ejemplo, cualquier día de enero de 2020 será algo similar a “1-2020” y cualquier día de febrero del 2021 será “2-2021”).
Haciendo uso de esa variable de grupo mes_year
y del vector de provincias permitidas que aparece debajo (busca en https://es.wikipedia.org/wiki/ISO_3166-2:ES#Provincias) obtén un resumen que nos devuelva en un tibble, por cada provincia permitida y cada mes-año, la media de casos (sin importar edad ni sexo)
Solo necesitamos recorrer cada provincia permitida y cada valor mes_year
distinto, así que creamos ambos vectores. También creamos un dataset vacío resumen_mes_provincia
donde, en cada iteración, pegaremos una fila con su resumen.
Una vez creados esos objetos, el bucle simplemente deberá recorrer cada valor de provincia y de mes-año: para el mes-año i y la provincia j, filtramos solo los casos para esos valores concretos (de todo el vector de casos nos quedamos con aquellos que cumplan dicha condición, eliminando también provincias ausentes). Tras filtrar guardamos la media ese filtro de casos
for (i in 1:length(for_values)) {
for (j in 1:length(provincias_permitidas)) {
casos_mes_provincia <-
tidy_covid2$casos[!is.na(tidy_covid2$provincia_iso) &
tidy_covid2$provincia_iso == provincias_permitidas[j] &
tidy_covid2$mes_year == for_values[i]]
resumen_mes_provincia <-
rbind(resumen_mes_provincia,
tibble("mes_year" = for_values[i], "provincia_iso" = provincias_permitidas[j],
"casos" = mean(casos_mes_provincia, na.rm = TRUE)))
}
}
Diseña una función resumen_por_fecha_provincia()
que dada un vector de códigos ISO de provincias permitidas, y un tabla, nos devuelva la media de casos diarios que hubo cada mes-año (sin importar sexo ni edad) en las provincias permitidas. Es decir: debes “adaptar” el código anterior a una función.
Simplemente debes copiar y pegar el código de los dos anteriores (salvo el vector de provincias que ahora será un argumento), y adaptarlo a que las provincias están en prov_iso
y la tabla en datos
.
resumen_por_fecha_provincia <- function(prov_iso, datos) {
datos$mes_year <- glue("{month(datos$fecha)}-{year(datos$fecha)}")
for_values <- unique(datos$mes_year)
resumen_mes_provincia <- tibble()
for (i in 1:length(for_values)) {
for (j in 1:length(prov_iso)) {
casos_mes_provincia <-
datos$casos[!is.na(datos$provincia_iso) &
datos$provincia_iso == prov_iso[j] &
datos$mes_year == for_values[i]]
resumen_mes_provincia <-
rbind(resumen_mes_provincia,
tibble("mes_year" = for_values[i], "provincia_iso" = prov_iso[j],
"casos" = mean(casos_mes_provincia, na.rm = TRUE)))
}
}
return(resumen_mes_provincia)
}
resumen_por_fecha_provincia(c("M", "B", "SE", "V", "Z"), tidy_covid2)
{tibble}
: optimizando data.frame{tidyr}
: limpieza de datos{readr}
: carga datos rectangulares (.csv){dplyr}
: gramática para depurar{stringr}
: manejo de textos{ggplot2}
: visualización de datos{tidymodels}
: modelización/predicciónDentro de {tidyverse}
usaremos el paquete {dplyr}
para el preprocesamiento y depuración de los datos.
La idea es que el código sea legible, como si fuese una lista de instrucciones que al leerla nos diga de manera muy evidente lo que está haciendo.
Toda la depuración que vamos a realizar es sobre la hipótesis de que nuestros datos están en tidydata
Recuerda que en {tidyverse}
será clave el operador pipe (tubería) definido como |>
(ctrl+shift+M): será una tubería que recorre los datos y los transforma.
Una de las operaciones más comunes es lo que se conoce en estadística como muestreo: una selección o filtrado de registros (una submuestra)
filter()
)slice()
)slice_sample()
)group_by()
+ slice_sample()
)El más simple es cuando filtramos registros en base a alguna condición lógica: con filter()
se seleccionarán solo individuos que cumplan ciertas condiciones (muestreo no aleatorio por condiciones)
==
, !=
: igual o distinto que (|> filter(variable == "a")
)>
, <
: mayor o menor que (|> filter(variable < 3)
)>=
, <=
: mayor o igual o menor o igual que (|> filter(variable >= 5)
)%in%
: valores pertenencen a un listado de opciones (|> filter(variable %in% c("azul", "verde"))
)between(variable, val1, val2)
: si los valores (continuos) caen dentro de un rango de valores (|> filter(between(variable, 160, 180))
)Dichas condiciones lógicas las podemos combinar de diferentes maneras (y, o, o excluyente)
Importante
Recuerda que dentro de filter()
debe ir siempre algo que devuelva un vector de valores lógicos.
¿Cómo harías para… filtrar los personajes de ojos marrones?
¿Qué tipo de variable es? –> La variable eye_color
es cualitativa así que está representada por textos
# A tibble: 21 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Leia Or… 150 49 brown light brown 19 fema… femin…
2 Biggs D… 183 84 black light brown 24 male mascu…
3 Han Solo 180 80 brown fair brown 29 male mascu…
4 Yoda 66 17 white green brown 896 male mascu…
5 Boba Fe… 183 78.2 black fair brown 31.5 male mascu…
6 Lando C… 177 79 black dark brown 31 male mascu…
7 Arvel C… NA NA brown fair brown NA male mascu…
8 Wicket … 88 20 brown brown brown 8 male mascu…
9 Padmé A… 185 45 brown light brown 46 fema… femin…
10 Quarsh … 183 NA black dark brown 62 male mascu…
# ℹ 11 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías para… filtrar los personajes que no tienen ojos marrones?
# A tibble: 66 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Owen La… 178 120 brown, gr… light blue 52 male mascu…
6 Beru Wh… 165 75 brown light blue 47 fema… femin…
7 R5-D4 97 32 <NA> white, red red NA none mascu…
8 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
9 Anakin … 188 84 blond fair blue 41.9 male mascu…
10 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
# ℹ 56 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías para … filtrar los personajes que tengan los ojos marrones o azules?
# A tibble: 40 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 Leia Or… 150 49 brown light brown 19 fema… femin…
3 Owen La… 178 120 brown, gr… light blue 52 male mascu…
4 Beru Wh… 165 75 brown light blue 47 fema… femin…
5 Biggs D… 183 84 black light brown 24 male mascu…
6 Anakin … 188 84 blond fair blue 41.9 male mascu…
7 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
8 Chewbac… 228 112 brown unknown blue 200 male mascu…
9 Han Solo 180 80 brown fair brown 29 male mascu…
10 Jek Ton… 180 110 brown fair blue NA <NA> <NA>
# ℹ 30 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Fíjate que %in%
es equivalente a concatenar varios ==
con una conjunción o (|
)
# A tibble: 40 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 Leia Or… 150 49 brown light brown 19 fema… femin…
3 Owen La… 178 120 brown, gr… light blue 52 male mascu…
4 Beru Wh… 165 75 brown light blue 47 fema… femin…
5 Biggs D… 183 84 black light brown 24 male mascu…
6 Anakin … 188 84 blond fair blue 41.9 male mascu…
7 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
8 Chewbac… 228 112 brown unknown blue 200 male mascu…
9 Han Solo 180 80 brown fair brown 29 male mascu…
10 Jek Ton… 180 110 brown fair blue NA <NA> <NA>
# ℹ 30 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías para … filtrar los personajes que midan entre 120 y 160 cm?
¿Qué tipo de variable es? –> La variable height
es cuantitativa continua así que deberemos filtrar por rangos de valores (intervalos) –> usaremos between()
# A tibble: 6 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Leia Org… 150 49 brown light brown 19 fema… femin…
2 Mon Moth… 150 NA auburn fair blue 48 fema… femin…
3 Nien Nunb 160 68 none grey black NA male mascu…
4 Watto 137 NA black blue, grey yellow NA male mascu…
5 Gasgano 122 NA none white, bl… black NA male mascu…
6 Cordé 157 NA brown light brown NA <NA> <NA>
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías… filtrar los personajes que tengan ojos y no sean humanos?
# A tibble: 3 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Yoda 66 17 white green brown 896 male mascu…
2 Wicket S… 88 20 brown brown brown 8 male mascu…
3 Eeth Koth 171 NA black brown brown NA male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías… filtrar los personajes que tengan ojos y no sean humanos, o que tengan más de 60 años? Piénsalo bien: los paréntesis son importantes: no es lo mismo \((a+b)*c\) que \(a+(b*c)\)
# A tibble: 18 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 C-3PO 167 75 <NA> gold yellow 112 none mascu…
2 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
3 Chewbac… 228 112 brown unknown blue 200 male mascu…
4 Jabba D… 175 1358 <NA> green-tan… orange 600 herm… mascu…
5 Yoda 66 17 white green brown 896 male mascu…
6 Palpati… 170 75 grey pale yellow 82 male mascu…
7 Wicket … 88 20 brown brown brown 8 male mascu…
8 Qui-Gon… 193 89 brown fair blue 92 male mascu…
9 Finis V… 170 NA blond fair blue 91 male mascu…
10 Quarsh … 183 NA black dark brown 62 male mascu…
11 Shmi Sk… 163 NA black fair brown 72 fema… femin…
12 Mace Wi… 188 84 none dark brown 72 male mascu…
13 Ki-Adi-… 198 82 white pale yellow 92 male mascu…
14 Eeth Ko… 171 NA black brown brown NA male mascu…
15 Cliegg … 183 NA brown fair blue 82 male mascu…
16 Dooku 193 80 white fair brown 102 male mascu…
17 Bail Pr… 191 NA black tan brown 67 male mascu…
18 Jango F… 183 79 black tan brown 66 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Hay un filtro especial para una de las operaciones más habituales en depuración: retirar los ausentes. Para ello podemos usar dentro de un filtro is.na()
, que nos devuelve TRUE/FALSE
en función de si es ausente, o bien …
Usar drop_na()
: si no indicamos variable, elimina registros con ausente en cualquier variable. Más adelante veremos como imputar esos ausentes
# A tibble: 7 × 4
name mass height hair_color
<chr> <dbl> <int> <chr>
1 Luke Skywalker 77 172 blond
2 C-3PO 75 167 <NA>
3 R2-D2 32 96 <NA>
4 Darth Vader 136 202 none
5 Leia Organa 49 150 brown
6 Owen Lars 120 178 brown, grey
7 Beru Whitesun Lars 75 165 brown
# A tibble: 7 × 4
name mass height hair_color
<chr> <dbl> <int> <chr>
1 Luke Skywalker 77 172 blond
2 Darth Vader 136 202 none
3 Leia Organa 49 150 brown
4 Owen Lars 120 178 brown, grey
5 Beru Whitesun Lars 75 165 brown
6 Biggs Darklighter 84 183 black
7 Obi-Wan Kenobi 77 182 auburn, white
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Selecciona del conjunto de starwars solo los personajes que sean androides o cuyo valor en species
sea desconocido
📝 Selecciona del conjunto de starwars solo los personajes cuyo peso esté entre 65 y 90 kg.
📝 Tras limpiar de ausentes en todas las variables, selecciona del conjunto de starwars solo los personajes que sean humanos y que vengan de Tatooine
📝 Selecciona del conjunto original de starwars los personajes no humanos, male
en el sexo y que midan entre 120 y 170 cm, o los personajes con ojos marrones o rojos.
📝 Busca información en la ayuda de la función str_detect()
del paquete {stringr}
(cargado en {tidyverse}
). Consejo: prueba antes las funciones que vayas a usar con algún vector de prueba para poder comprobar su funcionamiento. Tras saber lo que hace, filtra solo aquellos personajes con apellido Skywalker
A veces nos puede interesar realizar un muestreo no aleatorio discreccional, o lo que es lo mismo, filtrar por posición: con slice(posiciones)
podremos seleccionar filas concretas pasando como argumento un vector de índices
# A tibble: 1 × 4
name height mass hair_color
<chr> <int> <dbl> <chr>
1 Luke Skywalker 172 77 blond
# A tibble: 4 × 8
name height mass hair_color skin_color eye_color birth_year sex
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
1 C-3PO 167 75 <NA> gold yellow 112 none
2 Beru Whitesun L… 165 75 brown light blue 47 fema…
3 Obi-Wan Kenobi 182 77 auburn, w… fair blue-gray 57 male
4 Qui-Gon Jinn 193 89 brown fair blue 92 male
Disponemos de opciones por defecto:
slice_head(n = ...)
y slice_tail(n = ...)
podemos obtener la cabecera y cola de la tablaDisponemos de opciones por defecto:
slice_max()
y slice_min()
obtenemos la filas con menor/mayor valor de una variable (si empate, todas salvo que with_ties = FALSE
) que indicamos en order_by = ...
# A tibble: 2 × 4
name height mass hair_color
<chr> <int> <dbl> <chr>
1 Ratts Tyerel 79 15 none
2 Yoda 66 17 white
El conocido como muestreo aleatorio simple se basa en seleccionar individuos aleatoriamente, de forma que cada uno tenga ciertas probabilidades de ser seleccionado. Con slice_sample(n = ...)
podemos extraer n registros aleatoriamente (a priori equiprobables).
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Gregar T… 185 85 black dark brown NA <NA> <NA>
2 Anakin S… 188 84 blond fair blue 41.9 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Importante…
«Aleatorio» no implica equiprobable: es igual de aleatorio un dado normal que uno trucado. No hay cosas «más aleatorias» que otras, simplemente tienen subyacente distintas leyes de probabilidad.
También podremos indicarle la proporción de datos a samplear (en lugar del número) y si queremos que sea con reemplazamiento (que se puedan repetir).
# 5% de registros aleatorios con reemplazamiento
starwars |>
slice_sample(prop = 0.05, replace = TRUE)
# A tibble: 4 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sky… 172 77 blond fair blue 19 male mascu…
2 Bib Fort… 180 NA none pale pink NA male mascu…
3 Padmé Am… 185 45 brown light brown 46 fema… femin…
4 Obi-Wan … 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Como decíamos, «aleatorio» no es igual que «equiprobable», así que podemos pasarle un vector de probabilidades. Por ejemplo, vamos a forzar que sea muy improbable sacar una fila que no sean las dos primeras
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sky… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 C-3PO 167 75 <NA> gold yellow 112 none mascu…
2 Luke Sky… 172 77 blond fair blue 19 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
La función slice_sample()
es simplemente una integración de {tidyverse}
de la función básica de R
conocida como sample()
que nos permite muestrear elementos
Por ejemplo, vamos a muestrear 10 tiradas de un dado, indicándole
x
)size
)TRUE
entonces pueden salir repetidas, como en el caso del dado)La opción anterior lo que genera son sucesos de una variable aleatoria equiprobable pero al igual que antes, podemos asignarle un vector de probabilidades o función de masa concreta con el argumento prob = ...
¿Cómo harías el siguiente enunciado?
Supongamos que en una ciudad se han estudiado episodios de gripe estacional. Sean las variables aleatorias \(X_m\) y \(X_p\) tal que \(X_m=1\) si la madre tiene gripe, \(X_m=0\) si la madre no tiene gripe, \(X_p=1\) si el padre tiene gripe y \(X_p=0\) si el padre no tiene gripe. El modelo teórico asociado a este tipo de epidemias indica que la distribución conjunta viene dada por \(P(X_m = 1, X_p=1)=0.02\), \(P(X_m = 1, X_p=0)=0.08\), \(P(X_m = 1, X_p=0)=0.1\) y \(P(X_m = 0, X_p=0)=0.8\)
Genera una muestra de tamaño \(n = 1000\) (soporte "10"
, "01"
, "00"
y "11"
) haciendo uso de runif()
y haciendo uso de sample()
También podemos ordenar filas en función de alguna variable con arrange()
# A tibble: 5 × 6
name height mass hair_color skin_color eye_color
<chr> <int> <dbl> <chr> <chr> <chr>
1 Ratts Tyerel 79 15 none grey, blue unknown
2 Yoda 66 17 white green brown
3 Wicket Systri Warrick 88 20 brown brown brown
4 R2-D2 96 32 <NA> white, blue red
5 R5-D4 97 32 <NA> white, red red
Por defecto de menor a mayor pero podemos invertir el orden con desc()
# A tibble: 5 × 3
name height mass
<chr> <int> <dbl>
1 Yarael Poof 264 NA
2 Tarfful 234 136
3 Lama Su 229 88
4 Chewbacca 228 112
5 Roos Tarpals 224 82
Muchas veces necesitaremos asegurarnos que no hay duplicados en alguna variable (DNI) y podemos eliminar filas duplicadas con distinct()
.
# A tibble: 5 × 1
sex
<chr>
1 male
2 none
3 female
4 hermaphroditic
5 <NA>
Para mantener todas las columnas de la tabla usaremos .keep_all = TRUE
.
# A tibble: 3 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sky… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 Leia Org… 150 49 brown light brown 19 fema… femin…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Por último, podemos concatenar nuevas filas con bind_rows()
con las nuevas observaciones en tabla (si no cuadran columnas rellena con ausentes)
# A tibble: 2 × 2
nombre edad
<chr> <dbl>
1 javi 33
2 laura 50
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Selecciona solo los personajes que sean humanos y de ojos marrones, para después ordernarlos en altura descendente y peso ascendente.
📝 Extrae aleatoriamente 10 personajes pero de forma que la probabilidad de que salga cada uno sea proporcional a su peso (más pesados, más probable)
📝 Para saber que valores únicos hay en el color de pelo, elimina duplicados de la variable hair_color
, eliminando antes los ausentes de dicha variable.
📝 De los personajes que son humanos y miden más de 160 cm, elimina duplicados en color de ojos, elimina ausentes en peso, selecciona los 3 más altos, y orden de mayor a menor peso. Devuelve la tabla.
La clave de {tidyverse}
es la legibilidad: es importantísimo que el código se entienda, por nuestro yo el futuro pero también por la transparencia algorítmica hacia los demás
Por ejemplo: quitaremos ausentes de la variable peso, filtraremos los personajes humanos y altura superior a 140cm, sin duplicados en el color de pelo, extrayendo los 5 más altos y obteniendo 2 personajes aleatorios finalmente.
La clave de {tidyverse}
es la legibilidad: es importantísimo que el código se entienda, por nuestro yo el futuro pero también por la transparencia algorítmica hacia los demás
Por ejemplo: quitaremos ausentes de la variable peso, filtraremos los personajes humanos y altura superior a 140cm, sin duplicados en el color de pelo, extrayendo los 5 más altos y obteniendo 2 personajes aleatorios finalmente.
La clave de {tidyverse}
es la legibilidad: es importantísimo que el código se entienda, por nuestro yo el futuro pero también por la transparencia algorítmica hacia los demás
Por ejemplo: quitaremos ausentes de la variable peso, filtraremos los personajes humanos y altura superior a 140cm, sin duplicados en el color de pelo, extrayendo los 5 más altos y obteniendo 2 personajes aleatorios finalmente.
La clave de {tidyverse}
es la legibilidad: es importantísimo que el código se entienda, por nuestro yo el futuro pero también por la transparencia algorítmica hacia los demás
Por ejemplo: quitaremos ausentes de la variable peso, filtraremos los personajes humanos y altura superior a 140cm, sin duplicados en el color de pelo, extrayendo los 5 más altos y obteniendo 2 personajes aleatorios finalmente.
La clave de {tidyverse}
es la legibilidad: es importantísimo que el código se entienda, por nuestro yo el futuro pero también por la transparencia algorítmica hacia los demás
Por ejemplo: quitaremos ausentes de la variable peso, filtraremos los personajes humanos y altura superior a 140cm, sin duplicados en el color de pelo, extrayendo los 5 más altos y obteniendo 2 personajes aleatorios finalmente.
Vamos a volver aun viejo conocido: en el paquete {datasets}
(ya instalado por defecto) teníamos diversos conjuntos de datos y uno de ellos era airquality
con el que ya trabajamos. Los datos capturan medidas diarias (n = 153 observaciones) de la calidad del aire en Nueva York, de mayo a septiembre de 1973.
En ese momento lo trabajamos desde la perspectiva de R base y extrayendo algunas variables del mismo. El objetivo ahora será trabajarlo desde la perspectiva de {tidyverse}
fijándonos en las diferencias de una y otra forma.
Intenta responder a las preguntas planteadas en el workbook.
Tidyverse: operaciones por columnas
Hasta ahora todas las operaciones realizadas (aunque usásemos info de columnas) eran por filas. En elc aso de columnas, la acción más sencilla es seleccionar variables por nombre con select()
, dando como argumentos los nombres de columnas sin comillas.
# A tibble: 87 × 2
name hair_color
<chr> <chr>
1 Luke Skywalker blond
2 C-3PO <NA>
3 R2-D2 <NA>
4 Darth Vader none
5 Leia Organa brown
6 Owen Lars brown, grey
7 Beru Whitesun Lars brown
8 R5-D4 <NA>
9 Biggs Darklighter black
10 Obi-Wan Kenobi auburn, white
# ℹ 77 more rows
La función select()
nos permite seleccionar varias variables a la vez, incluso concatenando sus nombres como si fuesen índices numéricos
# A tibble: 4 × 6
name height mass hair_color skin_color eye_color
<chr> <int> <dbl> <chr> <chr> <chr>
1 Luke Skywalker 172 77 blond fair blue
2 C-3PO 167 75 <NA> gold yellow
3 R2-D2 96 32 <NA> white, blue red
4 Darth Vader 202 136 none white yellow
Tenemos además palabras reservadas: everything()
todas las variables…
# A tibble: 4 × 14
mass homeworld name height hair_color skin_color eye_color birth_year sex
<dbl> <chr> <chr> <int> <chr> <chr> <chr> <dbl> <chr>
1 77 Tatooine Luke … 172 blond fair blue 19 male
2 75 Tatooine C-3PO 167 <NA> gold yellow 112 none
3 32 Naboo R2-D2 96 <NA> white, bl… red 33 none
4 136 Tatooine Darth… 202 none white yellow 41.9 male
# ℹ 5 more variables: gender <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
…y last_col()
para referirnos a la última columna.
# A tibble: 4 × 5
name height mass homeworld starships
<chr> <int> <dbl> <chr> <list>
1 Luke Skywalker 172 77 Tatooine <chr [2]>
2 C-3PO 167 75 Tatooine <chr [0]>
3 R2-D2 96 32 Naboo <chr [0]>
4 Darth Vader 202 136 Tatooine <chr [1]>
También podemos jugar con patrones en el nombre, aquellas que comiencen por un prefijo (starts_with()
), terminen con un sufijo (ends_with()
), contengan un texto (contains()
) o cumplan una expresión regular (matches()
).
# variables cuyo nombre acaba en "color" y contengan sexo o género
starwars |> select(ends_with("color"), matches("sex|gender"))
# A tibble: 87 × 5
hair_color skin_color eye_color sex gender
<chr> <chr> <chr> <chr> <chr>
1 blond fair blue male masculine
2 <NA> gold yellow none masculine
3 <NA> white, blue red none masculine
4 none white yellow male masculine
5 brown light brown female feminine
6 brown, grey light blue male masculine
7 brown light blue female feminine
8 <NA> white, red red none masculine
9 black light brown male masculine
10 auburn, white fair blue-gray male masculine
# ℹ 77 more rows
Incluso podemos seleccionar por rango numérico si tenemos variables con un prefijo y números.
Por último, podemos seleccionar columnas por tipo de dato haciendo uso de where()
y dentro una función que devuelva un valor lógico de tipo de dato.
# A tibble: 87 × 11
height mass birth_year name hair_color skin_color eye_color sex gender
<int> <dbl> <dbl> <chr> <chr> <chr> <chr> <chr> <chr>
1 172 77 19 Luke Sk… blond fair blue male mascu…
2 167 75 112 C-3PO <NA> gold yellow none mascu…
3 96 32 33 R2-D2 <NA> white, bl… red none mascu…
4 202 136 41.9 Darth V… none white yellow male mascu…
5 150 49 19 Leia Or… brown light brown fema… femin…
6 178 120 52 Owen La… brown, gr… light blue male mascu…
7 165 75 47 Beru Wh… brown light blue fema… femin…
8 97 32 NA R5-D4 <NA> white, red red none mascu…
9 183 84 24 Biggs D… black light brown male mascu…
10 182 77 57 Obi-Wan… auburn, w… fair blue-gray male mascu…
# ℹ 77 more rows
# ℹ 2 more variables: homeworld <chr>, species <chr>
Para facilitar la recolocación de variables tenemos una función para ello, relocate()
, indicándole en .after
o .before
detrás o delante de qué columnas queremos moverlas.
# A tibble: 87 × 14
species name height mass hair_color skin_color eye_color birth_year sex
<chr> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
1 Human Luke S… 172 77 blond fair blue 19 male
2 Droid C-3PO 167 75 <NA> gold yellow 112 none
3 Droid R2-D2 96 32 <NA> white, bl… red 33 none
4 Human Darth … 202 136 none white yellow 41.9 male
5 Human Leia O… 150 49 brown light brown 19 fema…
6 Human Owen L… 178 120 brown, gr… light blue 52 male
7 Human Beru W… 165 75 brown light blue 47 fema…
8 Droid R5-D4 97 32 <NA> white, red red NA none
9 Human Biggs … 183 84 black light brown 24 male
10 Human Obi-Wa… 182 77 auburn, w… fair blue-gray 57 male
# ℹ 77 more rows
# ℹ 5 more variables: gender <chr>, homeworld <chr>, films <list>,
# vehicles <list>, starships <list>
A veces también podemos querer modificar la «metainformación» de los datos, renombrando columnas. Para ello usaremos de rename()
poniendo primero el nombre nuevo y luego el antiguo.
# A tibble: 87 × 14
nombre altura peso hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Leia Or… 150 49 brown light brown 19 fema… femin…
6 Owen La… 178 120 brown, gr… light blue 52 male mascu…
7 Beru Wh… 165 75 brown light blue 47 fema… femin…
8 R5-D4 97 32 <NA> white, red red NA none mascu…
9 Biggs D… 183 84 black light brown 24 male mascu…
10 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 77 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Si observas la salida de los select()
sigue siendo una tabla tibble, ya que nos preserva la naturaleza de nuestros datos.
A veces no querremos dicha estructura sino extraer literalmente la columna en un VECTOR, algo que podemos hacer con pull()
[1] "Luke Skywalker" "C-3PO" "R2-D2"
[4] "Darth Vader" "Leia Organa" "Owen Lars"
[7] "Beru Whitesun Lars" "R5-D4" "Biggs Darklighter"
[10] "Obi-Wan Kenobi" "Anakin Skywalker" "Wilhuff Tarkin"
[13] "Chewbacca" "Han Solo" "Greedo"
[16] "Jabba Desilijic Tiure" "Wedge Antilles" "Jek Tono Porkins"
[19] "Yoda" "Palpatine" "Boba Fett"
[22] "IG-88" "Bossk" "Lando Calrissian"
[25] "Lobot" "Ackbar" "Mon Mothma"
[28] "Arvel Crynyd" "Wicket Systri Warrick" "Nien Nunb"
[31] "Qui-Gon Jinn" "Nute Gunray" "Finis Valorum"
[34] "Padmé Amidala" "Jar Jar Binks" "Roos Tarpals"
[37] "Rugor Nass" "Ric Olié" "Watto"
[40] "Sebulba" "Quarsh Panaka" "Shmi Skywalker"
[43] "Darth Maul" "Bib Fortuna" "Ayla Secura"
[46] "Ratts Tyerel" "Dud Bolt" "Gasgano"
[49] "Ben Quadinaros" "Mace Windu" "Ki-Adi-Mundi"
[52] "Kit Fisto" "Eeth Koth" "Adi Gallia"
[55] "Saesee Tiin" "Yarael Poof" "Plo Koon"
[58] "Mas Amedda" "Gregar Typho" "Cordé"
[61] "Cliegg Lars" "Poggle the Lesser" "Luminara Unduli"
[64] "Barriss Offee" "Dormé" "Dooku"
[67] "Bail Prestor Organa" "Jango Fett" "Zam Wesell"
[70] "Dexter Jettster" "Lama Su" "Taun We"
[73] "Jocasta Nu" "R4-P17" "Wat Tambor"
[76] "San Hill" "Shaak Ti" "Grievous"
[79] "Tarfful" "Raymus Antilles" "Sly Moore"
[82] "Tion Medon" "Finn" "Rey"
[85] "Poe Dameron" "BB8" "Captain Phasma"
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Filtra el conjunto de personajes y quédate solo con aquellos que en la variable height
no tengan un dato ausente. Con los datos obtenidos del filtro anterior, selecciona solo las variables name, height, así como todas aquellas variables que CONTENGAN la palabra color en su nombre.
📝 Con los datos obtenidos del ejercicio anterior, traduce el nombre de las columnas a castellano.
📝 Con los datos obtenidos del ejercicio 1, coloca la variable de color de pelo justo detrás de la variable de nombres.
📝 Con los datos obtenidos del ejercicio 1, comprueba cuántas modalidades únicas hay en la variable de color de pelo (sin usar unique()
).
📝 Del conjunto de datos originales, elimina las columnas de tipo lista, y tras ello elimina duplicados en la variable eye_color
. Tras eliminar duplicados extrae dicha columna en un vector.
📝 Del conjunto de datos original de starwars, con solo los personajes cuya altura es conocida, extrae en un vector con dicha variable.
📝 Tras obtener el vector del ejercicio anterior, usa dicho vector para realizar un muestreo aleatorio del 50% de los datos de manera que la probabilidad de cada personaje de ser elegido sea inversamente proporcional a su altura (más bajitos, más opciones).
En muchas ocasiones querremos modificar o crear variables con mutate()
.
Vamos a crear por ejemplo una nueva variable height_m
con la altura en metros.
# A tibble: 87 × 15
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Leia Or… 150 49 brown light brown 19 fema… femin…
6 Owen La… 178 120 brown, gr… light blue 52 male mascu…
7 Beru Wh… 165 75 brown light blue 47 fema… femin…
8 R5-D4 97 32 <NA> white, red red NA none mascu…
9 Biggs D… 183 84 black light brown 24 male mascu…
10 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 77 more rows
# ℹ 6 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>, height_m <dbl>
Además con los argumentos opcionales podemos recolocar la columna modificada
# A tibble: 87 × 16
height_m IMC name height mass hair_color skin_color eye_color birth_year
<dbl> <dbl> <chr> <int> <dbl> <chr> <chr> <chr> <dbl>
1 1.72 26.0 Luke … 172 77 blond fair blue 19
2 1.67 26.9 C-3PO 167 75 <NA> gold yellow 112
3 0.96 34.7 R2-D2 96 32 <NA> white, bl… red 33
4 2.02 33.3 Darth… 202 136 none white yellow 41.9
5 1.5 21.8 Leia … 150 49 brown light brown 19
6 1.78 37.9 Owen … 178 120 brown, gr… light blue 52
7 1.65 27.5 Beru … 165 75 brown light blue 47
8 0.97 34.0 R5-D4 97 32 <NA> white, red red NA
9 1.83 25.1 Biggs… 183 84 black light brown 24
10 1.82 23.2 Obi-W… 182 77 auburn, w… fair blue-gray 57
# ℹ 77 more rows
# ℹ 7 more variables: sex <chr>, gender <chr>, homeworld <chr>, species <chr>,
# films <list>, vehicles <list>, starships <list>
Importante…
Cuando aplicamos mutate()
, debemos de acordarnos que las operaciones se realizan de manera vectorial, elemento a elemento, por lo que la función que usemos dentro debe devolver un vector de igual longitud. En caso contrario, devolverá una constante
# A tibble: 87 × 15
constante name height mass hair_color skin_color eye_color birth_year sex
<dbl> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
1 97.3 Luke… 172 77 blond fair blue 19 male
2 97.3 C-3PO 167 75 <NA> gold yellow 112 none
3 97.3 R2-D2 96 32 <NA> white, bl… red 33 none
4 97.3 Dart… 202 136 none white yellow 41.9 male
5 97.3 Leia… 150 49 brown light brown 19 fema…
6 97.3 Owen… 178 120 brown, gr… light blue 52 male
7 97.3 Beru… 165 75 brown light blue 47 fema…
8 97.3 R5-D4 97 32 <NA> white, red red NA none
9 97.3 Bigg… 183 84 black light brown 24 male
10 97.3 Obi-… 182 77 auburn, w… fair blue-gray 57 male
# ℹ 77 more rows
# ℹ 6 more variables: gender <chr>, homeworld <chr>, species <chr>,
# films <list>, vehicles <list>, starships <list>
También podemos combinar mutate()
con la expresión de control if_else()
para recategorizar la variable: si se cumple una condición, hace una cosa, en caso contrario otra.
starwars |>
mutate(human = if_else(species == "Human", "Human", "Not Human"),
.after = name) |>
select(name:mass)
# A tibble: 87 × 4
name human height mass
<chr> <chr> <int> <dbl>
1 Luke Skywalker Human 172 77
2 C-3PO Not Human 167 75
3 R2-D2 Not Human 96 32
4 Darth Vader Human 202 136
5 Leia Organa Human 150 49
6 Owen Lars Human 178 120
7 Beru Whitesun Lars Human 165 75
8 R5-D4 Not Human 97 32
9 Biggs Darklighter Human 183 84
10 Obi-Wan Kenobi Human 182 77
# ℹ 77 more rows
Para recategorizaciones más complejas tenemos case_when()
, por ejemplo, para crear una categoría de los personajes en función de su altura.
starwars |>
drop_na(height) |>
mutate(altura = case_when(height < 120 ~ "enanos",
height < 160 ~ "bajito",
height < 180 ~ "normal",
height < 200 ~ "alto",
TRUE ~ "gigante"), .before = name)
# A tibble: 81 × 15
altura name height mass hair_color skin_color eye_color birth_year sex
<chr> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
1 normal Luke S… 172 77 blond fair blue 19 male
2 normal C-3PO 167 75 <NA> gold yellow 112 none
3 enanos R2-D2 96 32 <NA> white, bl… red 33 none
4 gigante Darth … 202 136 none white yellow 41.9 male
5 bajito Leia O… 150 49 brown light brown 19 fema…
6 normal Owen L… 178 120 brown, gr… light blue 52 male
7 normal Beru W… 165 75 brown light blue 47 fema…
8 enanos R5-D4 97 32 <NA> white, red red NA none
9 alto Biggs … 183 84 black light brown 24 male
10 alto Obi-Wa… 182 77 auburn, w… fair blue-gray 57 male
# ℹ 71 more rows
# ℹ 6 more variables: gender <chr>, homeworld <chr>, species <chr>,
# films <list>, vehicles <list>, starships <list>
También podemos anidar o incrustar datasets uno dentro de otro. Imagina que tenemos un dataset de variables x
e y
con 2 registros, otro con las mismas variables pero solo un registro y otro de 3 registros.
Hasta ahora la única manera que sabemos de juntar los 3 datasets es haciendo uso de bind_rows()
(por cierto, si usas el argumento .id = "nombre_variable"
podemos hacer que se añade sola una nueva variable que nos diga a que dataset pertenecía cada fila
# A tibble: 6 × 3
dataset x y
<chr> <dbl> <dbl>
1 1 0 -1
2 1 2 NA
3 2 NA 5
4 3 -2 1.5
5 3 6 NA
6 3 7 -2
Sin embargo en muchas ocasiones lo que querremos es tener los 3 en un mismo objeto PERO cada dataset por su cuenta: un objeto (una lista) que almacene los 3 datasets separados entre sí.
# A tibble: 3 × 2
dataset data
<chr> <list>
1 1 <tibble [2 × 2]>
2 2 <tibble [1 × 2]>
3 3 <tibble [3 × 2]>
Fíjate que ahora data
es una lista ya que cada dataset almacenado podría tener diferentes longitudes
Para desanidar podemos usar unnest() indicando la columna que contiene los datasets
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Selecciona solo las variables nombre, altura y así como todas aquellas variables relacionadas con el color, a la vez que te quedas solo con aquellos que no tengan ausente en la altura.
📝 Del dataset original, selecciona solo las variables numéricas y de tipo texto. Tras ello define una nueva variable llamada under_18
que nos recategorice la variable de edad: TRUE
si es menor de edad y FALSE
en caso contrario
📝 Del dataset original, crea una nueva columna llamada auburn
(cobrizo/caoba) que nos diga TRUE si el color de pelo contiene dicha palabra y FALSE en caso contrario (reminder str_detect()
).
📝 Del dataset original, incluye una columna que calcule el IMC. Tras ello, crea una nueva variable que valga NA
si no es humano, delgadez
por debajo de 18, normal
entre 18 y 30, sobrepeso
por encima de 30.
Haciendo uso de todo lo aprendido, vamos a proceder a crear una tabla con datos de bebés de tamaño n = 20
en donde simulemos el sexo de los bebés y su peso
Crea un tibble
con dos columnas, una llamada id_bebe
y otra llamada sexo
. En el primer caso debe ir de 1 a 20. En el segundo caso, simula su sexo de manera que haya un 0.5 de probabilidad de chico
y 0.5 de chica
.
Conocido el sexo, crea una tercera columna llamada peso
en la que simules dicho valor. Supondremos que para los chicos el peso sigue una distribución \(N(\mu = 3.266kg, \sigma = 0.514)\) y que para las chicas sigue una distribución \(N(\mu = 3.155kg, \sigma = 0.495)\).
Puedes ver la solución en el workbook
¿Te acuerdas de la entrega de Taylor Swift? Vamos a repetir exactamente las mismas cuestiones pero ahora en modo tidyverse: prohibidos []
, $
y demás.
Intenta responder a las preguntas planteadas en el workbook
Para practicar algunas funciones de {dplyr}
vamos a usar datos de las películas de la trilogía de El Señor de los Anillos. Los datos los cargaremos directamente desde la web (Github en este caso), sin pasar por el ordenador antes, simplemente indicando como ruta la web donde está el archivo
La comunidad del anillo -> https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Fellowship_Of_The_Ring.csv
Las 2 torres -> https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Two_Towers.csv
El Retorno del Rey -> https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Return_Of_The_King.csv
library(readr)
lotr_1 <-
read_csv(file = "https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Fellowship_Of_The_Ring.csv")
lotr_2 <-
read_csv(file = "https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Two_Towers.csv")
lotr_3 <-
read_csv(file = "https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Return_Of_The_King.csv")
Intenta responder a las preguntas planteadas en el workbook
Summarise y group_by(): estadísticas desagregadas.
Hasta ahora solo hemos transformado o consultado los datos pero no hemos generado estadísticas. Empecemos por lo sencillo: ¿cómo contar (frecuencias)?
Cuando lo usamos en solitario count()
nos devolverá simplemente el número de registros , pero cuando lo usamos con variables count()
calcula lo que se conoce como frecuencias: número de elementos de cada modalidad.
Además si pasamos varias variables nos calcula lo que se conoce como una tabla de contigencia. Con sort = TRUE
nos devolverá el conteo ordenado (más frecuentes primero).
Una de las funciones más potentes a combinar con las acciones vistas es group_by()
, que nos permitirá agrupar nuestros registros previamente
Cuando apliquemos group_by()
es importante entender que NO MODIFICA los datos, sino que nos crea una variable de grupo (subtablas por cada grupo) que modificará las acciones futuras: las operaciones se aplicarán a cada subtabla por separado
Por ejemplo, imaginemos que queremos extraer el personaje más alto con slice_max()
.
# A tibble: 1 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Yarael P… 264 NA none white yellow NA male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Y si queremos extraer el personaje más alto pero…de cada uno de los sexos?
# A tibble: 5 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Taun We 213 NA none grey black NA fema… femin…
2 Jabba De… 175 1358 <NA> green-tan… orange 600 herm… mascu…
3 Yarael P… 264 NA none white yellow NA male mascu…
4 IG-88 200 140 none metal red 15 none mascu…
5 Gregar T… 185 85 black dark brown NA <NA> <NA>
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
La web https://tidydatatutor.com/ permite visualizar las operaciones de {tidyverse}
(con el pipe antiguo)
Importante
Recuerda siempre hacer ungroup para eliminar la variable de grupo creada
En la nueva versión de {dplyr}
ahora se permite incluir la variable de grupo en la llamada a muchas funciones con el argumento by = ...
o .by = ...
# A tibble: 5 × 6
name height mass hair_color skin_color eye_color
<chr> <int> <dbl> <chr> <chr> <chr>
1 Yarael Poof 264 NA none white yellow
2 IG-88 200 140 none metal red
3 Taun We 213 NA none grey black
4 Jabba Desilijic Tiure 175 1358 <NA> green-tan, brown orange
5 Gregar Typho 185 85 black dark brown
Una opción muy útil usada antes de una operación también es rowwise()
: toda operación que venga después se aplicará en cada fila por separado. Por ejemplo, vamos a definir un conjunto dummy de notas.
Si aplicamos la media directamente el valor será idéntico ya que nos ha hecho la media global, pero nos gustaría sacar una media por registro. Para eso usaremos rowwise()
Por último tenemos summarise()
, que nos permitirá sacar resúmenes estadísticos. Por ejemplo, vamos a calcular la media de las alturas.
# A tibble: 1 × 1
media_altura
<dbl>
1 175.
Cuidado
Fíjate que mutate()
devuelve tantas filas como registros originales, mientras que con summarise()
calcula un nuevo dataset de resumen, solo incluyendo aquello que esté indicado.
Si además esto lo combinamos con la agrupación de group_by()
o .by = ...
, en pocas líneas de código puedes obtener estadísticas desagreagadas
En el nuevo {dplyr}
han incluido reframe()
para evitar problemas de summarise()
cuando devolvemos más de un valor por variable.
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
dplyr 1.1.0.
ℹ Please use `reframe()` instead.
ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
always returns an ungrouped data frame and adjust accordingly.
# A tibble: 5 × 1
`quantile(mass)`
<dbl>
1 15
2 55.6
3 79
4 84.5
5 1358
Un truco es hacer uso de selectores across()
y where()
. El primero nos permite actuar sobre varias columnas por nombre (con mutate()
o summarise()
)
# A tibble: 5 × 2
sex medias$height $mass
<chr> <dbl> <dbl>
1 male 179. 80.2
2 none 131. 69.8
3 female 172. 54.7
4 hermaphroditic 175 1358
5 <NA> 175 81
El segundo, where()
, nos permite hacer lo mismo pero seleccionando por tipo.
# A tibble: 6 × 5
sex gender height mass birth_year
<chr> <chr> <dbl> <dbl> <dbl>
1 male masculine 179. 80.2 84.8
2 none masculine 140 69.8 53.3
3 female feminine 172. 54.7 47.2
4 hermaphroditic masculine 175 1358 600
5 <NA> <NA> 175 81 NaN
6 none feminine 96 NaN NaN
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Calcula cuántos personajes hay de cada especie, ordenados de más a menor frecuencia.
📝 Tras eliminar ausentes en las variables de peso y estatura, añade una nueva variable que nos calcule el IMC de cada personaje, y determina el IMC medio de nuestros personajes desagregada por sexo
📝 Obtén la edad del personaje más joven y más viejo de cada sexo.
Vamos a hacer un repaso de lo aprendido en {tidyverse}
con la tabla billboard del paquete {tidyr}
. El dataset representa algo parecido a Los 40 principales (pero versión americana y un top 100 en lugar de 40): para cada artista y canción se guarda la fecha en la que entró en el ranking, y la posición que ocupaba en el ranking en cada una de las semanas (wk1
, wk2
, …)
# A tibble: 317 × 8
artist track date.entered wk1 wk2 wk3 wk4 wk5
<chr> <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2 Pac Baby Don't Cry (Ke… 2000-02-26 87 82 72 77 87
2 2Ge+her The Hardest Part O… 2000-09-02 91 87 92 NA NA
3 3 Doors Down Kryptonite 2000-04-08 81 70 68 67 66
4 3 Doors Down Loser 2000-10-21 76 76 72 69 67
5 504 Boyz Wobble Wobble 2000-04-15 57 34 25 17 17
6 98^0 Give Me Just One N… 2000-08-19 51 39 34 26 26
7 A*Teens Dancing Queen 2000-07-08 97 97 96 95 100
8 Aaliyah I Don't Wanna 2000-01-29 84 62 51 41 38
9 Aaliyah Try Again 2000-03-18 59 53 38 28 21
10 Adams, Yolanda Open My Heart 2000-08-26 76 76 74 69 68
# ℹ 307 more rows
Intenta responder a las preguntas planteadas en el workbook
Vamos a seguir practicando lo aprendido en {tidyverse}
con el fichero futbol.csv
, donde tenemos datos de los jugadores de las 5 principales ligas de futbol masculinas, desde 2005 hasta 2019, recopilando diferentes estadísticas. Los datos se han extraído directamente haciendo uso del paquete {worldfootballR}
, que nos permite extraer datos de https://www.fbref.com
# A tibble: 40,393 × 16
season team league player country position date_birth minutes_playing
<dbl> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
1 2005 Ajaccio Ligue 1 Djamel Ab… ALG MF 1986 30
2 2005 Ajaccio Ligue 1 Gaspar Az… POR DF 1975 1224
3 2005 Ajaccio Ligue 1 Yacine Be… ALG MF 1981 140
4 2005 Ajaccio Ligue 1 Nicolas B… FRA MF 1976 892
5 2005 Ajaccio Ligue 1 Marcelinh… BRA MF 1971 704
6 2005 Ajaccio Ligue 1 Cyril Cha… FRA FW 1979 1308
7 2005 Ajaccio Ligue 1 Xavier Co… FRA DF 1974 2734
8 2005 Ajaccio Ligue 1 Renaud Co… FRA MF 1980 896
9 2005 Ajaccio Ligue 1 Yohan Dem… FRA DF,MF 1978 2658
10 2005 Ajaccio Ligue 1 Christoph… FRA DF 1973 74
# ℹ 40,383 more rows
# ℹ 8 more variables: minutes_per_match_playing <dbl>, goals <dbl>,
# assist <dbl>, goals_minus_pk <dbl>, pk <dbl>, pk_attemp <dbl>,
# yellow_card <dbl>, red_card <dbl>
Las variables capturan la siguiente información:
season
, team
, league
: temporada, equipo y ligaplayer
, country
, position
, date_birth
: nombre del jugador, país, posición y año de nacimiento.minutes_playing
, matches
: minutos totales jugados y partidos jugados en media (es decir, cuantos partidos de 90 minutos ha jugado con los minutos jugados).goals
, assist
: goles y asistencias totalespk
, pk_attemp
, goals_minus_pk
: penalties marcados, penalties tirados y goles marcados sin contar los penalties.yellow_card
, red_card
: tarjetas amarillas/rojas.# A tibble: 40,393 × 16
season team league player country position date_birth minutes_playing
<dbl> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
1 2005 Ajaccio Ligue 1 Djamel Ab… ALG MF 1986 30
2 2005 Ajaccio Ligue 1 Gaspar Az… POR DF 1975 1224
3 2005 Ajaccio Ligue 1 Yacine Be… ALG MF 1981 140
4 2005 Ajaccio Ligue 1 Nicolas B… FRA MF 1976 892
5 2005 Ajaccio Ligue 1 Marcelinh… BRA MF 1971 704
6 2005 Ajaccio Ligue 1 Cyril Cha… FRA FW 1979 1308
7 2005 Ajaccio Ligue 1 Xavier Co… FRA DF 1974 2734
8 2005 Ajaccio Ligue 1 Renaud Co… FRA MF 1980 896
9 2005 Ajaccio Ligue 1 Yohan Dem… FRA DF,MF 1978 2658
10 2005 Ajaccio Ligue 1 Christoph… FRA DF 1973 74
# ℹ 40,383 more rows
# ℹ 8 more variables: minutes_per_match_playing <dbl>, goals <dbl>,
# assist <dbl>, goals_minus_pk <dbl>, pk <dbl>, pk_attemp <dbl>,
# yellow_card <dbl>, red_card <dbl>
Intenta responder a las preguntas planteadas en el workbook
Vamos a hacer un ejercicio con textos. En el workbook tienes no solo un caso práctico sino un mini tutorial de cómo trabajar con textos en R haciendo uso del paquete {stringr}
. En concreto vamos a analizar los discursos de Navidad de los jefes de Estado españoles
Intenta responder a las preguntas planteadas en el workbook
Vamos a volver al dataset de covid de la entrega II pero esta vez desde una perspectiva tidyverse
Intenta responder a las preguntas planteadas en el workbook
En dicha clase se hicieron algunos de los casos prácticos del workbook sin avanzar temario
¿Cómo cruzar dos tablas? ¿Cómo importar y exportar archivos?
Al trabajar con datos no siempre tendremos la información en una sola tabla y a veces nos interesará cruzar la información de distintas fuentes.
Para ello usaremos un clásico de todo lenguaje que maneja datos: los famosos join, que nos permitirán cruzar una o varias tablas, haciendo uso de una columna identificadora de cada una de ellas (por ejemplo, imagina que cruzamos datos de hacienda y de antecedentes penales, haciendo join
por la columna DNI
).
La estructura básica es la siguiente:
Vamos a probar los distintos joins con un ejemplo sencillo
left_join()
: mantiene todos los registros de la primera tabla, y busca cuales tienen id también en la segunda (en caso de no tenerlo se rellena con NA los campos de la 2ª tabla).En nuestra caso queremos incorporar a tb_1
la información de tb_2
, identificando los registros por la columna key (by = "key"
, la columna por la que tiene que cruzar)
# A tibble: 3 × 3
key val_x val_y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
3 3 x3 <NA>
Fíjate que los registros de la primera cuya key no ha encontrado en la segunda les ha dado el valor de ausente.
right_join()
: mantiene todos los registros de la segunda tabla, y busca cuales tienen id también en la primera.Vamos ahora a incorporar a tb_2
la información de tb_1
, identificando los registros por la columna key (by = "key"
)
# A tibble: 3 × 3
key val_x val_y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
3 4 <NA> y3
Fíjate que ahora los registros de la segunda cuya key no ha encontrado en la primera son los que les ha dado el valor de ausente.
Las columnas clave que usaremos para el cruce no siempre se llamarán igual.
by = c("key_2" = "key_2")
: le indicaremos en qué columna de cada tabla están las claves por las que vamos a cruzar.Además podemos cruzar por varias columnas a la vez (interpretará como igual registro aquel que tenga el conjunto de claves igual), con by = c("var1_t1" = "var1_t2", "var2_t1" = "var2_t2", ...)
. Modifiquemos el ejemplo anterior
También podría suceder que al cruzar dos tablas, haya columnas de valores que se llamen igual
Dicho sufijo podemos especificárselo en el argumento opcional suffix = ...
, que nos permita distinguir las variables de una tabla y de otra.
full_join()
: mantiene todos los registros de ambas tablas.Los dos anteriores casos forman lo que se conoce como outer joins: cruces donde se mantienen observaciones que salgan en al menos una tabla. El tercer outer join es el conocido como full_join()
que nos mantendrá las observaciones de ambas tablas, añadiendo las filas que no casen con la otra tabla.
inner_join()
: solo sobreviven los registros cuyo id esté en ambas tablas.Frente a los outer join está lo que se conoce como inner join, con inner_join()
: un cruce en el que solo se mantienen las observaciones que salgan en ambas tablas, solo mantiene aquellos registros matcheados.
Fíjate que en términos de registros, inner_join
si es conmutativa, nos da igual el orden de las tablas: lo único que cambia es el orden de las columnas que añade.
Por último tenemos dos herramientas interesantes para filtrar (no cruzar) registros: semi_join()
y anti_join()
. El semi join nos deja en la primera tabla los registros que cuya clave está también en la segunda (como un inner join pero sin añadir la info de la segunda tabla). Y el segundo, los anti join, hace justo lo contrario (aquellos que no están).
Intenta realizar los siguientes ejercicios sin mirar las soluciones
Para los ejercicios usaremos las tablas disponibles en el paquete {nycflights13}
(echa un vistazo antes)
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Del paquete {nycflights13}
cruza la tabla flights
con airlines
. Queremos mantener todos los registros de vuelos, añadiendo la información de las aerolíneas a la tabla de aviones.
📝 A la tabla obtenida del cruce del apartado anterior, cruza después con los datos de los aviones en planes
, pero incluyendo solo aquellos vuelos de los que tengamos información de sus aviones (y viceversa).
📝 Repite el ejercicio anterior pero conservando ambas variables year
(en una es el año del vuelo, en la otra es el año de construcción del avión), y distinguiéndolas entre sí
📝 Al cruce obtenido del ejercicio anterior incluye la longitud y latitud de los aeropuertos en airports
, distinguiendo entre la latitud/longitud del aeropuerto en destino y en origen.
📝 Filtra de airports
solo aquellos aeropuertos de los que salgan vuelos. Repite el proceso filtrado solo aquellos a los que lleguen vuelos
Vamos a empezar a practicar joins sencillos con la tabla band_members
y band_instruments
ya incluidos en el paquete {dplyr}
.
# A tibble: 3 × 2
name band
<chr> <chr>
1 Mick Stones
2 John Beatles
3 Paul Beatles
# A tibble: 3 × 2
name plays
<chr> <chr>
1 John guitar
2 Paul bass
3 Keith guitar
Intenta responder a las preguntas planteadas en el workbook
En el archivo municipios.csv
tenemos guardada la información de los municipios de España a fecha de 2019.
La variable LAU_code
representa el código como unidad administrativa local según la estandarización de la UE (ver más en https://ec.europa.eu/eurostat/web/nuts/local-administrative-units).
La variable codigo_ine
está construida uniendo el código de la provincia y el de la comunidad autónoma.
# A tibble: 8,212 × 8
codauto ine.ccaa.name cpro ine.prov.name cmun name LAU_CODE codigo_ine
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 01 Andalucía 04 Almería 001 Abla 04001 04001
2 01 Andalucía 04 Almería 002 Abrucena 04002 04002
3 01 Andalucía 04 Almería 003 Adra 04003 04003
4 01 Andalucía 04 Almería 004 Albanchez 04004 04004
5 01 Andalucía 04 Almería 005 Alboloduy 04005 04005
6 01 Andalucía 04 Almería 006 Albox 04006 04006
7 01 Andalucía 04 Almería 007 Alcolea 04007 04007
8 01 Andalucía 04 Almería 008 Alcóntar 04008 04008
9 01 Andalucía 04 Almería 009 Alcudia … 04009 04009
10 01 Andalucía 04 Almería 010 Alhabia 04010 04010
# ℹ 8,202 more rows
Por otro lado el archivo renta_mun
contiene datos de la renta per capita edmai de cada unidad administrativa (municipios, distritos, provincias, comunidades autonónomas, etc) para diferentes años.
# A tibble: 55,273 × 7
Unidad `2019` `2018` `2017` `2016` `2015` codigo_ine
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
1 44001 Ababuj NA NA NA NA NA 44001
2 4400101 Ababuj distrito 01 NA NA NA NA NA 4400101
3 4400101001 Ababuj sección 01001 NA NA NA NA NA 4400101001
4 40001 Abades 11429 10731 10314 9816 9904 40001
5 4000101 Abades distrito 01 11429 10731 10314 9816 9904 4000101
6 4000101001 Abades sección 01001 11429 10731 10314 9816 9904 4000101001
7 10001 Abadía 8954 8589 8207 7671 8416 10001
8 1000101 Abadía distrito 01 8954 8589 8207 7671 8416 1000101
9 1000101001 Abadía sección 01001 8954 8589 8207 7671 8416 1000101001
10 27001 Abadín 10791 10258 9762 9478 9116 27001
# ℹ 55,263 more rows
Antes de empezar vamos a normalizar nombres de variables haciendo uso de clean_names()
del paquete {janitor}
.
Intenta responder a las preguntas planteadas en el workbook
Hasta ahora sólo hemos utilizado datos ya cargados en paquetes, pero muchas veces necesitaremos importar datos externamente. Una de las principales fortalezas de R
es que podemos importar datos muy fácilmente en diferentes formatos:
Formatos nativos de R: archivos .rda
, .RData
y .rds
Rectangular data (datos tabulados): archivos .csv
y .tsv
Datos sin tabular: archivos .txt
.
Datos en excel: archivos .xls
y.xlsx
.
Datos desde SAS/Stata/SPSS: archivos .sas7bdat
, .sav
y .dat
.
Datos desde Google Drive
Datos desde API’s: aemet, catastro, censo, spotify, etc.
Los ficheros más sencillos para importar a R
(y que suelen ocupar menos espacio en disco) son sus propias extensiones nativas: ficheros en formatos .RData
, .rda
y .rds
. Para cargar los primeros basta con utilizar la función load()
proporcionándole la ruta del fichero.
RData
: vamos a importar el archivo world_bank_pop.RData
que incluye la tabla world_bank_pop
# A tibble: 1,064 × 20
country indicator `2000` `2001` `2002` `2003` `2004` `2005` `2006`
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ABW SP.URB.TOTL 4.16e4 4.20e+4 4.22e+4 4.23e+4 4.23e+4 4.24e+4 4.26e+4
2 ABW SP.URB.GROW 1.66e0 9.56e-1 4.01e-1 1.97e-1 9.46e-2 1.94e-1 3.67e-1
3 ABW SP.POP.TOTL 8.91e4 9.07e+4 9.18e+4 9.27e+4 9.35e+4 9.45e+4 9.56e+4
4 ABW SP.POP.GROW 2.54e0 1.77e+0 1.19e+0 9.97e-1 9.01e-1 1.00e+0 1.18e+0
5 AFE SP.URB.TOTL 1.16e8 1.20e+8 1.24e+8 1.29e+8 1.34e+8 1.39e+8 1.44e+8
6 AFE SP.URB.GROW 3.60e0 3.66e+0 3.72e+0 3.71e+0 3.74e+0 3.81e+0 3.81e+0
7 AFE SP.POP.TOTL 4.02e8 4.12e+8 4.23e+8 4.34e+8 4.45e+8 4.57e+8 4.70e+8
8 AFE SP.POP.GROW 2.58e0 2.59e+0 2.61e+0 2.62e+0 2.64e+0 2.67e+0 2.70e+0
9 AFG SP.URB.TOTL 4.31e6 4.36e+6 4.67e+6 5.06e+6 5.30e+6 5.54e+6 5.83e+6
10 AFG SP.URB.GROW 1.86e0 1.15e+0 6.86e+0 7.95e+0 4.59e+0 4.47e+0 5.03e+0
# ℹ 1,054 more rows
# ℹ 11 more variables: `2007` <dbl>, `2008` <dbl>, `2009` <dbl>, `2010` <dbl>,
# `2011` <dbl>, `2012` <dbl>, `2013` <dbl>, `2014` <dbl>, `2015` <dbl>,
# `2016` <dbl>, `2017` <dbl>
.rda
: vamos a importar el dataset airquality desde airquality.rda
# A tibble: 153 × 6
Ozone Solar.R Wind Temp Month Day
<int> <int> <dbl> <int> <int> <int>
1 41 190 7.4 67 5 1
2 36 118 8 72 5 2
3 12 149 12.6 74 5 3
4 18 313 11.5 62 5 4
5 NA NA 14.3 56 5 5
6 28 NA 14.9 66 5 6
7 23 299 8.6 65 5 7
8 19 99 13.8 59 5 8
9 8 19 20.1 61 5 9
10 NA 194 8.6 69 5 10
# ℹ 143 more rows
Tenga en cuenta que los archivos cargados con load()
se cargan automáticamente en el entorno (con el nombre guardado originalmente), y no sólo se pueden cargar conjuntos de datos: load()
nos permite cargar múltiples objetos (no sólo datos tabulares).
Los archivos nativos .rda
y .RData
son una forma adecuada de guardar el entorno.
.rds
: para este tipo debemos utilizar readRDS()
, y necesitamos incorporar un argumento file
con la ruta. En este caso vamos a importar datos de cáncer de pulmón del North Central Cancer Treatment Group. Observe que ahora los archivos .rds incorporar solo una tabla, no un objeto en general# A tibble: 228 × 10
inst time status age sex ph.ecog ph.karno pat.karno meal.cal wt.loss
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 3 306 2 74 1 1 90 100 1175 NA
2 3 455 2 68 1 0 90 90 1225 15
3 3 1010 1 56 1 0 90 90 NA 15
4 5 210 2 57 1 1 90 60 1150 11
5 1 883 2 60 1 0 100 90 NA 0
6 12 1022 1 74 1 1 50 80 513 0
7 7 310 2 68 2 2 70 60 384 10
8 11 361 2 71 2 2 60 80 538 1
9 1 218 2 53 1 1 70 80 825 16
10 7 166 2 61 1 2 70 70 271 34
# ℹ 218 more rows
Importante
Las [rutas]{.hl-yellow deben ser siempre sin espacios, ñ, ni acentos.
El paquete {readr}
dentro del entorno {tidyverse}
contiene varias funciones útiles para cargar datos rectangulares (sin formatear pero tabulados).
read_csv()
: archivos .csv
variables separadas por comasread_csv2()
: variables separadas por punto y comaread_tsv()
: variables separadas por tabuladores.read_table()
: variables separadas por espacios.read_delim()
: función generar con opción de especificar el delimitador.Todos ellos necesitan como argumento la ruta del fichero más otros opcionales (saltar cabecera o no, decimales, etc). Ver más en https://readr.tidyverse.org/
La principal ventaja de {readr}
es que automatiza el formato para pasar de un fichero plano (sin formato) a un tibble (en filas y columnas, con formato).
.csv
: con read_csv()
cargaremos archivos separados por comas, pasando como argumento la ruta en file = ...
. Vamos a importar el conjunto de datos chickens.csv
(sobre pollos de dibujos animados, por qué no). Si nos fijamos en la salida nos da el tipo de variables.# A tibble: 5 × 4
chicken sex eggs_laid motto
<chr> <chr> <dbl> <chr>
1 Foghorn Leghorn rooster 0 That's a joke, ah say, that's a jok…
2 Chicken Little hen 3 The sky is falling!
3 Ginger hen 12 Listen. We'll either die free chick…
4 Camilla the Chicken hen 7 Bawk, buck, ba-gawk.
5 Ernie The Giant Chicken rooster 0 Put Captain Solo in the cargo hold.
El formato de la variable se hará normalmente automáticamente por read_csv()
, y podemos consultarlo con spec()
.
Aunque normalmente lo hace bien automáticamente podemos especificar el formato explícitamente en col_types = lista()
(en formato lista, con col_xxx()
para cada tipo de variable, por ejemplo eggs_laid
se importará como carácter).
chickens <-
read_csv(file = "./datos/chickens.csv",
col_types = list(col_character(), col_character(),
col_character(), col_character()))
chickens
# A tibble: 5 × 4
chicken sex eggs_laid motto
<chr> <chr> <chr> <chr>
1 Foghorn Leghorn rooster 0 That's a joke, ah say, that's a jok…
2 Chicken Little hen 3 The sky is falling!
3 Ginger hen 12 Listen. We'll either die free chick…
4 Camilla the Chicken hen 7 Bawk, buck, ba-gawk.
5 Ernie The Giant Chicken rooster 0 Put Captain Solo in the cargo hold.
Incluso podemos indicar que variables queremos seleccionar (sin ocupar memoria), indicándolo en col_select = ...
(en formato lista, con col_select = ...
).
¿Qué ocurre cuando el separador no es correcto?
Si usamos read_csv()
espera que el separador entre columnas sea una coma pero, como puedes ver con el siguiente .txt
, lo interpreta todo como una sola columna: no tiene coma y no sabe dónde separar
[1] 10 1
# A tibble: 10 × 1
`UCC PAY LAZ KPK RT COF BIH DII ENG ACU Rank Team Conf`
<chr>
1 1 1 1 1 1 1 1 1 1 1 1 Ohio St B10
2 2 2 2 2 2 2 2 2 4 2 2 Oregon P12
3 3 4 3 4 3 4 3 4 2 3 3 Alabama SEC
4 4 3 4 3 4 3 5 3 3 4 4 TCU B12
5 6 6 6 5 5 7 6 5 6 11 5 Michigan St B10
6 7 7 7 6 7 6 11 8 7 8 6 Georgia SEC
7 5 5 5 7 6 8 4 6 5 5 7 Florida St ACC
8 8 8 9 9 10 5 7 7 10 7 8 Baylor B12
9 9 11 8 13 11 11 12 9 14 9 9 Georgia Tech ACC
10 13 10 13 11 8 9 10 11 9 10 10 Mississippi SEC
Para ello tenemos.
read_csv2()
cuando el separador es punto y coma, read_tsv()
cuando es un tabulador y read_table()
cuando es un espacio.
read_delim()
en general.
# A tibble: 10 × 13
UCC PAY LAZ KPK RT COF BIH DII ENG ACU Rank Team Conf
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
1 1 1 1 1 1 1 1 1 1 1 1 Ohio St
2 2 2 2 2 2 2 2 2 4 2 2 Oreg… P12
3 3 4 3 4 3 4 3 4 2 3 3 Alab… SEC
4 4 3 4 3 4 3 5 3 3 4 4 TCU B12
5 6 6 6 5 5 7 6 5 6 11 5 Mich… St
6 7 7 7 6 7 6 11 8 7 8 6 Geor… SEC
7 5 5 5 7 6 8 4 6 5 5 7 Flor… St
8 8 8 9 9 10 5 7 7 10 7 8 Bayl… B12
9 9 11 8 13 11 11 12 9 14 9 9 Geor… Tech
10 13 10 13 11 8 9 10 11 9 10 10 Miss… SEC
Otro paquete de importación clave será el paquete {readxl}
para importar datos desde Excel. Tres funciones serán clave:
read_xls()
específica para .xls
, read_xlsx()
específica para .xlsx
.read_excel()
: tanto para .xls
como para .xlsx
.Vamos a importar deaths.xlsx
con los registros de defunciones de famosos.
# A tibble: 18 × 6
`Lots of people` ...2 ...3 ...4 ...5 ...6
<chr> <chr> <chr> <chr> <chr> <chr>
1 simply cannot resist writing <NA> <NA> <NA> <NA> some…
2 at the top <NA> of thei…
3 or merging <NA> <NA> <NA> cells
4 Name Profession Age Has … Date… Date…
5 David Bowie musician 69 TRUE 17175 42379
6 Carrie Fisher actor 60 TRUE 20749 42731
7 Chuck Berry musician 90 TRUE 9788 42812
8 Bill Paxton actor 61 TRUE 20226 42791
9 Prince musician 57 TRUE 21343 42481
10 Alan Rickman actor 69 FALSE 16854 42383
11 Florence Henderson actor 82 TRUE 12464 42698
12 Harper Lee author 89 FALSE 9615 42419
13 Zsa Zsa Gábor actor 99 TRUE 6247 42722
14 George Michael musician 53 FALSE 23187 42729
15 Some <NA> <NA> <NA> <NA> <NA>
16 <NA> also like to write stuff <NA> <NA> <NA> <NA>
17 <NA> <NA> at t… bott… <NA> <NA>
18 <NA> <NA> <NA> <NA> <NA> too!
# A tibble: 6 × 6
`Lots of people` ...2 ...3 ...4 ...5 ...6
<chr> <chr> <chr> <chr> <chr> <chr>
1 simply cannot resist writing <NA> <NA> <NA> <NA> some not…
2 at the top <NA> of their sp…
3 or merging <NA> <NA> <NA> cells
4 Name Profession Age Has kids Date of birth Date of …
5 David Bowie musician 69 TRUE 17175 42379
6 Carrie Fisher actor 60 TRUE 20749 42731
Una desgracia muy común es que haya algún tipo de comentario o texto al principio del fichero, teniendo que saltar esas filas.
Podemos saltar estas filas directamente en la carga con skip = ...
(indicando el número de filas a saltar).
# A tibble: 14 × 6
Name Profession Age `Has kids` `Date of birth` `Date of death`
<chr> <chr> <chr> <chr> <dttm> <chr>
1 David Bowie musician 69 TRUE 1947-01-08 00:00:00 42379
2 Carrie Fisher actor 60 TRUE 1956-10-21 00:00:00 42731
3 Chuck Berry musician 90 TRUE 1926-10-18 00:00:00 42812
4 Bill Paxton actor 61 TRUE 1955-05-17 00:00:00 42791
5 Prince musician 57 TRUE 1958-06-07 00:00:00 42481
6 Alan Rickman actor 69 FALSE 1946-02-21 00:00:00 42383
7 Florence Hen… actor 82 TRUE 1934-02-14 00:00:00 42698
8 Harper Lee author 89 FALSE 1926-04-28 00:00:00 42419
9 Zsa Zsa Gábor actor 99 TRUE 1917-02-06 00:00:00 42722
10 George Micha… musician 53 FALSE 1963-06-25 00:00:00 42729
11 Some <NA> <NA> <NA> NA <NA>
12 <NA> also like… <NA> <NA> NA <NA>
13 <NA> <NA> at t… bottom, NA <NA>
14 <NA> <NA> <NA> <NA> NA too!
Además con col_names = ...
ya podemos renombrar las columnas en la importación (proporcionar nombres supone 1ª línea ya como dato)
deaths <-
read_xlsx(path = "./datos/deaths.xlsx", skip = 5,
col_names = c("nombre", "profesion", "edad", "hijos", "nacimiento", "muerte"))
deaths
# A tibble: 14 × 6
nombre profesion edad hijos nacimiento muerte
<chr> <chr> <chr> <chr> <dttm> <chr>
1 David Bowie musician 69 TRUE 1947-01-08 00:00:00 42379
2 Carrie Fisher actor 60 TRUE 1956-10-21 00:00:00 42731
3 Chuck Berry musician 90 TRUE 1926-10-18 00:00:00 42812
4 Bill Paxton actor 61 TRUE 1955-05-17 00:00:00 42791
5 Prince musician 57 TRUE 1958-06-07 00:00:00 42481
6 Alan Rickman actor 69 FALSE 1946-02-21 00:00:00 42383
7 Florence Henderson actor 82 TRUE 1934-02-14 00:00:00 42698
8 Harper Lee author 89 FALSE 1926-04-28 00:00:00 42419
9 Zsa Zsa Gábor actor 99 TRUE 1917-02-06 00:00:00 42722
10 George Michael musician 53 FALSE 1963-06-25 00:00:00 42729
11 Some <NA> <NA> <NA> NA <NA>
12 <NA> also like to write… <NA> <NA> NA <NA>
13 <NA> <NA> at t… bott… NA <NA>
14 <NA> <NA> <NA> <NA> NA too!
A veces las fechas de Excel tienen un formato incorrecto (sorpresa): podemos utilizar convertToDate()
del paquete {openxlsx}
para convertirlo.
# A tibble: 14 × 6
nombre profesion edad hijos nacimiento muerte
<chr> <chr> <chr> <chr> <dttm> <date>
1 David Bowie musician 69 TRUE 1947-01-08 00:00:00 2016-01-10
2 Carrie Fisher actor 60 TRUE 1956-10-21 00:00:00 2016-12-27
3 Chuck Berry musician 90 TRUE 1926-10-18 00:00:00 2017-03-18
4 Bill Paxton actor 61 TRUE 1955-05-17 00:00:00 2017-02-25
5 Prince musician 57 TRUE 1958-06-07 00:00:00 2016-04-21
6 Alan Rickman actor 69 FALSE 1946-02-21 00:00:00 2016-01-14
7 Florence Henderson actor 82 TRUE 1934-02-14 00:00:00 2016-11-24
8 Harper Lee author 89 FALSE 1926-04-28 00:00:00 2016-02-19
9 Zsa Zsa Gábor actor 99 TRUE 1917-02-06 00:00:00 2016-12-18
10 George Michael musician 53 FALSE 1963-06-25 00:00:00 2016-12-25
11 Some <NA> <NA> <NA> NA NA
12 <NA> also like to w… <NA> <NA> NA NA
13 <NA> <NA> at t… bott… NA NA
14 <NA> <NA> <NA> <NA> NA NA
También podemos cargar un Excel con varias hojas: para indicar la hoja (ya sea por su nombre o por su número) utilizaremos el argumento sheet = ...
.
# A tibble: 32 × 11
mpg cyl disp hp drat wt qsec vs am gear carb
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 21 6 160 110 3.9 2.62 16.5 0 1 4 4
2 21 6 160 110 3.9 2.88 17.0 0 1 4 4
3 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1
4 21.4 6 258 110 3.08 3.22 19.4 1 0 3 1
5 18.7 8 360 175 3.15 3.44 17.0 0 0 3 2
6 18.1 6 225 105 2.76 3.46 20.2 1 0 3 1
7 14.3 8 360 245 3.21 3.57 15.8 0 0 3 4
8 24.4 4 147. 62 3.69 3.19 20 1 0 4 2
9 22.8 4 141. 95 3.92 3.15 22.9 1 0 4 2
10 19.2 6 168. 123 3.92 3.44 18.3 1 0 4 4
# ℹ 22 more rows
Incluso podemos indicar el rango de celdas a cargar con range = ...
.
El paquete {haven}
dentro del universo tidyverse nos permitirá importar ficheros de los 3 software de pago más importantes: SAS, SPSS y Stata.
De la misma forma que podemos importar también podemos exportar
.RData
(opción recomendada para variables almacenadas en R
). Recuerda que esta extensión sólo se puede utilizar en R
. Para ello, basta con utilizar save(object, file = path)
.La ventaja de .RData
es que podemos exportar múltiples variables de nuestro environment, no solo un único dataset.
.csv
. Para ello simplemente utilizamos write_csv(object, file = path)
, y es el más recomendable para exportar bases de datos de tamaño pequeño o mediano. Ver https://arrow.apache.org/docs/r/ para bases de datos masivas.Una de las principales ventajas de R
es que podemos hacer uso de todas las funciones anteriores de importar pero directamente desde una web, sin necesidad de realizar la descarga manual: en lugar de pasarle la ruta local le indicaremos el enlace. Por ejemplo, vamos a descargar los datos covid del ISCIII (https://cnecovid.isciii.es/covid19/#documentaci%C3%B3n-y-datos)
# A tibble: 500 × 8
provincia_iso sexo grupo_edad fecha num_casos num_hosp num_uci num_def
<chr> <chr> <chr> <date> <dbl> <dbl> <dbl> <dbl>
1 A H 0-9 2020-01-01 0 0 0 0
2 A H 10-19 2020-01-01 0 0 0 0
3 A H 20-29 2020-01-01 0 0 0 0
4 A H 30-39 2020-01-01 0 0 0 0
5 A H 40-49 2020-01-01 0 0 0 0
6 A H 50-59 2020-01-01 0 0 0 0
7 A H 60-69 2020-01-01 0 0 0 0
8 A H 70-79 2020-01-01 0 0 0 0
9 A H 80+ 2020-01-01 0 0 0 0
10 A H NC 2020-01-01 0 0 0 0
# ℹ 490 more rows
El paquete {rvest}
, uno de los más útiles de {tidyverse}
nos permite importar (scrappear) directamente desde un html
. Por ejemplo, para exportar tablas de wikipedia basta con read_html()
para importar el html, html_element(«table»)
para extraer los objetos tabla, y html_table()
para convertir la tabla html a tibble
.
library(rvest)
wiki_jump <- 'https://en.wikipedia.org/wiki/Men%27s_long_jump_world_record_progression'
wiki_jump |> read_html() |>
html_element("table") |>
html_table()
# A tibble: 19 × 5
Mark Wind Athlete Place Date
<chr> <chr> <chr> <chr> <chr>
1 7.61 m (24 ft 11+1⁄2 in) "" Peter O'Connor (IRE) Dublin, Ire… 5 Au…
2 7.69 m (25 ft 2+3⁄4 in) "" Edward Gourdin (USA) Cambridge, … 23 J…
3 7.76 m (25 ft 5+1⁄2 in) "" Robert LeGendre (USA) Paris, Fran… 7 Ju…
4 7.89 m (25 ft 10+1⁄2 in) "" DeHart Hubbard (USA) Chicago, Un… 13 J…
5 7.90 m (25 ft 11 in) "" Edward Hamm (USA) Cambridge, … 7 Ju…
6 7.93 m (26 ft 0 in) "0.0" Sylvio Cator (HAI) Paris, Fran… 9 Se…
7 7.98 m (26 ft 2 in) "0.5" Chuhei Nambu (JPN) Tokyo, Japan 27 O…
8 8.13 m (26 ft 8 in) "1.5" Jesse Owens (USA) Ann Arbor, … 25 M…
9 8.21 m (26 ft 11 in) "0.0" Ralph Boston (USA) Walnut, Uni… 12 A…
10 8.24 m (27 ft 1⁄4 in) "1.8" Ralph Boston (USA) Modesto, Un… 27 M…
11 8.28 m (27 ft 1+3⁄4 in) "1.2" Ralph Boston (USA) Moscow, Sov… 16 J…
12 8.31 m (27 ft 3 in) A "−0.1" Igor Ter-Ovanesyan (URS) Yerevan, So… 10 J…
13 8.33 m (27 ft 3+3⁄4 in)[2] "" Phil Shinnick (USA) Modesto, Un… 25 M…
14 8.31 m (27 ft 3 in) "0.0" Ralph Boston (USA) Kingston, J… 15 A…
15 8.34 m (27 ft 4+1⁄4 in) "1.0" Ralph Boston (USA) Los Angeles… 12 S…
16 8.35 m (27 ft 4+1⁄2 in)[5] "0.0" Ralph Boston (USA) Modesto, Un… 29 M…
17 8.35 m (27 ft 4+1⁄2 in) A "0.0" Igor Ter-Ovanesyan (URS) Mexico City… 19 O…
18 8.90 m (29 ft 2+1⁄4 in) A "2.0" Bob Beamon (USA) Mexico City… 18 O…
19 8.95 m (29 ft 4+1⁄4 in) "0.3" Mike Powell (USA) Tokyo, Japan 30 A…
Otra opción disponible (especialmente si trabajamos con otras personas trabajando) es importar desde una hoja de cálculo de Google Drive, haciendo uso de read_sheet()
del paquete {googlesheets4}
.
La primera vez se te pedirá un permiso tidyverse para interactuar con tu drive
Otra opción interesante es la descarga de datos desde una API: un intermediario entre una app o proveedor de datos y nuestro R
. Por ejemplo, carguemos la librería {owidR}
, que nos permite descargar datos de la web https://ourworldindata.org/. Por ejemplo, la función owid_covid()
carga sin darnos cuenta más de 400 000 registros con más de 60 variables de 238 países.
En muchas ocasiones para conectarnos a la API primero tendremos que registrarnos y obtener una clave, este es el caso del paquete {climaemet}
para acceder a datos meteorológicos de España (https://opendata.aemet.es/centrodedescargas/inicio).
Una vez tengamos la clave API la registramos en nuestro RStudio para poder utilizarla en el futuro.
library(climaemet)
# Api key
apikey <- "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZhbHYwOUB1Y20uZXMiLCJqdGkiOiI4YTU1ODUxMS01MTE3LTQ4MTYtYmM4OS1hYmVkNDhiODBkYzkiLCJpc3MiOiJBRU1FVCIsImlhdCI6MTY2NjQ2OTcxNSwidXNlcklkIjoiOGE1NTg1MTEtNTExNy00ODE2LWJjODktYWJlZDQ4YjgwZGM5Iiwicm9sZSI6IiJ9.HEMR77lZy2ASjmOxJa8ppx2J8Za1IViurMX3p1reVBU"
aemet_api_key(apikey, install = TRUE)
Con este paquete podemos hacer una búsqueda de estaciones para conocer tanto su código postal como su código identificador dentro de la red AEMET
Por ejemplo, para obtener datos de la estación del aeropuerto de El Prat, Barcelona, el código a proporcionar es «0076»
, obteniendo datos horarios.
Una de las herramientas más útiles de los últimos años es la conocida como {tidycensus}
: una herramienta para facilitar el proceso de descarga de datos censales de Estados Unidos desde R
get_decennial()
: para acceder a los datos de censos (US Decennial Census), se hacen cada 10 años (años 2000, 2010 y 2020).
get_acs()
: para acceder a los datos anuales y quinquenales (5 años) de la ACS (American Community Survey) (censo != encuesta)
get_estimates()
: para acceder a las estimaciones anuales de población, natalidad y mortalidad
get_pums()
: para acceder a los microdatos (datos sin agregar) de la ACS (anonimizados a nivel individual)
get_flows()
: para acceder a los datos de flujo de migraciones
Por ejemplo vamos a descargarnos los datos censales (get_decennial()
) a nivel estado (geography = "state"
) de la población (variable variables = "P001001"
) para el año 2010 (ver variables en tidycensus::load_variables()
)
total_population_10 <-
get_decennial(geography = "state",
variables = "P001001",
year = 2010)
total_population_10
# A tibble: 52 × 4
GEOID NAME variable value
<chr> <chr> <chr> <dbl>
1 01 Alabama P001001 4779736
2 02 Alaska P001001 710231
3 04 Arizona P001001 6392017
4 05 Arkansas P001001 2915918
5 06 California P001001 37253956
6 22 Louisiana P001001 4533372
7 21 Kentucky P001001 4339367
8 08 Colorado P001001 5029196
9 09 Connecticut P001001 3574097
10 10 Delaware P001001 897934
# ℹ 42 more rows
Otras opciones
{chessR}
: datos de partidas de ajedrez de las plataformas chess.com y lychess. Ver más en https://github.com/JaseZiv/chessR
{spotifyr}
: datos de canciones de Spotify. Ver más en https://www.rcharlie.com/spotifyr/
{gtrendsR}
: datos de Google Trends. Ver más en https://github.com/PMassicotte/gtrendsR
{scholar}
: datos de Google Scholar. Ver más en https://github.com/jkeirstead/scholar
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 El conjunto de datos who2
del paquete {tidyr}
que hemos utilizado en ejercicios anteriores, expórtalo a un formato nativo R
en la carpeta datos
de tu proyecto de RStudio
📝 Carga el conjunto de datos who2
pero desde la carpeta data (importa el fichero creado en el ejercicio anterior)
📝 Repite lo mismo (exportar e importar) en 4 formatos: .csv
, .xlsx
, .sav
(spss) y .dta
(stata)
# csv
library(readr)
write_csv(who2, file = "./datos/who2.csv")
who2_data <- read_csv(file = "./datos/who2.csv")
# excel
library(openxlsx)
write.xlsx(who2, file = "./datos/who2.xlsx")
who2_data <- read_xlsx(path = "./datos/who2.xlsx")
# sas y stata
library(haven)
write_sav(who2, path = "./datos/who2.sav")
who2_data <- read_spss(path = "./datos/who2.sav")
write_dta(who2, path = "./datos/who2.dta")
who2_data <- read_dta(path = "./datos/who2.dta")
pendiente de subir
Empieza la fantasía: visualización de datos
La aparición de gráficos estadísticos es relativamente reciente en la ciencia ya que hasta la Edad Media la única visualización estaba en los mapas. 1 Las propias palabras chart y cartography derivan del mismo origen latino, charta, aunque el primer uso de coordenadas viene de los egipcios. 2 3
No es hasta la Edad Media, cuando la navegación y la astronomía empezaban a tomar relevancia, cuando aparece la primera gráfica (no propiamente estadística), del movimiento cíclico de los planetas (siglos X y XI)
La mayoría de expertos, como Tufte 1 2, consideran este gráfico casi longitudinal como la primera visualización de datos de la historia, hecha por Van Langren en 1644, representando la distancia entre Toledo y Roma.
¿Es una gráfica estadística? ¿Por qué sí o por qué no?
No hay ninguna INFORMACIÓN representada
¿Es una gráfica estadística? ¿Por qué sí o por qué no?
No hay ningún PROCESO DE MEDIDA representado, no cuantifica nada (real).
¿Es una gráfica estadística? ¿Por qué sí o por qué no?
No hay ningún DATO representado en él, es una magnitud física teórica, no un dato (medido empíricamente o simulado).
Esas mismas preguntas se hizo Joaquín Sevilla 1, proporcionando 3 requisitos:
La figura elegida (persona caminando) sin relación con lo visualizado: mala metáfora.
Los sectores señalados sin relación con el ítem a representar, lo que dificulta su interpretación.
Los colores sin codificar: no dan información de ningún tipo.
Las formas irregulares impiden la comparación de las áreas (amén de que la suma total supera el 100%).
Sin fuente
Una buena idea puede estar mal ejecutada: la forma de llevarla a cabo es importante
En el siglo XVII hubo un boom de la estadística al empezar a aplicarse en demografía. Uno de los autores más importantes fue J. Graunt, autor de «Natural and Political Observations Made upon the Bills of Mortality» (1662), estimando la población de Londres con las primeras tablas de natalidad y mortalidad.
Son precisamente las tablas de Graunt las que usó Christiaan Huygens para generar la primera gráfica de densidad de una distribución continua (esperanza de vida vs edad).
La figura que cambió el dataviz fue, sin lugar a dudas, el economista y político William Playfair (1759-1823), publicando en 1786 el «Atlas político y comercial» 1 [^12] con 44 gráficas (43 series temporales y el diagrama de barras más famoso de la historia).
Playfair es además el autor del gráfico de barras más famoso (no fue el primero pero sí quien lo hizo mainstream).
Otro pionero en combinar visualizaciones fue Minard, autor del famoso «Carte figurative des pertes successives en hommes de l’Armée Française dans la campagne de Russie 1812-1813», según Tufte «el mejor gráfico estadístico jamás dibujado», publicado en 1869 sobre la desastrosa campaña rusa de Napoleón en 1812 (3 variables en un gráfico bidimensional)
Según J. Sevilla, se considera al astrónomo británico John Frederick William Herschel el autor del primer diagrama de dispersión o scatterplot en 1833, visualizando el movimiento de la estrella doble Virginis (tiempo en el eje horizontal, posición angular en el eje vertical)
El 21 de octubre de 1854 Florence Nigthingale fue enviada para mejorar las condiciones sanitarias de los soldados británicos en la guerra de Crimea.
A su regreso demostró que los soldados fallecían por las condiciones sanitarias. Nigthingale es la creadora del famoso diagrama de rosa, visualizando tres variables a la vez y su estacionalidad.
El 8 de febrero de 1955, The Times la describió como la «ángel guardián» de los hospitales, y acabó siendo conocida como «The Lady with the Lamp» tras un poema de H. W. Longfellow (1857).
Años después se convirtió en la primera mujer en la Royal Statistical Society.
Florence Nigthingale es la creadora del famoso diagrama de rosa, permitiendo pintar tres variables a la vez y su estacionalidad: tiempo (cada gajo es un mes), nº de muertes (área del gajo) y causa de la muerte (color del gajo: azules enfermedades infecciosas, rojas por heridas, negras otras causas).
📚 «The Functional Art: an introduction to information graphics and visualization» de Alberto Cairo
📚 «Gramática de las gráficas: pistas para mejorar las representaciones de datos» de Joaquín Sevilla
📚 «A Brief History of Visualization» de Friendly et al. (2008)
📚 «The Grammar of Graphics» de Leland Wilkinson
📚 «The Minard System: The Graphical Works of Charles-Joseph Minard» de Sandra Rendgen
📚 «The Visual Display of Quantitative Information» de E. W. Tufte
El paquete {ggplot2}
se basa en la idea de Wilkinson en «Grammar of graphics»: dotar a los gráficos de una gramática propia. Una de las principales fortalezas de R
es la visualización con {ggplot2}
.
La visualización de datos debería ser una parte fundamental de todo análisis de datos. No es solo una cuestión estética.
La filosofía detrás de {ggplot2}
es entender los gráficos como parte del flujo de trabajo, dotándoles de una gramática. El objetivo es empezar con un lienzo en blanco e ir añadiendo capas a tu gráfico. La ventaja de {ggplot2}
es poder mapear atributos estéticos (color, forma, tamaño) de objetos geométricos (puntos, barras, líneas) en función de los datos.
La documentación del paquete puedes consultarla en https://ggplot2-book.org/introduction.html
Un gráfico se podrá componer de capas
Veamos un primer intento para entender la filosofía ggplot. Imagina que queremos dibujar un scatter plot (diagrama de dispersión de puntos). Para ello vamos a usar el conjunto de datos gapminder
, del paquete homónimo: un fichero con datos de esperanzas de vida, poblaciones y renta per cápita de distintos países en distintos momentos temporales.
# A tibble: 1,704 × 6
country continent year lifeExp pop gdpPercap
<fct> <fct> <int> <dbl> <int> <dbl>
1 Afghanistan Asia 1952 28.8 8425333 779.
2 Afghanistan Asia 1957 30.3 9240934 821.
3 Afghanistan Asia 1962 32.0 10267083 853.
4 Afghanistan Asia 1967 34.0 11537966 836.
5 Afghanistan Asia 1972 36.1 13079460 740.
6 Afghanistan Asia 1977 38.4 14880372 786.
7 Afghanistan Asia 1982 39.9 12881816 978.
8 Afghanistan Asia 1987 40.8 13867957 852.
9 Afghanistan Asia 1992 41.7 16317921 649.
10 Afghanistan Asia 1997 41.8 22227415 635.
# ℹ 1,694 more rows
El fichero consta de 1704 registros y 6 variables: country
, continent
, year
, lifeExp
(esperanza de vida), pop
(población) y gdpPercap
(renta per cápita).
Rows: 1,704
Columns: 6
$ country <fct> "Afghanistan", "Afghanistan", "Afghanistan", "Afghanistan", …
$ continent <fct> Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, …
$ year <int> 1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, …
$ lifeExp <dbl> 28.801, 30.332, 31.997, 34.020, 36.088, 38.438, 39.854, 40.8…
$ pop <int> 8425333, 9240934, 10267083, 11537966, 13079460, 14880372, 12…
$ gdpPercap <dbl> 779.4453, 820.8530, 853.1007, 836.1971, 739.9811, 786.1134, …
Para empezar con algo sencillo filtraremos solo los datos de 2007
¿Qué elementos necesitamos para realizar un diagrama de puntos? Para iniciar el lienzo necesitamos una base de datos y dos variables a representar.
Dentro de geom_point()
tenemos varios argumentos a usar:
na.rm = ...
: si queremos que nos quite ausentes.
color = ...
: color (si tiene dimensión, color del contorno)
fill = ...
: color del relleno.
Los colores también podemos asignárselos por su código hexadecimal, consultando en https://htmlcolorcodes.com/es/, eligiendo el color que queramos. El código hexadecimal siempre comenzará con #
Hasta ahora los atributos estéticos se los hemos pasado fijos y constantes. Pero la verdadera potencia y versatilidad de ggplot
es que podemos mapear los atributos estéticos en función de los datos en aes()
para que dependan de variables de los datos.
Podemos combinarlo con lo que hemos hecho anteriormente:
color en función del continente.
tamaño en función de la población.
transparencia la fijamos constante del 50%.
A este scatter plot particular se le conoce BUBBLE CHART
Reflexionemos sobre el gráfico anterior:
Usando los datos hemos conseguido dibujar en un gráfico bidimensional 4 variables: lifeExp
y gdpPercap
en los ejes , continent
como color y pop
como tamaño de la geometría, con muy pocas líneas de código.
Podemos personalizar de manera sencilla haciendo uso de la capa labs()
:
title, subtitle
: título/subtítulocaption
: pie de gráficax, y
: nombres de los ejessize, color, fill, ...
: nombre en la leyenda de las variables que codifiquen los distintos atributosggplot(gapminder_2007,
aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.7) +
labs(x = "Esperanza de vida", y = "Renta per cápita",
title = "Primer ggplot", subtitle = "Datos de gapminder",
caption = "J. Álvarez Liébana", color = "continente", size = "población")
Podemos eliminar variables de la leyenda con guides(atributo = "none")
Una de las capas más importantes es la capa de escalas (personalizar) ya que dentro de aes()
solo le indicamos qué variable mapeamos pero no sus ajustes.
Por ejemplo, con scale_color_...()
y scale_fill_...()
podremos personalizar qué colores usar.
Con scale_color_manual()
podemos indicar manualmente una paleta (https://htmlcolorcodes.com/)
pal <- c("#A02B85", "#2DE86B", "#4FB2CA", "#E8DA2D", "#E84C2D")
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent)) +
geom_point(alpha = 0.7) +
scale_color_manual(values = pal) +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente")
Otra opción es elegir alguna de las paletas de colores diseñadas en el paquete {ggthemes}
:
scale_color_economist()
: paleta de colores basada en los colores de The Economist.Otra opción es elegir alguna de las paletas de colores diseñadas en el paquete {ggthemes}
:
scale_color_colorblind()
: paleta de colores basada en los colores de daltónicos/as.Incluso cargar paletas de colores diseñadas en base a películas o arte
{harrypotter}
(repositorio de Github aljrico/harrypotter
) usando scale_color_hp_d()
.devtools::install_github(repo = "aljrico/harrypotter")
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.7) +
harrypotter::scale_color_hp_d(option = "ravenclaw")+
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente")
Incluso cargar paletas de colores diseñadas en base a películas o arte
{MetBrewer}
(repositorio de Github BlakeRMills/MetBrewer
) usando scale_colour_manual(values = met.brewer(...))
.devtools::install_github(repo = "BlakeRMills/MetBrewer")
library(MetBrewer)
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.7) +
scale_color_manual(values = met.brewer("Monet")) +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente")
Incluso cargar paletas de colores diseñadas en base a películas o arte
{peRReo}
(repositorio de Github jbgb13/peRReo
) usando scale_colour_manual(values = latin_palette())
.devtools::install_github(repo = "jbgb13/peRReo")
library(peRReo)
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.7) +
scale_color_manual(values = latin_palette("rosalia")) +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente")
Incluso cargar paletas de colores diseñadas en base a películas o arte
{taylor}
usando scale_color_taylor_d(album = ...)
para usar paletas de álbumes de Taylor Swift.library(taylor)
ggplot(gapminder_2007,
aes(y = gdpPercap, x = lifeExp,
color = continent, size = pop)) +
geom_point(alpha = 0.7) +
scale_color_taylor_d(album = "Speak Now") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente")
Ya vimos como definir paletas de colores pero si te fijas la variable que codifica el color era cualitativa o discreta, ¿y si la variable que codifica color fuese cuantitativa numérica?
Existen unas paletas de colores conocidas como ColorBrewer pudiendo definirse de manera secuencial, divergente o de manera cualitativa (ver info en https://colorbrewer2.org)
maxcolors category colorblind
BrBG 11 div TRUE
PiYG 11 div TRUE
PRGn 11 div TRUE
PuOr 11 div TRUE
RdBu 11 div TRUE
RdGy 11 div FALSE
RdYlBu 11 div TRUE
RdYlGn 11 div FALSE
Spectral 11 div FALSE
Accent 8 qual FALSE
Dark2 8 qual TRUE
Paired 12 qual TRUE
Pastel1 9 qual FALSE
Pastel2 8 qual FALSE
Set1 9 qual FALSE
Set2 8 qual TRUE
Set3 12 qual FALSE
Blues 9 seq TRUE
BuGn 9 seq TRUE
BuPu 9 seq TRUE
GnBu 9 seq TRUE
Greens 9 seq TRUE
Greys 9 seq TRUE
Oranges 9 seq TRUE
OrRd 9 seq TRUE
PuBu 9 seq TRUE
PuBuGn 9 seq TRUE
PuRd 9 seq TRUE
Purples 9 seq TRUE
RdPu 9 seq TRUE
Reds 9 seq TRUE
YlGn 9 seq TRUE
YlGnBu 9 seq TRUE
YlOrBr 9 seq TRUE
YlOrRd 9 seq TRUE
Con RColorBrewer::brewer.pal()
podemos obtener el vector de n colores para una paleta dada, por ejemplo la paleta divergente RdYlBu
[1] "#D7191C" "#FDAE61" "#FFFFBF" "#ABD9E9" "#2C7BB6"
Un ejemplo de paleta secuencial es PuBuGn
Un ejemplo de paleta cualitativa es Pastel1
Para incluirlo podemos usar scale_color_brewer()
o bien scale_color_distiller()
si queremos crear una escala continua (interpolando entre los colores)
ggplot(gapminder_2007, aes(y = gdpPercap, x = pop, color = lifeExp)) +
geom_point(alpha = 0.7, size = 3) +
scale_color_distiller(palette = "RdYlBu") +
guides(size = "none") +
labs(x = "Población", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "esperanza de vida") +
theme_minimal()
También podemos crear gradientes de color personalizados con scale_color_gradient()
, scale_fill_gradient()
, scale_color_gradient2()
, scale_fill_gradient2()
Vamos a usar scale_color_gradient2(low = ..., mid = ..., high = ..., midpoint = ...)
indicándole el color más bajo y el más alto, pero también el de en medio (y el valor numérico al que queremos asociarlo)
ggplot(gapminder_2007, aes(y = gdpPercap, x = pop, color = lifeExp)) +
geom_point(alpha = 0.7, size = 3) +
scale_color_gradient2(low = "#825598", mid = "#9dd0c1",
high = "#d93535", midpoint = 60) +
guides(size = "none") +
labs(x = "Población", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "esperanza de vida") +
theme_minimal()
La misma idea que usamos para personalizar colores la podemos aplicar para, por ejemplo, personalizar los ejes
Vamos a personalizar las marcas del eje x con scale_x_continuous(breaks = ...)
para tener marcas cada 10 unidades.
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent)) +
geom_point(alpha = 0.7) +
ggthemes::scale_color_colorblind() +
scale_x_continuous(breaks = seq(30, 90, by = 10)) +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente")
Vamos a profundizar un poco dentro de nuestro scatter plot en escalas
scale_x_continuous()
y scale_y_continuous()
, además de “saltos” podemos indicar límites con limits = ...
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.7) +
scale_x_continuous(limits = c(50, 70), breaks = seq(50, 70, by = 5)) +
scale_y_continuous(limits = c(1000, 18000), breaks = seq(0, 18000, by = 1000)) +
ggthemes::scale_color_colorblind() +
guides(size = "none") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
{scales}
podemos añadir prefijos/sufijos con labels = label_number(...)
library(scales)
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.7) +
scale_x_continuous(limits = c(50, 70), breaks = seq(50, 70, by = 5),
labels = label_number(suffix = " años")) +
scale_y_continuous(limits = c(1000, 18000), breaks = seq(0, 18000, by = 1000),
labels = label_number(suffix = " $")) +
ggthemes::scale_color_colorblind() +
guides(size = "none") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
Vamos a profundizar un poco dentro de nuestro scatter plot en escalas
scale_x_...()
o scale_color_...()
, tenemos también scale_size_...()
y scale_alpha_...()
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(aes(alpha = pop)) +
scale_size(range = c(4, 12)) +
scale_alpha(range = c(0.1, 0.5)) +
ggthemes::scale_color_colorblind() +
guides(size = "none", alpha = "none") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
Vamos a profundizar un poco dentro de nuestro scatter plot en escalas
scale_x_sqrt()
o scale_x_log10()
podemos cambiar la escala de los ejes.ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.7) +
scale_y_log10() +
ggthemes::scale_color_colorblind() +
guides(size = "none") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
En {ggplot2}
vamos a poder personalizar todo el gráfico incluido definir nuestro propio tema estético haciendo uso de las capas theme_...()
Por ejemplo, vamos a usar theme_minimal()
para tener un tema “austero” y minimalista (aprenderemos a definir cada detalle).
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.7) +
scale_x_continuous(breaks = seq(35, 85, by = 10)) +
ggthemes::scale_color_colorblind() +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
Además de escalas tenemos una capa de coordenadas con coord_...
para indicar si queremos un sistema cartesiano, coordenadas polares (coord_polar()
), si queremos coordenadas iguales (coord_equal()
) o invertir su rol (coord_flip()
)
ggplot(gapminder_2007, aes(y = gdpPercap, x = pop, color = lifeExp)) +
geom_point(alpha = 0.8, size = 3) +
scale_x_log10() +
scale_color_gradient2(low = "#E92745", mid = "#F4ED5B", high = "#56B1F7", midpoint = 60) +
coord_flip() +
labs(x = "Población", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "esperanza de vida") +
theme_minimal()
Una capa también importante es la capa de estadísticas
stat_smooth()
: visualiza un ajuste suavizado de los datos (reg. lineal, glm, loess, gam, etc). Con stat_smooth(method = "lm")
una recta de regresión. También con geom_smooth(method = "lm")
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp)) +
geom_point(aes(color = continent, size = pop), alpha = 0.8) +
stat_smooth(method = "lm", se = FALSE, linewidth = 1.5) +
scale_y_log10() +
guides(size = "none") +
ggthemes::scale_color_colorblind() +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
Fíjate que si usas en la primera capa parámetros estéticos se acaban heredando a capas posteriores, en concreto al ajuste visualizado.
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, color = continent, size = pop)) +
geom_point(alpha = 0.8) +
stat_smooth(method = "lm", se = FALSE, linewidth = 1.5) +
scale_y_log10() +
guides(size = "none") +
ggthemes::scale_color_colorblind() +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
Dentro de stat_smooth()
podemos especificarle otro ajuste polinómico dándole expresión en formula = ...
, por ejemplo un polinomio de grado 5 formula = y ~ x + I(x^2) + I(x^3) + I(x^4) + I(x^5)
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp)) +
geom_point(aes(color = continent, size = pop), alpha = 0.8) +
stat_smooth(method = "lm", formula = y ~ x + I(x^2) + I(x^3) + I(x^4) + I(x^5),
color = "firebrick", se = FALSE, linewidth = 1.2) +
guides(size = "none") +
ggthemes::scale_color_colorblind() +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
Con stat_summary()
podemos incluso añadir estadísticas por grupos, como la media o mediana.
ggplot(gapminder, aes(y = gdpPercap, x = year)) +
geom_point(size = 1.7, alpha = 0.2) +
stat_summary(fun = "mean", size = 0.4, color = "coral") +
stat_summary(fun = "median", size = 0.4, color = "darkcyan") +
ggthemes::scale_color_colorblind() +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
Por último la capa facet_()
: podemos desagregar los gráficos (facetar) por grupos, equivalente al group_by()
en tidyverse.
Por ejemplo, vamos a crear un gráfico por continente, mostrando todos los gráficos a la vez (pero por separado) con facet_wrap(~continent)
.
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, size = pop, color = continent)) +
geom_point(alpha = 0.75) +
ggthemes::scale_color_colorblind() +
facet_wrap(~continent) +
guides(size = "none") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
También podemos desagregar los gráficos (facetar) por grupos, equivalente al group_by()
en tidyverse.
Por defecto las escalas en los ejes son compartidas. Si queremos que la escala de los ejes vaya por libre debemos usar scales = "free_x"
, scales = "free_y"
o scales = "free"
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, size = pop, color = continent)) +
geom_point(alpha = 0.75) +
ggthemes::scale_color_colorblind() +
facet_wrap(~continent, scales = "free") +
guides(size = "none") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
También podemos desagregar los gráficos (facetar) por grupos, equivalente al group_by()
en tidyverse.
Con nrow = ...
y ncol = ...
podemos especificar cuantas columnas y filas tenemos en la cuadrícula de gráficas
ggplot(gapminder_2007, aes(y = gdpPercap, x = lifeExp, size = pop, color = continent)) +
geom_point(alpha = 0.75) +
ggthemes::scale_color_colorblind() +
facet_wrap(~continent, scales = "free", nrow = 3) +
guides(size = "none") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
También le podemos pasar dos argumentos (variables) para formar un grid de gráficas con facet_grid(var1 ~ var2)
ggplot(gapminder |> filter(year >= 1962), aes(y = gdpPercap, x = lifeExp, size = pop, color = continent)) +
geom_point(alpha = 0.7) +
ggthemes::scale_color_colorblind() +
facet_grid(continent ~ year, scales = "free") +
guides(size = "none") +
labs(x = "Esperanza de vida", y = "Renta per cápita", title = "Primer ggplot",
caption = "J. Álvarez Liébana", color = "continente") +
theme_minimal()
Hemos aprendido a realizar uno de los gráficos más famosos, un diagrama de dispersión, pero…¿qué propiedades deben cumplir las variables?
Para visualizar dos variables con un diagrama de dispersión es necesario que ambas sean variables numéricas continuas
¿Se te ocurre algúna gráfico básico para variables discretas?
¿Y si tengo variables discretas o cualitativas?
La ventaja de ggplot es que, al trabajar por capas, todo lo que hemos aprendido nos sirve: solo tenemos que cambiar la geometría.
En este caso para realizar un diagrama de barras usaremos geom_bar()
en lugar de geom_point()
, indicando solo la variable de grupo con x = sex
(ggplot hará solo el recuento)
Podemos aplicar lo aprendido sobre colores para codificar la información, en este caso vamos a usar las paletas ya cargadas en scale_color_colorblind()
del paquete {ggthemes}
Fíjate que ahora solo nos ha coloreado el contorno: en otras geometrías, como las barras, será importante distinguir entre color
y fill
Podemos personalizar el gráfico haciendo uso de las opciones ya vistas, por ejemplo, con escalas en ejes, títulos de las variables, leyendas, etc
starwars |>
drop_na(sex) |>
ggplot(aes(x = sex)) +
geom_bar(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
scale_y_continuous(breaks = seq(0, 70, by = 10)) +
labs(x = "sexo", y = "frecuencia absoluta", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana") +
theme_minimal()
Para cambiar el rol de los ejes, generando un diagrama de barras horizontales, podemos dejar el gráfico igual y luego simplemente hacer coord_flip()
starwars |>
drop_na(sex) |>
ggplot(aes(x = sex)) +
geom_bar(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
scale_y_continuous(breaks = seq(0, 70, by = 10)) +
coord_flip() +
labs(x = "sexo", y = "frecuencia absoluta", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana") +
theme_minimal()
Vamos a hacer un paréntesis y aprender a personalizar más nuestras gráficas
theme_set(theme_minimal())
fija tema base
theme_update(...)
personaliza parámetros.
Por ejemplo, en plot.title
vamos a indicarle el tamaño y negrita en el título, dentro de element_text()
theme_set(theme_minimal())
theme_update(plot.title = element_text(size = 25, face = "bold"))
starwars |>
drop_na(sex) |>
ggplot(aes(x = sex)) +
geom_bar(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
coord_flip() +
labs(x = "sexo", y = "frecuencia absoluta", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente", caption = "J. Álvarez Liébana")
Podemos hacer lo mismo con otros textos con plot.subtitle
o plot.caption
Vamos incluso a elegir fuente o el color
sysfonts::font_add_google()
: le indicaremos la tipografía de https://fonts.google.com/
showtext_auto()
del paquete {showtext}
nos permite su uso.
library(showtext)
library(sysfonts)
font_add_google(name = "Roboto")
showtext_auto()
theme_set(theme_minimal(base_family = "Roboto"))
# Configurar tema
theme_update(
plot.title = element_text(color = "#C34539", face = "bold", size = 33),
plot.subtitle = element_text(color = "#3E6FCB", face = "bold", size = 21),
axis.title.x = element_text(size = 19),
axis.title.y = element_text(size = 19))
¿Podríamos visualizar dos variables discretas/cualis a la vez?
Podemos incluir una en x = ...
y otra en fill = ...
, de manera que por defecto nos visualiza barras apiladas, por ejemplo, para ver el reparto de sexos entre humanos y no humanos.
starwars |>
drop_na(sex) |>
mutate(Human = species == "Human") |>
ggplot(aes(x = Human)) +
geom_bar(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
labs(x = "¿Humanos?", y = "frecuencia absoluta", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
Con position = "dodge2"
visualizamos las barras sin apilar, solapadas una al lado de otra
starwars |>
drop_na(sex) |>
mutate(Human = species == "Human") |>
ggplot(aes(x = Human)) +
geom_bar(aes(fill = sex), alpha = 0.5, position = "dodge2") +
scale_fill_colorblind() +
labs(x = "¿Humanos?", y = "frecuencia absoluta", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
Con position = "fill"
visualizamos las barras en forma de frecuencia relativa, con las barras de la misma altura para facilitar la comparativa.
starwars |>
drop_na(sex) |>
mutate(Human = species == "Human") |>
ggplot(aes(x = Human)) +
geom_bar(aes(fill = sex), alpha = 0.5, position = "fill") +
scale_fill_colorblind() +
labs(x = "¿Humanos?", y = "frecuencia relativa", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
En el caso de las variables cualitativas, llamaremos niveles o modalidades a los diferentes valores que pueden tomar estos datos. Por ejemplo, en el caso de la variable sex
del conjunto starwars
, tenemos 4 niveles permitidos: female
, hermaphroditic
, male
y none
(amén de datos ausentes).
Este tipo de variables se conocen en R
como factores. Y el paquete fundamental para tratarlos es {forcats}
(del entorno {tidyverse}
).
Este paquete nos permite fijar los niveles (guardados internamente como levels
) que toma una determinada variable categórica, dándoles un tratamiento diferente a las cadena de texto normales.
Veamos un ejempo sencillo definiendo una variable estado
que tome los valores "sano"
, "leve"
y "grave"
de la siguiente manera.
estado <-
c("leve", "grave", "sano", "sano", "leve", "sano", "sano", "grave",
"grave", "leve", "grave", "sano", "sano")
estado
[1] "leve" "grave" "sano" "sano" "leve" "sano" "sano" "grave" "grave"
[10] "leve" "grave" "sano" "sano"
La variable estado
actualmente es de tipo texto, de tipo chr
, algo que podemos comprobar con class(estado)
.
Desde un punto de vista estadístico y computacional, para R
esta variable ahora mismo sería equivalente una variable de nombres. Pero estadísticamente no es lo mismo una variable con nombres (que identifican muchas veces el registro) que una variable categórica como estado que solo puede tomar esos 3 niveles. ¿Cómo convertir a factor?
Haciendo uso de la función as_factor()
del paquete {forcats}
.
No solo ha cambiado la clase de la variable sino que ahora, debajo del valor guardado, nos aparece la frase Levels: grave leve sano
: son las modalidades o niveles de nuestra cualitativa.
Imagina que ese día en el hospital no tuviésemos a nadie en estado grave: aunque ese día nuestra variable no tome dicho valor, el estado grave
es un nivel permitido en la base de datos, así que aunque lo eliminemos, por ser un factor, el nivel permanece (no lo tenemos ahora pero es un nivel permitido).
Con factor()
podemos especificar explícitamente los nombres de las modalidades, incluso si son nominales u ordinales
Con levels = ...
podemos indicarle explícitamente el orden de las modalidades
Si queremos indicarle que elimine un nivel no usado en ese momento (y que queremos excluir de la definición) podemos hacerlo con fct_drop()
Al igual que podemos eliminar niveles podemos ampliar los niveles existentes (aunque no existan datos de ese nivel en ese momento) con fct_expand()
Además con fct_explicit_na()
podemos asignar un nivel a los valores para que sea incluido dicho nivel en los análisis y visualizaciones.
Incluso una vez definidos podemos reordenar los níveles con fct_relevel()
estado_fct_expand <-
estado_fct |>
mutate(estado = fct_expand(estado, c("UCI", "fallecido"))) |>
pull(estado)
estado_fct_expand |>
fct_relevel(c("fallecido", "leve", "sano", "grave", "UCI"))
[1] leve grave sano sano leve sano sano grave grave leve grave sano
[13] sano
Levels: fallecido < leve < sano < grave < UCI
Esta forma de trabajar con variables cualitativas nos permite dar una definición teórica de nuestra base de datos, pudiendo incluso contar valores que aún no existen (pero que podrían), haciendo uso de fct_count()
Los níveles también podemos ordenarlos por frecuencia con fct_infreq()
A veces querremos agrupar niveles, por ejemplo, no permitiendo niveles que no sucedan un mínimo de veces con fct_lump_min(.., min = ..)
(las observaciones que no lo cumplan irán a un nivel genérico llamado Other
, aunque se puede cambiar con el argumento other_level
).
Podemos hacer algo equivalente pero en función de su frecuencia relativa con fct_lump_prop()
.
Esto lo podemos aplicar a nuestros conjuntos de datos para recategorizar variables de forma muy rápida.
Con fct_reorder()
podemos también indicar que queremos ordenar los factores en función de una función aplicada a otra variable.
[1] Human Droid Droid Human Human Human Human Droid Human Human
[11] Human Human Otras Human Otras Otras Human Otras Human Human
[21] Droid Otras Human Human Otras Human Otras Otras Human Otras
[31] Human Human Gungan Gungan Gungan Human Otras Otras Human Human
[41] Otras Otras Otras Otras Otras Otras Otras Human Otras Otras
[51] Otras Otras Otras Otras Otras Otras Human Otras Otras Otras
[61] Human Human Human Human Otras Otras Otras Otras Human Droid
[71] Otras Otras Otras Otras Otras Human Otras
Levels: Droid Gungan Human Otras
[1] Human Droid Droid Human Human Human Human Droid Human Human
[11] Human Human Otras Human Otras Otras Human Otras Human Human
[21] Droid Otras Human Human Otras Human Otras Otras Human Otras
[31] Human Human Gungan Gungan Gungan Human Otras Otras Human Human
[41] Otras Otras Otras Otras Otras Otras Otras Human Otras Otras
[51] Otras Otras Otras Otras Otras Otras Human Otras Otras Otras
[61] Human Human Human Human Otras Otras Otras Otras Human Droid
[71] Otras Otras Otras Otras Otras Human Otras
Levels: Droid Otras Human Gungan
Haciendo uso de los que sabemos sobre factores podemos indicarle que nos ordene las columnas de manera personalizada definiendo la variable cuali como un factor.
starwars |>
drop_na(sex) |>
mutate(sex = factor(sex, levels = c("female", "male", "hermaphroditic", "none"))) |>
ggplot(aes(x = sex)) +
geom_bar(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
labs(x = "Sexo", y = "frecuencia absoluta", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
También podemos indicarle que nos ordene las columnas de mayor a menor frecuencia usando simplemente fct_infreq()
starwars |>
drop_na(sex) |>
mutate(sex = factor(sex, levels = c("female", "male", "hermaphroditic", "none"))) |>
ggplot(aes(x = fct_infreq(sex))) +
geom_bar(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
labs(x = "Sexo", y = "frecuencia absoluta", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
Para invertir el orden de los factores basta usar fct_rev()
starwars |>
drop_na(sex) |>
mutate(sex = factor(sex, levels = c("female", "male", "hermaphroditic", "none"))) |>
ggplot(aes(x = fct_rev(fct_infreq(sex)))) +
geom_bar(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
labs(x = "Sexo", y = "frecuencia absoluta", fill = "sexo", title = "Primer diagrama de barras",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
La capa geom_bar()
está solo pensada para conteos de variables discretas o cualitativas. ¿Y si queremos visualizar en el peso por sexo?
Usaremos geom_col()
(ahora si necesitamos x,y
)
starwars |>
drop_na(mass, sex) |>
ggplot(aes(x = sex, y = mass)) +
geom_col(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
labs(x = "Sexo", y = "Peso", fill = "sexo", title = "Primer diagrama de columnas",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente", caption = "J. Álvarez Liébana")
Fíjate que por defecto lo que hace es sumar la variable continua. ¿Cómo pedir que visualice, por ejemplo, la media por grupos?
La forma más inmediata es hacer un geom_col()
pero en lugar de a la tabla original a un resumen de la misma.
starwars |>
drop_na(mass, sex) |>
summarise(mean_mass = mean(mass), .by = sex) |>
ggplot(aes(x = sex, y = mean_mass)) +
geom_col(aes(fill = sex), alpha = 0.5) +
scale_fill_colorblind() +
labs(x = "Sexo", y = "Peso (medio)", fill = "sexo", title = "Primer diagrama de columnas",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente", caption = "J. Álvarez Liébana")
Otra opción es no usar la capa geométrica sino la capa estadística, con stat_summary()
e indicándole la función a visualizar y el geometría
starwars |>
drop_na(mass, sex) |>
mutate(sex = factor(sex, levels = c("female", "male", "hermaphroditic", "none"))) |>
ggplot(aes(x = sex, y = mass, fill = sex)) +
stat_summary(geom = "col", fun = mean, alpha = 0.5) +
scale_fill_colorblind() +
labs(x = "Sexo", y = "Peso (medio)", fill = "sexo",
title = "Primer diagrama de columnas",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
Fíjate que ambas formas nos permiten visualizar cualquier otro estadístico, por ejempo, la mediana
starwars |>
drop_na(mass, sex) |>
mutate(sex = factor(sex, levels = c("female", "male", "hermaphroditic", "none"))) |>
ggplot(aes(x = sex, y = mass, fill = sex)) +
stat_summary(geom = "col", fun = median, alpha = 0.5) +
scale_fill_colorblind() +
labs(x = "Sexo", y = "Peso (mediana)", fill = "sexo",
title = "Primer diagrama de columnas",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
La última opción es volver a nuestra conocida geom_bar()
, indicándole stat = "summary", fun = "mean"
, por ejemplo (por defecto stat = "count"
) con ahora sí dos variables
starwars |>
drop_na(mass, sex) |>
mutate(sex = factor(sex, levels = c("female", "male", "hermaphroditic", "none"))) |>
ggplot(aes(x = sex, y = mass)) +
geom_bar(aes(fill = sex), alpha = 0.5,
stat = "summary", fun = "mean") +
scale_fill_colorblind() +
labs(x = "Sexo", y = "Peso (media)", fill = "sexo",
title = "Primer diagrama de columnas",
subtitle = "Sexos: femenino, masculino, hemafrodita, ninguno y ausente",
caption = "J. Álvarez Liébana")
Vamos a intentar replicar el famoso gráfico de rosa o diagrama de área polar de Florence Nightingale, cargando los datos de {HistData}
. Los datos representan, por meses, las diferentes causas de mortalidad de los soldados ingleses en la Guerra de Crimea (busca en la ayuda para saber qué es cada cosa)
".rate"
. Tras ello prepara los datos de manera adecuada para su visualización (todo en castellano)datos_filtrados <-
datos |>
select(Date:Year, contains("rate")) |>
pivot_longer(cols = contains("rate"),
names_to = "causa",
values_to = "tasa") |>
rename(fecha = Date, mes = Month, anno = Year) |>
mutate(causa =
case_when(causa == "Disease.rate" ~ "infecciosas",
causa == "Wounds.rate" ~ "heridas",
TRUE ~ "otras"))
datos_filtrados
# A tibble: 72 × 5
fecha mes anno causa tasa
<date> <ord> <int> <chr> <dbl>
1 1854-04-01 Apr 1854 infecciosas 1.4
2 1854-04-01 Apr 1854 heridas 0
3 1854-04-01 Apr 1854 otras 7
4 1854-05-01 May 1854 infecciosas 6.2
5 1854-05-01 May 1854 heridas 0
6 1854-05-01 May 1854 otras 4.6
7 1854-06-01 Jun 1854 infecciosas 4.7
8 1854-06-01 Jun 1854 heridas 0
9 1854-06-01 Jun 1854 otras 2.5
10 1854-07-01 Jul 1854 infecciosas 150
# ℹ 62 more rows
datos_filtrados <-
datos_filtrados |>
mutate(periodo = ifelse(fecha >= "1855-04-01", "Abril 1855 - Marzo 1856", "Abril 1854 - Marzo 1855"),
periodo =
factor(periodo, levels = c("Abril 1854 - Marzo 1855", "Abril 1855 - Marzo 1856"),
ordered = TRUE))
theme_set(theme_minimal())
ggplot(datos_filtrados, aes(x = mes, y = tasa, fill = causa)) +
geom_col(alpha = 0.85) +
scale_fill_manual(values = c("#e3aeae", "#a5acb0", "#594b4a")) +
facet_wrap(~periodo) +
labs(fill = "Causas", title = "Causas de mortalidad",
subtitle = "Periodos: Abril 1854 - Marzo 1855 y Abril 1855 - Marzo 1856",
caption = "Autor: J. Álvarez Liébana | Data: {HistData}")
datos_filtrados <-
datos_filtrados |>
mutate(periodo = ifelse(fecha >= "1855-04-01", "Abril 1855 - Marzo 1856", "Abril 1854 - Marzo 1855"),
periodo =
factor(periodo, levels = c("Abril 1854 - Marzo 1855", "Abril 1855 - Marzo 1856"),
ordered = TRUE),
mes = fct_relevel(mes, "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
"Dec", "Jan", "Feb", "Mar"))
theme_set(theme_minimal())
ggplot(datos_filtrados, aes(x = mes, y = tasa, fill = causa)) +
geom_col(alpha = 0.85) +
scale_fill_manual(values = c("#e3aeae", "#a5acb0", "#594b4a")) +
facet_wrap(~periodo) +
labs(fill = "Causas", title = "Causas de mortalidad",
subtitle = "Periodos: Abril 1854 - Marzo 1855 y Abril 1855 - Marzo 1856",
caption = "Autor: J. Álvarez Liébana | Data: {HistData}")
theme_set(theme_minimal())
ggplot(datos_filtrados, aes(x = mes, y = tasa, fill = causa)) +
geom_col(alpha = 0.85) +
scale_fill_manual(values = c("#e3aeae", "#a5acb0", "#594b4a")) +
coord_polar() +
facet_wrap(~periodo) +
labs(fill = "Causas", title = "Causas de mortalidad",
subtitle = "Periodos: Abril 1854 - Marzo 1855 y Abril 1855 - Marzo 1856",
caption = "Autor: J. Álvarez Liébana | Data: {HistData}")
theme_set(theme_minimal())
ggplot(datos_filtrados, aes(x = mes, y = tasa, fill = causa)) +
geom_col(alpha = 0.85) +
scale_fill_manual(values = c("#e3aeae", "#a5acb0", "#594b4a")) +
coord_polar() +
scale_y_sqrt() +
facet_wrap(~periodo) +
labs(fill = "Causas", title = "Causas de mortalidad",
subtitle = "Periodos: Abril 1854 - Marzo 1855 y Abril 1855 - Marzo 1856",
caption = "Autor: J. Álvarez Liébana | Data: {HistData}")
theme()
Situa además la leyenda en la parte inferiortheme_set(theme_minimal())
theme_update(axis.title.y = element_blank(), axis.text.y = element_blank(),
axis.ticks.y = element_blank(), axis.text.x = element_text(size = 6, face = "bold"),
legend.position = "bottom")
... +
scale_x_discrete(labels =
c("JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
"JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE"))
Usa el dataset gapminder
y visualiza en un gráfico la media de la variable gdpPercap
por continente y año mediante un diagrama de barras en 3 formas:
El objetivo es analizar un conjunto de datos que contiene las respuestas a las pregunta «¿Qué probabilidad (%) asignarías al término (entre otros) …
…con el objetivo de comprender cómo la gente percibe el vocabulario de la probabilidad.
datos <-
read_csv("https://raw.githubusercontent.com/zonination/perceptions/master/probly.csv")
datos
# A tibble: 46 × 17
`Almost Certainly` `Highly Likely` `Very Good Chance` Probable Likely
<dbl> <dbl> <dbl> <dbl> <dbl>
1 95 80 85 75 66
2 95 75 75 51 75
3 95 85 85 70 75
4 95 85 85 70 75
5 98 95 80 70 70
6 95 99 85 90 75
7 85 95 65 80 40
8 97 95 75 70 70
9 95 95 80 70 65
10 90 85 90 70 75
# ℹ 36 more rows
# ℹ 12 more variables: Probably <dbl>, `We Believe` <dbl>,
# `Better Than Even` <dbl>, `About Even` <dbl>, `We Doubt` <dbl>,
# Improbable <dbl>, Unlikely <dbl>, `Probably Not` <dbl>,
# `Little Chance` <dbl>, `Almost No Chance` <dbl>, `Highly Unlikely` <dbl>,
# `Chances Are Slight` <dbl>
datos <-
read_csv("https://raw.githubusercontent.com/zonination/perceptions/master/probly.csv")
datos
# A tibble: 46 × 17
`Almost Certainly` `Highly Likely` `Very Good Chance` Probable Likely
<dbl> <dbl> <dbl> <dbl> <dbl>
1 95 80 85 75 66
2 95 75 75 51 75
3 95 85 85 70 75
4 95 85 85 70 75
5 98 95 80 70 70
6 95 99 85 90 75
7 85 95 65 80 40
8 97 95 75 70 70
9 95 95 80 70 65
10 90 85 90 70 75
# ℹ 36 more rows
# ℹ 12 more variables: Probably <dbl>, `We Believe` <dbl>,
# `Better Than Even` <dbl>, `About Even` <dbl>, `We Doubt` <dbl>,
# Improbable <dbl>, Unlikely <dbl>, `Probably Not` <dbl>,
# `Little Chance` <dbl>, `Almost No Chance` <dbl>, `Highly Unlikely` <dbl>,
# `Chances Are Slight` <dbl>
Solo haciendo uso de los gráficos aprendidos hasta ahora, ¿cómo visualizarías dicho dataset? Realiza las tranformaciones que consideres para una correcta preparación de los datos.
Javier Álvarez Liébana • Máster de Bioestadística (UCM)