|>
tabla_1 xxx_join(tabla_2, by = id)
Joins e importación: como combinar, exportar e importar en R
Cuadernos prácticos de Software II del Grado en Ciencia de Datos Aplicada (curso 2024-2025)
1 Relacionando datos: joins
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
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.1 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.1
✔ purrr 1.0.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
<- tibble("key" = 1:3, "val_x" = c("x1", "x2", "x3"))
tb_1 <- tibble("key" = c(1, 2, 4), "val_y" = c("y1", "y2", "y3")) tb_2
tb_1
# A tibble: 3 × 2
key val_x
<int> <chr>
1 1 x1
2 2 x2
3 3 x3
tb_2
# A tibble: 3 × 2
key val_y
<dbl> <chr>
1 1 y1
2 2 y2
3 4 y3
1.1 Left join
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)
|>
tb_1 left_join(tb_2, by = "key")
# A tibble: 3 × 3
key val_x val_y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
3 3 x3 <NA>
|>
tb_1 left_join(tb_2, by = "key")
# 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.
1.2 Right join
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"
)
|>
tb_1 right_join(tb_2, 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
|>
tb_1 right_join(tb_2, 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.
1.3 Claves y sufijos
Las columnas clave que usaremos para el cruce no siempre se llamarán igual.
<- tibble("key_1" = 1:3, "val_x" = c("x1", "x2", "x3"))
tb_1 <- tibble("key_2" = c(1, 2, 4), "val_y" = c("y1", "y2", "y3")) tb_2
by = c("key_2" = "key_2")
: le indicaremos en qué columna de cada tabla están las claves por las que vamos a cruzar.
# Left
|>
tb_1 left_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 3 × 3
key_1 val_x val_y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
3 3 x3 <NA>
# Right
|>
tb_1 right_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 3 × 3
key_1 val_x val_y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
3 4 <NA> y3
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
<- tibble("k_11" = 1:3, "k_12" = c("a", "b", "c"), "val_x" = c("x1", "x2", "x3"))
tb_1 <- tibble("k_21" = c(1, 2, 4), "k_22" = c("a", "b", "e"), "val_y" = c("y1", "y2", "y3")) tb_2
# Left
|>
tb_1 left_join(tb_2,
by = c("k_11" = "k_21", "k_12" = "k_22"))
# A tibble: 3 × 4
k_11 k_12 val_x val_y
<dbl> <chr> <chr> <chr>
1 1 a x1 y1
2 2 b x2 y2
3 3 c x3 <NA>
También podría suceder que al cruzar dos tablas, haya columnas de valores que se llamen igual
<- tibble("key_1" = 1:3, "val" = c("x1", "x2", "x3"))
tb_1 <- tibble("key_2" = c(1, 2, 4), "val" = c("y1", "y2", "y3")) tb_2
# Left
|>
tb_1 left_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 3 × 3
key_1 val.x val.y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
3 3 x3 <NA>
Fíjate que por defecto nos añade los sufijos .x
y .y
para indicarnos de que tabla vienen. Dicho sufijo podemos especificárselo en el argumento opcional suffix = ...
, que nos permita distinguir las variables de una tabla y de otra.
# Left
|>
tb_1 left_join(tb_2, by = c("key_1" = "key_2"), suffix = c("_tabla1", "_tabla2"))
# A tibble: 3 × 3
key_1 val_tabla1 val_tabla2
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
3 3 x3 <NA>
1.4 Full join
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.
|>
tb_1 full_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 4 × 3
key_1 val.x val.y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
3 3 x3 <NA>
4 4 <NA> y3
1.5 Inner join
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.
|>
tb_1 inner_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 2 × 3
key_1 val.x val.y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
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.
|>
tb_1 inner_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 2 × 3
key_1 val.x val.y
<dbl> <chr> <chr>
1 1 x1 y1
2 2 x2 y2
|>
tb_2 inner_join(tb_1, by = c("key_2" = "key_1"))
# A tibble: 2 × 3
key_2 val.x val.y
<dbl> <chr> <chr>
1 1 y1 x1
2 2 y2 x2
1.6 Anti/semi join
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).
# semijoin
|>
tb_1 semi_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 2 × 2
key_1 val
<int> <chr>
1 1 x1
2 2 x2
# antijoin
|>
tb_1 anti_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 1 × 2
key_1 val
<int> <chr>
1 3 x3
1.7 💻 Tu turno
Intenta realizar los siguientes ejercicios sin mirar las soluciones
Para los ejercicios usaremos las tablas disponibles en el paquete {nycflights13}
(echa un vistazo antes)
library(nycflights13)
- airlines: nombre de aerolíneas (con su abreviatura).
- airports: datos de aeropuertos (nombres, longitud, latitud, altitud, etc).
- flights: datos de vuelos.
- planes: datos de los aviones.
- weather: datos meteorológicos horarios de las estaciones LGA, JFK y EWR.
📝 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 dicha tabla.
Code
<-
flights_airlines |>
flights left_join(airlines, by = "carrier")
flights_airlines
📝 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).
Code
<-
flights_airlines_planes |>
flights_airlines inner_join(planes, by = "tailnum")
flights_airlines_planes
📝 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í
Code
<-
flights_airlines_planes |>
flights_airlines inner_join(planes, by = "tailnum",
suffix = c("_flight", "_build_aircraft"))
flights_airlines_planes
📝 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.
Code
%>%
flights_airlines_planes left_join(airports %>% select(faa, lat, lon),
by = c("origin" = "faa")) |>
rename(lat_origin = lat, lon_origin = lon) |>
left_join(airports %>% select(faa, lat, lon),
by = c("dest" = "faa")) |>
rename(lat_dest = lat, lon_dest = lon)
📝 Filtra de airports
solo aquellos aeropuertos de los que salgan vuelos. Repite el proceso filtrado solo aquellos a los que lleguen vuelos
Code
|>
airports semi_join(flights, by = c("faa" = "origin"))
|>
airports semi_join(flights, by = c("faa" = "dest"))
📝 ¿De cuántos vuelos no disponemos información del avión? Elimina antes los vuelos que no tengan identificar (diferente a NA) del avión
Code
|>
flights drop_na(tailnum) |>
anti_join(planes, by = "tailnum") |>
count(tailnum, sort = TRUE) # de mayor a menor ya de paso
2 🐣Caso práctico I: Beatles y Rolling Stones
Vamos a empezar a practicar joins sencillos con la tabla band_members
y band_instruments
ya incluidos en el paquete {dplyr}
.
library(dplyr)
band_members
# A tibble: 3 × 2
name band
<chr> <chr>
1 Mick Stones
2 John Beatles
3 Paul Beatles
band_instruments
# A tibble: 3 × 2
name plays
<chr> <chr>
1 John guitar
2 Paul bass
3 Keith guitar
En la primera tenemos una serie de artistas y la banda a la que pertenecen; en la segunda tenemos una serie de artistas y el instrumento que tocan. Además de realizar las acciones solicitadas intenta visualizar qué tabla final tendrías antes de ejecutar el código..
2.1 Pregunta 1
Dada la tabla
band_members
, incorpora la información de qué instrumento toca cada miembro (band_instruments
) de los que tienes en esa tabla.
Code
<-
left_join_band |>
band_members left_join(band_instruments, by = "name")
2.2 Pregunta 2
Dadas las tablas
band_members
yband_instruments
, ¿qué tipo de join deberías hacer para tener una tabla completa, sin ausencias, donde todos los miembros de la banda tengan la información de su instrumento, y cada instrumento tenga un miembro asociado a él?
Code
<-
inner_join_band |>
band_members inner_join(band_instruments, by = "name")
2.3 Pregunta 3
Dada la tabla
band_instruments
, ¿cómo incorporar la información de quién toca cada instrumento (en caso de que la conozcamos)?
Code
<-
right_join_band |>
band_members right_join(band_instruments, by = "name")
# other option
<-
left_join_instruments |>
band_instruments left_join(band_members, by = "name")
2.4 Pregunta 4
Dadas las tablas
band_members
yband_instruments
, ¿qué tipo de join deberías hacer para tener una tabla con toda la información, tanto de los miembros como de los instrumentos, aunque haya miembros cuyo instrumento no conozcas, e instrumentos cuyo portador no conozcas?
Code
<-
full_join_band |>
band_members full_join(band_instruments, by = "name")
3 🐣 Caso práctico II: renta municipios
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.
# datos 2019
<- read_csv(file = "./datos/municipios.csv") mun_data
Rows: 8212 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (8): codauto, ine.ccaa.name, cpro, ine.prov.name, cmun, name, LAU_CODE, ...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
mun_data
# 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.
<- read_csv(file = "./datos/renta_mun.csv") renta_mun
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
dat <- vroom(...)
problems(dat)
Rows: 55273 Columns: 7
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): Unidad, codigo_ine
dbl (5): 2019, 2018, 2017, 2016, 2015
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
renta_mun
# 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}
.
<-
mun_data |>
mun_data ::clean_names()
janitor<-
renta_mun |>
renta_mun ::clean_names() janitor
3.1 Pregunta 1
Convierte a tidydata
renta_mun
obteniendo una tabla de 4 columnas:unidad
,year
,renta
ycodigo_ine
(sin ausentes y cada dato del tipo correcto)
Code
<-
renta_mun_tidy |>
renta_mun pivot_longer(cols = contains("x"), names_to = "year",
values_to = "renta", names_prefix = "x",
names_transform = list(year = as.numeric),
values_drop_na = TRUE)
3.2 Pregunta 2
Si te fijas en la tabla anterior, tenemos datos de diferentes unidades administrativas que no siempre son municipios. Sabiendo que todos los municipios tienen un código de 5 caracteres (que representan todos ellos números), filtra sólo aquellos registros que correspondan a unidades municipales.
Code
<-
renta_mun_tidy |>
renta_mun_tidy filter(str_detect(codigo_ine, pattern = "[0-9]{5}") &
str_length(codigo_ine) == 5)
3.3 Pregunta 3
A continuación separa adecuadamente la variable de unidad en dos columnas: una con el código (que ya tiene, por lo que debe eliminar uno de los dos) y el nombre. Elimina los espacios sobrantes (echa un vistazo a las opciones del paquete
{stringr}
).
Code
<-
renta_mun_tidy |>
renta_mun_tidy separate(col = "unidad", into = c("cod_rm", "name"), sep = 5) |>
select(-cod_rm) |>
mutate(name = str_trim(name))
3.4 Pregunta 4
¿En qué año fue mayor la renta media? ¿Y más baja? ¿Cuál fue la renta mediana de los municipios de España en 2019?
<-
summary_renta |>
renta_mun_tidy summarise("mean_renta" = mean(renta, na.rm = TRUE),
.by = year)
|>
summary_renta slice_min(mean_renta, n = 1)
# A tibble: 1 × 2
year mean_renta
<dbl> <dbl>
1 2015 10083.
|>
summary_renta slice_max(mean_renta, n = 1)
# A tibble: 1 × 2
year mean_renta
<dbl> <dbl>
1 2019 11672.
|>
renta_mun_tidy filter(year == 2019) |>
summarise("median_renta" = median(renta, na.rm = TRUE))
# A tibble: 1 × 1
median_renta
<dbl>
1 11462
3.5 Pregunta 5
Haz lo que consideres para obtener la provincia con la renta media más alta en 2019 y la más baja. Asegúrate de obtener su nombre.
Code
<-
summary_by_prov |>
renta_mun_tidy filter(year == 2019) |>
left_join(mun_data, by = "codigo_ine", suffix = c("", "_rm")) |>
select(-contains("rm")) |>
summarise("mean_by_prov" = mean(renta, na.rm = TRUE),
.by = c("cpro", "ine_prov_name"))
|>
summary_by_prov slice_max(mean_by_prov, n = 1)
# A tibble: 1 × 3
cpro ine_prov_name mean_by_prov
<chr> <chr> <dbl>
1 20 Gipuzkoa 15890.
Code
|>
summary_by_prov slice_min(mean_by_prov, n = 1)
# A tibble: 1 × 3
cpro ine_prov_name mean_by_prov
<chr> <chr> <dbl>
1 06 Badajoz 8805.
3.6 Pregunta 6
Obten de cada ccaa el nombre del municipio con mayor renta en 2019.
Code
|>
renta_mun_tidy filter(year == 2019) |>
left_join(mun_data, by = "codigo_ine", suffix = c("", "_rm")) |>
select(-contains("rm")) |>
slice_max(renta, by = "codauto")
# A tibble: 19 × 10
name codigo_ine year renta codauto ine_ccaa_name cpro ine_prov_name cmun
<chr> <chr> <dbl> <dbl> <chr> <chr> <chr> <chr> <chr>
1 Navas… 40904 2019 20242 07 Castilla y L… 40 Segovia 904
2 Almar… 10019 2019 13940 11 Extremadura 10 Cáceres 019
3 Oleir… 15058 2019 16447 12 Galicia 15 Coruña, A 058
4 Laukiz 48053 2019 21672 16 País Vasco 48 Bizkaia 053
5 Lumbr… 26091 2019 24007 17 Rioja, La 26 Rioja, La 091
6 Murcia 30030 2019 11631 14 Murcia, Regi… 30 Murcia 030
7 Rajad… 08178 2019 23401 09 Cataluña 08 Barcelona 178
8 Alocén 19023 2019 19700 08 Castilla - L… 19 Guadalajara 023
9 Arguis 22037 2019 19219 02 Aragón 22 Huesca 037
10 Alcud… 04009 2019 14196 01 Andalucía 04 Almería 009
11 Arang… 31023 2019 15517 15 Navarra, Com… 31 Navarra 023
12 Santa… 35021 2019 15982 05 Canarias 35 Palmas, Las 021
13 Rocaf… 46216 2019 17872 10 Comunitat Va… 46 Valencia/Val… 216
14 Pozue… 28115 2019 26367 13 Madrid, Comu… 28 Madrid 115
15 Valld… 07063 2019 19565 04 Balears, Ill… 07 Balears, Ill… 063
16 Valde… 39093 2019 15574 06 Cantabria 39 Cantabria 093
17 Tever… 33072 2019 15409 03 Asturias, Pr… 33 Asturias 072
18 Ceuta 51001 2019 12038 18 Ceuta 51 Ceuta 001
19 Melil… 52001 2019 11463 19 Melilla 52 Melilla 001
# ℹ 1 more variable: lau_code <chr>
4 Importar/exportar
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.
4.1 Formatos nativos de R
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.
- Archivos
RData
: vamos a importar el archivoworld_bank_pop.RData
que incluye la tablaworld_bank_pop
load("./datos/world_bank_pop.RData")
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>
- Archivos
.rda
: vamos a importar el dataset airquality desdeairquality.rda
load("./datos/airquality.rda")
|> as_tibble() airquality
# 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
Ten 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.
load(file = "./datos/multiple_objects.rda")
- Archivos
.rds
: para este tipo debemos utilizarreadRDS()
, y necesitamos incorporar un argumentofile
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
<-
lung_cancer readRDS(file = "./datos/NCCTG_lung_cancer.rds") |>
as_tibble()
lung_cancer
# 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
Las [rutas]{.hl-yellow deben ser siempre sin espacios, ñ, ni acentos.
4.2 Datos tabulados: readr
El paquete {readr}
dentro del entorno {tidyverse}
contiene varias funciones útiles para cargar datos rectangulares (sin formatear pero tabulados).
:::: columns ::: {.column width=“50%”}
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/
4.2.1 Archivos .csv
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).
- Archivo
.csv
: conread_csv()
cargaremos archivos separados por comas, pasando como argumento la ruta enfile = ...
. Vamos a importar el conjunto de datoschickens.csv
(sobre pollos de dibujos animados, por qué no). Si nos fijamos en la salida nos da el tipo de variables.
library(readr)
<- read_csv(file = "./datos/chickens.csv") chickens
Rows: 5 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): chicken, sex, motto
dbl (1): eggs_laid
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
chickens
# 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()
.
spec(chickens)
cols(
chicken = col_character(),
sex = col_character(),
eggs_laid = col_double(),
motto = col_character()
)
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 = ...
).
<-
chickens read_csv(file = "./datos/chickens.csv",
col_select = c(chicken, sex, eggs_laid))
Rows: 5 Columns: 3
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): chicken, sex
dbl (1): eggs_laid
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
chickens
# A tibble: 5 × 3
chicken sex eggs_laid
<chr> <chr> <dbl>
1 Foghorn Leghorn rooster 0
2 Chicken Little hen 3
3 Ginger hen 12
4 Camilla the Chicken hen 7
5 Ernie The Giant Chicken rooster 0
4.2.2 Archivos .txt, .tsv
¿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
<- read_csv(file = "./datos/massey-rating.txt") datos_txt
Rows: 10 Columns: 1
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (1): UCC PAY LAZ KPK RT COF BIH DII ENG ACU Rank Team Conf
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
dim(datos_txt)
[1] 10 1
as_tibble(datos_txt)
# 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 yread_table()
cuando es un espacio.read_delim()
en general.
<- read_table(file = "./datos/massey-rating.txt") datos_txt
── Column specification ────────────────────────────────────────────────────────
cols(
UCC = col_double(),
PAY = col_double(),
LAZ = col_double(),
KPK = col_double(),
RT = col_double(),
COF = col_double(),
BIH = col_double(),
DII = col_double(),
ENG = col_double(),
ACU = col_double(),
Rank = col_double(),
Team = col_character(),
Conf = col_character()
)
Warning: 10 parsing failures.
row col expected actual file
1 -- 13 columns 15 columns './datos/massey-rating.txt'
2 -- 13 columns 14 columns './datos/massey-rating.txt'
3 -- 13 columns 14 columns './datos/massey-rating.txt'
4 -- 13 columns 14 columns './datos/massey-rating.txt'
5 -- 13 columns 15 columns './datos/massey-rating.txt'
... ... .......... .......... ...........................
See problems(...) for more details.
as_tibble(datos_txt)
# 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
4.3 Datos Excel (.xls, .xlsx)
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.
library(readxl)
<- read_xlsx(path = "./datos/deaths.xlsx") deaths
New names:
• `` -> `...2`
• `` -> `...3`
• `` -> `...4`
• `` -> `...5`
• `` -> `...6`
deaths
# 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!
|> slice(1:6) deaths
# 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).
<- read_xlsx(path = "./datos/deaths.xlsx", skip = 4)
deaths deaths
# 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.
library(openxlsx)
$muerte <- convertToDate(deaths$muerte) deaths
Warning in convertToDate(deaths$muerte): NAs introduced by coercion
deaths
# 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 = ...
.
<- read_xlsx(path = "./datos/datasets.xlsx", sheet = "mtcars")
mtcars mtcars
# 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 = ...
.
<- read_xlsx(path = "./datos/datasets.xlsx", sheet = "iris", range = "C1:E4")
iris iris
# A tibble: 3 × 3
Petal.Length Petal.Width Species
<dbl> <dbl> <chr>
1 1.4 0.2 setosa
2 1.4 0.2 setosa
3 1.3 0.2 setosa
4.4 Importar desde SAS/STATA/SPSS
El paquete {haven}
dentro del universo tidyverse nos permitirá importar ficheros de los 3 software de pago más importantes: SAS, SPSS y Stata.
library(haven)
# SAS
<- read_sas(data_file = "./datos/iris.sas7bdat")
iris_sas
# SPSS
<- read_sav(file = "./datos/iris.sav")
iris_spss
# Stata
<- read_dta(file = "./datos/iris.dta") iris_stata
4.5 Exportar
De la misma forma que podemos importar también podemos exportar
- exportar en
.RData
(opción recomendada para variables almacenadas enR
). Recuerda que esta extensión sólo se puede utilizar enR
. Para ello, basta con utilizarsave(object, file = path)
.
<- tibble("a" = 1:4, "b" = 1:4)
table save(table, file = "./datos/table.RData")
rm(table) # eliminar
load("./datos/table.RData")
table
# A tibble: 4 × 2
a b
<int> <int>
1 1 1
2 2 2
3 3 3
4 4 4
La ventaja de .RData
es que podemos exportar múltiples variables de nuestro environment, no solo un único dataset.
<- tibble("a" = 1:4, "b" = 1:4)
table <- 1
a <- c("javi", "sandra")
b save(table, a, b, file = "./datos/mult_obj.RData")
rm(list = c("a", "b", "table"))
load("./datos/mult_obj.RData")
table
# A tibble: 4 × 2
a b
<int> <int>
1 1 1
2 2 2
3 3 3
4 4 4
- exportado en
.csv
. Para ello simplemente utilizamoswrite_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.
write_csv(table, file = "./datos/table.csv")
read_csv(file = "./datos/table.csv")
Rows: 4 Columns: 2
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl (2): a, b
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# A tibble: 4 × 2
a b
<dbl> <dbl>
1 1 1
2 2 2
3 3 3
4 4 4
4.6 Importar desde web
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)
<-
covid_data read_csv(file = "https://cnecovid.isciii.es/covid19/resources/casos_hosp_uci_def_sexo_edad_provres.csv")
covid_data
Rows: 500 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): provincia_iso, sexo, grupo_edad
dbl (4): num_casos, num_hosp, num_uci, num_def
date (1): fecha
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# 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
4.7 Importar desde wikipedia
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)
Attaching package: 'rvest'
The following object is masked from 'package:readr':
guess_encoding
<- 'https://en.wikipedia.org/wiki/Men%27s_long_jump_world_record_progression'
wiki_jump |> read_html() |>
wiki_jump 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…
4.8 Importar desde google drive
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
library(googlesheets4)
<-
google_sheet read_sheet("https://docs.google.com/spreadsheets/d/1Uz38nHjl3bmftxDpcXj--DYyPo1I39NHVf-xjeg1_wI/edit?usp=sharing")
! Using an auto-discovered, cached token.
To suppress this message, modify your code or options to clearly consent to
the use of a cached token.
See gargle's "Non-interactive auth" vignette for more details:
<https://gargle.r-lib.org/articles/non-interactive-auth.html>
ℹ The googlesheets4 package is using a cached token for 'javalv09@ucm.es'.
✔ Reading from "import-google-sheet".
✔ Range 'Hoja 1'.
google_sheet
# A tibble: 3 × 4
a b c d
<chr> <dbl> <dbl> <chr>
1 d1 0.5 1 hombre
2 d2 -0.3 2 hombre
3 d3 1.1 3 mujer
4.9 Importar desde API
4.9.1 owid
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.
library(owidR)
owid_covid() |> as_tibble()
# A tibble: 7 × 67
iso_code continent location date total_cases new_cases
<chr> <chr> <chr> <IDate> <int> <int>
1 AFG Asia Afghanistan 2020-01-05 0 0
2 AFG Asia Afghanistan 2020-01-06 0 0
3 AFG Asia Afghanistan 2020-01-07 0 0
4 AFG Asia Afghanistan 2020-01-08 0 0
5 AFG Asia Afghanistan 2020-01-09 0 0
6 AFG Asia Afghanistan 2020-01-10 0 0
7 AFG Asia Afghanistan 2020-01-11 0 0
# ℹ 61 more variables: new_cases_smoothed <dbl>, total_deaths <int>,
# new_deaths <int>, new_deaths_smoothed <dbl>, total_cases_per_million <dbl>,
# new_cases_per_million <dbl>, new_cases_smoothed_per_million <dbl>,
# total_deaths_per_million <dbl>, new_deaths_per_million <dbl>,
# new_deaths_smoothed_per_million <dbl>, reproduction_rate <dbl>,
# icu_patients <int>, icu_patients_per_million <dbl>, hosp_patients <int>,
# hosp_patients_per_million <dbl>, weekly_icu_admissions <int>, …
4.9.2 aemet
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
<- "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZhbHYwOUB1Y20uZXMiLCJqdGkiOiI4YTU1ODUxMS01MTE3LTQ4MTYtYmM4OS1hYmVkNDhiODBkYzkiLCJpc3MiOiJBRU1FVCIsImlhdCI6MTY2NjQ2OTcxNSwidXNlcklkIjoiOGE1NTg1MTEtNTExNy00ODE2LWJjODktYWJlZDQ4YjgwZGM5Iiwicm9sZSI6IiJ9.HEMR77lZy2ASjmOxJa8ppx2J8Za1IViurMX3p1reVBU"
apikey
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
<- aemet_stations()
stations stations
# A tibble: 947 × 7
indicativo indsinop nombre provincia altitud longitud latitud
<chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
1 B013X "08304" ESCORCA, LLUC ILLES BA… 490 2.89 39.8
2 B051A "08316" SÓLLER, PUERTO ILLES BA… 5 2.69 39.8
3 B087X "" BANYALBUFAR ILLES BA… 60 2.51 39.7
4 B103B "99103" ANDRATX - SANT ELM ILLES BA… 52 2.37 39.6
5 B158X "" CALVIÀ, ES CAPDELLÀ ILLES BA… 50 2.47 39.6
6 B228 "08301" PALMA, PUERTO ILLES BA… 3 2.63 39.6
7 B236C "" PALMA, UNIVERSIDAD ILLES BA… 95 2.64 39.6
8 B248 "08303" SIERRA DE ALFABIA, BU… ILLES BA… 1030 2.71 39.7
9 B275E "08302" SON BONET, AEROPUERTO BALEARES 49 2.71 39.6
10 B278 "08306" PALMA DE MALLORCA, AE… ILLES BA… 8 2.74 39.6
# ℹ 937 more rows
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.
aemet_last_obs("0076")
# A tibble: 13 × 23
idema lon fint prec alt vmax vv dv lat dmax
<chr> <dbl> <dttm> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 0076 2.07 2024-11-02 23:00:00 0 4 5.7 3 360 41.3 350
2 0076 2.07 2024-11-03 00:00:00 0 4 5.7 3.9 360 41.3 360
3 0076 2.07 2024-11-03 01:00:00 0 4 6.2 3.6 10 41.3 20
4 0076 2.07 2024-11-03 02:00:00 0 4 5.1 3.8 360 41.3 10
5 0076 2.07 2024-11-03 03:00:00 0 4 5.7 4.3 360 41.3 350
6 0076 2.07 2024-11-03 04:00:00 0 4 6.7 4.6 360 41.3 20
7 0076 2.07 2024-11-03 05:00:00 0 4 7.2 5.7 360 41.3 10
8 0076 2.07 2024-11-03 06:00:00 0.2 4 7.2 5 360 41.3 20
9 0076 2.07 2024-11-03 07:00:00 0 4 6.2 4.6 10 41.3 10
10 0076 2.07 2024-11-03 08:00:00 0 4 11.3 5 30 41.3 10
11 0076 2.07 2024-11-03 09:00:00 0.1 4 10.3 4.5 40 41.3 30
12 0076 2.07 2024-11-03 10:00:00 9 4 6.2 3.4 290 41.3 320
13 0076 2.07 2024-11-03 11:00:00 0.1 4 10.3 3.5 360 41.3 20
# ℹ 13 more variables: ubi <chr>, pres <dbl>, hr <dbl>, stdvv <dbl>, ts <dbl>,
# pres_nmar <dbl>, tamin <dbl>, ta <dbl>, tamax <dbl>, tpr <dbl>, vis <dbl>,
# stddv <dbl>, inso <dbl>
4.9.3 US census
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
library(tidycensus)
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 mortalidadget_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)
Getting data from the 2010 decennial Census
Using Census Summary File 1
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
4.9.4 otras
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
4.10 💻 Tu turno
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
Code
library(tidyr)
save(who2, file = "./datos/who2.RData")
📝 Carga el conjunto de datos who2
pero desde la carpeta data (importa el fichero creado en el ejercicio anterior)
Code
load("./datos/who2.RData")
📝 Repite lo mismo (exportar e importar) en 4 formatos: .csv
, .xlsx
, .sav
(spss) y .dta
(stata)
Code
# csv
library(readr)
write_csv(who2, file = "./datos/who2.csv")
<- read_csv(file = "./datos/who2.csv")
who2_data
# excel
library(openxlsx)
write.xlsx(who2, file = "./datos/who2.xlsx")
<- read_xlsx(path = "./datos/who2.xlsx")
who2_data
# sas y stata
library(haven)
write_sav(who2, path = "./datos/who2.sav")
<- read_spss(path = "./datos/who2.sav")
who2_data
write_dta(who2, path = "./datos/who2.dta")
<- read_dta(path = "./datos/who2.dta") who2_data
📝 Repita la carga de who2.csv
pero seleccione sólo las 4 primeras columnas ya cargadas.
Code
<-
who_select read_csv(file = "./datos/who2.csv",
col_select = c("country", "iso2", "iso3", "year"))
who_select
5 🐣 Caso práctico III: encuesta CIS
Vamos a poner en práctica la carga y preprocesado de un fichero generado por uno de los programas informáticos más utilizados (SPSS). El fichero contiene datos del barómetro del CIS (Centro de Investigaciones Sociológicas) «Percepciones sobre igualdad entre hombres y mujeres y estereotipos de género» cuyo trabajo muestral se realizó del 6 al 14 de noviembre (4000 entrevistas a mayores de 16 años de ambos sexos en 1174 municipios y 50 provincias).
5.1 Pregunta 1
Carga el archivo de extensión
.sav
que tienes en la subcarpetaCIS-feminismo
dentro de la carpeta de datos. Tras la carga normaliza los nombres de las variables con el paquete{janitor}
.
Code
library(haven)
<-
data read_sav(file = "./datos/CIS-feminismo/3428.sav") |>
::clean_names() janitor
5.2 Pregunta 2
Usando tidyverse calcula la cantidad de elementos diferentes de cada variable y procede a eliminar aquellas cuyo valor sea constante (es decir solo haya una modalidad). Primero obtén el vector de nombres de dichas variables para luego poder usarlo en un
select()
(de manera que no dependa de los nombres de variables de la tabla)
Code
<-
rm_variables |>
data summarise(across(everything(), n_distinct)) |>
pivot_longer(cols = everything(), names_to = "variable", values_to = "n_values") |>
filter(n_values <= 1) |>
pull(variable)
<-
data |>
data select(-rm_variables)
Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
ℹ Please use `all_of()` or `any_of()` instead.
# Was:
data %>% select(rm_variables)
# Now:
data %>% select(all_of(rm_variables))
See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
5.3 Pregunta 3
Haz lo mismo pero para eliminar aquellas variables que en cada fila tomen un valor distinto (no haya ni un solo repetido). Estas columnas harán funciones de
id
del encuestado pero solo necesitamos una de ellas, elimina las demás.
Code
<-
rm_variables |>
data summarise(across(everything(), n_distinct)) |>
pivot_longer(cols = everything(), names_to = "variable", values_to = "n_values") |>
filter(n_values == nrow(data)) |>
pull(variable)
<-
data |>
data select(-rm_variables[-1])
5.4 Pregunta 4
Para entender qué significan las variables, tienes en la carpeta
CIS-feminismos
el cuestionario que se hizo a cada encuestada encues3428.pdf
y la ficha técnica enFT3428.pdf
. En estos documentos verás cómo, por ejemplo, las variables del cuestionario comienzan casi todas porp...
, aunque también tenemos otro tipo de variables relacionadas con las tareas del hogar (tarhog...
), los hijos (hijomenor...
), el cuidado de los niños (cuidadohijos...
), las tareas de cuidado en general (tareascuid...
) y otras variables relacionadas con la escala ideológica, la religión, el recuerdo de voto, etc. Además, todas las variables que comienzan poria_xxx
se refieren a códigos relativos a posibles incidencias en la recogida de datos y las variablespeso...
al tipo de ponderación utilizado en el muestreo. Proceda a eliminar estos dos últimos tipos de variables.
Code
<-
data |>
data select(-contains("ia_"), -contains("peso"))
5.5 Pregunta 5
Calcula el número de entrevistas por comunidad autónoma (
ccaa
) y extrae las 5 con menos entrevistas.
Code
|>
data count(ccaa, sort = TRUE)
|>
data count(ccaa) |>
slice_min(n = 5, n)
5.6 Pregunta 6
Usa el paquete
{datapasta}
para copiar de la página del INE la población de cada provincia e importarlo a un tibble
Code
# este código debería generarse automáticamente: cuando instalas el
# paquete se instala un addin en R Studio (parte superior) que te
# permite ctrl+c una tabla y diferentes opciones para importarla
<-
population ::tribble(~Total, ~`47.385.107`,
tibble"01 Andalucía", "8.472.407",
"02 Aragón", "1.326.261",
"03 Asturias, Principado de", "1.011.792",
"04 Balears, Illes", "1.173.008",
"05 Canarias", "2.172.944",
"06 Cantabria", "584.507",
"07 Castilla y León", "2.383.139",
"08 Castilla - La Mancha", "2.049.562",
"09 Cataluña", "7.763.362",
"10 Comunitat Valenciana", "5.058.138",
"11 Extremadura", "1.059.501",
"12 Galicia", "2.695.645",
"13 Madrid, Comunidad de", "6.751.251",
"14 Murcia, Región de", "1.518.486",
"15 Navarra, Comunidad Foral de", "661.537",
"16 País Vasco", "2.213.993",
"17 Rioja, La", "319.796",
"18 Ceuta", "83.517",
"19 Melilla", "86.261"
)
5.7 Pregunta 7
Renombra de manera adecuada las variables de
population
, convierte la población a variable numérica y separa la variable de la comunidad autónoma en 2, una para el código y otro para el nomnbre (procesa ete último de manera adecuada con el paquete{stringr}
para eliminar espacios en blanco).
Code
<-
population |>
population rename(ccaa = Total, pop = `47.385.107`) |>
mutate("pop" = as.numeric(str_replace_all(pop, "\\.", ""))) |>
separate(col = ccaa, into = c("cod_INE", "name"), sep = 2) |>
mutate(name = str_squish(name))
5.8 Pregunta 8
Incorpora la información de la población a nuestra tabla del CIS. Tras incorporar dicha información calcula una tabla resumen con el ratio entre el porcentaje de población que representa cada ccaa respecto al total de España y el porcentaje de encuestados de dicha comunidad respecto al total de encuestados. ¿Cuáles son las 3 provincias más sobrerepresentadas? ¿Y las 3 más infrarrepresentads?
Code
<-
data |>
data left_join(population |> mutate("cod_INE" = as.numeric(cod_INE)),
by = c("ccaa" = "cod_INE")) |>
relocate(name, pop, .after = "ccaa")
<-
prop_surv_pop |>
data summarise("prop_surv" = n() / sum(nrow(data)),
"pop" = unique(pop),
.by = ccaa) |>
mutate("prop_pop" = pop / sum(pop),
"ratio" = prop_pop / prop_surv)
# sobre
|>
prop_surv_pop slice_min(ratio, n = 3)
# infra
|>
prop_surv_pop slice_max(ratio, n = 3)
6 Listas
Ya hemos visto que las listas son un objeto en R que nos permite almacenar colecciones de variables de distinto tipo (como con data.frame
y tibble
) pero también diferentes longitudes, con estructuras totalmente heterogéneas (incluso una lista puede tener dentro otra lista).
<- "Javi"
nombre <- 34
edad <- c(7, 8, 5, 3, 10, 9)
notas <- c("Paloma", "Goyo")
progenitores
# Podemos no ponerle nombre pero solo podremos acceder por índice
<- list("nombre" = nombre, "edad" = edad,
list_var "notas" = notas, "parents" = progenitores)
$nombre list_var
[1] "Javi"
$progenitores list_var
NULL
7 Listas
También podemos hacer listas con otras listas dentro, de forma que para acceder a cada nivel debamos utilizar el operador [[]]
.
<- list("list_1" = list_var[1:2], "list_2" = list_var[3:4])
list_of_lists names(list_of_lists)
[1] "list_1" "list_2"
names(list_of_lists[[1]])
[1] "nombre" "edad"
1]][[1]] list_of_lists[[
[1] "Javi"
¡Se nos permite almacenar datos n-dimensionales!
Sin embargo una de las desventajas es no puede ser vectorizada inmediatamente, por lo que cualquier operación aritmética aplicada a una lista dará error. Por ejemplo, en el código anterior, uno esperaría que hiciese la operación en cada elemento de la lista (en a
y en b
) pero no funciona.
<- list("a" = 1:5, "b" = 10:20)
data / 2 data
Error in data/2: non-numeric argument to binary operator
Para ello una de las opciones habituales (de R
base) era hacer uso de la familia lapply(lista, FUN = ...)
: aplica una función FUN = ...
a cada elemento de la lista
lapply(data, FUN = function(x) { x / 2})
$a
[1] 0.5 1.0 1.5 2.0 2.5
$b
[1] 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5 10.0
Pero por defecto la salida de lapply()
es siempre una lista de igual longitud.
7.1 Paquete purrr
Una opción más flexible y versátil es hacer uso del paquete {purrr}
del entorno {tidyverse}
.
library(purrr)
Este paquete pretende imitar la programación funcional de otros lenguajes como Scala o la estrategia map-reduce de Hadoop (de Google).
7.1.1 map()
La función más sencilla del paquete {purrr}
es la función map()
, que aplica una función vectorizada a cada uno de los elementos de una lista. Veamos un primer ejemplo aplicado a vectores: imagina que tenemos la siguiente lista y queremos aplicar, a cada uno de sus elementos, la suma.
<- list("x1" = 1:4, "x2" = 11:20) x
map(lista, función)
nos permite mapear la lista y aplicar la función que queramos elemento a elemento
map(x, sum)
$x1
[1] 10
$x2
[1] 155
La salida por defecto de map
es a su vez otra lista.
Veamos otro ejemplo que puedes sernos útil: imagina que tenemos dos muestras aleatorias de distinto tamaño. La única forma en la que podemos almacenarlas juntas es con una lista pero…¿cómo hacer la media a ambas distribuciones sino podemos vectorizarlas?
<- list(rnorm(n = 1500, mean = 0, sd = 0.7),
x rnorm(n = 2800, mean = 2, sd = 1.5))
Con map()
y la función mean()
dentro
map(x, mean)
[[1]]
[1] -0.02559818
[[2]]
[1] 1.999454
¿Y si quisiéramos calcular la media de sus valores al cuadrado? En este caso no disponemos de una función ya definida así que tenemos dos opciones: definirla antes (con un nombre) o bien definir la función dentro del propio map
map(x, function(x) { mean(x^2) })
[[1]]
[1] 0.4670886
[[2]]
[1] 6.294425
Una forma de definir la función más rápido es así:
map(x, \(x) mean(x^2))
[[1]]
[1] 0.4670886
[[2]]
[1] 6.294425
7.1.2 map_xxx()
Además de ser más legible y eficiente, con {purrr}
podemos decidir el formato de salida tras la operación
- salida como un vector numérico (decimales) con
map_dbl()
- salida como un vector numérico (enteros) con
map_int()
- salida como un vector de caracteres con
map_chr()
- salida como un vector numérico lógico con
map_lgl()
library(glue)
map_dbl(x, mean)
[1] -0.02559818 1.99945417
map_chr(x, function(x) { glue("Mean is {round(mean(x), 3)}") })
[1] "Mean is -0.026" "Mean is 1.999"
map_lgl(x, function(x) { all(x < 3)})
[1] TRUE FALSE
También puede sernos útil para acceder a un elemento de CADA lista si le pasas un número en lugar de una función.
c(x[[1]][3], x[[2]][3])
[1] 0.3515323 3.5592939
map_dbl(x, 3)
[1] 0.3515323 3.5592939
7.1.3 pluck()
También podemos hacer uso de pluck()
para acceder al i-th objeto de la lista.
<- list("a" = starwars, "b" = billboard)
lista |> pluck(1) lista
# 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>
7.1.4 map2()
También tenemos la opción de generalizarlo para poder utilizar funciones que necesiten dos argumentos (operaciones binarias) con map2()
. Por ejemplo, supongamos que tenemos las siguientes dos listas, y queremos que suma el vector del primer elemento de ambas entre sí, y lo mismo con el segundo
<- list("a" = 1:3, "b" = 4:6)
x <- list("a" = c(-1, 4, 0), "b" = c(5, -4, -1))
y $a + y$a x
[1] 0 6 3
$b + x$b x
[1] 8 10 12
En map2()
introducimos las 2 listas y una función con 2 argumentos
map2(x, y, function(x, y) { x + y})
$a
[1] 0 6 3
$b
[1] 9 1 5
Podemos también obtener la salida en forma de data.frame
añadiendo list_rbind()
o list_cbind()
, que convierte una lista en una tabla.
<- c("a", "b")
x <- 1:2
y map2(x, y, function(x, y) { tibble(x, y) })
[[1]]
# A tibble: 1 × 2
x y
<chr> <int>
1 a 1
[[2]]
# A tibble: 1 × 2
x y
<chr> <int>
1 b 2
map2(x, y, function(x, y) { tibble(x, y) }) |> list_rbind()
# A tibble: 2 × 2
x y
<chr> <int>
1 a 1
2 b 2
7.1.5 pmap()
Podemos generalizarlo aún más con pmap_xxx()
que nos permite utilizar múltiples argumentos (múltiples listas para funciónes multivariantes).
<- list(1, 1, 1)
x <- list(10, 20, 30)
y <- list(100, 200, 300)
z pmap_dbl(list(x, y, z), sum)
[1] 111 221 331
7.1.6 walk()
Tenemos otros tipos de iteradores que, aunque asumen entradas, no devuelven nada, como walk()
(sólo un argumento de entrada), walk2()
(dos argumentos) y pwalk()
(múltiples argumentos), todos devuelven algo invisible, sólo llaman a una función por sus efectos secundarios en lugar de por su valor de retorno.
list("a" = 1:3, "b" = 4:6) |>
map2(list("a" = 11:13, "b" = 14:16),
function(x, y) { x + y }) |>
walk(print)
[1] 12 14 16
[1] 18 20 22
7.2 💻 Tu turno
📝 Define una lista de 4 elementos de distintos tipos y accede al segundo de ellos (incluiré uno que sea un tibble para que veas que en una lista cabe de todo).
Code
<-
list_example list("name" = "Javier", "cp" = 28019,
"siblings" = TRUE,
"marks" = tibble("maths" = c(7.5, 8, 9),
"lang" = c(10, 5, 6)))
list_example
📝 From the list above, access the elements that occupy places 1 and 4 of the list defined above.
Code
c(1, 4)]
list_example[
$name
list_example$marks
list_example
c("name", "marks")] list_example[
📝 Load the starwars
dataset from the {dplyr}
package and access the second movie that appears in starwars$films
(for each character). Determine which ones do not appear in more than one movie.
<- map(starwars$films, 2)
second_film map_lgl(second_film, is.null)
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE
[13] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE TRUE TRUE FALSE
[25] TRUE FALSE TRUE TRUE TRUE TRUE TRUE FALSE TRUE FALSE FALSE TRUE
[37] TRUE TRUE FALSE TRUE TRUE FALSE TRUE TRUE FALSE TRUE TRUE TRUE
[49] TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE TRUE TRUE
[61] TRUE FALSE FALSE TRUE TRUE FALSE FALSE TRUE TRUE TRUE TRUE TRUE
[73] TRUE FALSE TRUE TRUE FALSE TRUE TRUE FALSE FALSE TRUE TRUE TRUE
[85] TRUE TRUE TRUE
📝 Convierte a tibble la base de datos iris
incluida en R base
(no necesitas ningún paquete, ya la tienes). Vamos a agrupar por species para después usar group_split()
: nos genera una lista tal que cada elemento es la tabla para cada uno de los grupos (como un filter de cada grupo pero en un mismo objeto).
<-
iris_list |> as_tibble() |>
iris group_by(Species) |>
group_split()
Accede a cada variable Sepal.Length
para cada una de las subtablas y haz la media (nos tendría que salir un vector de medias)
Code
|>
iris_list map("Sepal.Length") |>
map_dbl(mean)
8 🐣 Caso práctico IV: encuesta de satisfacción
Para este ejercicio usaremos la tabla de datos de satisfacción de pacientes en un hospital guardada en el archivo .csv
llamada SatisfaccionPacientes.csv
library(tidyverse)
<-
datos read_csv("./datos/SatisfaccionPacientes.csv") |>
# normalización de nombres de variables
::clean_names() janitor
Rows: 100 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): Genero, EstadoCivil, EstadoSalud
dbl (5): ID, Edad, TiempoEspera, GradoSatisfaccion, NumeroVisitas
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
datos
# A tibble: 100 × 8
id edad genero estado_civil tiempo_espera grado_satisfaccion
<dbl> <dbl> <chr> <chr> <dbl> <dbl>
1 1 60 Masculino Casado 28 8
2 2 44 Femenino Soltero 22 8
3 3 43 Masculino Soltero 8 9
4 4 32 Masculino Soltero 21 8
5 5 66 Masculino Divorciado 7 10
6 6 43 Masculino Divorciado 20 8
7 7 54 Masculino Casado 18 6
8 8 55 Masculino Soltero 29 6
9 9 56 Masculino Viudo 17 9
10 10 34 Femenino Casado 34 8
# ℹ 90 more rows
# ℹ 2 more variables: numero_visitas <dbl>, estado_salud <chr>
8.1 Pregunta 1
Aplica el código que sea necesario para determinar el tamaño muestral, el número de variables y el tipo de estas.
Code
# Tamaño muestral / número de observaciones
<- datos |> nrow()
n
# Número de variables
<- datos |> ncol()
p
# Tipo de variables
::glimpse(datos) dplyr
Rows: 100
Columns: 8
$ id <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, …
$ edad <dbl> 60, 44, 43, 32, 66, 43, 54, 55, 56, 34, 56, 57, 38,…
$ genero <chr> "Masculino", "Femenino", "Masculino", "Masculino", …
$ estado_civil <chr> "Casado", "Soltero", "Soltero", "Soltero", "Divorci…
$ tiempo_espera <dbl> 28, 22, 8, 21, 7, 20, 18, 29, 17, 34, 20, 29, 2, 31…
$ grado_satisfaccion <dbl> 8, 8, 9, 8, 10, 8, 6, 6, 9, 8, 9, 8, 8, 8, 9, 8, 10…
$ numero_visitas <dbl> 10, 1, 2, 8, 3, 10, 7, 1, 2, 3, 3, 6, 10, 5, 4, 2, …
$ estado_salud <chr> "Regular", "Bueno", "Bueno", "Bueno", "Malo", "Buen…
8.2 Pregunta 2
Usando tidyverse, obtén la tabla de frecuencias absolutas de la variable
genero
.
Code
<-
tabla_freq |>
datos count(genero)
8.3 Pregunta 3
Incorpora a la tabla anterior las frecuencias relativas. ¿Podríamos calcular las frecuencias acumuladas? ¿Por qué?
Code
<-
tabla_freq |>
tabla_freq rename(frecuencia_abs = n) |>
mutate(frecuencia_rel = frecuencia_abs/sum(frecuencia_abs))
# No podemos acumuladas ya que necesitamos una jerarquía de orden
# son cualis nominales, no ordinales
Haciendo uso de la tabla, ¿qué % de pacientes son mujeres?
Haz lo mismo tu solo con la variable estado_civil
y determina el número de personas casadas (deberían ser 26).
8.4 Pregunta 4
En R
las variables cualitativas pueden ser tratadas como tales convirtiendo una cadena de texto a lo que se conoce como factor. Por ejemplo, supongamos que tenemos un vector de notas
<- c("suspenso", "notable", "suspenso", "aprobado", "notable", "suspenso")
notas notas
[1] "suspenso" "notable" "suspenso" "aprobado" "notable" "suspenso"
Para convertir a factor nos basta con factor()
. ¿Qué notas diferente?
<- factor(notas)
notas_fct notas_fct
[1] suspenso notable suspenso aprobado notable suspenso
Levels: aprobado notable suspenso
Si te fijas ahora tenemos disponibles unos niveles (levels): son las posibles modalidades de nuestra variable cualitativa, el soporte, de manera que aunque borremos uno de ellos (vamos a borrar todos los aprobados), la opción sigue disponible si entrase un dato nuevo (algo así como un menú de opciones permitidas)
!= "aprobado"] notas_fct[notas_fct
[1] suspenso notable suspenso notable suspenso
Levels: aprobado notable suspenso
En el caso de las cualitativas ordinales podemos incluso establecer una jerarquía, indicando explícitamente los niveles y ordered = TRUE
<- factor(notas, levels = c("aprobado", "notable", "suspenso"),
notas_fct_ord ordered = TRUE)
notas_fct_ord
[1] suspenso notable suspenso aprobado notable suspenso
Levels: aprobado < notable < suspenso
Fíjate que ahora tenemos una jerarquía y aunque sea cualitativa podemos buscar elementos <= o >= que otros
<= "notable"] notas_fct_ord[notas_fct_ord
[1] notable aprobado notable
Levels: aprobado < notable < suspenso
Haz que tu variable
estado_salud
sea una factor representando una cualitativa ordinal
Code
<-
datos |>
datos mutate(estado_salud =
factor(estado_salud, levels = c("Malo", "Regular", "Bueno", "Excelente"),
ordered = TRUE))
8.5 Pregunta 5
Calcula la tabla de frecuencias para
estado_salud
incluyendo todo lo que puedas (¿se puede ahora calcular las frecuencias acumuladas). ¿Cuántos pacientes están en un estado de salud regular o peor? (pisa: 59 personas).
Code
<-
tabla_freq |>
datos count(estado_salud) |>
rename(frecuencia_abs = n) |>
mutate(frecuencia_rel = frecuencia_abs/sum(frecuencia_abs),
frecuencia_acum_abs = cumsum(frecuencia_abs),
frecuencia_acum_rel = cumsum(frecuencia_rel))
|>
datos count(estado_salud <= "Regular")
8.6 Pregunta 6
Calcula la media, mediana, cuartiles y desviación típica de edad y tiempo de espera. Guarda los resultados en
resumen
y expórtalo a unresumen.csv
Code
<-
resumen |>
datos summarise(media_edad = mean(edad), sd_edad = sd(edad),
mediana_edad = median(edad),
Q1_edad = quantile(edad, probs = 0.25),
Q3_edad = quantile(edad, probs = 0.75),
# tiempo espera
media_tiempo_espera = mean(tiempo_espera),
sd_tiempo_espera = sd(tiempo_espera),
mediana_tiempo_espera = median(tiempo_espera),
Q1_tiempo_espera = quantile(tiempo_espera, probs = 0.25),
Q3_tiempo_espera = quantile(tiempo_espera, probs = 0.75))
write_csv(resumen, file = "./datos/resumen.csv")
8.7 Pregunta 7
Haz una tabla de frecuencias (absolutas) cruzada entre
genero
yestado_salud
con tidyverse para que quede como la tabla inferior
Code
# Primero cuento de manera bidimensional con count(var1, var2)
<-
conteo_bidim |>
datos count(genero, estado_salud)
conteo_bidim
# A tibble: 7 × 3
genero estado_salud n
<chr> <ord> <int>
1 Femenino Malo 5
2 Femenino Regular 24
3 Femenino Bueno 24
4 Masculino Malo 10
5 Masculino Regular 20
6 Masculino Bueno 16
7 Masculino Excelente 1
8.8 Pregunta 8
Haciendo uso de la tabla anterior, ¿qué debes hacer para obtener una tabla de frecuencias bidimensional como la de abajo? (la tabla de frecuencias en formato habitual)
Code
# Después pivoto para que use la columna estado_salud
# como futuros nombres de variables (pivota de vertical a horizontal)
# - names_from: de donde saldrán los futuros nombres de columnas
# - values_from: de donde sacamos los valores numéricos (en este caso n)
# para rellenar la tabla
<-
tabla_freq_abs |> pivot_wider(names_from = estado_salud, values_from = n)
conteo_bidim tabla_freq_abs
# A tibble: 2 × 5
genero Malo Regular Bueno Excelente
<chr> <int> <int> <int> <int>
1 Femenino 5 24 24 NA
2 Masculino 10 20 16 1
8.9 Pregunta 9
También se puede hacer en R base
con table()
(que además de ser más sencillo respeta la jerarquía de la ordinal). Moraleja: a veces R base nos facilita la vida, no lo olvidemos porque lo vas a necesitar
<- table(datos$genero, datos$estado_salud)
tabla_freq tabla_freq
Malo Regular Bueno Excelente
Femenino 5 24 24 0
Masculino 10 20 16 1
La tabla anterior se puede calcular con frecuencias relativa por filas y por columnas (es decir,una que toda las filas sumen el total, 1, y otra que las columnas sumen 1) haciendo uso de prop.table()
aplicada a la tabla anterior (si margin = 1
normaliza por filas, si margin = 2
por columnas).
Code
prop.table(tabla_freq, margin = 1)
Malo Regular Bueno Excelente
Femenino 0.09433962 0.45283019 0.45283019 0.00000000
Masculino 0.21276596 0.42553191 0.34042553 0.02127660
Code
prop.table(tabla_freq, margin = 2)
Malo Regular Bueno Excelente
Femenino 0.3333333 0.5454545 0.6000000 0.0000000
Masculino 0.6666667 0.4545455 0.4000000 1.0000000
Moraleja: tablas de frecuencia podemos hacerlas en tidyverse pero bidimensionales R base nos ayuda mejor. Deberemos viajar entre los dos mundos muchas veces
Haciendo uso de las tablas anteriores contesta a las siguientes preguntas:
¿Qué porcentaje de entre las mujeres tiene un buen estado de salud?
¿Qué porcentaje de entre los hombres tiene un estado de salud regular?
¿Qué porcentaje de los que tienen estado de salud malo son mujeres?
Clicka debajo para ver la respuesta cuando lo tengas
Code
# De entre las mujeres un 45.28% tiene un buen estado de salud
# De entre los hombres un 34.04% tiene un estado de salud regular
# Un 33.33% de los que tienen un estado de salud malo, son mujeres
8.10 Pregunta 10
Un poquito de reminder de inferencia. Haciendo uso de la tabla de frecuencias absolutas, ejecuta el código que consideres para responder a la pregunta: ¿están estas dos variables (estado_salud
y genero
) asociadas? ¿Existe algún tipo de dependencia entre ellas? Hazlo considerando \(\alpha = 0.05\).
Code
# Opción 1: prueba de chi-cuadrado que nos permite sacar conclusiones
# sobre la independencia de dos variables cualitativas
# chisq.test() hace el contraste haciendo uso de la tabla de frec
chisq.test(tabla_freq)
# Como el p-value = 0.2322 y alpha = 5%, no podemos rechazar la
# hipótesis nula de independencia: no hay evidencias suficientes
# CON LA MUESTRA QUE TENEMOS para concluir que haya alguna asociación
# entre género y estado de salud
# Opción 2: test exacto de Fisher, especialmente útil cuando
# las frecuencias esperadas son bajas.
fisher.test(tabla_freq)
# Diferente p-valor pero misma conclusión
Las funciones chisq.test()
y fisher.test()
pueden tomar como argumento una tabla de frecuencias ya resumida o puede tomar [dos variables y la función ya realiza el conteo]{.hl-yellow. Por ejemplo si hacemos chisq.test(var1, var2)
obtenemos un objeto htest
que dentro contiene el p-valor.
<- chisq.test(datos$genero, datos$estado_salud) test_chisq
Warning in chisq.test(datos$genero, datos$estado_salud): Chi-squared
approximation may be incorrect
names(test_chisq)
[1] "statistic" "parameter" "p.value" "method" "data.name" "observed"
[7] "expected" "residuals" "stdres"
Por tanto si aplicamos la función y hacemos después $p.value
podemos obtener directamente el valor numérico que nos interesa
chisq.test(datos$genero, datos$estado_salud)$p.value
Warning in chisq.test(datos$genero, datos$estado_salud): Chi-squared
approximation may be incorrect
[1] 0.2322175
8.11 Pregunta 11
¿Cómo usar tidyverse para tener en una tabla resumen ambos p-valores?
Code
<-
tabla_p_valores |>
datos summarise("sig_chisq" = chisq.test(genero, estado_salud)$p.value,
"sig_fisher" = fisher.test(genero, estado_salud)$p.value)
Warning: There was 1 warning in `summarise()`.
ℹ In argument: `sig_chisq = chisq.test(genero, estado_salud)$p.value`.
Caused by warning in `chisq.test()`:
! Chi-squared approximation may be incorrect
8.12 Pregunta 12
Cálcula la matriz correlaciones de Pearson entre las variables numéricas. ¿Existe dependencia LINEAL entre la variable edad y el tiempo de espera? (recuerda: correlación de Pearson solo mide asociación lineal). Echa un vistazo al paquete
{corrr}
Code
# Opción 1
<-
mat_cor |>
datos select(where(is.numeric)) |>
cor()
# Opción 2
<-
mat_cor |>
datos select(where(is.numeric)) |>
::correlate() corrr
Correlation computed with
• Method: 'pearson'
• Missing treated using: 'pairwise.complete.obs'
Code
# La correlación es de 0.0669 por lo que no parezca exista relación
8.13 Pregunta 13
Calcula en una tabla resumen la correlación y el p-valor derivado de un test de correlaciones (
cor.test()
) entre ambas variables
Code
<-
cor_summary |>
datos summarise("cor" = cor(edad, tiempo_espera),
"sig_cor" = cor.test(edad, tiempo_espera)$p.value)
# No parece existir evidencia significativa de dependencia lineal
8.14 Pregunta 14
Repite todo lo anterior con las variables tiempo de espera y grado de satisfacción
Code
<-
mat_cor |>
datos select(where(is.numeric)) |>
::correlate() corrr
Correlation computed with
• Method: 'pearson'
• Missing treated using: 'pairwise.complete.obs'
Code
<-
cor_summary |>
datos summarise("cor" = cor(grado_satisfaccion, tiempo_espera),
"sig_cor" = cor.test(grado_satisfaccion, tiempo_espera)$p.value)
# Sí parece existir evidencia significativa de dependencia lineal
# concretamente negativa: a más espera, menor satisfacción