Descargar,
importar (y exportar)
datos con R

Programación y manejo de datos con R
(Web del curso aquí)

Antes de empezar: paquetes que usaremos en este tema


  • En este tema usaremos funciones de varios paquetes. Si no los tienes instalados, please, instálalos


# install.packages("pak")

pak::pak(c("rio", "fs", "curl", "here", "readr", "readxl", "openxlsx",
           "purrr", "rvest", "tidyverse", "refugees", "eurostat",
           "ineapir", "quantmod", "nanoparquet"))


Descargar datos con R



  • Los datos que hay en la web pueden cambiar o desaparecer, así que es buena idea descargarlos y guardarlos en nuestro proyecto

¿Cómo solemos descargar unos datos?


¿Y cómo se descargan datos con R?

  • Usando código


  • Al principio es un poco más complicado, pero tiene ventajas, y además estamos aprendiendo R

  • Veámoslo con un ejemplo

Tarea: descargar un fichero de datos con R



  • Intenta descargar el fichero con R.


  • En los siguientes “Tabs” tienes pistas y luego la solución
  1. Usaremos la función download.file()

  2. download.file() tiene varios argumentos. Nos centramos en los 2 primeros: url y destfile


  1. Tendrás que dar valores a esos dos argumentos:

    • el url donde está el fichero de datos

    • la ruta de tu ordenador donde quieres guardar los datos. (Lo guardaremos en la subcarpeta “pruebas”)


  1. Recuerda que estamos trabajando con proyectos, así que podemos usar rutas relativas
my_url <- "https://raw.githubusercontent.com/perezp44/archivos_download/refs/heads/master/plazas_turisticas.csv"


download.file(my_url, "plazas_turisticas.csv")

download.file(url = my_url,                               
              destfile = "plazas_turisticas.csv")       



  • ¿Donde se guardarán los datos? En el directorio de trabajo
  • Siempre se guardan en el directorio de trabajo, pero como estamos trabajando dentro de un Rproject … el directorio de trabajo es la propia carpeta del proyecto
  • Vamos a ser organizados y guardar los datos en una subcarpeta que llamaremos “pruebas”
1my_url <- "https://raw.githubusercontent.com/perezp44/archivos_download/refs/heads/master/plazas_turisticas.csv"

2fs::dir_create("pruebas")

3download.file(my_url, "./pruebas/plazas_turisticas.csv")

4download.file(url = my_url,
              destfile = "./pruebas/plazas_turisticas.csv")
1
Almacenamos la url en el objeto my_url
2
Creamos, con código, una carpeta llamada pruebas donde guardaremos los datos
3
Usamos la función download.file() para descargar los datos que hay en my_url. Los guardamos en la carpeta pruebas de nuestro proyecto. Concretamente en el fichero plazas_turisticas.csv
4
Hace exactamente lo mismo que 3, pero esta vez, hemos especificado el nombre de los dos argumentos de la función


my_url <- "https://raw.githubusercontent.com/perezp44/archivos_download/refs/heads/master/plazas_turisticas.csv"  

1my_ruta <- "./pruebas/plazas_turisticas.csv"


2curl::curl_download(url = my_url,
                    destfile = my_ruta)       
1
Es bueno hacer el código modular (por si queremos cambiar las rutas en el futuro). Por eso, especificamos en my_ruta el sitio donde guardaremos los datos
2
Usamos curl::curl_download() para descargar el fichero. Es una función más moderna que download.file()
  • Es buena idea dejar anotado de dónde descargamos los datos y cuándo, en un fichero de texto junto a los datos
my_url  <- "https://raw.githubusercontent.com/perezp44/archivos_download/refs/heads/master/plazas_turisticas.csv"
my_ruta <- "./pruebas/plazas_turisticas.csv"
1curl::curl_download(url = my_url, destfile = my_ruta)

2info <- c(paste("Fichero:", my_ruta),
          paste("Descargado de:", my_url),
          paste("Fecha de descarga:", Sys.Date()))

3writeLines(info, con = paste0(my_ruta, "_lee.txt"))

4writeLines(info, con = paste0(fs::path_ext_remove(my_ruta), "_lee.txt"))
1
Descargamos los datos, como en la solución anterior
2
Creamos un vector con 3 textos (cada uno será una línea del fichero): paste() “pega” trozos de texto y Sys.Date() devuelve la fecha de hoy
3
writeLines() guarda el vector en un fichero de texto. Así se llamaría plazas_turisticas.csv_lee.txt: un nombre con 2 extensiones puede dar problemas
4
Mejor así: fs::path_ext_remove() le quita la extensión (.csv) a la ruta, y el fichero se llama plazas_turisticas_lee.txt
plazas_turisticas_lee.txt
Fichero: ./pruebas/plazas_turisticas.csv
Descargado de: https://raw.githubusercontent.com/perezp44/archivos_download/refs/heads/master/plazas_turisticas.csv
Fecha de descarga: 2026-10-03

Importación de datos



  • La primera etapa de un análisis de datos consiste en cargar los datos en R (en la memoria de R, en el Global)

podemos usar/cargar datos de los paquetes

  • Muchos paquetes de R contienen datos que podemos usar

… intenta entender el siguiente chunk de código


#- cuando iniciamos R se cargan automáticamente un grupo de paquetes (R-base)
print(.packages()) #- [🌶]imprimimos los nombres de los "currently attached packages"

#- en uno de esos paquetes hay un conjunto de datos llamado "iris"
iris          #- llamamos a "iris"
find("iris")  #- [🌶] ¿donde está iris?
str(iris)     #- qué es iris?


my_iris <- iris  #- "hacemos una copia" de iris en el Global
find("my_iris")  #- ¿donde está my_iris?

iris <- c(2, 4)    #- creamos un vector llamado iris
find("iris")       #- ¿donde está ahora iris?

rm(iris)           #- borramos nuestro iris del Global (si no, ¡taparía al iris "de verdad"!)
find("iris")       #- volvemos a tener solo el iris de R-base


Tarea: usar unos datos de un paquete


  • Quiero usar el conjunto de datos flows que está en el paquete refugees


  • refugees es un paquete que no está en R-base


  • Lógicamente, para poder usar esos datos, tenemos que tener instalado el paquete. Podemos hacerlo con install.packages("refugees") o mejor con pak::pak("refugees")


  • Después hay que cargar el paquete con library(refugees)


  • Una vez hecho esto, ya podemos ver y usar el conjunto de datos flows


1library(refugees)

2my_flows <- flows
1
Cargamos el paquete en el Global, en la memoria de R.
Lógicamente antes hay que instalar el paquete con install.packages("refugees"), o mejor aún con pak::pak("refugees")
2
Creamos un objeto llamado my_flows que es una “copia” de los datos en flows


1my_flows_2 <- refugees::flows
1
Es una forma alternativa de cargar los datos de flows sin cargar todo el paquete

… seguimos importando



  • Vamos a cargar en R datos que tenemos en nuestro ordenador


  • Posiblemente, los hemos descargado previamente de la WEB

Los datos más habituales en Ciencias Sociales son los datos tabulares

  • Hay múltiples formatos tabulares: .csv, .xlsx, .sav , .dta, etc …

Cuando carguemos/importemos esos datos tabulares en R …

  • Se almacenarán internamente en R como data.frames

RStudio permite cargar datos usando menús.

  • Usando menús (File > Import Dataset) se pueden cargar datos CSV, EXCEL, SPSS, SAS y STATA.

  • Se puede hacer con menús, pero nosotros usaremos código


(La razón es que queremos que nuestros análisis sean reproducibles)

por supuesto hay muchos otros formatos de datos

  • Por ejemplo: .json, .parquet, .rds etc etc … Tienes una lista amplia de formatos en la web del paquete rio

Para importar (y exportar) datos utilizaremos código

  • Usaremos funciones, concretamente funciones del paquete rio que permite importar y exportar ficheros en casi cualquier formato.


  • Para importar datos en R usaremos la función rio::import()
  • Para exportar datos en R usaremos la función rio::export()

importando datos con rio::import()

  • La función rio::import() tiene varios argumentos, pero solo usaremos el primero.


  • Su primer argumento se llama file y su descripción dice que … el valor de file ha de ser: “A character string naming a file, URL, or single-file .zip or .tar archive”.   WTF!!


  • Es decir, el valor de file ha de ser la ruta (de nuestro ordenador) donde están los datos que queremos importar


  • Bueno, … en realidad también podemos importar directamente desde un “url”


Tarea: importar a R datos que tenemos guardados en nuestro ordenador


  • ¿Te acuerdas de que antes hemos descargado unos datos en el fichero ./pruebas/plazas_turisticas.csv?


  • La tarea consiste en cargar esos datos en R (en el Global, en memoria de R)


  • Has de usar la función rio::import()


  • En su primer argumento (llamado file) has de especificar la ruta donde están los datos


  • Recuerda que hemos guardado los datos en "./pruebas/plazas_turisticas.csv"


1my_ruta <- "./pruebas/plazas_turisticas.csv"

2df <- rio::import(my_ruta)
1
Guardamos en my_ruta la ruta a los datos que quiero importar
2
Creamos un objeto llamado df con los datos que hay en my_ruta


my_ruta <- "./pruebas/plazas_turisticas.csv"   

1df <- rio::import(file = my_ruta)
1
Esta vez ponemos tanto el nombre del argumento (file) como el valor del argumento (my_ruta)

cuidado con los .csv “españoles”

  • Muchos ficheros .csv de organismos españoles (INE, CCAA, ayuntamientos …) no siguen el formato “internacional”:
CSV CSV “a la española”
separador de columnas , ;
separador decimal . ,
codificación (tildes, ñ) UTF-8 a veces Latin-1
.CSV
municipio,habitantes,renta
València,807693,13.5
Alcoi,59128,11.2
Xàtiva,30378,10.8
.CSV (a la española)
municipio;habitantes;renta
València;807693;13,5
Alcoi;59128;11,2
Xàtiva;30378;10,8


  • Si al importar uno de estos ficheros ves caracteres raros (o un error), tendrás que decirle a R cómo está codificado:
df <- rio::import("./datos/datos_csv_a_la_espanola.csv",
1                  sep = ";",
2                  dec = ",",
3                  encoding = "Latin-1")

4df <- readr::read_csv2("./datos/datos_csv_a_la_espanola.csv",
                       locale = readr::locale(encoding = "Latin1"))
1
le decimos a import() que las columnas están separadas por ;
2
le decimos que los decimales se marcan con ,
3
la codificación: solo hace falta si ves caracteres raros en las tildes, las eñes …
4
readr::read_csv2() ya está preparada para el formato “español”: espera ; para separar las columnas y , para los decimales. La codificación se le indica con locale (solo si hay problemas con tildes, eñes …)

¿y esos caracteres raros? R y el fichero no se entienden [🌶🌶]

  • R (desde la versión 4.2) espera que los ficheros estén en UTF-8. Los problemas llegan cuando la codificación del fichero y la que usa R no coinciden. Hay 2 casos:

(1) El fichero está en Latin-1 y no se lo digo a R

df <- rio::import(
  "./datos/datos_csv_a_la_espanola.csv",
  sep = ";", dec = ",")
en RStudio
Error: invalid multibyte string, element 1
  • R no entiende la è de València y RStudio ni siquiera puede enseñarte el data.frame

  • Solución: decirle la codificación con encoding = "Latin-1"

df <- rio::import(
  "./datos/datos_csv_a_la_espanola.csv",
  sep = ";", dec = ",", encoding = "Latin-1")

(2) El fichero está en UTF-8 y le digo a R que es Latin-1

df <- rio::import(
  "./datos/datos_csv_a_la_espanola_utf8.csv",
  sep = ";", dec = ",", encoding = "Latin-1")
df
  municipio habitantes renta
1 València     807693  13.5
2     Alcoi      59128  11.2
3   XÃ tiva      30378  10.8
  • Ahora sí se ve el data.frame, pero con los famosos “caracteres raros”

  • Solución: quitar encoding (el fichero ya está en el formato que R espera)


  • ¿Y cómo sé en qué está codificado un fichero? Pregúntaselo a R: te dirá que (probablemente) es ISO-8859-1, que es otro nombre de Latin-1
readr::guess_encoding("./datos/datos_csv_a_la_espanola.csv")

Una vez tenemos los datos en R …

  • … podemos comenzar a trabajar en nuestros datos; por ejemplo para limpiarlos


… imagina que alguien nos pide esos datos limpios

¿Qué hacemos?


  • Tendremos que exportar/guardar los datos en un fichero de nuestro ordenador para poder enviárselos

Exportación de datos



  • Vamos a exportar/guardar en nuestro ordenador datos que tengamos en la memoria de R


Exportando datos en R con el paquete rio


  • Para exportar datos en R usaremos la función rio::export()

  • La función rio::export() tiene varios argumentos, pero solo usaremos los dos primeros.


  1. El primer argumento se llama x (su valor ha de ser un objeto/datos de R que queremos exportar)


  1. El segundo argumento se llama file (su valor ha de ser la ruta donde queremos guardar los datos)


Tarea: exportar unos datos de R a un fichero

  • Carga unos datos en R y expórtalos a un fichero en tu ordenador


  • Por ejemplo, utiliza la función rio::export() para exportar a un fichero (de tu ordenador) el conjunto de datos iris en formato .csv
  • Has de usar la función rio::export()


  • Tienes que darle valores a dos argumentos de la función: x y file


(*) el valor de x ha de ser el objeto que quieres exportar (por ejemplo iris)

(*) el valor de file ha de ser la ruta donde quieres guardar los datos


1df <- iris

2rio::export(df, "iris.csv")

3rio::export(x = df,
            file = "iris.csv")
1
Cargamos los datos de iris en R: creamos el objeto df
2
Exportamos df a un fichero llamado iris.csv
3
Hace lo mismo que 2, pero ahora especificamos los nombres de los argumentos ( x y file) de la función rio::export()
  • ¿Donde se ha guardado iris.csv?       pulsa Av. Pág para ver la respuesta
  • La respuesta es “en el directorio de trabajo”

  • OK, pero, ¿exactamente donde?

  • Como estamos trabajando en un Rproject, el directorio de trabajo es la propia carpeta del proyecto
  • Por favor, guarda/graba iris.csv en la subcarpeta “pruebas” del Rproject


rio::export(df, "./pruebas/iris.csv") 

rio::export(x = df,                       
            file = "./pruebas/iris.csv")   

Práctica: importar/exportar datos

  1. Descarga (usando código) estos datos a tu ordenador: https://raw.githubusercontent.com/perezp44/iris_data/master/data/iris.csv


  1. Importa a R esos datos que has descargado

  2. Exporta los datos a formato .xlsx y .rds


Antes de empezar limpiamos la carpeta pruebas de nuestro proyecto

 if (fs::dir_exists("pruebas")) { fs::dir_delete("pruebas") }    #- borra la carpeta "pruebas" solo si existe


fs::dir_create("pruebas")    
  1. Descargando los datos
#- en esta url hay un fichero de datos en formato .csv
my_url <- "https://raw.githubusercontent.com/perezp44/iris_data/master/data/iris.csv"

curl::curl_download(url = my_url, 
                    destfile =  "./pruebas/iris.csv")
  1. Importando los datos
my_ruta <- "./pruebas/iris.csv" 

iris_df <- rio::import(my_ruta)    #- otro nombre para no "tapar" a iris
  1. Exportando los datos
rio::export(x = iris_df, file = "./pruebas/iris.xlsx")  

rio::export(x = iris_df, file = "./pruebas/iris.rds")  
  • Veremos que … para gestionar/construir nuestras rutas, es conveniente usar here::here()

  • La razón es que favorecerá que nuestros análisis sean reproducibles


1here::here()
1
here::here() devuelve la ruta al directorio de trabajo
Como trabajamos con Rproject’s, entonces devuelve la ruta a la carpeta del Rproject en tu ordenador.
Esto nos será útil cuando trabajemos con ficheros .qmd


  • Lo veremos más adelante en una extensión, pero ahora ejecuta here::here() para ver qué os sale

Formatos de datos propios de R



  • R tiene 2 formatos de datos propios: .rda y .rds

Formatos de datos propios de R

  • Guardar datos en formatos como .csv o .xlsx es lo más habitual si quieres abrir estos datos en otros programas, pero …

  • … pero al grabar en estos formatos guardas los datos, PERO no guardas los metadatos!!!


R tiene 2 formatos de datos propios: .rda y .rds

  • Estos formatos sí guardan los metadatos


  • Vamos a verlo: por ejemplo, en el datset iris la variable Species es un factor (un metadato). Veamos qué pasa al exportar e importar:
rio::export(iris, "./pruebas/iris.csv")
rio::export(iris, "./pruebas/iris.rds")

class(rio::import("./pruebas/iris.csv")$Species)   #- "character" ... hemos  perdido la info de que Species es factor!
class(rio::import("./pruebas/iris.rds")$Species)   #- "factor"   

Formato .rds

  • Exportar datos a formato .rds
rio::export(x = iris, file = "./pruebas/iris.rds")   

Extensión: dos formas alternativas de exportar a .rds (😱😱)

(Sí, en R se pueden hacer las cosas de muchas maneras!!! 🚨🚨)

saveRDS(iris, file = "./pruebas/iris.rds")        #- con base-R

readr::write_rds(iris, "./pruebas/iris.rds")      #- con pkg "readr"


  • Importar datos en formato .rds
df <- rio::import(file = "./pruebas/iris.rds")

Extensión: dos formas alternativas de importar .rds

(Sí, en R se pueden hacer las cosas de muchas maneras!!! 🚨🚨)

my_iris_1 <- readRDS("./pruebas/iris.rds")         #- con R-base

my_iris_2 <- readr::read_rds("./pruebas/iris.rds")  #- tidyverse

Formato .RData (o .rda)

  • El formato .RData tiene la ventaja de que puedes guardar varios objetos a la vez
  • Para exportar datos a formato .rda
#- guardamos los objetos mtcars e iris en un fichero llamado "mtcars_and_iris.RData"

save(mtcars, iris,  file = "./pruebas/mtcars_and_iris.RData")


  • Para importar datos en formato .rda, tenemos que usar la función load()
load(file = "./pruebas/mtcars_and_iris.RData")
  • ⚠️ Cuidado: load() no se asigna a ningún objeto (df <- load(...) solo guarda los nombres de los objetos) y además sobrescribe sin avisar los objetos del Global que se llamen igual. Por eso, para un único data.frame, es mejor .rds


  • Extensión: Para importar un .rda desde la web, lo más seguro es descargarlo primero y después usar load()
my_url <- "https://github.com/perezp44/LAU2boundaries4spain/blob/master/data/Provincias.rda?raw=true"
my_ruta <- "./pruebas/Provincias.rda"

curl::curl_download(url = my_url, destfile = my_ruta)  #- 1) lo descargamos
load(my_ruta)                                          #- 2) lo cargamos: aparece en el Global

#- load(url(my_url)) a veces falla (si el archivo está comprimido)

Extensión: un formato moderno, Parquet [🌶🌶]


  • Parquet es un formato cada vez más usado en ciencia de datos:

    • es muy compacto y rápido (ideal para datos grandes)
    • guarda los tipos de las columnas (como .rds) …
    • … pero, a diferencia de .rds, se puede leer desde otros lenguajes (Python, SQL …)


nanoparquet::write_parquet(iris, "./pruebas/iris.parquet")   #- exportar

df <- nanoparquet::read_parquet("./pruebas/iris.parquet")    #- importar

Extensión: el formato JSON [🌶🌶]

  • JSON es un formato de texto muy usado en internet: es el formato en el que suelen responder las API’s

    • cada dato va con su nombre: "nombre": valor
    • distingue los textos (entre comillas) de los números
    • por norma está en UTF-8: adiós a los “caracteres raros”
    • admite datos anidados (una lista dentro de otra), cosa que un .csv no puede hacer
mis_datos.json
[
  {
    "municipio": "València",
    "habitantes": 807693,
    "renta": 13.5
  },
  {
    "municipio": "Alcoi",
    "habitantes": 59128,
    "renta": 11.2
  }
]


rio::export(iris, "./pruebas/mis_datos.json")             #- exportar

df <- rio::import("./pruebas/mis_datos.json")           #- importar

df <- jsonlite::fromJSON("./pruebas/mis_datos.json")    #- rio usa el paquete jsonlite


  • Cuidado: si el JSON tiene datos anidados, puede que no obtengas un data.frame “limpio”, sino una lista (o un data.frame con columnas que son listas)

Acceder a datos a través de API’s



  • Hay muuuuuchas organizaciones que ofrecen datos a través de API’s y …

  • … en muchas ocasiones, hay un paquete de R para acceder fácilmente a ellos!!!

Practica: importar datos de Eurostat

  • Podemos buscar un tema en Eurostat; por ejemplo aquí hay datos de “Employment and unemployment (LFS)”, aquí datos sobre “Population and social conditions” y aquí sobre “Crime and criminal justice”
  • Para mí es más cómodo buscar datos con el paquete eurostat. ¿Probamos?
library(eurostat)

#- podemos buscar un  "tema" con la f. search_eurostat()
my_tema <- "employment"

aa <- eurostat::search_eurostat(pattern = my_tema, type = "all") 

#- elegimos un dataset; por ejemplo "hlth_silc_17"
my_table <- "hlth_silc_17" 

#- da información sobre la Base de datos que estas buscando
eurostat::label_eurostat_tables(my_table) 

#-  importamos los datos de "my_table" con get_eurostat()
df <- eurostat::get_eurostat(my_table, time_format = "raw", keepFlags = TRUE )   

#- pedimos los descriptores/labels de las series
df_l <- eurostat::label_eurostat(df)        


  • Trabajaremos más con el pkg eurostat usando ejemplos

El paquete quantmod

  • Permite acceder a una gran variedad de datos económicos. Aquí tienes información
library(quantmod)  #- install.packages("quantmod")

#- series económicas: fuente FRED (Reserva Federal de St. Louis)
#- tipo de cambio dólares por euro
tc_euro_dolar <- getSymbols(Symbols = 'DEXUSEU', src = 'FRED', auto.assign = FALSE)

#- PIB de Japón
Japan_GDP <- getSymbols(Symbols = 'JPNNGDP', src = 'FRED', auto.assign = FALSE)
#- acciones: fuente Yahoo Finance
#- ("META" es Facebook)
facebook  <- getSymbols(Symbols = 'META',
                        src = 'yahoo',
                        auto.assign = FALSE)

barChart(facebook)

hay paquetes de R para (casi) todas las fuentes de datos oficiales

  • Además de ineapir y eurostat, hay muchos más paquetes que descargan los datos de un organismo y nos los devuelven como data.frame’s:
Datos de … Organismo Paquete Función principal
España INE ineapir get_data_table()
España: tipos de interés, euríbor … Banco de España tidyBdE bde_series_load()
España: resultados electorales Ministerio del Interior infoelectoral provincias(), municipios()
Unión Europea Eurostat eurostat get_eurostat()
Zona euro: tipos de interés, tipos de cambio … Banco Central Europeo ecb get_data()
Mundo: indicadores por países Banco Mundial WDI, wbstats WDI(), wb_data()
Mundo: previsiones económicas FMI imfweo weo_get()
EE.UU. y series internacionales FRED (Reserva Federal) fredr, tidyquant fredr(), tq_get()
De todo un poco DBnomics rdbnomics rdb()
  • Casi todos funcionan igual: buscas el código de la tabla o de la serie que te interesa y se lo pasas a la función principal

  • Casi ninguno pide registrarse; fredr sí necesita una clave (gratuita)


¿recordaís que hay que hacer un trabajo en grupo y otro individual?

Algunos BONUS de I/O



  • Con lo que hemos visto hasta ahora nos apañamos, pero conviene ver alguna cosa más …

Bonus 1 (🌶🌶 … 🌶): exportar los datos de un df a un archivo .xlsx ya existente



#- bonus: le añadimos un libro mas al archivo "./pruebas/iris.xlsx"

rio::export(iris, "./pruebas/iris.xlsx")  #- por si acaso lo hubiésemos borrado


rio::export(iris, "./pruebas/iris.xlsx", which = "iris_2")

#- lo mismo pero poniendo los nombres de los argumentos
rio::export(x = iris, 
           file ="./pruebas/iris.xlsx", 
           which = "iris_3")


  • Esta solución ya no funciona 😱 !!
  • Tendremos que usar nuevo paquete: openxlsx
#- tenemos que usar el pkg openxlsx
library(openxlsx) #- pak::pak("openxlsx")

#- tenemos que crear un "Woorkbook" con la f. loadWorkbook()
my_workbook <- openxlsx::loadWorkbook("./pruebas/iris.xlsx")

#- ahora añadimos hojas/sheets al Woorkbook
openxlsx::addWorksheet(my_workbook, "iris_2")
openxlsx::addWorksheet(my_workbook, "my_mtcars")

#- ahora escribimos datos en las nuevas hojas/sheets q hemos creado
openxlsx::writeData(my_workbook, sheet = "iris_2",    x = iris)
openxlsx::writeData(my_workbook, sheet = "my_mtcars", x = mtcars)

#- exportamos/guardamos el archivo (con las nuevas sheets)
openxlsx::saveWorkbook(my_workbook, "./pruebas/iris.xlsx", overwrite = TRUE)

Bonus 2 (🌶🌶🌶): exportar 2 df’s en un único archivo .xlsx


  • Seguimos usando el paquete rio, pero …
      … los objetos que queremos exportar se incluyen dentro de la función list()


rio::export(x = list(my_iris = iris, my_pinguinos = penguins), 
            file = "./pruebas/my_iris_pinguinos.xlsx")


  • penguins está en R-base desde la versión 4.5 de R. Si tienes una versión anterior, usa palmerpenguins::penguins

Bonus 3 (🌶🌶): importar una hoja/libro específica de un archivo .xlsx


  • Seguimos usando el paquete rio, pero …
      … tenemos que usar un nuevo argumento: sheet


iris_1 <- rio::import("./pruebas/my_iris_pinguinos.xlsx")  #- solo importa el primer libro
  

pinguinos_1 <- rio::import("./pruebas/my_iris_pinguinos.xlsx", sheet = 2)


pinguinos_2 <- rio::import(file = "./pruebas/my_iris_pinguinos.xlsx", 
                           sheet = "my_pinguinos")

Bonus 4: (!!!! 😱 😱) importamos todos los libros de un archivo .xlsx


  • Seguimos usando el paquete rio, pero ahora con la función rio::import_list()
my_dfs_list <- rio::import_list("./pruebas/my_iris_pinguinos.xlsx")


  • Ok, pero ahora tenemos los 2 data.frames dentro de una lista ¿qué hacemos?
#- Extrayendo un data.frame de una lista: con [[]]
my_iris <- my_dfs_list[[1]]               #- por posición
my_pinguinos <- my_dfs_list[["my_pinguinos"]]  #- por nombre
library(readxl) #- necesitamos la función readxl::excel_sheets()

my_sheets <- readxl::excel_sheets(here::here("pruebas", "my_iris_pinguinos.xlsx"))

#- con base::lapply()
my_dfs_list <- lapply(my_sheets,
                      readxl::read_excel,
                      path = here::here("pruebas", "my_iris_pinguinos.xlsx"))
#- con purrr::map()
my_dfs_list <- purrr::map(my_sheets, 
                          readxl::read_excel,
                          path = here::here("pruebas", "my_iris_pinguinos.xlsx"))

Bonus 5: (!!!!!!) importamos todos archivos de datos de una carpeta concreta


#- importamos todos los archivos .csv que hay en "./pruebas/"
my_carpeta <- here::here("pruebas")

1lista_de_archivos <- fs::dir_ls(my_carpeta, glob = "*.csv")

2my_dfs_list_2 <- rio::import_list(lista_de_archivos)

3my_dfs_list_3 <- purrr::map(lista_de_archivos, rio::import)
1
Obtenemos las rutas de los ficheros .csv de la carpeta. Filtramos por tipo porque en pruebas también hay .xlsx con varias hojas, .RData con varios objetos, etc.
2
rio::import_list() importa todos los ficheros y los guarda en una lista
3
[🌶] lo mismo con purrr::map()


  • Ya veremos, con un ejemplo, cómo podemos juntar todos los df’s en un único data.frame

Extensiones




Web scraping [🌶🌶🌶 🌶🌶]

  • Web scraping consiste en obtener datos directamente de páginas web, de su código html.

Web scraping (ejemplo)

  • Para obtener esos datos en R, podemos usar el paquete rvest
library(rvest)      #- html_elements() es el nuevo nombre de html_nodes()
library(tidyverse)
my_url <- "https://es.wikipedia.org/wiki/Anexo:Municipios_de_la_provincia_de_Teruel"
content <- read_html(my_url)

body_table <- content %>%
              html_elements('body')  %>%
              html_elements('table') %>%
              html_table(dec = ",")

#- la tabla que queremos es la primera
Teruel <- body_table[[1]]  #- estoy haciendo subsetting de una lista

Web scraping (ejemplo): arreglamos la tabla [🌶🌶🌶 🌶🌶]

  • Podemos arreglar un poco la tabla (!!!!!!!!)
names(Teruel) <- c("Nombre",  "Poblacion", "Superficie",
                   "Densidad", "Altitud", "Comarca", "Partido_judicial")
library(stringr)
Teruel <- Teruel %>% map(str_trim) %>% as_tibble() #- quita caracteres al final
Teruel <- Teruel %>% mutate(Altitud = str_replace_all(Altitud,"[[:punct:]]", ""))
Teruel <- Teruel %>% mutate(Altitud = as.double(Altitud)) %>% arrange(desc(Altitud))


  • Y ya podemos ver cuáles son los pueblos más “altos” de Teruel
Teruel %>% select(Nombre, Altitud, Comarca) %>% head(n = 5)
  Nombre       Altitud Comarca
1 Valdelinares    1695 Gúdar-Javalambre
2 Griegos         1601 Sierra de Albarracín
3 Gúdar           1587 Gúdar-Javalambre
4 Bronchales      1569 Sierra de Albarracín
5 Guadalaviar     1519 Sierra de Albarracín

Web scraping (otro ejemplo): cuando los datos no están en una tabla [🌶🌶🌶 🌶🌶]

  • En el ejemplo anterior los datos estaban en una tabla (<table>) y bastaba con html_table(). Lo normal es que no sea así

  • Esta web es una librería “de mentira”, hecha para practicar: cada libro es una “caja” con su título, su precio …

  • Hay que decirle a R dónde está cada dato, usando selectores CSS (se ven con el botón derecho > “Inspeccionar”)

library(rvest)
library(tidyverse)
my_url <- "https://books.toscrape.com/"
content <- read_html(my_url)

1libros <- content %>% html_elements(".product_pod")

df <- tibble(
2  titulo    = libros %>% html_element("h3 a") %>% html_attr("title"),
3  precio    = libros %>% html_element(".price_color") %>% html_text2(),
4  estrellas = libros %>% html_element(".star-rating") %>% html_attr("class"))

5df <- df %>% mutate(precio = parse_number(precio),
                    estrellas = str_remove(estrellas, "star-rating "))
1
Cada libro está en una “caja” con la clase product_pod: obtenemos las 20 cajas de la página
2
El título está en el atributo title del enlace (a) que hay dentro del h3
3
El precio es el texto del elemento con la clase price_color: sale como texto (“£51.77”)
4
Las estrellas no son un texto: están “escondidas” en el nombre de la clase (“star-rating Three”)
5
Arreglamos los datos: el precio pasa a ser un número y nos quedamos solo con el nº de estrellas

digresión: es lo mismo un data.frame que una tibble [🌶🌶🌶]

Sí y no (!!!)

  • No son exactamente lo mismo, pero para nosotros son totalmente equivalentes. Podemos pensar que las tibbles son data.frame’s modernizados o tuneados
  • Las dos son estructuras de R para almacenar datos tabulares. Si quieres ver en qué se diferencian lee esto.
   Veámoslo un poco:
typeof(iris)
[1] "list"
class(iris)
[1] "data.frame"
names(attributes(iris))
[1] "names"     "class"     "row.names"
   Convertimos iris en tibble:
iris_2 <- tibble::as_tibble(iris)
typeof(iris_2)
[1] "list"
class(iris_2)
[1] "tbl_df"     "tbl"        "data.frame"
names(attributes(iris_2))
[1] "names"     "row.names" "class"    


  • Algunas funciones antiguas puede que no funcionen con tibbles. Si te pasa esto, tienes que usar as.data.frame(). Puedes leerlo aquí

OK, pero entonces … ¿en qué se diferencian un data.frame y una tibble? [🌶🌶🌶]

  • Acabamos de ver que por dentro son lo mismo: las dos son una lista de columnas (por eso typeof() devuelve "list")

  • Lo único que cambia es la clase: una tibble es un data.frame al que se le añaden 2 clases (tbl_df y tbl). Como "data.frame" sigue ahí, (casi) todo lo que funciona con un data.frame, funciona con una tibble

   Entonces, ¿qué notaré? Pues que las tibbles son más “estrictas” (y más educadas). Tres diferencias:
   (1) Al seleccionar una columna con [ ], la tibble sigue siendo una tibble (el data.frame se convierte en un vector !!)
class(iris[ , 1])
[1] "numeric"
class(iris_2[ , 1])
[1] "tbl_df"     "tbl"        "data.frame"
   (2) Las tibbles no “adivinan” el nombre de una columna si lo escribes a medias (el data.frame sí, y eso es peligroso)
head(iris$Spec, n = 3)
[1] setosa setosa setosa
Levels: setosa versicolor virginica
iris_2$Spec
NULL
   (3) Al imprimirlas: una tibble muestra solo las 10 primeras filas y el tipo de cada columna; un data.frame intenta mostrarlo todo. Compruébalo: ejecuta iris y luego iris_2


  • La diferencia (1) es la razón por la que algunas funciones antiguas fallan con tibbles: esperaban un vector y reciben una tibble. Ya sabes, si te pasa: as.data.frame()

seleccionando “manualmente” un archivo de datos 😱

  • Este truco es chulo: abre una ventana para que elijas el fichero con el ratón …
my_data <- rio::import(file.choose())
  • … pero no hay que hacerlo si queremos que nuestros análisis sean reproducibles
  • El script no “sabe” qué fichero elegiste. En unos días tú tampoco te acordarás. Si le das el script a otra persona no podrá repetir tu análisis

  • Además, el script ya no puede ejecutarse solo: se queda esperando a que alguien haga clic


así NO 😱

my_data <- rio::import(file.choose())

la ruta debe estar escrita en el script

my_data <- rio::import("./pruebas/iris.csv")


  • Bueno … sí tiene un uso: averiguar la ruta de un fichero. Ejecuta file.choose() en la consola, elige el fichero, y te devuelve su ruta

  • Pero OJO: te devuelve una ruta absoluta (C:/Users/tu-usuario/...), que solo funciona en tu ordenador. En el script hay que escribirla como ruta relativa ("./pruebas/iris.csv") o, mejor aún, con here::here(). Lo vemos ahora

gestionando las rutas con here::here()

Problemas con las rutas “tradicionales”

  • Las rutas absolutas son “frágiles”
    • C:/Users/my_usuario/my_proyecto/datos/archivo.csv
    • Solo funcionan en tu ordenador


  • Las rutas relativas dependen del “directorio de trabajo”
    • "./datos/archivo.csv"
    • ¿Relativas a qué? Al directorio de trabajo (lo ves con getwd())
    • En un Rproject es la carpeta del proyecto: por eso, de momento nos basta con las rutas relativas


  • PERO esto cambiará cuando trabajemos con archivos .qmd
    • Al compilar un .qmd, el directorio de trabajo pasa a ser la carpeta donde está el .qmd (no la del proyecto)
    • Resultado: “Error: No se puede encontrar el archivo”

Rutas con here::here()

  • here::here() busca la carpeta raíz de tu proyecto (donde está el fichero .Rproj) y construye la ruta desde ahí
here::here("datos", "archivo.csv")
C:/Users/my_usuario/my_proyecto/datos/archivo.csv


  • Devuelve una ruta absoluta, PERO no la escribes a mano: se construye al ejecutar el código, así que es la correcta en cada ordenador


  • Funciona igual en ficheros .R y en ficheros .qmd


  • ➡ Olvídate de setwd() al inicio!! Tu yo futuro y tus coautores te lo agradecerán!! ✨

un ejemplo: ¿qué pasa si mi .qmd está en una subcarpeta del proyecto?

  • Imagina que este es tu proyecto:
my_proyecto/           👈 carpeta raíz
├── my_proyecto.Rproj
├── datos/
│   └── iris.csv       👈 quiero importarlo
├── scripts/
│   └── analisis.R
└── informes/
    └── informe.qmd


  • Quiero importar iris.csv desde analisis.R y desde informe.qmd
  • Desde analisis.R: el directorio de trabajo es la carpeta raíz del proyecto
rio::import("./datos/iris.csv")
   ✅ R busca en: my_proyecto/datos/iris.csv


  • Desde informe.qmd: al compilar, el directorio de trabajo es la carpeta del .qmd
rio::import("./datos/iris.csv")
   ❌ R busca en: my_proyecto/informes/datos/iris.csv … y ahí no hay nada!!


  • Con here::here() la ruta siempre se construye desde la carpeta raíz: funciona en los dos
rio::import(here::here("datos", "iris.csv"))
   ✅ R busca en: my_proyecto/datos/iris.csv (tanto en el .R como en el .qmd)


  • En el .qmd también funcionaría "../datos/iris.csv" (.. es “sube una carpeta”), pero es frágil: si mueves el .qmd de sitio, deja de funcionar

¿cómo construir nuestras rutas con here::here()?

  • Recuerda que si queríamos exportar el fichero iris, hacíamos algo como:
rio::export(iris, file = "./pruebas/iris.xlsx")
  • Para hacer lo mismo con here::here() haremos:
rio::export(iris, file = here::here("pruebas", "iris.xlsx"))


  • Fíjate que here::here() nos devuelve una ruta absoluta, pero diferente para cada ordenador:
here::here("pruebas", "iris.xlsx")
en mi ordenador (Linux):      /home/pedro/my_proyecto/pruebas/iris.xlsx
en el de Andrés (Windows):    C:/Users/andres/my_proyecto/pruebas/iris.xlsx
en el de Ana (Mac):           /Users/ana/my_proyecto/pruebas/iris.xlsx


vamos a usar un bucle for … para exportar varios archivos


  • Imagina que quiero exportar un fichero, por ejemplo iris, a varios formatos y quiero hacerlo con un “for loop”


  • Exporta iris a los formatos .csv, .xlsx y .dta usando un bucle for.
  • Esta vez no hay solución 😧 !!

  • Bueno … la solución estará en uno de los ejemplos del Rproject del curso. ¿Lo veremos?

no siempre es fácil publicar bien unos datos


  • Una guía para publicar datos en .csv. Aquí la cheatsheet

  • Un buen post sobre la misma temática

Un caso más complejo



  • Resulta que quiero saber qué provincia tiene más municipios, así que vamos a ir al INE, concretamente aquí. En esa página hay un fichero que nos permitirá contar el nº de municipios

un caso más complejo

Queremos saber qué provincia tiene más municipios. Para contar el nº de municipios por provincia tenemos que descargar el fichero con la relación de municipios del INE. El fichero (a 1 de enero de 2026) está aquí: https://www.ine.es/daco/daco42/codmun/diccionario26.xlsx


  • Vamos a importarlo de varias formas:

    1. con el paquete rio

    2. utilizando los menús de RStudio

    3. con el paquete readxl

    4. Finalmente lo haremos de forma más “pofesional”

rm(list = ls())  #- antes vamos a limpiar el Global env.

my_url <- "https://www.ine.es/daco/daco42/codmun/diccionario26.xlsx"

my_ruta_guardar <- here::here("pruebas", "diccionario26.xlsx")

curl::curl_download(my_url, my_ruta_guardar) #- lo guardo en disco

1df_1 <- rio::import(my_ruta_guardar)

2df_1 <- rio::import(my_ruta_guardar, skip = 1)
1
¿Qué problema tenemos? Mira df_1: la primera fila del Excel es un título, y R la ha usado como nombres de las columnas
2
Solucionado: con skip = 1 le decimos que se salte esa primera fila
  • Vamos a usar menús, pero que no nos vea Hadley!!!

  • Seguid la siguiente ruta de menús: File > Import Dataset > From Excel ...

  • Pegáis en la caja de texto el url y pincháis el botón update

df_2 <- readxl::read_xlsx(my_url, skip = 1)  #- no funciona. readxl solo lee del PC

my_ruta_guardar <- here::here("pruebas", "diccionario26.xlsx")

df_2 <- readxl::read_xlsx(my_ruta_guardar, skip = 1)
  • Hacemos lo que vimos en la primera tarea: descargamos el fichero y dejamos anotado de dónde y cuándo en un fichero de texto
#- script para bajar la relación de municipios INE a 1 de enero de 2026
my_url  <- "https://www.ine.es/daco/daco42/codmun/diccionario26.xlsx"
1my_ruta <- here::here("pruebas", basename(my_url))

2curl::curl_download(url = my_url, destfile = my_ruta)

3info <- c(paste("Fichero:", basename(my_ruta)),
          paste("Descargado de:", my_url),
          paste("Fecha de descarga:", Sys.Date()))

writeLines(info, con = paste0(fs::path_ext_remove(my_ruta), "_lee.txt"))

4df <- readxl::read_xlsx(path = my_ruta, skip = 1)
1
basename() se queda con el nombre del fichero (diccionario26.xlsx): así no hay que escribirlo a mano
2
Descargamos el fichero y lo guardamos en la carpeta pruebas
3
Creamos un vector con 3 textos (qué fichero es, de dónde lo hemos descargado y cuándo) y lo guardamos en un fichero de texto junto a los datos
4
Importamos el fichero a la memoria de R (skip = 1 también hubiese funcionado con rio, ya que rio llama a readxl)
diccionario26_lee.txt
Fichero: diccionario26.xlsx
Descargado de: https://www.ine.es/daco/daco42/codmun/diccionario26.xlsx
Fecha de descarga: 2026-10-03

seguimos con el ejemplo

aparecen muchas cosas/funciones que veremos más adelante [🌶🌶🌶🌶]

  • Arreglo los datos a mi gusto
library(tidyverse)

df <- df %>%
1      mutate(ine_muni = paste0(CPRO, CMUN)) %>%
2      mutate(year = 2026L) %>%
3      rename(ine_muni.n = NOMBRE) %>%
      rename(ine_prov = CPRO) %>%
4      select(ine_muni, ine_muni.n, ine_prov, year)

str(df)
1
El código de un municipio es el de su provincia (CPRO) seguido del suyo (CMUN): los “pegamos” con paste0()
2
Añadimos una columna con el año de los datos (la L es para que sea un nº entero)
3
Cambiamos nombres de columnas: nombre_nuevo = nombre_antiguo
4
Nos quedamos solo con las columnas que nos interesan
  • Podemos contar el nº de municipios por provincia (de muchas maneras distintas)
1df_muni <- df %>% count(ine_prov)

2df_muni <- df %>% group_by(ine_prov) %>% summarise(n = n())
1
count() cuenta cuántas filas (municipios) hay de cada provincia: el resultado se guarda en una columna llamada n
2
Lo mismo “a mano”: agrupamos por provincia y contamos las filas de cada grupo. El resultado es idéntico
  • En df_muni solo tenemos el código de cada provincia. Los nombres los tiene el INE en esta página. ¡Hacemos web scraping!
library(rvest)
my_url_prov <- "https://www.ine.es/daco/daco42/codmun/cod_provincia.htm"

1tablas <- read_html(my_url_prov, encoding = "latin1") %>%
          html_elements("table") %>%
          html_table()

2codigos_prov <- bind_rows(tablas[2:4]) %>%
3                mutate(ine_prov = sprintf("%02d", Código)) %>%
                rename(ine_prov.n = Literal) %>%
                select(ine_prov, ine_prov.n)
1
La web del INE no está en UTF-8 sino en Latin-1: hay que decírselo (¿te suena?)
2
En la página hay 4 tablas y las provincias están repartidas en la 2, la 3 y la 4: las juntamos con bind_rows()
3
En la web el código es un número (1) y en nuestros datos es un texto ("01"): sprintf() lo convierte
  • Fusionamos los 2 data.frames (lo veremos!!) y ordenamos de más a menos municipios
df_ok <- left_join(df_muni, codigos_prov, by = "ine_prov") %>% arrange(desc(n))

head(df_ok, n = 5)
  ine_prov   n  ine_prov.n
1       09 371      Burgos
2       37 362   Salamanca
3       08 311   Barcelona
4       50 293    Zaragoza
5       19 288 Guadalajara


  • Ya tenemos la respuesta: la provincia con más municipios es Burgos, con 371 (en toda España hay 8.132)
  • Si quisiera exportar el archivo
readr::write_csv(df, file = here::here("pruebas", "my_relacion_muni_2026.csv"))


  • Borro los archivos de la subcarpeta pruebas (para dejar el Rproject limpio)
fs::file_delete(fs::dir_ls("./pruebas"))  #- [🌶] borra el contenido de la carpeta "pruebas"

fs::dir_delete("pruebas")  # borro la carpeta pruebas

algunas referencias