1. Dados todos obtidos a partir de um apanhado de 180 planilhas distintas obtidas no site https://qedu.org.br/ que disponibiliza dados de todos os municipios do país.

library(tidyverse)
## Warning: pacote 'purrr' foi compilado no R versão 4.5.2
## Warning: pacote 'dplyr' foi compilado no R versão 4.5.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.1     ✔ stringr   1.5.2
## ✔ ggplot2   4.0.0     ✔ tibble    3.3.0
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.2.0     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
pasta <- "C:/Users/davia/Downloads/analise/"

arquivos <- list.files(pasta, full.names = TRUE, pattern = "\\.csv$")

ler_csv_completo <- function(arq) {
  read_csv(arq, col_types = cols(.default = "c"))
}

dados_gerais <- arquivos %>%
  map_dfr(ler_csv_completo) %>%
  mutate(across(everything(), ~ type.convert(., as.is = TRUE)))

glimpse(dados_gerais)
## Rows: 45,957
## Columns: 46
## $ Variável        <chr> "ibge_id", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Descrição       <chr> "Código IBGE da unidade da federação e municípios", NA…
## $ Códigos         <chr> "7-Brasil", "11-RO", "12-AC", "13-AM", "14-RR", "15-PA…
## $ ano             <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ inep_id         <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ dependencia_id  <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ localizacao_id  <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_1ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_2ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_3ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_4ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_5ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_6ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_7ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_8ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_9ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_total_ai     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_total_af     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ef_total        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ em_1ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ em_2ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ em_3ano         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ em_4ano         <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ em_total        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ibge_id         <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ciclo_id        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ideb            <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ fluxo           <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ aprendizado     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ nota_mt         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ nota_lp         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ lp_adequado     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ mt_adequado     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ lp_insuficiente <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ lp_basico       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ lp_proficiente  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ lp_avancado     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ mt_insuficiente <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ mt_basico       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ mt_proficiente  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ mt_avancado     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ matriculas      <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ serie_id        <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ aprovados       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ reprovados      <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ abandonos       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…

Acima o formato dos dados obtidos.

inep_ids <- dados_gerais %>%
  filter(!is.na(inep_id)) %>%
  distinct(inep_id) %>%
  arrange(inep_id)

inep_ids
## # A tibble: 203 × 1
##     inep_id
##       <int>
##  1 26115441
##  2 26115450
##  3 26115476
##  4 26115530
##  5 26115557
##  6 26115654
##  7 26115662
##  8 26115700
##  9 26115808
## 10 26115824
## # ℹ 193 more rows

2. Análise de aprendizado

Busquei analisar os resultados do municipio de 2015 em diante (dados registrados de 2 em 2 anos), sobre como se comportou, se a pandemia de alguma forma impactou e quais pontos apresentam piores indices.

colunas_interesse <- c(
  "lp_adequado",
  "mt_adequado",
  "lp_insuficiente",
  "lp_basico",
  "lp_proficiente",
  "lp_avancado",
  "mt_insuficiente",
  "mt_basico",
  "mt_proficiente",
  "mt_avancado"
)

dados_filtrados <- dados_gerais %>%
  filter(
    inep_id %in% inep_ids$inep_id,
    if_any(all_of(colunas_interesse), ~ !is.na(.))
  ) %>%
  select(inep_id, ano, all_of(colunas_interesse))

dados_filtrados
## # A tibble: 960 × 12
##     inep_id   ano lp_adequado mt_adequado lp_insuficiente lp_basico
##       <int> <int>       <dbl>       <dbl>           <dbl>     <dbl>
##  1 26115530  2015      0.161       0.035            0.496     0.343
##  2 26115530  2015      0.275       0.137            0.345     0.380
##  3 26115883  2015      0.348       0.140            0.148     0.504
##  4 26115913  2015      0.142       0.0383           0.273     0.585
##  5 26115964  2015      0.283       0.0459           0.272     0.444
##  6 26115972  2015      0.217       0.0408           0.140     0.643
##  7 26115999  2015      0.343       0.0917           0.172     0.485
##  8 26116057  2015      0.0801      0.0383           0.197     0.723
##  9 26116057  2015      0.185       0.106            0.489     0.327
## 10 26116081  2015      0.213       0.0989           0.356     0.432
## # ℹ 950 more rows
## # ℹ 6 more variables: lp_proficiente <dbl>, lp_avancado <dbl>,
## #   mt_insuficiente <dbl>, mt_basico <dbl>, mt_proficiente <dbl>,
## #   mt_avancado <dbl>
colunas_interesse <- c(
  "lp_adequado",
  "mt_adequado",
  "lp_insuficiente",
  "lp_basico",
  "lp_proficiente",
  "lp_avancado",
  "mt_insuficiente",
  "mt_basico",
  "mt_proficiente",
  "mt_avancado"
)

medias_ano_indicador <- dados_filtrados %>%
  select(ano, all_of(colunas_interesse)) %>%
  pivot_longer(
    cols = all_of(colunas_interesse),
    names_to = "indicador",
    values_to = "valor"
  ) %>%
  group_by(ano, indicador) %>%
  summarise(media = mean(valor, na.rm = TRUE), .groups = "drop") %>%
  arrange(ano, indicador)

medias_ano_indicador
## # A tibble: 40 × 3
##      ano indicador         media
##    <int> <chr>             <dbl>
##  1  2015 lp_adequado     0.247  
##  2  2015 lp_avancado     0.0350 
##  3  2015 lp_basico       0.499  
##  4  2015 lp_insuficiente 0.254  
##  5  2015 lp_proficiente  0.212  
##  6  2015 mt_adequado     0.0999 
##  7  2015 mt_avancado     0.00625
##  8  2015 mt_basico       0.495  
##  9  2015 mt_insuficiente 0.405  
## 10  2015 mt_proficiente  0.0936 
## # ℹ 30 more rows

Foco apenas na Lingua portuguesa.

# Filtrar apenas indicadores de LP
lp_medias <- medias_ano_indicador %>%
  filter(indicador %in% c(
    "lp_adequado", "lp_insuficiente", "lp_basico",
    "lp_proficiente", "lp_avancado"
  ))

ggplot(lp_medias, aes(x = ano, y = media * 100, group = indicador, color = indicador)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  theme_minimal(base_size = 14) +
  labs(
    title = "Evolução do Aprendizado em Língua Portuguesa no Município",
    x = "Ano",
    y = "Percentual (%)",
    color = "Indicador"
  ) +
  scale_x_continuous(breaks = c(2015, 2017, 2019, 2021, 2023)) +
  scale_y_continuous(breaks = c(0,5,10,15,20,25,30,35,40,45,50)) +
  theme(
    plot.title = element_text(face = "bold", size = 16),
    legend.position = "right"
  )

Foco em matemática.

# Filtrar apenas indicadores de MT
mt_medias <- medias_ano_indicador %>%
  filter(indicador %in% c(
    "mt_adequado", "mt_insuficiente", "mt_basico",
    "mt_proficiente", "mt_avancado"
  ))

ggplot(mt_medias, aes(x = ano, y = media * 100, group = indicador, color = indicador)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  theme_minimal(base_size = 14) +
  labs(
    title = "Evolução do Aprendizado em Matemática no Município",
    x = "Ano",
    y = "Percentual (%)",
    color = "Indicador"
  ) +
  scale_x_continuous(breaks = c(2015, 2017, 2019, 2021, 2023)) +
  scale_y_continuous(breaks = c(0,5,10,15,20,25,30,35,40,45,50)) +
  theme(
    plot.title = element_text(face = "bold", size = 16),
    legend.position = "right"
  )

3. Análise da presença dos estudantes.

desistiram <- dados_gerais |>
  dplyr::filter(
    !is.na(matriculas),
    !is.na(abandonos)
  ) |>
  dplyr::select(
    inep_id,
    ano,
    matriculas,
    abandonos
  ) |>
  dplyr::distinct()

desistiram
## # A tibble: 3,850 × 4
##     inep_id   ano matriculas abandonos
##       <int> <int>      <int>     <dbl>
##  1 26115441  2015        119         0
##  2 26115441  2015        195         0
##  3 26115441  2015        164         0
##  4 26115450  2015         33         0
##  5 26115450  2015         51         0
##  6 26115450  2015         39         0
##  7 26115450  2015         54         0
##  8 26115450  2015         30         0
##  9 26115450  2015         41         0
## 10 26115450  2015         24         0
## # ℹ 3,840 more rows
pizza_abandono <- desistiram |>
  dplyr::mutate(
    abandonos_reais = matriculas * (abandonos / 100)
  ) |>
  dplyr::group_by(ano) |>
  dplyr::summarise(
    total_matriculas = sum(matriculas, na.rm = TRUE),
    total_abandonos = sum(abandonos_reais, na.rm = TRUE)
  ) |>
  dplyr::mutate(
    total_nao_abandonaram = total_matriculas - total_abandonos
  )

Taxas de abandono.

library(ggplot2)

for (ano_atual in unique(pizza_abandono$ano)) {
  
  df_ano <- pizza_abandono |>
    dplyr::filter(ano == ano_atual) |>
    tidyr::pivot_longer(
      cols = c(total_abandonos, total_nao_abandonaram),
      names_to = "tipo",
      values_to = "quantidade"
    )
  
  print(
    ggplot(df_ano, aes(x = "", y = quantidade, fill = tipo)) +
      geom_col(width = 1) +
      coord_polar(theta = "y") +
      labs(
        title = paste("Distribuição de Abandono Escolar –", ano_atual),
        fill = "Situação"
      ) +
      theme_void()
  )
}

Distorção nos anos do fundamental 1 e 2.

Analisando como a distorção se comportou em cada ano letivo e nivel de ensino (Distorção se trata de estudantes que estão abaixo série ideal para a idade do mesmo)

library(tidyverse)

# colunas
cols_distorcao <- paste0("ef_", 1:9, "ano")

# limpar e converter valores
dg_clean <- dados_gerais %>%
  mutate(across(all_of(cols_distorcao), ~na_if(trimws(.), ""))) %>%  # "" vira NA
  mutate(across(all_of(cols_distorcao), ~as.numeric(str_replace(., ",", "."))))

# gerar tabela final
distoados <- dg_clean %>%
  filter(ano >= 2015) %>%                                   # anos válidos
  select(ano, all_of(cols_distorcao)) %>%
  pivot_longer(cols = starts_with("ef_"),
               names_to = "serie",
               values_to = "distorcao") %>%
  filter(!is.na(distorcao)) %>%                             # só o que existe
  mutate(
    serie = as.numeric(str_extract(serie, "\\d+"))           # extrai número da série
  ) %>%
  group_by(ano, serie) %>%
  summarise(distorcao = mean(distorcao, na.rm = TRUE),
            .groups = "drop") %>%
  arrange(ano, serie)

Distorção do ponto de vista apenas das séries.

library(ggplot2)

for (s in sort(unique(distoados$serie))) {
  
  temp <- distoados %>% filter(serie == s)
  
  p <- ggplot(temp, aes(x = ano, y = distorcao)) +
    geom_col(fill = "#2c3e50") +
    geom_text(aes(label = round(distorcao,1)), vjust = -0.5) +
    labs(
      title = paste0("Distorção Idade-Série – ", s, "º Ano"),
      x = "Ano Letivo",
      y = "Percentual (%)"
    ) +
    scale_x_continuous(breaks = c(2015, 2017, 2019, 2021, 2023))
    theme_minimal(base_size = 10)
  
  print(p)
}

Analise focada nos anos do fundamental.

ggplot(distoados, aes(x = ano, y = distorcao, color = factor(serie))) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  labs(
    title = "Evolução da Distorção Idade-Série (1º ao 9º ano)",
    x = "Ano Letivo",
    y = "Percentual (%)",
    color = "Série"
  ) +
  scale_x_continuous(breaks = c(2015, 2017, 2019, 2021, 2023)) +
  scale_y_continuous(breaks = c(1,26)) +
  theme_minimal(base_size = 14)

Analise geral da distorção ano a ano no municipio.

library(tidyverse)

# anos fixos que você sempre quer ver
anos_desejados <- c(2015, 2017, 2019, 2021)

# --- 1) preparar matriculas por ano/serie (a partir de dados_gerais) ---
matriculas_ano_serie <- dados_gerais %>%
  # garantir tipos / limpar strings vazias
  mutate(
    serie_id = as.character(serie_id) |> trimws(),
    matriculas = as.numeric(str_replace(as.character(matriculas), ",", ".")),
    ano = as.numeric(as.character(ano))
  ) %>%
  filter(!is.na(serie_id), !is.na(matriculas), !is.na(ano)) %>%
  mutate(serie = as.integer(str_extract(serie_id, "\\d+"))) %>%
  filter(!is.na(serie)) %>%
  group_by(ano, serie) %>%
  summarise(total_matriculas = sum(matriculas, na.rm = TRUE), .groups = "drop") %>%
  filter(ano %in% anos_desejados)

if (nrow(matriculas_ano_serie) == 0) {
  matriculas_ano_serie <- dados_gerais %>%
    mutate(
      matriculas = as.numeric(str_replace(as.character(matriculas), ",", ".")),
      ano = as.numeric(as.character(ano))
    ) %>%
    filter(!is.na(matriculas), !is.na(ano)) %>%
    group_by(ano) %>%
    summarise(total_matriculas = sum(matriculas, na.rm = TRUE), .groups = "drop") %>%
    filter(ano %in% anos_desejados) %>%
    mutate(serie = NA_integer_)
}

# --- 2) preparar distoados (garantir tipos, filtrar anos desejados) ---
distoados_clean <- distoados %>%
  mutate(
    ano = as.numeric(as.character(ano)),
    serie = as.integer(serie),
    distorcao = as.numeric(distorcao)   # percent (ex: 12.5)
  ) %>%
  filter(ano %in% anos_desejados)

joined <- distoados_clean %>%
  left_join(matriculas_ano_serie, by = c("ano", "serie"))

matriculas_por_ano <- matriculas_ano_serie %>%
  group_by(ano) %>%
  summarise(total_matriculas_ano = sum(total_matriculas, na.rm = TRUE), .groups = "drop")

joined <- joined %>%
  left_join(matriculas_por_ano, by = "ano") %>%
  mutate(
    matriculas_usadas = if_else(!is.na(total_matriculas), total_matriculas, total_matriculas_ano)
  )

if (all(is.na(joined$matriculas_usadas))) {
  warning("Nenhuma contagem de matriculas por serie/ano encontrada. Resultado usará média simples das distorções por ano (não ponderada).")
  resumo_ano <- joined %>%
    group_by(ano) %>%
    summarise(
      distorcao_percent = mean(distorcao, na.rm = TRUE),
      total_matriculas = NA_real_,
      metodo = "media_simples",
      .groups = "drop"
    )
} else {
  # calcula abandonos reais = distorcao(%) /100 * matriculas_usadas
  resumo_ano <- joined %>%
    filter(!is.na(distorcao)) %>%
    mutate(abandonos_reais = (distorcao / 100) * matriculas_usadas) %>%
    group_by(ano) %>%
    summarise(
      total_abandonos = sum(abandonos_reais, na.rm = TRUE),
      total_matriculas = sum(matriculas_usadas, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      distorcao_percent = if_else(total_matriculas > 0,
                                  (total_abandonos / total_matriculas) * 100,
                                  NA_real_),
      metodo = "ponderada"
    )
}
library(ggplot2)

# Garante a ordem dos anos (sempre mostrar 2015..2023)
resumo_ano <- resumo_ano %>%
  filter(ano %in% anos_desejados) %>%
  mutate(ano = factor(ano, levels = anos_desejados))

ggplot(resumo_ano, aes(x = ano, y = distorcao_percent)) +
  geom_col(fill = "#2c3e50", width = 0.6) +
  geom_text(aes(label = ifelse(is.na(distorcao_percent), "NA", paste0(round(distorcao_percent,1), "%"))),
            vjust = -0.5, size = 4) +
  scale_y_continuous(limits = c(0, 20), expand = expansion(mult = c(0, 0.05))) +
  labs(
    title = "Distorção (percentual ponderado) por Ano Letivo",
    x = "Ano",
    y = "Distorção (%)"
  ) +
  theme_minimal(base_size = 14) +
  theme(plot.title = element_text(face = "bold")) +
  scale_x_discrete(drop = FALSE)

4. Conclusão

Dados apontam que quanto maior o nivel de ensino, maiores são os problemas em manter o estudante de acordo com as taxas ideais, tanto no sentido de abandono, distorção e desempenho, mostrando também que após o periodo de pandemia/troca de gestão no municipio, os dados tiveram uma tendência a piorar.