| Sexo | CC.AA. | Edad | Periodo | Total |
|---|---|---|---|---|
| Ambos sexos | Total Nacional | Total | 2026T3 | 10,18 |
| Ambos sexos | Total Nacional | Total | 2026T2 | 9,87 |
| Ambos sexos | Total Nacional | Total | 2026T1 | 10,83 |
Programación y manejo de datos con R (Slides 04-C)
El Instituto Nacional de Estadística (INE) es el organismo público encargado de la estadística oficial en España
Hace las grandes estadísticas del país: el censo y el padrón, el IPC, la EPA, la Contabilidad Nacional, …
Si necesitas un dato sobre España (población, precios, empleo, turismo, renta, …), lo más probable es que lo tenga el INE
| Operación | Código | ¿Qué mide? | Periodicidad |
|---|---|---|---|
| Índice de Precios de Consumo | IPC |
los precios (la inflación) | mensual |
| Encuesta de Población Activa | EPA |
ocupados, parados, tasa de paro, … | trimestral |
| Contabilidad Nacional Trimestral | CNTR2010 |
el PIB y sus componentes | trimestral |
| Padrón municipal | DPOP |
la población de cada municipio | anual |
| Encuesta de Condiciones de Vida | ECV |
renta, pobreza y desigualdad | anual |
ineapir … pero ojo, solo sirve para ver qué tablas tiene esa operación. Los datos se descargan con el identificador de la tabla, que es un número (lo vemos enseguida)| Nivel | ¿Qué es? | En la EPA |
|---|---|---|
| Operación | una estadística se identifica con un código de letras |
EPAla Encuesta de Población Activa |
| Tabla | dentro de cada operación hay muchas tablas se identifican con un número |
65334tasas de paro por edad, sexo y comunidad autónoma |
65334 tiene 5 columnas:
Total)| Sexo | CC.AA. | Edad | Periodo | Total |
|---|---|---|---|---|
| Ambos sexos | Total Nacional | Total | 2026T3 | 10,18 |
| Ambos sexos | Total Nacional | Total | 2026T2 | 9,87 |
| Ambos sexos | Total Nacional | Total | 2026T1 | 10,83 |
Por lo tanto, la tabla 65334 contiene 3 × 7 × 20 = 420 series, cada una con un dato por trimestre desde 2002.
Una serie de esas 420 es la de los valores (tasa de paro) para las Mujeres, Menores de 25 años de la Comunitat Valenciana
Por menús: entras en la web, eliges la tabla, pinchas en el botón de descarga y guardas el fichero
Con la url del fichero: descargas el fichero desde R, sin pasar por la web
Con la API: le pides los datos directamente al INE desde R, con el paquete ineapir
Veremos primero cómo hacerlo “a mano” (formas 1 y 2) y después con ineapir
Descargar un fichero del INE (CSV o PC-Axis) y limpiarlo
Queremos la tasa de paro por comunidades autónomas. Buscando en INEbase (Mercado laboral → EPA → …) llegamos a la página de la tabla
El número del final de la url es el identificador de la tabla: 65334. Apúntalo, lo vamos a necesitar!!
El botón de descarga ofrece varios formatos. ¿Cuál elegimos?
| Formato | ¿Cómo es? | ¿Sirve para R? |
|---|---|---|
| CSV separado por ; | una fila por dato | sí, es el mejor |
Excel (.xlsx) |
la tabla tal como se ve en la web, con títulos y notas | con mucho trabajo |
PC-Axis (.px) |
los datos y sus metadatos (título, unidades, notas, …) | sí, y muy bien, con el paquete pxR |
| Json | el formato de la API | mejor con ineapir |
Pero … no hace falta pinchar en el botón: la url del fichero siempre tiene la misma forma. Solo cambian el formato y el identificador de la tabla:
https://www.ine.es/jaxiT3/files/t/es/csv_bdsc/65334.csv (el PC-Axis: .../es/px/65334.px)
my_url <- "https://www.ine.es/jaxiT3/files/t/es/csv_bdsc/65334.csv"
1my_ruta <- here::here("pruebas", "epa_65334.csv")
curl::curl_download(my_url, my_ruta)pruebas, que tiene que existir (fs::dir_create("pruebas"))
; y los decimales llevan coma. Lo importamos con read_csv2():| Sexo <chr> |
Comunidades y Ciudades Autónomas <chr> |
Edad <chr> |
Periodo <chr> |
Total <chr> |
|---|---|---|---|---|
| Ambos sexos | Total Nacional | Total | 2026T3 | 10,18 |
| Ambos sexos | Total Nacional | Total | 2026T2 | 9,87 |
| Ambos sexos | Total Nacional | Total | 2026T1 | 10,83 |
Periodo es texto (normal: 2026T2 no es un número), pero … ¡Total también es texto! Debería ser un númeroTotal no es numérica?Porque el INE marca con .. los datos que no publica (grupos con muy pocas personas en la muestra).
Basta con que haya un .. en la columna para que toda ella se importe como texto
Solución: decirle a read_csv2() que .. significa “no hay dato” (NA):
| Sexo <chr> |
Comunidades y Ciudades Autónomas <chr> |
Edad <chr> |
Periodo <chr> |
Total <dbl> |
|---|---|---|---|---|
| Ambos sexos | Total Nacional | Total | 2026T3 | 10.18 |
| Ambos sexos | Total Nacional | Total | 2026T2 | 9.87 |
| Ambos sexos | Total Nacional | Total | 2026T1 | 10.83 |
df_epa %>% filter(is.na(parse_number(Total))) %>% count(Total)df_epa_2 <- df_epa %>%
1 janitor::clean_names() %>%
rename(ccaa = comunidades_y_ciudades_autonomas,
2 tasa_paro = total) %>%
3 mutate(fecha = lubridate::yq(periodo)) %>%
mutate(ccaa = if_else(ccaa == "Total Nacional",
4 "00 Total Nacional", ccaa)) %>%
separate(ccaa, into = c("ccaa_cod", "ccaa"),
5 sep = " ", extra = "merge") %>%
select(fecha, periodo, ccaa_cod, ccaa,
sexo, edad, tasa_paro)2026T2 pasa a ser una fecha de verdad: 2026-04-01 (el primer día del trimestre)
Total Nacional es la única sin código delante: le ponemos 00, como si fuera una comunidad más
10 Comunitat Valenciana): los separamos por el primer espacio (con extra = "merge", el resto va junto al nombre)
if_else() y separate()):df_epa_2 <- df_epa %>%
janitor::clean_names() %>%
rename(ccaa = comunidades_y_ciudades_autonomas,
tasa_paro = total) %>%
mutate(fecha = lubridate::yq(periodo)) %>%
mutate(ccaa_cod = str_extract(ccaa, "^[0-9]{2}"),
ccaa = str_remove(ccaa, "^[0-9]{2} ")) %>%
select(fecha, periodo, ccaa_cod, ccaa,
sexo, edad, tasa_paro)df_epa_2 %>%
filter(sexo == "Ambos sexos", edad == "Total") %>%
filter(fecha == max(fecha)) %>%
ggplot(aes(x = tasa_paro, y = fct_reorder(ccaa, tasa_paro))) +
geom_col(aes(fill = ccaa == "Total Nacional"),
show.legend = FALSE) +
labs(title = "Tasa de paro por CCAA (último trimestre)",
x = "%", y = NULL, caption = "Fuente: INE (EPA)")| Trampa | Ejemplo | Solución |
|---|---|---|
separador ; y coma decimal |
9,87 |
read_csv2() |
| datos no disponibles | .. |
read_csv2(..., na = "..") |
| código y nombre en la misma columna | 10 Comunitat Valenciana |
separate() o str_extract() |
| el periodo no es una fecha | 2026T2, 2026M09 |
lubridate::yq(), lubridate::ym() |
| los totales mezclados con las categorías | Total Nacional, Ambos sexos |
filter(): cuidado al sumar |
Después de importar, comprueba siempre el tipo de cada columna y cuenta sus categorías con count().
Y una regla: del INE, nunca el Excel. Mejor el CSV separado por ; o, como vamos a ver ahora, el PC-Axis
.px) y el paquete pxRpxRmy_url_px <- "https://www.ine.es/jaxiT3/files/t/es/px/65334.px"
1px_epa <- pxR::read.px(my_url_px)
2df_px <- px_epa %>% as.data.frame() %>% as_tibble()px, con los datos y los metadatos
as.data.frame() sacamos los datos del objeto px
| Periodo <fct> |
Edad <fct> |
Comunidades.y.Ciudades.Autónomas <fct> |
Sexo <fct> |
value <dbl> |
|---|---|---|---|---|
| 2026T3 | Total | Total Nacional | Ambos sexos | 10.18 |
| 2026T2 | Total | Total Nacional | Ambos sexos | 9.87 |
| 2026T1 | Total | Total Nacional | Ambos sexos | 10.83 |
value ya es numérica, los .. ya son NA y las tildes están bien. Las variables llegan como factores (con las categorías en el orden de la web)px guarda todo lo que el INE cuenta sobre la tabla:px_epa$TITLE$value
#> [1] "Tasas de paro por distintos grupos de edad, sexo y comunidad autónoma"
px_epa$UNITS$value
#> [1] "Tasas"
names(px_epa$VALUES) #- las "variables" de la tabla
#> [1] "Sexo" "Comunidades.y.Ciudades.Autónomas"
#> [3] "Edad" "Periodo"
px_epa$VALUES$Edad #- los valores de una "variable"
#> [1] "Total" "Menores de 25 años" "25 y más años" "De 16 a 19 años"
#> [5] "De 20 a 24 años" "De 25 a 54 años" "De 55 y más años"
px_epa$NOTE$value %>% str_trunc(110)
#> [1] "Los resultados de Ceuta y Melilla deben tomarse con precaución porque pueden \"\n\"estar afectados por grandes..."CSV con read_csv2() |
PC-Axis con pxR |
|
|---|---|---|
| separador, decimales, tildes | hay que acertar con la función | automático |
datos no disponibles (..) |
na = ".." |
automático |
| título, unidades, notas | no vienen | sí |
| tipo de las variables | texto | factores |
| el periodo y los códigos | hay que limpiarlos | hay que limpiarlos |
Un fichero del INE: léelo con pxR::read.px() (o con read_csv2(), con cuidado). Y después, comprueba y limpia
Ojo!! Usa siempre pxR::read.px() y nunca hagas library(pxR): carga el paquete plyr, que tapa varias funciones de dplyr (mutate(), summarise(), arrange(), count(), rename()). Tu código seguiría funcionando … pero haciendo otra cosa!!
2903 del INE tiene la población de los municipios de la provincia de Valencia, año a año, según el Padrón;) de la tabla e impórtaloSexo?count(Sexo). Fíjate en la población: en el fichero lleva punto de miles (1.015)separate(): el código y el nombre están separados por el primer espacio46 Valencia/València)filter() + group_by() + summarise() con first() y last()my_url <- "https://www.ine.es/jaxiT3/files/t/es/csv_bdsc/2903.csv"
1df_pob <- read_csv2(my_url) %>%
janitor::clean_names() %>%
rename(anyo = periodo, poblacion = total) %>%
separate(municipios, into = c("muni_cod", "muni"),
sep = " ", extra = "merge") %>%
2 filter(muni_cod != "46") %>%
select(anyo, muni_cod, muni, sexo, poblacion)read_csv2() también lee directamente de una url
46 Valencia/València): su código es el de la provincia, 46
| muni | pob_2015 | pob_hoy | crec |
|---|---|---|---|
| San Antonio de Benagéber | 7.981 | 11.192 | 40.2% |
| Montserrat | 7.231 | 10.062 | 39.2% |
| Nàquera/Náquera | 6.204 | 8.556 | 37.9% |
| Canet d'En Berenguer | 6.426 | 8.297 | 29.1% |
| Bétera | 22.349 | 28.560 | 27.8% |
mapSpainmapSpain tiene las geometrías de España (CCAA, provincias, municipios, …)my_url <- "https://www.ine.es/jaxiT3/files/t/es/csv_bdsc/65349.csv"
1df_prov <- read_csv2(my_url, na = "..") %>%
janitor::clean_names() %>%
filter(sexo == "Ambos sexos", periodo == max(periodo),
tasas == "Tasa de paro de la población") %>%
2 mutate(cpro = str_extract(provincias, "^[0-9]{2}"))
3geo_prov <- mapSpain::esp_get_prov()
mapa_prov <- geo_prov %>% left_join(df_prov, by = "cpro")65349 de la EPA: tasas de actividad, paro y empleo por provincia
46 Valencia/València)
cpro
df_pob): ¿cuánto ha crecido la población de cada municipio de la provincia de Valencia desde 2015?df_crec_muni <- df_pob %>%
filter(sexo == "Total", anyo %in% c(2015, max(anyo))) %>%
arrange(anyo) %>%
group_by(muni_cod) %>%
summarise(crec = last(poblacion) / first(poblacion) - 1)
geo_muni <- mapSpain::esp_get_munic(
1 region = "Valencia/València") %>%
2 mutate(muni_cod = paste0(cpro, cmun))
mapa_muni <- geo_muni %>% left_join(df_crec_muni, by = "muni_cod")cpro) más el del municipio (cmun)
Los datos del INE, directamente desde R
ineapir es el paquete del INE para descargar sus datos desde RVentajas frente a descargar ficheros:
Tiene una web con muchos ejemplos y una cheatsheet
| Para … | función |
|---|---|
| ver las operaciones | get_metadata_operations() |
| ver las tablas de una operación | get_metadata_tables_operation() |
| ver las “variables” de una tabla | get_metadata_table_groups() |
| ver los valores de una “variable” | get_metadata_table_values() |
| descargar una tabla | get_data_table() |
El recorrido habitual: operación → tabla → (“variables” y valores) → datos
Si ya conoces el identificador de la tabla (el t= de la url), puedes ir directamente a get_data_table()
| Id <int> |
Nombre <chr> |
Codigo <chr> |
|---|---|---|
| 6 | Índice de Coste Laboral Armonizado | ICLA |
| 7 | Estadística de Transmisión de Derechos de la Propiedad | ETDP |
| 8 | Censos de Población y Viviendas | CENSOPV |
| Id <int> |
Nombre <chr> |
Codigo <chr> |
|---|---|---|
| 205 | Flujos de la Población Activa | EFPA |
| 293 | Encuesta de Población Activa (EPA) | EPA |
Codigo: EPA, IPC, …tablas_ipc <- get_metadata_tables_operation(operation = "IPC")
tablas_ipc %>% select(Id, Nombre, Anyo_Periodo_ini) %>% slice(1:3)| Id <int> |
Nombre <chr> |
Anyo_Periodo_ini <chr> |
|---|---|---|
| 24077 | Índice general nacional. Series desde enero de 1961 | 1961 |
| 24080 | Índice nacionales de grupos ECOICOP. Series desde enero de 1993 | 1993 |
| 35083 | Índices nacionales: Componentes para el análisis de la COVID-19 | 2017 |
| Id <int> |
Nombre <chr> |
|---|---|
| 76125 | Índices nacionales: general y de grupos ECOICOP ver.2 |
| 79181 | Índices nacionales: general y de grupos ECOICOP ver.2 |
| 76144 | Índices nacionales: general y de grupos ECOICOP ver.2 |
| 25333 | Índices nacionales: general y de grupos ECOICOP |
76125: es la que sale en INEbase76125). La descargamos con get_data_table():nlast se descarga la tabla completa
unnest = TRUE los datos llegan “escondidos” en una columna-lista
"A" de amigable), en lugar de códigos internos del INE. La fecha llega como texto: con as.Date() pasa a ser una fecha
n periodos, con nlast:dateStart y dateEnd (en formato "aaaa/mm/dd"). Si solo ponemos dateStart, llega hasta el último dato disponible:| desde <date> |
hasta <date> |
filas <int> |
|---|---|---|
| 2025-01-01 | 2025-12-01 | 672 |
Sin nlast ni fechas se descarga la tabla entera: en tablas grandes puede tardar bastante
NombreNombre está todo junto: el territorio, el grupo y el tipo de dato| Nombre <chr> |
|---|
| Nacional. Índice general. Índice. |
| Nacional. Índice general. Variación mensual. |
| Nacional. Índice general. Variación anual. |
separate(), pero hay una forma “mejor”:tip = "AM" y metanames = TRUEdf <- get_data_table(idTable = 76125, nlast = 2, unnest = TRUE,
1 tip = "AM", metanames = TRUE)
df %>% as_tibble() %>%
select(Nombre, Grupos.ECOICOP.ver.2, Tipo.de.dato, Valor) %>% slice(1:2)metanames = TRUE se añade una columna por cada “variable” de la tabla. ¿Y «Nacional»? No tiene columna: en esta tabla todos los datos son nacionales, así que el territorio no es una “variable” de la tabla
| Nombre <chr> |
Grupos.ECOICOP.ver.2 <chr> |
Tipo.de.dato <chr> |
Valor <dbl> |
|---|---|---|---|
| Nacional. Índice general. Índice. | Índice general | Índice | 104.98 |
| Nacional. Índice general. Índice. | Índice general | Índice | 104.64 |
| Id <int> |
Nombre <chr> |
|---|---|
| 155577 | Grupos ECOICOP ver.2 |
| 155578 | Tipo de dato |
| Id <int> |
Fk_Variable <int> |
Nombre <chr> |
|---|---|---|
| 304092 | 762 | Índice general |
| 304093 | 762 | Alimentos y bebidas no alcohólicas |
| Primeras 2 filas de 14 | ||
| Id <int> |
Fk_Variable <int> |
Nombre <chr> |
|---|---|---|
| 83 | 3 | Índice |
| 84 | 3 | Variación mensual |
| 74 | 3 | Variación anual |
| 87 | 3 | Variación en lo que va de año |
| 4 filas de 4 | ||
76125 contiene 56 series (14 Grupos ECOICOP × 4 tipos de dato)Id y de Fk_Variable que nos interesen.3 y 74; mientras que Índice general es 762 y 304092Ojo!! Cada “variable” tiene dos números: su Id (155578) sirve para pedir sus valores; su Fk_Variable (3), para el filtro
filter pedimos solo la serie que queremos: la inflación en Españafilter es una lista,my_filtro <- list(
1 "3" = "74",
2 "762" = "304092")
df_ipc <- get_data_table(idTable = 76125,
unnest = TRUE, tip = "A",
dateStart = "2019/01/01",
filter = my_filtro)3 (Tipo de dato), valor 74 (Variación anual)
762 (Grupos ECOICOP), valor 304092 (Índice general)
list("3" = c("74", "84"))filter con atajostipodato, grupo, sexo, edad, ccaa, prov, …) se ven con get_filter_shortcuts()65334):df_paro <- get_data_table(idTable = 65334, nlast = 1, unnest = TRUE, tip = "AM", metanames = TRUE,
filter = list(sexo = "Ambos sexos", edad = "Total"))
df_paro %>% as_tibble() %>%
select(Comunidades.y.Ciudades.Autónomas, Sexo, Edad, T3_Periodo, Anyo, Valor) %>% slice(1:3)| Comunidades.y.Ciudades.Autónomas <chr> |
Sexo <chr> |
Edad <chr> |
T3_Periodo <chr> |
Anyo <int> |
Valor <dbl> |
|---|---|---|---|---|---|
| Total Nacional | Ambos sexos | Total | T3 | 2026 | 10.18 |
| Andalucía | Ambos sexos | Total | T3 | 2026 | 14.91 |
| Aragón | Ambos sexos | Total | T3 | 2026 | 9.23 |
No todo INEbase está en la API: si una operación no aparece en get_metadata_operations(), toca descargar el fichero a mano
El último dato puede ser un avance: el INE publica primero el índice general y días después el detalle. Mientras tanto, el resto de series tienen NA en ese periodo
Los identificadores cambian: cuando el INE cambia la base de una estadística crea tablas nuevas. Si un script antiguo deja de traer datos recientes, mira si hay una tabla nueva
Guarda en disco lo que descargues (rio::export()): así el análisis no depende de tener conexión
76125 del IPC (Índices nacionales: general y de grupos ECOICOP):filter = list("3" = "74"), con tip = "AM" y metanames = TRUENA: quítalos antes de quedarte con la fecha más recientearrange(desc(Valor))geom_col(), con fct_reorder() para ordenar las barrasdf_grupos <- get_data_table(idTable = 76125, nlast = 3, unnest = TRUE,
tip = "AM", metanames = TRUE, filter = list("3" = "74")) %>%
as_tibble() %>%
mutate(fecha = as.Date(Fecha)) %>%
select(grupo = Grupos.ECOICOP.ver.2, fecha, var_anual = Valor) %>%
1 filter(!is.na(var_anual)) %>%
group_by(fecha) %>% filter(n() > 1) %>% ungroup() %>%
filter(fecha == max(fecha)) %>%
arrange(desc(var_anual))NA y los meses que solo tienen el índice general (los datos avance)
df_grupos %>%
mutate(grupo = str_trunc(grupo, 45)) %>%
ggplot(aes(x = var_anual, y = fct_reorder(grupo, var_anual))) +
geom_col(aes(fill = grupo == "Índice general"), show.legend = FALSE) +
labs(title = "Variación anual del IPC por grupos",
subtitle = paste("Mes:", format(max(df_grupos$fecha), "%m/%Y")), x = "%", y = NULL)65334 de la EPA (Tasas de paro por distintos grupos de edad, sexo y comunidad autónoma):get_metadata_table_groups() y get_metadata_table_values()filter puedes usar los atajos y el nombre del valor: edad = "Menores de 25 años"filter(fecha == max(fecha)) y slice_max()filter(ccaa %in% c(...)) y geom_line(aes(color = ccaa))| Id <int> |
Nombre <chr> |
|---|---|
| 133219 | Sexo |
| 133220 | Comunidades y Ciudades Autónomas |
| 133221 | Edad |
| Id <int> |
Fk_Variable <int> |
Nombre <chr> |
|---|---|---|
| 15668 | 356 | Total |
| 10568 | 357 | Menores de 25 años |
| 10569 | 357 | 25 y más años |
| 10553 | 360 | De 16 a 19 años |
| 15661 | 360 | De 20 a 24 años |
| 10561 | 360 | De 25 a 54 años |
| 10560 | 357 | De 55 y más años |
df_jov <- get_data_table(idTable = 65334, unnest = TRUE, tip = "AM", metanames = TRUE,
dateStart = "2008/01/01",
filter = list(sexo = "Ambos sexos", edad = "Menores de 25 años")) %>%
as_tibble() %>%
mutate(fecha = as.Date(Fecha)) %>%
select(ccaa = Comunidades.y.Ciudades.Autónomas, fecha, tasa_paro = Valor)La web del paquete ineapir, con sus tutoriales, y su cheatsheet
La documentación de la API del INE
INEbase: la lista completa de operaciones, por temas
El calendario de publicaciones del INE: qué día sale cada dato
Para datos de otros países: el paquete eurostat (lo vimos en las slides 04B)
| Para … | usamos … |
|---|---|
| encontrar una tabla | INEbase: el identificador es el t= de la url |
| descargar su fichero | https://www.ine.es/jaxiT3/files/t/es/csv_bdsc/id.csv (o .../px/id.px) |
| importarlo | read_csv2(ruta, na = "..") |
| … o mejor, el fichero PC-Axis | pxR::read.px(url) %>% as.data.frame() |
| limpiarlo | janitor::clean_names(), separate() o str_extract(), lubridate::yq() / ym() |
| buscar operaciones y tablas desde R | get_metadata_operations(), get_metadata_tables_operation() |
| ver qué contiene una tabla | get_metadata_table_groups(), get_metadata_table_values() |
| descargar una tabla | get_data_table(idTable, unnest = TRUE, tip = "AM", metanames = TRUE) |
| … solo unos periodos | nlast, dateStart, dateEnd |
| … solo unas categorías | filter = list(...) |