1 Introducción

Este trabajo describe el análisis de los datos de préstamo de libros durante 2024 en la red de bibliotecas públicas de la ciudad de Madrid.

2 Carga de paquetes y tratamiento de los datos

Cargamos los paquetes que utilizaremos en el análisis.

library(tidyverse)
library(stringr)
library(ineq)
library(scales)
library(DT)

Los datos mensuales de préstamo en 2024 están disponibles en el Portal de Datos Abiertos del Ayuntamiento de Madrid.

a2024.01 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-214-bibliotecas-prestamos-historico.csv")

a2024.02 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-216-bibliotecas-prestamos-historico.csv")

a2024.03 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-218-bibliotecas-prestamos-historico.csv")

a2024.04 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-220-bibliotecas-prestamos-historico.csv")

a2024.05 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-222-bibliotecas-prestamos-historico.csv")

a2024.06 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-224-bibliotecas-prestamos-historico.csv")

a2024.07 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-232-bibliotecas-prestamos-historico.csv")

a2024.08 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-228-bibliotecas-prestamos-historico.csv")

a2024.09 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-230-bibliotecas-prestamos-historico.csv")

a2024.10 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-233-bibliotecas-prestamos-historico.csv")

a2024.11 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-235-bibliotecas-prestamos-historico.csv")

a2024.12 <- read_csv2(
  "https://datos.madrid.es/egob/catalogo/212700-237-bibliotecas-prestamos-historico.csv")

a2024.12 <- a2024.12[, colSums(!is.na(a2024.12)) > 0]

Agrupamos los datos mensuales de préstamo en un único fichero.

prestamos.2024 <- bind_rows(a2024.01,
                            a2024.02,
                            a2024.03,
                            a2024.04,
                            a2024.05,
                            a2024.06,
                            a2024.07,
                            a2024.08,
                            a2024.09,
                            a2024.10,
                            a2024.11,
                            a2024.12)

Seleccionamos los préstamos de libros.

prestamos.libros <- prestamos.2024 %>%
  filter(prcocs == "LIB")

Si buscamos, por ejemplo, el número de préstamos de la novela “El camino del ninja” observamos que los títulos incluyen una barra que, en el catálogo de la biblioteca, sirve para separarlos del nombre del autor. Asimismo, se observa que la inconsistencia en el uso de mayúsculas iniciales provoca la existencia de dos entradas diferentes para la misma obra. Antes de proceder al análisis de los datos, resolveremos estos problemas.

prestamos.libros %>%
     filter(str_detect(tititu, regex("El camino del ninja", ignore_case = TRUE))) %>%
     count(tititu, tiauto, sort = TRUE)
## # A tibble: 2 × 3
##   tititu                tiauto                         n
##   <chr>                 <chr>                      <int>
## 1 El Camino del Ninja / Gómez-Jurado, Juan (1977-)   272
## 2 El camino del ninja / Gómez-Jurado, Juan (1977-)    22

Eliminamos de los títulos las barras que sirven, en el catálogo de la biblioteca, para separarlos del nombre del autor.

prestamos.libros$tititu <- gsub(" */ *$", "", prestamos.libros$tititu)

Convertimos todos los títulos a minúsculas.

prestamos.libros <- prestamos.libros %>%
  mutate(tititu = str_to_lower(tititu))

Separamos en dos ficheros los préstamos a usuarios adultos e infantiles.

adulto <- prestamos.libros %>%
  filter(pradul == "1")

infantil <- prestamos.libros %>%
  filter(pradul == "0")

Nos quedamos con los datos de autores y títulos, que serán los que analizaremos. También conservamos el ISBN para buscar las obras en el catálogo.

adulto <- adulto %>%
  select(autor = tiauto,
        titulo = tititu,
        ISBN = tiisxn)

infantil <- infantil %>%
  select(autor = tiauto,
        titulo = tititu,
        ISBN = tiisxn)

3 Análisis del préstamo a usuarios adultos

3.1 Autores y títulos más prestados

Para determinar el número de préstamos de cada obra, agrupamos los registros por autor y, a continuación, por título. No podemos hacer el recuento directamente por título ya que hay casos de diferentes obras con el mismo título. Por ejemplo, numerosos autores han escrito “Diarios”. Conservamos el primer ISBN para buscar la obra en el catálogo.

prestamos <- adulto %>%
  group_by(autor, titulo) %>%
  summarise(prestamos = n(),
            ISBN = first(ISBN),
            .groups = "drop")

La siguiente tabla muestra, para cada autor, el número de títulos prestados y el total de préstamos. Es posible modificar el número de registros que se visualizan y realizar búsquedas.

autores <- prestamos %>%
  filter(!is.na(autor)) %>%
  group_by(autor) %>%
  summarise(num_titulos = n(),
            total_prestamos = sum(prestamos), .groups = "drop")

autores_ordenados <- autores %>%
  arrange(desc(total_prestamos))

datatable(autores_ordenados, 
          options = list(pageLength = 10), 
          rownames = FALSE)

La siguiente tabla muestra el número de préstamos de cada título.

tabla_prestamos <- prestamos %>%
  arrange(desc(prestamos)) %>%
  select(1:3)

datatable(tabla_prestamos,
          options = list(pageLength = 10),
          rownames = FALSE)

El siguiente código permite realizar búsquedas para determinar la posición de un autor en el ranking de préstamos.

autores %>%
  arrange(desc(total_prestamos)) %>%
  mutate(posicion = row_number()) %>%
  filter(str_detect(as.character(autor), "Grandes, Almudena"))
## # A tibble: 1 × 4
##   autor                         num_titulos total_prestamos posicion
##   <chr>                               <int>           <int>    <int>
## 1 Grandes, Almudena (1960-2021)          21            3443       21

3.2 Desigualdad en el préstamo de autores y títulos

Cabe esperar una gran desigualdad en los préstamos de autores y títulos. Calculamos el número de préstamos por autor y título.

prestamos_autor <- prestamos %>%
  filter(autor != "") %>% # eliminamos los libros para los que no consta autor
  group_by(autor) %>%
  summarise(prestamos = sum(prestamos), .groups = "drop")

prestamos_titulo <- prestamos %>%
  group_by(autor, titulo) %>% # agrupamos por autor dada la duplicidad de títulos
  summarise(prestamos = sum(prestamos), .groups = "drop")

Creamos una función para calcular el porcentaje de autores y títulos necesarios para alcanzar el 80% de los préstamos.

calc_pct_para_80 <- function(df, columna_entidad, columna_valor) {
  df_ordenado <- df %>%
    arrange(desc(.data[[columna_valor]])) %>%
    mutate(
      prestamos_acum = cumsum(.data[[columna_valor]]),
      pct_acum_prestamos = prestamos_acum / sum(.data[[columna_valor]]),
      pct_entidades = row_number() / n()
    )
  
  # Encontrar el primer valor donde se alcanza o supera el 80%
  umbral <- df_ordenado %>%
    filter(pct_acum_prestamos >= 0.80) %>%
    slice(1)
  
  return(umbral$pct_entidades * 100)
}

pct_autores_para_80 <- calc_pct_para_80(prestamos_autor, "autor", "prestamos")
pct_titulos_para_80 <- calc_pct_para_80(prestamos_titulo, "titulo", "prestamos")

cat("Porcentaje acumulado de autores que generan el 80% de los préstamos:", round(pct_autores_para_80, 2), "%\n")
## Porcentaje acumulado de autores que generan el 80% de los préstamos: 11.73 %
cat("Porcentaje acumulado de títulos que generan el 80% de los préstamos:", round(pct_titulos_para_80, 2), "%\n")
## Porcentaje acumulado de títulos que generan el 80% de los préstamos: 24.21 %

Para visualizar la concentración de préstamos en autores y títulos, creamos una figura con dos curvas de Lorenz, una por autor y otra por título.

lorenz_autor <- Lc(prestamos_autor$prestamos)
lorenz_titulo <- Lc(prestamos_titulo$prestamos)

Para representar ambas curvas en un único gráfico necesitamos crear una secuencia común de percentiles.

# Crear una secuencia común de percentiles
p_common <- seq(0, 1, length.out = 100)

# Interpolación de cada curva
L_autor_interp <- approxfun(lorenz_autor$p, lorenz_autor$L)(p_common)
L_titulo_interp <- approxfun(lorenz_titulo$p, lorenz_titulo$L)(p_common)

# Crear data frame largo para ggplot
df_lorenz <- data.frame(
  p = rep(p_common, 2),
  L = c(L_autor_interp, L_titulo_interp),
  grupo = rep(c("Autores", "Títulos"), each = length(p_common))
)

Finalmente, generamos el gráfico.

ggplot(df_lorenz, aes(x = p, y = L, color = grupo)) +
  geom_line(size = 1.3) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed") +
  labs(
    x = "Proporción acumulada de autores / títulos",
    y = "Proporción acumulada de préstamos",
    color = ""
  ) +
  theme_minimal()

Calculamos el índice de Gini para autores y títulos

Gini(prestamos_autor$prestamos)
## [1] 0.8361037
Gini(prestamos_titulo$prestamos)
## [1] 0.7150756

3.3 Relación entre el número de obras de un autor y el número de préstamos

Existe una correlación moderadamente fuerte y lineal entre el número de obras de un autor y el número de préstamos.

cor(autores$num_titulos, autores$total_prestamos, method = "pearson")
## [1] 0.6519601

Creamos un modelo de regresión lineal.

modelo <- lm(total_prestamos ~ num_titulos, data = autores)
summary(modelo)
## 
## Call:
## lm(formula = total_prestamos ~ num_titulos, data = autores)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1678.2    -3.0    -0.6     2.4  6628.4 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -14.62188    0.48428  -30.19   <2e-16 ***
## num_titulos  17.27014    0.07944  217.40   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 112.7 on 63929 degrees of freedom
## Multiple R-squared:  0.4251, Adjusted R-squared:  0.425 
## F-statistic: 4.726e+04 on 1 and 63929 DF,  p-value: < 2.2e-16

Lo representamos gráficamente en un diagrama de dispersión.

ggplot(autores, aes(x = num_titulos, y = total_prestamos)) +
  geom_point(alpha = 0.6, color = "steelblue") +
  geom_smooth(method = "lm", se = FALSE, color = "darkred", linetype = "dashed") +
  annotate("text", x = 250, y = 6000, label = "y = -14.62 + 17.27 * x", color = "black", size = 5) +
  labs(
    x = "Número de títulos",
    y = "Préstamos"
  ) +
  theme_minimal()

3.4 Préstamos por año de nacimiento del autor

En primer lugar, extraemos el año de nacimiento del autor, si existe.

autores$anio_nacimiento <- as.numeric(
  sub(".*\\((\\d{4})-?.*\\)", "\\1", autores$autor))

Calculamos la década de nacimiento y agrupamos los autores nacidos antes de 1850 en un único grupo.

autores$decada <- floor(autores$anio_nacimiento / 10) * 10

autores <- autores %>%
  mutate(
    decada_agrupada = case_when(
      is.na(decada) ~ NA_character_,
      decada < 1850 ~ "< 1850",
      TRUE ~ as.character(decada)
    ))

Veamos cuántos son.

autores %>%
  filter(!is.na(decada_agrupada)) %>%      # Filtra filas con decada_agrupada no NA
  summarise(
    n_autores = n_distinct(autor),          # Cuenta autores distintos
    suma_prestamos = sum(total_prestamos, na.rm = TRUE)  # Suma de préstamos
  )
## # A tibble: 1 × 2
##   n_autores suma_prestamos
##       <int>          <int>
## 1     25690        1266355

Para hacer el gráfico, filtramos los registros sin autor y hacemos el recuento.

prestamos_y_autores <- autores %>%
  filter(!is.na(decada_agrupada)) %>%
  group_by(decada_agrupada) %>%
  summarise(
    total_prestamos = sum(total_prestamos),
    numero_autores = n(),
    .groups = "drop")

Ordenamos las columnas cronológicamente.

prestamos_y_autores$decada_agrupada <- factor(
  prestamos_y_autores$decada_agrupada,
  levels = c("< 1850", sort(unique(prestamos_y_autores$decada_agrupada[prestamos_y_autores$decada_agrupada != "< 1850"])))
)

Y generamos el gráfico.

ggplot(prestamos_y_autores, aes(x = decada_agrupada, y = total_prestamos)) +
  geom_col(fill = "steelblue") +
  labs(
    x = "",
    y = "Número de préstamos"
  ) +
  scale_y_continuous(labels = label_comma(decimal.mark = ",", big.mark = ".")) +
  theme_minimal()

4 Análisis del préstamo a usuarios infantiles

En esta seguna parte se aplica el mismo código que en el apartado anterior pero sobre los datos de préstamos a usuarios infantiles.

4.1 Autores y títulos más prestados

prestamos.i <- infantil %>%
  group_by(autor, titulo) %>%
  summarise(prestamos = n(),
            ISBN = first(ISBN),
            .groups = "drop")

La siguiente tabla muestra, para cada autor, el número de títulos prestados y el total de préstamos.

autores.i <- prestamos.i %>%
  filter(autor != "") %>%
  group_by(autor) %>%
  summarise(num_titulos = n(),
            total_prestamos = sum(prestamos),
            .groups = "drop")

datatable(
  autores.i %>% arrange(desc(total_prestamos)),
  options = list(pageLength = 10),
  rownames = FALSE
)

La siguiente tabla muestra los préstamos de cada título.

datatable(
  prestamos.i %>%
    arrange(desc(prestamos)) %>%
    select(1:3),
  options = list(pageLength = 10),
  rownames = FALSE
)

4.2 Desigualdad en el préstamo de autores y títulos

Visualizamos la desiguadad en los préstamos de autores y títulos. Para ello, eliminamos en primer lugar la primera fila correspondiente a los préstamos de títulos para los que no consta un autor.

# Filtrar autores no vacíos y agrupar por autor
prestamos_autor.i <- prestamos.i %>%
  filter(autor != "") %>%
  group_by(autor) %>%
  summarise(prestamos = sum(prestamos), .groups = "drop")

# Agrupar por título
prestamos_titulo.i <- prestamos.i %>%
  group_by(autor, titulo) %>%
  summarise(prestamos = sum(prestamos), .groups = "drop")

Calculamos el porcentaje de autores y títulos necesarios para alcanzar el 80% de los préstamos.

calc_pct_para_80(prestamos_autor.i, "autor", "prestamos")
## [1] 7.811408
calc_pct_para_80(prestamos_titulo.i, "titulo", "prestamos")
## [1] 18.85599

Finalmente, calculamos el índice Gini de autores y títulos.

# Cálculo del índice de Gini
Gini(prestamos_autor.i$prestamos)
## [1] 0.8832824
Gini(prestamos_titulo.i$prestamos)
## [1] 0.7626877