1. Introdução

1.1 Declaração do Problema

O acesso a medicamentos nas unidades de saúde pública é um dos pilares fundamentais do Sistema Único de Saúde (SUS). No município do Recife, capital de Pernambuco,a rede municipal de saúde atende milhões de cidadãos que dependem exclusivamente do sistema público para obter tratamentos e medicamentosessenciais.

No entanto, a distribuição de medicamentos entre os distritos sanitários e unidades de saúde nem sempre ocorre de forma equitativa. Estoques zerados ou em nível crítico em determinadas unidades podem comprometer tratamentos contínuos, especialmente para pacientes com doenças crônicas, gerando deslocamentos desnecessários, abandono de tratamento e agravamento de quadros clínicos.

O interesse e o impacto dessa questão estendem-se por diferentes esferas da sociedade: permite que gestores de saúde identifiquem distritos e unidades em estado crítico para priorizar o reabastecimento, ao mesmo tempo que possibilita aos cidadãos verificar a disponibilidade de remédios antes de se deslocarem até uma unidade. Além disso, fornece a pesquisadores e formuladores de políticas os subsídios necessários para mapear padrões e propor melhorias na logística de distribuição, promovendo, em última análise, um sistema de saúde pública mais eficiente, transparente e benéfico para toda a sociedade.

1.2 Dados e Metodologia

Esta análise utiliza o dataset “Medicamentos por Unidade de Saúde”, disponibilizado publicamente pelo portal de Dados Abertos da Prefeitura do Recife. Os dados contêm informações sobre os estoques de medicamentos em cada unidade de saúde municipal, organizados por produto, apresentação, classe terapêutica, bairro e distrito sanitário.

A metodologia empregada segue as seguintes etapas:

  1. Importação dos dados diretamente da URL pública
  2. Limpeza e padronização das variáveis (tipos, capitalização, valores ausentes)
  3. Criação de variáveis derivadas (nível de estoque categórico)
  4. Análise exploratória com gráficos interativos e tabelas resumo
  5. Desenvolvimento de aplicação interativa (Shiny) para exploração dinâmica

1.3 Abordagem Técnica

A análise foi desenvolvida inteiramente em R, utilizando o paradigma do tidyverse para manipulação de dados. Para a visualização, foram combinadas abordagens estáticas (ggplot2) e interativas (plotly), além de uma aplicação Shiny que permite a exploração dinâmica dos dados por diferentes filtros (distrito, bairro, unidade, produto, etc.).

A criação da variável nivel_estoque — categorizada em Sem Estoque, Crítico, Regular e Adequado — é central para a análise, pois transforma uma variável numérica contínua em categorias interpretáveis que facilitam a identificação de situações de risco.

1.4 Valor para os Clientes da Análise

Os resultados desta análise agregam valor direto a diversos atores da saúde coletiva e do controle social: auxiliam a Secretaria Municipal de Saúde do Recife na identificação de gargalos logísticos e no planejamento de reabastecimentos prioritários, além de permitir que os Coordenadores de Distrito Sanitário monitorem continuamente as condições de abastecimento em suas unidades. Paralelamente, o estudo serve como base científica para pesquisadores de saúde pública investigarem a equidade no acesso a remédios, ao mesmo tempo em que funciona como uma ferramenta essencial para jornalistas e organizações da sociedade civil na fiscalização e promoção da transparência do SUS municipal.

2. Pacotes Requeridos

Os pacotes abaixo são necessários para replicar esta análise. Certifique-se de instalá-los antes de executar o código (install.packages("nome_do_pacote")).

library(dplyr)          # Manipulação e transformação de dados tabulares
library(ggplot2)        # Gráficos estáticos baseados na gramática dos gráficos
library(plotly)         # Gráficos interativos com hover, zoom e filtros dinâmicos
library(DT)             # Tabelas interativas com paginação, busca e filtros por coluna
library(stringr)        # Funções para manipulação e limpeza de strings de texto
library(scales)         # Formatação de escalas em gráficos (%, moeda, notação, etc.)
library(kableExtra)     # Formatação avançada de tabelas estáticas em R Markdown
library(skimr)          # Resumos estatísticos detalhados por variável
library(shiny)          # Framework para criação de aplicações web interativas em R
library(shinydashboard) # Componentes de layout de dashboard para aplicações Shiny
library(lubridate)      # Manipulação e parsing de datas de forma intuitiva
library(tidyr)          # Reformulação de dados (pivot_longer, pivot_wider, etc.)
library(RColorBrewer)   # Paletas de cores para visualizações

3. Preparação dos Dados

3.1 Fonte dos Dados

Os dados utilizados nesta análise foram obtidos do portal de Dados Abertos da Prefeitura do Recife, disponível gratuitamente em:

📦 Medicamentos por Unidade de Saúde — Dados Abertos Recife

url_dados <- paste0(
  "https://dados.recife.pe.gov.br/dataset/",
  "8ae2b6b5-51b5-403a-9dea-973a8ed1aca4/resource/",
  "1b8142f9-d801-4b54-88ca-5a1df3bd6884/download/",
  "medicamentos-por-unidade-de-saude.csv"
)

dados_brutos <- tryCatch({
  read.csv(url_dados, encoding = "UTF-8", sep = ";", stringsAsFactors = FALSE)
}, error = function(e) {
  warning(paste("Erro ao carregar dados:", e$message))
  data.frame()
})

3.2 Descrição da Fonte Original

O dataset “Medicamentos por Unidade de Saúde” é mantido e atualizado pela Secretaria de Saúde do Município do Recife com o objetivo de garantir transparência e controle público sobre o estoque de medicamentos da rede municipal.

# Verificando dimensões e estrutura do dataset original
cat("Dimensões do dataset bruto:\n")
## Dimensões do dataset bruto:
cat("  Linhas  :", nrow(dados_brutos), "\n")
##   Linhas  : 40392
cat("  Colunas :", ncol(dados_brutos), "\n\n")
##   Colunas : 11
cat("Nomes das variáveis originais:\n")
## Nomes das variáveis originais:
print(names(dados_brutos))
##  [1] "distrito"       "bairro"         "unidade"        "classe"        
##  [5] "apresentacao"   "tipo_produto"   "codigo_produto" "produto"       
##  [9] "cadum"          "data_carga"     "quantidade"

Características identificadas no dataset original:

  • Separador decimal: A variável quantidade utiliza vírgula como separador decimal, padrão brasileiro que precisa ser convertido para ponto antes de qualquer operação numérica.
  • Capitalização inconsistente: Variáveis como produto e bairro apresentam mistura de maiúsculas e minúsculas (ex.: "AMOXICILINA" vs "Amoxicilina"), o que geraria duplicatas em agrupamentos diretos.
  • Espaços extras: A variável unidade contém espaços iniciais, finais e duplos internos que precisam ser removidos.
  • Valores ausentes: Registros com campos em branco ("") nas variáveis categóricas e NA após a conversão numérica de quantidade.
  • Variável derivada necessária: Não existe classificação qualitativa do nível de estoque, ou seja, ela precisa ser criada a partir de quantidade.
# Verificando valores ausentes e strings vazias no dataset bruto
ausentes <- data.frame(
  Variavel       = names(dados_brutos),
  NAs            = sapply(dados_brutos, function(x) sum(is.na(x))),
  Strings_Vazias = sapply(dados_brutos, function(x) sum(x == "", na.rm = TRUE))
)

ausentes %>%
  kableExtra::kable(
    caption   = "Tabela 1: Diagnóstico de Valores Ausentes e Strings Vazias",
    col.names = c("Variável", "Alertas de NAs", "Strings Vazias"),
    align     = c("l", "c", "c"), 
    row.names = FALSE
  ) %>%
  kableExtra::kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = FALSE,
    position          = "center"
  ) %>%
  # Formatação condicional exclusiva para a coluna de NAs (Coluna 2)
  kableExtra::column_spec(2, 
    color = ifelse(ausentes$NAs > 0, "#D9534F", "#999999"), 
    bold  = ifelse(ausentes$NAs > 0, TRUE, FALSE)
  ) %>%
  # Formatação condicional exclusiva para Strings Vazias (Coluna 3)
  kableExtra::column_spec(3, 
    color = ifelse(ausentes$Strings_Vazias > 0, "#D9534F", "#999999"), 
    bold  = ifelse(ausentes$Strings_Vazias > 0, TRUE, FALSE)
  )
Tabela 1: Diagnóstico de Valores Ausentes e Strings Vazias
Variável Alertas de NAs Strings Vazias
distrito 4 0
bairro 0 306
unidade 0 0
classe 0 14268
apresentacao 0 0
tipo_produto 0 0
codigo_produto 0 0
produto 0 0
cadum 1756 0
data_carga 0 0
quantidade 0 0

3.3 Limpeza e Preparação dos Dados

As etapas de limpeza seguem um processo lógico: primeiro corrigimos os tipos de dados, depois padronizamos o texto, criamos variáveis derivadas e por fim removemos registros inválidos.

dados_limpos <- dados_brutos %>%

  mutate(
    # ETAPA 1: CONVERSÃO DE TIPOS

    # Como usamos read.csv2, a quantidade já é numérica. 
    # Mantemos a conversão de data:
    data_carga = as.Date(data_carga),

    # ETAPA 2: PADRONIZAÇÃO DE TEXTO

    produto      = str_to_title(produto),
    bairro       = str_to_title(bairro),
    classe       = str_to_title(classe),
    tipo_produto = str_to_title(tipo_produto),
    apresentacao = str_to_upper(apresentacao),
    unidade      = str_squish(unidade),

    # ETAPA 3: REFORMATAR VARIÁVEL

    distrito = paste0("Distrito ", str_pad(distrito, 2, pad = "0")),

    # ETAPA 4: CRIAÇÃO DE VARIÁVEL DERIVADA

    nivel_estoque = case_when(
      quantidade == 0  ~ "Sem Estoque",
      quantidade < 50  ~ "Crítico",
      quantidade < 200 ~ "Regular",
      TRUE             ~ "Adequado"
    )
  ) %>%

  # ETAPA 5: REMOÇÃO DE REGISTROS INVÁLIDOS

  filter(
    !is.na(quantidade),
    !is.na(unidade)      & unidade      != "",
    !is.na(produto)      & produto      != "",
    !is.na(apresentacao) & apresentacao != "",
    !is.na(tipo_produto) & tipo_produto != "",
    !is.na(distrito)     & distrito     != "",
    distrito != "Distrito NA"
  )

3.4 Dataset Final

Após a limpeza, as 10 primeiras linhas do dataset possuem a seguinte estrutura:

# 1. Separamos a amostra para facilitar a formatação condicional das colunas
amostra <- dados_limpos %>%
  head(10) %>%
  select(distrito, bairro, unidade, produto,
         apresentacao, quantidade, nivel_estoque, data_carga)

# 2. Geramos a tabela estilizada
amostra %>%
  kableExtra::kable(
    caption   = "Tabela 2: Amostra do Dataset Final (10 primeiras linhas)",
    col.names = c("Distrito", "Bairro", "Unidade de Saúde", "Medicamento", 
                  "Apres.", "Qtd", "Nível de Estoque", "Data Carga"),
    align     = c("c", "l", "l", "l", "c", "r", "c", "c"), 
    row.names = FALSE,
    format.args = list(big.mark = ".", decimal.mark = ",")
  ) %>%
  kableExtra::kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = TRUE
  ) %>%
  # Formatação condicional para a coluna "Nível de Estoque" (Coluna 7)
  kableExtra::column_spec(7,
    bold  = TRUE,
    color = case_when(
      amostra$nivel_estoque %in% c("Sem Estoque", "Crítico") ~ "#D9534F",
      amostra$nivel_estoque == "Regular"                     ~ "#F0AD4E", 
      TRUE                                                   ~ "#5CB85C"  
    )
  ) %>%
  kableExtra::column_spec(6, bold = TRUE) %>%
  kableExtra::scroll_box(width = "100%")
Tabela 2: Amostra do Dataset Final (10 primeiras linhas)
Distrito Bairro Unidade de Saúde Medicamento Apres. Qtd Nível de Estoque Data Carga
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Bicarbonato De Sodio 10% Solucao Injetavel - 10ml AMP 0,0 Crítico 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Alendronato De Sodio 70mg COMPR 76,0 Adequado 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Alopurinol 300mg COMPR 370,0 Crítico 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Cetoprofeno 50mg/Ml Solucao Injetavel - 2ml AMP 34,0 Crítico 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Acido Acetilsalicilico 100mg COMPR 2580,0 Crítico 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Dipirona 500mg/Ml Solucao Injetavel - 2ml AMP 29,0 Crítico 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Dipirona 500mg COMPR 6630,0 Adequado 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Ibuprofeno 600mg COMPR 1840,0 Crítico 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Paracetamol 500mg COMPR 910,0 Adequado 2024-10-10
Distrito 01 Boa Vista Us 131 Distrito Sanitario I Recife Dipirona 500mg/Ml Solucao Oral - 10ml FRASCO 88,0 Adequado 2024-10-10

3.5 Resumo das Variáveis de Interesse

# Resumo das variáveis categóricas
resumo_categorico <- data.frame(
  Variavel = c(
    "distrito", "bairro", "unidade", "produto",
    "apresentacao", "classe", "tipo_produto", "nivel_estoque"
  ),
  Tipo = c(
    "Categórica", "Categórica", "Categórica", "Categórica",
    "Categórica", "Categórica", "Categórica", "Categórica (derivada)"
  ),
  Categorias_Unicas = c(
    n_distinct(dados_limpos$distrito),
    n_distinct(dados_limpos$bairro),
    n_distinct(dados_limpos$unidade),
    n_distinct(dados_limpos$produto),
    n_distinct(dados_limpos$apresentacao),
    n_distinct(dados_limpos$classe),
    n_distinct(dados_limpos$tipo_produto),
    n_distinct(dados_limpos$nivel_estoque)
  ),
  Exemplos = c(
    paste(head(sort(unique(dados_limpos$distrito)),     3), collapse = ", "),
    paste(head(sort(unique(dados_limpos$bairro)),        3), collapse = ", "),
    paste(head(sort(unique(dados_limpos$unidade)),      2), collapse = ", "),
    paste(head(sort(unique(dados_limpos$produto)),      3), collapse = ", "),
    paste(head(sort(unique(dados_limpos$apresentacao)), 3), collapse = ", "),
    paste(head(sort(unique(dados_limpos$classe)),        3), collapse = ", "),
    paste(unique(dados_limpos$tipo_produto),                 collapse = ", "),
    paste(sort(unique(dados_limpos$nivel_estoque)),          collapse = ", ")
  )
)

resumo_categorico %>%
  kableExtra::kable(
    caption   = "Tabela 3: Resumo Geral das Variáveis Categóricas",
    col.names = c("Variável", "Tipo de Dado", "Categorias Únicas", "Exemplos de Registros"),
    align     = c("l", "l", "c", "l"), 
    row.names = FALSE
  ) %>%
  kableExtra::kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = TRUE
  ) %>%
  kableExtra::column_spec(3, bold = TRUE, color = "#2C3E50")
Tabela 3: Resumo Geral das Variáveis Categóricas
Variável Tipo de Dado Categorias Únicas Exemplos de Registros
distrito Categórica 8 Distrito 01, Distrito 02, Distrito 03
bairro Categórica 71 , Af0gados, Afogados
unidade Categórica 184 HOSPITAL AGAMENON MAGALHAES, Us 101 Policlinica Prof Waldemar de Oliveira
produto Categórica 1020 Acebrofilina Sol. Oral 50mg / 5ml, Acetato De Medroxiprogesterona 5mg, *Acetato De Prednisolona 1% Colirio - 5ml
apresentacao Categórica 24 ADESIVO, AMP, BISN.
classe Categórica 123 , Agente Alcalinizante, Agente Antiressortivo
tipo_produto Categórica 2 Medicamentos, Medicamentos P/ Pacientes Especiais
nivel_estoque Categórica (derivada) 2 Adequado, Crítico
#Garantir tipo numérico
dados_limpos <- dados_limpos %>%
  mutate(
    quantidade = quantidade %>%
      stringr::str_replace_all("\\.", "") %>%
      stringr::str_replace(",", ".")    %>%
      as.numeric()
  )

# Resumo estatístico da variável numérica principal
resumo_numerico <- dados_limpos %>%
  summarise(
    Minimo     = min(quantidade,                na.rm = TRUE),
    Q1         = quantile(quantidade, 0.25,     na.rm = TRUE),
    Mediana    = median(quantidade,             na.rm = TRUE),
    Media      = round(mean(quantidade,         na.rm = TRUE), 2),
    Q3         = quantile(quantidade, 0.75,     na.rm = TRUE),
    Maximo     = max(quantidade,                na.rm = TRUE),
    Desvio_Pad = round(sd(quantidade,           na.rm = TRUE), 2),
    Total      = sum(quantidade,                na.rm = TRUE)
  )

# Tabela Formatada
resumo_numerico %>%
  kableExtra::kable(
    caption     = "Tabela 4: Resumo Estatístico da Variável Quantidade",
    col.names   = c("Mínimo", "1º Quartil (Q1)", "Mediana", "Média",
                    "3º Quartil (Q3)", "Máximo", "Desvio Padrão", "Total Geral"),
    align       = "r",
    row.names   = FALSE,
    format.args = list(big.mark = ".", decimal.mark = ",")
  ) %>%
  kableExtra::kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width        = TRUE
  ) %>%
  kableExtra::column_spec(c(3, 4), bold = TRUE, color = "#2C3E50") %>%
  kableExtra::column_spec(8, bold = TRUE, color = "#337AB7")
Tabela 4: Resumo Estatístico da Variável Quantidade
Mínimo 1º Quartil (Q1) Mediana Média 3º Quartil (Q3) Máximo Desvio Padrão Total Geral
-431 0 0 701,83 70 462.040 5.825,77 28.345.681

O dataset final contém 40388 registros e 12 variáveis, cobrindo 184 unidades de saúde distribuídas em 8 distritos sanitários do Recife. Ao todo, estão catalogados 1020medicamentos distintos. A quantidade média por registro é de 701.8 unidades,com mediana de 0,indicando uma distribuição fortemente assimétrica à direita, ou seja,a maioria dos registros tem quantidades baixas, mas alguns poucos medicamentos concentram estoques muito elevados.

4. Análise Exploratória dos Dados

4.1 Panorama Geral do Estoque Municipal

# Distribuição geral dos níveis de estoque em toda a rede municipal
dist_nivel <- dados_limpos %>%
  count(nivel_estoque) %>%
  mutate(
    pct           = n / sum(n) * 100,
    nivel_estoque = factor(nivel_estoque,
                           levels = c("Sem Estoque", "Crítico","Regular", "Adequado"))
  ) %>%
  arrange(nivel_estoque)

dist_nivel %>%
  kableExtra::kable(
    caption   = "Tabela 5: Distribuição dos registros por nível de estoque",
    col.names = c("Nível de Estoque", "Registros", "Proporção (%)"),
    digits    = 1,
    row.names = FALSE,
    format.args = list(big.mark = ".")
  ) %>%
  kableExtra::kable_styling(
    bootstrap_options = c("striped", "hover")
  ) %>%
  kableExtra::row_spec(
    which(dist_nivel$nivel_estoque %in% c("Sem Estoque", "Crítico")),
    color      = "white",
    background = "#d73027"
  ) %>%
  kableExtra::row_spec(
    which(dist_nivel$nivel_estoque == "Adequado"),
    background = "#d4edda"
  )
Tabela 5: Distribuição dos registros por nível de estoque
Nível de Estoque Registros Proporção (%)
Crítico 34.936 86.5
Adequado 5.452 13.5

Insight: Do total de registros, 86.5% estão em situação crítica ou sem estoque, um sinal de alerta expressivo para os gestores de saúde do Recife, indicando que uma parcela relevante dos medicamentos cadastrados não está disponível para a população no momento da coleta dos dados.


4.2 Nível de Estoque por Distrito Sanitário

# Identifica as 15 classes com maior proporção de registros com estoque zerado
# Filtramos classes com pelo menos 10 registros para garantir representatividade
classes_criticas <- dados_limpos %>%
  group_by(classe) %>%
  summarise(
    total       = n(),
    sem_estoque = sum(nivel_estoque == "Sem Estoque"),
    pct_sem     = sem_estoque / total * 100,
    .groups     = "drop"
  ) %>%
  filter(total >= 10) %>%
  arrange(desc(pct_sem)) %>%
  slice(1:15)

p_classes <- ggplot(
  classes_criticas,
  aes(x    = reorder(classe, pct_sem),
      y    = pct_sem,
      fill = pct_sem,
      text = paste0(classe,
                    "\nSem estoque: ", round(pct_sem, 1), "%",
                    "\nTotal registros: ", total))
) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = paste0(round(pct_sem, 1), "%")),
            hjust = -0.1, size = 3) +
  scale_fill_gradient(low = "#fee08b", high = "#d73027") +
  scale_y_continuous(
    limits = c(0, 105),
    labels = scales::percent_format(scale = 1)
  ) +
  coord_flip() +
  labs(
    title   = "Gráfico 1: Top 15 Classes com Maior % de Estoque Zero",
    x       = "Classe Terapêutica",
    y       = "% de Registros com Estoque Zero",
    caption = "Fonte: Dados Abertos — Prefeitura do Recife"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    plot.title = element_text(size = 12, face = "plain") 
  )

ggplotly(p_classes, tooltip = "text") %>%
  layout(margin = list(t = 60))

Insight: Algumas classes terapêuticas apresentam taxas alarmantes de estoque zero. Isso é especialmente preocupante quando se trata de medicamentos para condições crônicas, onde a interrupção do tratamento pode gerar complicações graves e internações evitáveis.


4.3 Medicamentos Mais Estocados vs. Mais Escassos

# Top 10 medicamentos com maior quantidade total em estoque
top10_maior <- dados_limpos %>%
  group_by(produto) %>%
  summarise(total = sum(quantidade, na.rm = TRUE), .groups = "drop") %>%
  arrange(desc(total)) %>%
  slice(1:10)

# Top 10 medicamentos com mais registros de quantidade zerada
top10_escasso <- dados_limpos %>%
  filter(quantidade == 0) %>%
  count(produto, name = "total") %>%
  arrange(desc(total)) %>%
  slice(1:10)

# Gráfico dos mais estocados
p_maior <- plot_ly(
  top10_maior,
  x = ~total, y = ~reorder(produto, total),
  type        = "bar",
  orientation = "h",
  marker      = list(color = "#1a9850"),
  hovertemplate = "<b>%{y}</b><br>Total em Estoque: %{x:,.0f}<extra></extra>"
) %>%
  layout(
    xaxis = list(title = "Quantidade Total"),
    yaxis = list(title = "")
  )

# Gráfico dos mais escassos
p_escasso <- plot_ly(
  top10_escasso,
  x = ~total, y = ~reorder(produto, total),
  type        = "bar",
  orientation = "h",
  marker      = list(color = "#d73027"),
  hovertemplate = "<b>%{y}</b><br>Registros Zerados: %{x:,.0f}<extra></extra>"
) %>%
  layout(
    xaxis = list(title = "Nº de Registros com Estoque Zero"),
    yaxis = list(title = "") 
  )

# Combina os dois gráficos
subplot(p_maior, p_escasso, nrows = 2, shareX = FALSE, titleY = TRUE, margin = 0.08) %>%
  layout(
    title      = list(text = "Gráfico 2: Medicamentos Mais Estocados vs. Mais Escassos", font = list(size = 14)),
    showlegend = FALSE,
    margin     = list(l = 400, t = 80, b = 60),
    annotations = list(
      list(
        text      = "Fonte: Dados Abertos — Prefeitura do Recife",
        showarrow = FALSE, x = 1, y = -0.15,
        xref      = "paper", yref = "paper",
        font      = list(size = 9)
      ),
      list(
        text      = "<b>Top 10 — Mais Estocados</b>",
        showarrow = FALSE, x = 0.7, y = 1.05,
        xref      = "paper", yref = "paper",
        font      = list(size = 12)
      ),
      list(
        text      = "<b>Top 10 — Mais Escassos</b>",
        showarrow = FALSE, x = 0.7, y = 0.46,
        xref      = "paper", yref = "paper",
        font      = list(size = 12)
      )
    )
  )

Insight: A comparação evidencia um descompasso na gestão de inventário. Enquanto alguns medicamentos acumulam estoques elevados, outros aparecem zerados em diversas unidades simultaneamente, sugerindo falhas na previsão de demanda ou na cadeia de suprimentos municipal.


4.4 Diversidade de Medicamentos Disponíveis por Distrito

# Compara o total de medicamentos catalogados com os efetivamente disponíveis
diversidade <- dados_limpos %>%
  group_by(distrito) %>%
  summarise(
    total_medicamentos       = n_distinct(produto),
    medicamentos_disponiveis = n_distinct(produto[quantidade > 0]),
    pct_disponivel           = round(medicamentos_disponiveis /
                                     total_medicamentos * 100, 1),
    total_unidades           = n_distinct(unidade),
    qtd_total                = sum(quantidade, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(pct_disponivel))

diversidade %>%
  kableExtra::kable(
    caption   = "Tabela 6: Diversidade e disponibilidade por distrito",
    col.names = c("Distrito", "Total Medicamentos",
                  "Medicamentos Disponíveis", "% Disponível",
                  "Unidades de Saúde", "Qtd. Total em Estoque"),
    row.names   = FALSE,
    format.args = list(big.mark = ".", decimal.mark = ",")
  ) %>%
  kableExtra::kable_styling(
    bootstrap_options = c("striped", "hover")
  ) %>%
  kableExtra::column_spec(4,
    color      = "white",
    background = ifelse(diversidade$pct_disponivel >= 70, "#1a9850",
                 ifelse(diversidade$pct_disponivel >= 50, "#fc8d59",
                                                          "#d73027"))
  )
Tabela 6: Diversidade e disponibilidade por distrito
Distrito Total Medicamentos Medicamentos Disponíveis % Disponível Unidades de Saúde Qtd. Total em Estoque
Distrito 08 549 300 54,6 29 2.962.167
Distrito 05 617 328 53,2 27 6.872.712
Distrito 03 650 333 51,2 18 1.500.092
Distrito 02 618 314 50,8 22 2.704.322
Distrito 01 622 300 48,2 22 1.924.346
Distrito 07 525 244 46,5 23 2.266.236
Distrito 06 563 258 45,8 19 5.767.543
Distrito 04 633 285 45,0 24 4.348.263

Insight: Nem todos os distritos têm acesso à mesma diversidade de medicamentos. Distritos com menor percentual disponível podem estar atendendo populações vulneráveis com um portfólio terapêutico reduzido, o que compromete a integralidade do cuidado em saúde.


4.5 Distribuição por Tipo de Apresentação

# Agrupa os 12 tipos de apresentação mais frequentes
dados_apres <- dados_limpos %>%
  count(apresentacao, sort = TRUE) %>%
  mutate(pct = n / sum(n) * 100) %>%
  slice(1:12)

plot_ly(
  dados_apres,
  labels    = ~apresentacao,
  values    = ~n,
  type      = "pie",
  textinfo  = "label+percent",
  hovertemplate = paste0(
    "<b>%{label}</b><br>",
    "Registros: %{value:,.0f}<br>",
    "Proporção: %{percent}<extra></extra>"
  ),
  marker = list(
    colors = RColorBrewer::brewer.pal(
      min(nrow(dados_apres), 12), "Set3"
    )
  )
) %>%
  layout(
    title  = list(
      text = "Gráfico 3: Proporção de Registros por Tipo de Apresentação",
      font = list(size = 14)
    ),
    legend = list(orientation = "v"),
    annotations = list(
      list(
        text      = "Fonte: Dados Abertos Recife",
        showarrow = FALSE, x = 1.1, y = -0.1,
        xref      = "paper", yref = "paper",
        font      = list(size = 9)
      )
    )
  )

Insight: O domínio de determinadas apresentações farmacêuticas (como comprimidos) sobre outras (como soluções injetáveis ou pomadas) reflete o perfil epidemiológico da população atendida e os protocolos terapêuticos adotados pela rede municipal de saúde.