library(readxl)
library(dplyr)
library(tibble)
library(purrr)
library(tidyr)
library(openxlsx)

############################################################################
################# GIBRAT Y ZIPF PARA GRADO Y MASTER#########################
############################################################################

# Leer datos

df <- datos %>%
  filter(
    !is.na(Valor), Valor > 0,
    !is.na(`Tipo 1`), !is.na(`Tipo 2`), !is.na(Nivel), !is.na(`Rama de Enseñanza`)
  )

# Función de descriptivos robustos
desc <- function(x) {
  x <- x[is.finite(x)]
  tibble(
    N = length(x),
    Mean = mean(x),
    SD = sd(x),
    Median = median(x),
    P10 = quantile(x, 0.10, names = FALSE),
    P90 = quantile(x, 0.90, names = FALSE),
    Min = min(x),
    Max = max(x)
  )
}

# Poner a "formato largo" SOLO las 4 variables que quieres resumir
df_long <- df %>%
  pivot_longer(
    cols = c(`Tipo 1`, `Tipo 2`, Nivel, `Rama de Enseñanza`),
    names_to = "Variable",
    values_to = "Categoria"
  )

# Tabla final: descriptivos de Valor por Variable x Categoria
tabla_desc <- df_long %>%
  group_by(Variable, Categoria) %>%
  summarise(desc(Valor), .groups = "drop") %>%
  arrange(Variable, Categoria)

# Guardar a Excel con dos hojas
wb <- createWorkbook()
addWorksheet(wb, "Descriptivos_Valor")
writeData(wb, "Descriptivos_Valor", tabla_desc)

saveWorkbook(wb, "descriptivos_por_variable.xlsx", overwrite = TRUE)


############################################################################
################# GIBRAT Y ZIPF PARA GRADO Y MASTER#########################
############################################################################


datos_filtrados <- datos %>%
  filter(Nivel %in% c("Grado", "Máster"),
         !is.na(Valor), Valor > 0,
         !is.na(Curso), !is.na(Universidad))

# Función para asteriscos según p-valor
asteriscos <- function(p) {
  if (is.na(p)) return("")
  if (p <= 0.01) return("***")
  if (p <= 0.05) return("**")
  if (p <= 0.10) return("*")
  return("")
}

# Función para formatear p-valores en notación científica
formatear_p <- function(p) {
  if (is.na(p)) return(NA)
  formatC(p, format = "e", digits = 3)
}

# Función para interpretación del contraste
interpretacion_contraste <- function(p) {
  if (is.na(p)) return("")
  if (p <= 0.01) return("strongly rejected")
  if (p <= 0.05) return("rejected")
  if (p <= 0.10) return("marginal")
  return("not rejected")
}

# Función de extracción de resultados
extraer_resultados <- function(modelo, nivel, curso, ley = "Zipf") {
  resumen <- summary(modelo)
  coef <- resumen$coefficients
  r2 <- resumen$r.squared
  df_res <- resumen$df[2]
  
  inter <- coef[1, ]
  pend <- coef[2, ]
  
  est_pend <- pend["Estimate"]
  se_pend <- pend["Std. Error"]
  pval_pend <- pend["Pr(>|t|)"]
  
  est_int <- inter["Estimate"]
  se_int <- inter["Std. Error"]
  pval_int <- inter["Pr(>|t|)"]
  
  # Contraste específico
  beta_0 <- ifelse(ley == "Zipf", -1, 0)
  t_stat <- (est_pend - beta_0) / se_pend
  p_contraste <- 2 * pt(-abs(t_stat), df = df_res)
  decision <- interpretacion_contraste(p_contraste)
  
  tibble(
    Nivel = nivel,
    Curso = curso,
    intercepto = sprintf("%.3f%s (%.3f)", est_int, asteriscos(pval_int), se_int),
    pval_intercepto = formatear_p(pval_int),
    pendiente = sprintf("%.3f%s (%.3f)", est_pend, asteriscos(pval_pend), se_pend),
    pval_pendiente = formatear_p(pval_pend),
    r2 = round(r2, 3),
    pval_contraste = paste0(formatear_p(p_contraste), " (", decision, ")")
  )
}

# -----------------------------
# LEY DE ZIPF por Nivel y Curso
# -----------------------------
zipf_resultados <- datos_filtrados %>%
  group_by(Nivel, Curso) %>%
  group_split() %>%
  map_df(~{
    .x <- .x %>%
      mutate(rank = rank(-Valor, ties.method = "first"),
             log_valor = log(Valor),
             log_rank = log(rank)) %>%
      filter(is.finite(log_valor), is.finite(log_rank))
    
    if (nrow(.x) >= 2) {
      modelo <- lm(log_valor ~ log_rank, data = .x)
      extraer_resultados(modelo, unique(.x$Nivel), unique(.x$Curso), ley = "Zipf")
    } else {
      NULL
    }
  })

# -----------------------------
# LEY DE GIBRAT por Nivel y Curso
# -----------------------------
gibrat_resultados <- datos_filtrados %>%
  arrange(Universidad, Curso) %>%
  group_by(Nivel, Universidad) %>%
  mutate(log_valor = log(Valor),
         crecimiento = log_valor - lag(log_valor)) %>%
  ungroup() %>%
  filter(is.finite(crecimiento), is.finite(log_valor)) %>%
  group_by(Nivel, Curso) %>%
  group_split() %>%
  map_df(~{
    if (nrow(.x) >= 2) {
      modelo <- lm(crecimiento ~ log_valor, data = .x)
      extraer_resultados(modelo, unique(.x$Nivel), unique(.x$Curso), ley = "Gibrat")
    } else {
      NULL
    }
  })

# Mostrar resultados
cat("\n📌 RESULTADOS LEY DE ZIPF (Grado y Máster):\n")
print(zipf_resultados, n = Inf)

cat("\n📌 RESULTADOS LEY DE GIBRAT (Grado y Máster):\n")
print(gibrat_resultados, n = Inf)

# Guardar como CSV
write.table(zipf_resultados, "zipf_resultados.csv", sep = ";", dec = ",", row.names = FALSE)
write.table(gibrat_resultados, "gibrat_resultados.csv", sep = ";", dec = ",", row.names = FALSE)

###############################################################################################
################# GIBRAT Y ZIPF PARA GRADO Y MASTER POR RAMA DE CONOCIMIENTO###################
###############################################################################################

library(readxl)
library(dplyr)
library(tibble)
library(purrr)

# Filtrar
datos_filtrados <- datos %>%
  filter(Nivel %in% c("Grado", "Máster"),
         !is.na(Valor), Valor > 0,
         !is.na(Curso), !is.na(Universidad), !is.na(`Rama de Enseñanza`))

# Funciones auxiliares
asteriscos <- function(p) {
  if (is.na(p)) return("")
  if (p <= 0.01) return("***")
  if (p <= 0.05) return("**")
  if (p <= 0.10) return("*")
  return("")
}

formatear_p <- function(p) {
  if (is.na(p)) return(NA)
  formatC(p, format = "e", digits = 3)
}

interpretacion_contraste <- function(p) {
  if (is.na(p)) return("")
  if (p <= 0.01) return("strongly rejected")
  if (p <= 0.05) return("rejected")
  if (p <= 0.10) return("marginal")
  return("not rejected")
}

# Función para obtener resultados formateados
extraer_resultados <- function(modelo, nivel, rama, curso, ley = "Zipf") {
  resumen <- summary(modelo)
  coef <- resumen$coefficients
  r2 <- resumen$r.squared
  df_res <- resumen$df[2]
  
  inter <- coef[1, ]
  pend <- coef[2, ]
  
  est_pend <- pend["Estimate"]
  se_pend <- pend["Std. Error"]
  pval_pend <- pend["Pr(>|t|)"]
  
  est_int <- inter["Estimate"]
  se_int <- inter["Std. Error"]
  pval_int <- inter["Pr(>|t|)"]
  
  beta_0 <- ifelse(ley == "Zipf", -1, 0)
  t_stat <- (est_pend - beta_0) / se_pend
  p_contraste <- 2 * pt(-abs(t_stat), df = df_res)
  decision <- interpretacion_contraste(p_contraste)
  
  tibble(
    Nivel = nivel,
    Rama = rama,
    Curso = curso,
    intercepto = sprintf("%.3f%s (%.3f)", est_int, asteriscos(pval_int), se_int),
    pval_intercepto = formatear_p(pval_int),
    pendiente = sprintf("%.3f%s (%.3f)", est_pend, asteriscos(pval_pend), se_pend),
    pval_pendiente = formatear_p(pval_pend),
    r2 = round(r2, 3),
    pval_contraste = paste0(formatear_p(p_contraste), " (", decision, ")")
  )
}

# ---------------------------
# LEY DE ZIPF por Rama
# ---------------------------
zipf_resultados_rama <- datos_filtrados %>%
  group_by(Nivel, `Rama de Enseñanza`, Curso) %>%
  group_split() %>%
  map_df(~{
    .x <- .x %>%
      mutate(rank = rank(-Valor, ties.method = "first"),
             log_valor = log(Valor),
             log_rank = log(rank)) %>%
      filter(is.finite(log_valor), is.finite(log_rank))
    
    if (nrow(.x) >= 2) {
      modelo <- lm(log_valor ~ log_rank, data = .x)
      extraer_resultados(modelo,
                         nivel = unique(.x$Nivel),
                         rama = unique(.x$`Rama de Enseñanza`),
                         curso = unique(.x$Curso),
                         ley = "Zipf")
    } else {
      NULL
    }
  })

# ---------------------------
# LEY DE GIBRAT por Rama
# ---------------------------
gibrat_resultados_rama <- datos_filtrados %>%
  arrange(Universidad, Curso) %>%
  group_by(Nivel, `Rama de Enseñanza`, Universidad) %>%
  mutate(log_valor = log(Valor),
         crecimiento = log_valor - lag(log_valor)) %>%
  ungroup() %>%
  filter(is.finite(crecimiento), is.finite(log_valor)) %>%
  group_by(Nivel, `Rama de Enseñanza`, Curso) %>%
  group_split() %>%
  map_df(~{
    if (nrow(.x) >= 2) {
      modelo <- lm(crecimiento ~ log_valor, data = .x)
      extraer_resultados(modelo,
                         nivel = unique(.x$Nivel),
                         rama = unique(.x$`Rama de Enseñanza`),
                         curso = unique(.x$Curso),
                         ley = "Gibrat")
    } else {
      NULL
    }
  })

# Mostrar resultados
cat("\n📌 RESULTADOS LEY DE ZIPF POR RAMA:\n")
print(zipf_resultados_rama, n = Inf)

cat("\n📌 RESULTADOS LEY DE GIBRAT POR RAMA:\n")
print(gibrat_resultados_rama, n = Inf)

# Guardar como CSV
write.table(zipf_resultados_rama, "zipf_resultados_rama.csv", sep = ";", dec = ",", row.names = FALSE)
write.table(gibrat_resultados_rama, "gibrat_resultados_rama.csv", sep = ";", dec = ",", row.names = FALSE)

###############################################################################################
################# GIBRAT Y ZIPF PARA PÚBLICA Y PRIVADA###########################################
###############################################################################################

library(readxl)
library(dplyr)
library(tibble)
library(purrr)

# Filtrar datos
datos_filtrados <- datos %>%
  filter(Nivel %in% c("Grado", "Máster"),
         !is.na(Valor), Valor > 0,
         !is.na(Curso), !is.na(Universidad), !is.na(`Tipo 2`))

# Funciones auxiliares
asteriscos <- function(p) {
  if (is.na(p)) return("")
  if (p <= 0.01) return("***")
  if (p <= 0.05) return("**")
  if (p <= 0.10) return("*")
  return("")
}

formatear_p <- function(p) {
  if (is.na(p)) return(NA)
  formatC(p, format = "e", digits = 3)
}

interpretacion_contraste <- function(p) {
  if (is.na(p)) return("")
  if (p <= 0.01) return("strongly rejected")
  if (p <= 0.05) return("rejected")
  if (p <= 0.10) return("marginal")
  return("not rejected")
}

# Función de extracción general
extraer_resultados <- function(modelo, nivel, tipo, curso, ley = "Zipf") {
  resumen <- summary(modelo)
  coef <- resumen$coefficients
  r2 <- resumen$r.squared
  df_res <- resumen$df[2]
  
  inter <- coef[1, ]
  pend <- coef[2, ]
  
  est_pend <- pend["Estimate"]
  se_pend <- pend["Std. Error"]
  pval_pend <- pend["Pr(>|t|)"]
  
  est_int <- inter["Estimate"]
  se_int <- inter["Std. Error"]
  pval_int <- inter["Pr(>|t|)"]
  
  beta_0 <- ifelse(ley == "Zipf", -1, 0)
  t_stat <- (est_pend - beta_0) / se_pend
  p_contraste <- 2 * pt(-abs(t_stat), df = df_res)
  decision <- interpretacion_contraste(p_contraste)
  
  tibble(
    Nivel = nivel,
    Tipo = tipo,
    Curso = curso,
    intercepto = sprintf("%.3f%s (%.3f)", est_int, asteriscos(pval_int), se_int),
    pval_intercepto = formatear_p(pval_int),
    pendiente = sprintf("%.3f%s (%.3f)", est_pend, asteriscos(pval_pend), se_pend),
    pval_pendiente = formatear_p(pval_pend),
    r2 = round(r2, 3),
    pval_contraste = paste0(formatear_p(p_contraste), " (", decision, ")")
  )
}

# -------------------------------
# LEY DE ZIPF por Tipo (Pública/Privada)
# -------------------------------
zipf_resultados_tipo <- datos_filtrados %>%
  group_by(Nivel, `Tipo 2`, Curso) %>%
  group_split() %>%
  map_df(~{
    .x <- .x %>%
      mutate(rank = rank(-Valor, ties.method = "first"),
             log_valor = log(Valor),
             log_rank = log(rank)) %>%
      filter(is.finite(log_valor), is.finite(log_rank))
    
    if (nrow(.x) >= 2) {
      modelo <- lm(log_valor ~ log_rank, data = .x)
      extraer_resultados(modelo,
                         nivel = unique(.x$Nivel),
                         tipo = unique(.x$`Tipo 2`),
                         curso = unique(.x$Curso),
                         ley = "Zipf")
    } else {
      NULL
    }
  })

# -------------------------------
# LEY DE GIBRAT por Tipo (Pública/Privada)
# -------------------------------
gibrat_resultados_tipo <- datos_filtrados %>%
  arrange(Universidad, Curso) %>%
  group_by(Nivel, `Tipo 2`, Universidad) %>%
  mutate(log_valor = log(Valor),
         crecimiento = log_valor - lag(log_valor)) %>%
  ungroup() %>%
  filter(is.finite(crecimiento), is.finite(log_valor)) %>%
  group_by(Nivel, `Tipo 2`, Curso) %>%
  group_split() %>%
  map_df(~{
    if (nrow(.x) >= 2) {
      modelo <- lm(crecimiento ~ log_valor, data = .x)
      extraer_resultados(modelo,
                         nivel = unique(.x$Nivel),
                         tipo = unique(.x$`Tipo 2`),
                         curso = unique(.x$Curso),
                         ley = "Gibrat")
    } else {
      NULL
    }
  })

# -------------------------------
# Mostrar resultados
# -------------------------------
cat("\n📌 RESULTADOS LEY DE ZIPF (Públicas y Privadas):\n")
print(zipf_resultados_tipo, n = Inf)

cat("\n📌 RESULTADOS LEY DE GIBRAT (Públicas y Privadas):\n")
print(gibrat_resultados_tipo, n = Inf)

# Guardar como CSV
write.table(zipf_resultados_tipo, "zipf_resultados_tipo.csv", sep = ";", dec = ",", row.names = FALSE)
write.table(gibrat_resultados_tipo, "gibrat_resultados_tipo.csv", sep = ";", dec = ",", row.names = FALSE)

###############################################################################################
################# GIBRAT Y ZIPF PARA PRESENCIAL Y A DISTANCIA###########################################
###############################################################################################

library(readxl)
library(dplyr)
library(tibble)
library(purrr)

# Filtrar datos
datos_filtrados <- datos %>%
  filter(Nivel %in% c("Grado", "Máster"),
         !is.na(Valor), Valor > 0,
         !is.na(Curso), !is.na(Universidad), !is.na(`Tipo 1`))

# Funciones auxiliares
asteriscos <- function(p) {
  if (is.na(p)) return("")
  if (p <= 0.01) return("***")
  if (p <= 0.05) return("**")
  if (p <= 0.10) return("*")
  return("")
}

formatear_p <- function(p) {
  if (is.na(p)) return(NA)
  formatC(p, format = "e", digits = 3)
}

interpretacion_contraste <- function(p) {
  if (is.na(p)) return("")
  if (p <= 0.01) return("strongly rejected")
  if (p <= 0.05) return("rejected")
  if (p <= 0.10) return("marginal")
  return("not rejected")
}

extraer_resultados <- function(modelo, nivel, modalidad, curso, ley = "Zipf") {
  resumen <- summary(modelo)
  coef <- resumen$coefficients
  r2 <- resumen$r.squared
  df_res <- resumen$df[2]
  
  inter <- coef[1, ]
  pend <- coef[2, ]
  
  est_pend <- pend["Estimate"]
  se_pend <- pend["Std. Error"]
  pval_pend <- pend["Pr(>|t|)"]
  
  est_int <- inter["Estimate"]
  se_int <- inter["Std. Error"]
  pval_int <- inter["Pr(>|t|)"]
  
  beta_0 <- ifelse(ley == "Zipf", -1, 0)
  t_stat <- (est_pend - beta_0) / se_pend
  p_contraste <- 2 * pt(-abs(t_stat), df = df_res)
  decision <- interpretacion_contraste(p_contraste)
  
  tibble(
    Nivel = nivel,
    Modalidad = modalidad,
    Curso = curso,
    intercepto = sprintf("%.3f%s (%.3f)", est_int, asteriscos(pval_int), se_int),
    pval_intercepto = formatear_p(pval_int),
    pendiente = sprintf("%.3f%s (%.3f)", est_pend, asteriscos(pval_pend), se_pend),
    pval_pendiente = formatear_p(pval_pend),
    r2 = round(r2, 3),
    pval_contraste = paste0(formatear_p(p_contraste), " (", decision, ")")
  )
}

# -------------------------------
# LEY DE ZIPF por Modalidad (Tipo 1)
# -------------------------------
zipf_resultados_modalidad <- datos_filtrados %>%
  group_by(Nivel, `Tipo 1`, Curso) %>%
  group_split() %>%
  map_df(~{
    .x <- .x %>%
      mutate(rank = rank(-Valor, ties.method = "first"),
             log_valor = log(Valor),
             log_rank = log(rank)) %>%
      filter(is.finite(log_valor), is.finite(log_rank))
    
    if (nrow(.x) >= 2) {
      modelo <- lm(log_valor ~ log_rank, data = .x)
      extraer_resultados(modelo,
                         nivel = unique(.x$Nivel),
                         modalidad = unique(.x$`Tipo 1`),
                         curso = unique(.x$Curso),
                         ley = "Zipf")
    } else {
      NULL
    }
  })

# -------------------------------
# LEY DE GIBRAT por Modalidad (Tipo 1)
# -------------------------------
gibrat_resultados_modalidad <- datos_filtrados %>%
  arrange(Universidad, Curso) %>%
  group_by(Nivel, `Tipo 1`, Universidad) %>%
  mutate(log_valor = log(Valor),
         crecimiento = log_valor - lag(log_valor)) %>%
  ungroup() %>%
  filter(is.finite(crecimiento), is.finite(log_valor)) %>%
  group_by(Nivel, `Tipo 1`, Curso) %>%
  group_split() %>%
  map_df(~{
    if (nrow(.x) >= 2) {
      modelo <- lm(crecimiento ~ log_valor, data = .x)
      extraer_resultados(modelo,
                         nivel = unique(.x$Nivel),
                         modalidad = unique(.x$`Tipo 1`),
                         curso = unique(.x$Curso),
                         ley = "Gibrat")
    } else {
      NULL
    }
  })

# -------------------------------
# Mostrar resultados
# -------------------------------
cat("\n📌 RESULTADOS LEY DE ZIPF POR MODALIDAD:\n")
print(zipf_resultados_modalidad, n = Inf)

cat("\n📌 RESULTADOS LEY DE GIBRAT POR MODALIDAD:\n")
print(gibrat_resultados_modalidad, n = Inf)

# Guardar como CSV
write.table(zipf_resultados_modalidad, "zipf_resultados_modalidad.csv", sep = ";", dec = ",", row.names = FALSE)
write.table(gibrat_resultados_modalidad, "gibrat_resultados_modalidad.csv", sep = ";", dec = ",", row.names = FALSE)


###########################################################################
############### PARA HACER GRÁFICOS#######################################
##########################################################################

library(dplyr)

# Función auxiliar para extraer valor y error estándar de string
extraer_valores <- function(x) {
  nums <- unlist(regmatches(x, gregexpr("-?\\d+\\.\\d+", x)))
  nums <- as.numeric(nums)
  list(estimate = nums[1], se = nums[2])
}

# Gibrat
gibrat_plot <- gibrat_resultados %>%
  mutate(anio = as.numeric(substr(Curso, 1, 4)),
         extra = lapply(pendiente, extraer_valores),
         beta = sapply(extra, function(x) x$estimate),
         se = sapply(extra, function(x) x$se),
         Ley = "Gibrat's Law",
         ymin = beta - 1.96 * se,
         ymax = beta + 1.96 * se) %>%
  select(Nivel, anio, beta, ymin, ymax, Ley) %>%
  rename(valor = beta)

# Zipf
zipf_plot <- zipf_resultados %>%
  mutate(anio = as.numeric(substr(Curso, 1, 4)),
         extra = lapply(pendiente, extraer_valores),
         alpha = sapply(extra, function(x) x$estimate),
         se = sapply(extra, function(x) x$se),
         Ley = "Zipf's Law",
         ymin = alpha - 1.96 * se,
         ymax = alpha + 1.96 * se) %>%
  select(Nivel, anio, alpha, ymin, ymax, Ley) %>%
  rename(valor = alpha)

library(ggplot2)

comparacion <- bind_rows(gibrat_plot, zipf_plot)

comparacion <- bind_rows(gibrat_plot, zipf_plot) %>%
  mutate(Nivel = recode(Nivel, "Grado" = "Bachelor", "Máster" = "Master"))

comparacion$Degree <- comparacion$Nivel
comparacion$Law <- comparacion$Ley

ggplot(comparacion, aes(x = anio, y = valor, color = Law, linetype = Degree)) +
  geom_line(size = 1) +
  geom_ribbon(aes(ymin = ymin, ymax = ymax, fill = Law), alpha = 0.2, color = NA) +
  geom_point() +
  labs(
    title = "Estimated Coefficients",
    x = "Academic Year",
    y = "Coefficient"
  ) +
  theme_minimal() +
  scale_color_manual(values = c("Gibrat's Law" = "darkred", "Zipf's Law" = "steelblue")) +
  scale_fill_manual(values = c("Gibrat's Law" = "darkred", "Zipf's Law" = "steelblue")) +
  theme(legend.position = "bottom")

library(dplyr)
library(ggplot2)

# Función auxiliar
extraer_valores <- function(x) {
  nums <- unlist(regmatches(x, gregexpr("-?\\d+\\.\\d+", x)))
  nums <- as.numeric(nums)
  list(estimate = nums[1], se = nums[2])
}

# Gibrat
gibrat_plot_tipo <- gibrat_resultados_tipo %>%
  mutate(year = as.numeric(substr(Curso, 1, 4)),
         extra = lapply(pendiente, extraer_valores),
         beta = sapply(extra, function(x) x$estimate),
         se = sapply(extra, function(x) x$se),
         Law = "Gibrat's Law",
         ymin = beta - 1.96 * se,
         ymax = beta + 1.96 * se) %>%
  select(Nivel, Tipo, year, beta, ymin, ymax, Law) %>%
  rename(value = beta)

# Zipf
zipf_plot_tipo <- zipf_resultados_tipo %>%
  mutate(year = as.numeric(substr(Curso, 1, 4)),
         extra = lapply(pendiente, extraer_valores),
         alpha = sapply(extra, function(x) x$estimate),
         se = sapply(extra, function(x) x$se),
         Law = "Zipf's Law",
         ymin = alpha - 1.96 * se,
         ymax = alpha + 1.96 * se) %>%
  select(Nivel, Tipo, year, alpha, ymin, ymax, Law) %>%
  rename(value = alpha)

# Unir resultados y traducir
comparison_tipo <- bind_rows(gibrat_plot_tipo, zipf_plot_tipo) %>%
  mutate(
    Degree = recode(Nivel, "Grado" = "Bachelor", "Máster" = "Master"),
    Type = recode(Tipo, "Pública" = "Public", "Privada" = "Private")
  )

# Plot con facet_wrap por tipo de universidad
ggplot(comparison_tipo, aes(x = year, y = value, color = Law, linetype = Degree)) +
  geom_line(size = 1) +
  geom_ribbon(aes(ymin = ymin, ymax = ymax, fill = Law), alpha = 0.2, color = NA) +
  geom_point() +
  facet_wrap(~ Type) +
  labs(
    title = "Estimated Coefficients by University Type",
    x = "Academic Year",
    y = "Coefficient",
    color = "Law",
    fill = "Law",
    linetype = "Degree"
  ) +
  theme_minimal() +
  scale_color_manual(values = c("Gibrat's Law" = "darkred", "Zipf's Law" = "steelblue")) +
  scale_fill_manual(values = c("Gibrat's Law" = "darkred", "Zipf's Law" = "steelblue")) +
  theme(legend.position = "bottom")

library(dplyr)
library(ggplot2)

# Función auxiliar para extraer valor y error estándar de string
extraer_valores <- function(x) {
  nums <- unlist(regmatches(x, gregexpr("-?\\d+\\.\\d+", x)))
  nums <- as.numeric(nums)
  list(estimate = nums[1], se = nums[2])
}

# Gibrat por modalidad
gibrat_plot <- gibrat_resultados_modalidad %>%
  mutate(anio = as.numeric(substr(Curso, 1, 4)),
         extra = lapply(pendiente, extraer_valores),
         beta = sapply(extra, function(x) x$estimate),
         se = sapply(extra, function(x) x$se),
         Ley = "Gibrat",
         ymin = beta - 1.96 * se,
         ymax = beta + 1.96 * se) %>%
  select(Nivel, Modalidad, anio, beta, ymin, ymax, Ley) %>%
  rename(valor = beta)

# Zipf por modalidad
zipf_plot <- zipf_resultados_modalidad %>%
  mutate(anio = as.numeric(substr(Curso, 1, 4)),
         extra = lapply(pendiente, extraer_valores),
         alpha = sapply(extra, function(x) x$estimate),
         se = sapply(extra, function(x) x$se),
         Ley = "Zipf",
         ymin = alpha - 1.96 * se,
         ymax = alpha + 1.96 * se) %>%
  select(Nivel, Modalidad, anio, alpha, ymin, ymax, Ley) %>%
  rename(valor = alpha)

# Unir y traducir etiquetas
comparacion <- bind_rows(gibrat_plot, zipf_plot) %>%
  mutate(
    Degree = recode(Nivel, "Grado" = "Bachelor", "Máster" = "Master"),
    Law = recode(Ley, "Gibrat" = "Gibrat's Law", "Zipf" = "Zipf's Law"),
    Modality = recode(Modalidad,
                      "Presencial" = "On-campus Education",
                      "A distancia" = "Online Education",
                      "No Presencial" = "Online Education")
  )

# Gráfico
ggplot(comparacion, aes(x = anio, y = valor, color = Law, linetype = Degree)) +
  geom_line(size = 1) +
  geom_ribbon(aes(ymin = ymin, ymax = ymax, fill = Law), alpha = 0.2, color = NA) +
  geom_point() +
  facet_wrap(~Modality) +
  labs(
    title = "Estimated Coefficients by Education Modality",
    x = "Academic Year",
    y = "Coefficient",
    color = "Law",
    fill = "Law",
    linetype = "Degree Level"
  ) +
  theme_minimal() +
  scale_color_manual(values = c("Gibrat's Law" = "darkred", "Zipf's Law" = "steelblue")) +
  scale_fill_manual(values = c("Gibrat's Law" = "darkred", "Zipf's Law" = "steelblue")) +
  theme(legend.position = "bottom")

library(dplyr)
library(ggplot2)

# Función auxiliar para extraer valor y error estándar
extraer_valores <- function(x) {
  nums <- unlist(regmatches(x, gregexpr("-?\\d+\\.\\d+", x)))
  nums <- as.numeric(nums)
  list(estimate = nums[1], se = nums[2])
}

# Gibrat por rama
gibrat_plot <- gibrat_resultados_rama %>%
  mutate(anio = as.numeric(substr(Curso, 1, 4)),
         extra = lapply(pendiente, extraer_valores),
         beta = sapply(extra, function(x) x$estimate),
         se = sapply(extra, function(x) x$se),
         Ley = "Gibrat",
         ymin = beta - 1.96 * se,
         ymax = beta + 1.96 * se) %>%
  select(Nivel, Rama, anio, beta, ymin, ymax, Ley) %>%
  rename(valor = beta)

# Zipf por rama
zipf_plot <- zipf_resultados_rama %>%
  mutate(anio = as.numeric(substr(Curso, 1, 4)),
         extra = lapply(pendiente, extraer_valores),
         alpha = sapply(extra, function(x) x$estimate),
         se = sapply(extra, function(x) x$se),
         Ley = "Zipf",
         ymin = alpha - 1.96 * se,
         ymax = alpha + 1.96 * se) %>%
  select(Nivel, Rama, anio, alpha, ymin, ymax, Ley) %>%
  rename(valor = alpha)

# Unir y traducir
comparacion <- bind_rows(gibrat_plot, zipf_plot) %>%
  mutate(
    Degree = recode(Nivel, "Grado" = "Bachelor", "Máster" = "Master"),
    Law = recode(Ley, "Gibrat" = "Gibrat's Law", "Zipf" = "Zipf's Law"),
    Field = recode(Rama,
                   "Artes y Humanidades" = "Arts and Humanities",
                   "Ciencias" = "Natural Sciences",
                   "Ciencias de la Salud" = "Health Sciences",
                   "Ciencias Sociales y Jurídicas" = "Social Sciences and Law",
                   "Ingeniería y Arquitectura" = "Engineering and Architecture")
  )

# Gráfico por rama
ggplot(comparacion, aes(x = anio, y = valor, color = Law, linetype = Degree)) +
  geom_line(size = 1) +
  geom_ribbon(aes(ymin = ymin, ymax = ymax, fill = Law), alpha = 0.2, color = NA) +
  geom_point() +
  facet_wrap(~Field) +
  labs(
    title = "Estimated Coefficients by Field of Study",
    x = "Academic Year",
    y = "Coefficient",
    color = "Law",
    fill = "Law",
    linetype = "Degree Level"
  ) +
  theme_minimal() +
  scale_color_manual(values = c("Gibrat's Law" = "darkred", "Zipf's Law" = "steelblue")) +
  scale_fill_manual(values = c("Gibrat's Law" = "darkred", "Zipf's Law" = "steelblue")) +
  theme(legend.position = "bottom")
