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 medicamentos essenciais.
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.
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:
A abordagem técnica adota uma estratégia orientada a dados
(data-driven) desenvolvida inteiramente em R,
utilizando o paradigma do tidyverse para manipulação de
dados. Para a visualização, foram combinadas abordagens estáticas
(ggplot2, kableExtra) e interativas
(plotly, DT), permitindo uma exploração detalhada do
panorama de abastecimento. 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.
Para garantir que essa modelagem técnica seja de fato adotada, a entrega final foca na clareza e acessibilidade da informação estruturada através da geração de relatórios visuais limpos e diretos. A adoção da solução no dia a dia ocorre porque essas visualizações transformam a complexidade do código R em artefatos de fácil interpretação. Isso democratiza o acesso aos dados, permitindo que gestores e profissionais de saúde sem formação técnica em programação analisem os indicadores visuais para tomar decisões rápidas sobre remanejamento utilizando os recursos oferecidos para explorar detalhes críticos do estoque.
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.
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(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
Os dados utilizados nesta análise foram obtidos do portal de Dados Abertos da Prefeitura do Recife, disponível gratuitamente em:
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()
})
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:
quantidade utiliza vírgula como separador
decimal, padrão brasileiro que precisa ser convertido para ponto antes
de qualquer operação numérica.produto e bairro apresentam mistura de
maiúsculas e minúsculas (ex.: "AMOXICILINA" vs
"Amoxicilina"), o que geraria duplicatas em agrupamentos
diretos.unidade
contém espaços iniciais, finais e duplos internos que precisam ser
removidos."") nas variáveis categóricas e NA após a
conversão numérica de quantidade.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)
)
| 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 |
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"
)
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%")
| 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 |
# 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")
| 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")
| 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 1020 medicamentos 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.
A análise foi estruturada a partir do agrupamento e sumarização dos
dados utilizando o pacote dplyr, tendo como eixo central a
variável categórica nivel_estoque(criada na etapa de
preparação). Para a apresentação visual, adotou-se uma estrutura de
organização em abas (.tabset). Os gráficos foram
inicialmente construídos com a gramática do ggplot2 e, em
seguida, convertidos em visualizações dinâmicas através do pacote
plotly. Para os resumos tabulares, utilizou-se o
kableExtra para garantir formatação limpa e
legibilidade.
Esta exploração permite diagnosticar gargalos de forma pontual, revelando quais classes terapêuticas estão em alerta vermelho, quais distritos possuem acesso restrito à diversidade de medicamentos e quais itens específicos apresentam falhas severas de planejamento.
# 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"
)
| 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.
# 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.
# 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.
# 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"))
)
| 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.
# 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.
Esta análise investigou a distribuição e disponibilidade de medicamentos nas unidades de saúde pública do Recife, partindo da hipótese de que existem desigualdades expressivas entre distritos sanitários e que uma parcela significativa dos medicamentos se encontra em situação crítica de estoque, comprometendo assim o atendimento à população dependente do SUS. Os resultados obtidos são consistentes com a hipótese inicial: o acesso a tratamentos essenciais na rede municipal é marcado por uma severa assimetria geográfica e por um volume preocupante de itens em estado crítico ou zerado. Isso valida o problema logístico e social levantado, evidenciando a urgência de intervenções no abastecimento do SUS municipal para evitar o prejuízo crônico à saúde da população dependente.
Utilizamos dados abertos da Prefeitura do Recife, importados diretamente via URL pública. O processo envolveu:
nivel_estoque a partir de quantidadeggplot2) e interativas (plotly)kableExtra| # | Insight | Implicação |
|---|---|---|
| 1 | 86.5% dos registros estão em nível crítico ou zerado | Indica falha sistêmica na gestão de estoque municipal |
| 2 | Há disparidade significativa entre distritos na % de medicamentos disponíveis | Aponta desigualdade geográfica no acesso à saúde |
| 3 | Algumas classes terapêuticas concentram taxas muito altas de estoque zero | Representa risco direto para pacientes com doenças crônicas |
| 4 | Existe descompasso entre os medicamentos mais estocados e os mais escassos | Sugere ineficiência na previsão e distribuição de demanda |
| 5 | A diversidade de medicamentos disponíveis varia consideravelmente entre distritos | Compromete a integralidade do cuidado em diferentes regiões |
Secretaria Municipal de Saúde do Recife: deve priorizar o reabastecimento nos distritos e classes terapêuticas com maiores índices de estoque zero, especialmente medicamentos para doenças crônicas.
Coordenadores de Distrito Sanitário: podem usar os rankings desta análise para negociar cotas maiores de abastecimento junto à central de distribuição municipal.
Cidadãos e organizações da sociedade civil: têm subsídios concretos para cobrar transparência e melhora na gestão farmacêutica municipal, com dados objetivos por unidade e distrito.
Pesquisadores de Saúde Pública: este dataset pode ser cruzado com dados demográficos e epidemiológicos para análises mais aprofundadas de equidade no acesso a medicamentos.
| Limitação | Impacto |
|---|---|
| Dados representam um snapshot único no tempo | Não é possível avaliar tendências temporais de melhora ou piora no abastecimento. |
| Ausência de dados sobre demanda ou consumo | Um estoque classificado como ‘Adequado’ pode ser insuficiente se a demanda local for muito alta. |
| Limiares de nivel_estoque definidos arbitrariamente | A categorização pode não refletir as regras de negócio ou as necessidades reais de cada medicamento. |
| Sem informações sobre reposição programada | Estoques baixos ou zerados podem indicar um reabastecimento iminente que não é capturado pelo sistema. |
Para trabalhos futuros, sugere-se primeiramente a incorporação de
séries históricas que permitam superar a limitação do snapshot único,
possibilitando a análise de tendências temporais e padrões de
sazonalidade no abastecimento. A limitação relacionada à ausência de
dados de demanda poderia ser endereçada pelo cruzamento com registros de
prescrição médica e dispensação, permitindo avaliar se o estoque
disponível é de fato suficiente para a demanda real de cada unidade.
Quanto aos limiares arbitrários utilizados na classificação do
nivel_estoque, uma melhoria relevante seria calibrá-los em
conjunto com farmacêuticos e gestores da rede municipal, de modo a
refletir as necessidades reais de cada classe terapêutica. Por fim, a
integração com o sistema de reposição programada da Secretaria de Saúde
eliminaria a ambiguidade de registros zerados, permitindo distinguir
escassez real de reabastecimento iminente. De forma complementar, a
aplicação de modelos de clustering poderia agrupar unidades com perfis
similares de estoque, e o desenvolvimento de um sistema automatizado de
alertas tornaria o monitoramento contínuo e proativo, substituindo a
análise pontual por uma ferramenta de gestão em tempo real.
Análise desenvolvida com R 4.2.2 | Dados: Portal de Dados Abertos da Prefeitura do Recife | Relatório gerado em: 09/06/2026