1. Introdução

1.1 Introdução e definição do problema

Os eventos adversos relacionados a medicamentos e vacinas representam um importante desafio para a saúde pública, tornando a farmacovigilância essencial para monitorar riscos e garantir maior segurança aos pacientes. Nesse contexto, o sistema VigiMed, implantado pela ANVISA em 2018, disponibiliza dados sobre notificações de suspeitas de eventos adversos registradas por profissionais de saúde, pacientes e instituições. Este projeto busca analisar essas notificações nos estados do Nordeste brasileiro, região escolhida devido às suas particularidades socioeconômicas, demográficas e estruturais, que podem influenciar tanto a ocorrência quanto o registro desses eventos. A análise pretende identificar padrões e tendências relacionados aos medicamentos, vacinas e características das notificações, contribuindo para uma melhor compreensão do cenário de farmacovigilância na região.

1.2 Metodologia e abordagem do estudo

O estudo utilizará a base de dados aberta de farmacovigilância da ANVISA, contendo registros de notificações de eventos adversos relacionados a medicamentos e vacinas no sistema VigiMed. Inicialmente, os dados passarão por etapas de limpeza, organização e seleção das variáveis mais relevantes para a análise. Em seguida, serão aplicadas técnicas de análise exploratória de dados e estatística descritiva para investigar padrões, frequências, distribuição temporal e diferenças entre os estados do Nordeste. Além disso, serão utilizadas visualizações gráficas e tabelas comparativas para facilitar a interpretação dos resultados e identificar tendências relevantes no comportamento das notificações. O conjunto de dados utilizado no estudo contém informações sobre as notificações de suspeitas de eventos adversos a medicamentos e vacinas. Os dados abertos em Farmacovigilância permitem a visualização das notificações de suspeitas de eventos adversos recebidas pelo VigiMed, sistema implantado pela Gerência de Farmacovigilância (ANVISA) em 2018.

1.3 Abordagem proposta e técnica adotada

A abordagem proposta neste estudo baseia-se na aplicação de técnicas de análise exploratória de dados e estatística descritiva para compreender o comportamento das notificações de eventos adversos registradas no sistema VigiMed nos estados do Nordeste brasileiro. A partir do tratamento e organização da base de dados, serão utilizadas ferramentas de visualização e análise para identificar padrões de ocorrência, frequência de notificações, tendências temporais e possíveis associações entre medicamentos, vacinas e os eventos adversos reportados. Essa técnica possibilita uma visão ampla do cenário de farmacovigilância na região, permitindo detectar características relevantes das notificações e contribuir para uma interpretação mais detalhada dos fatores que influenciam os registros de eventos adversos.

1.4 Contribuições e utilidade da análise

A análise desenvolvida poderá auxiliar diferentes públicos interessados na área da saúde, incluindo gestores públicos, profissionais de saúde, pesquisadores e órgãos reguladores, fornecendo informações relevantes sobre o perfil das notificações de eventos adversos no Nordeste brasileiro. Os resultados obtidos podem contribuir para o fortalecimento das ações de farmacovigilância, apoiar a formulação de políticas públicas mais direcionadas e auxiliar na identificação de possíveis riscos relacionados ao uso de medicamentos e vacinas. Além disso, a compreensão das tendências e padrões presentes nos registros pode favorecer estratégias de prevenção, monitoramento e conscientização, promovendo maior segurança para a população e melhoria na qualidade dos serviços de saúde.


2. Pacotes Requeridos

O Tidyverse é uma coleção popular de pacotes R projetados especificamente para ciência de dados. Ele fornece um conjunto padronizado de ferramentas para importar, limpar (organizar), manipular e visualizar dados, tornando o código muito mais fácil de ler, escrever

O Lubridate é um pacote R amplamente utilizado que facilita muito o trabalho com datas e horas. Ele simplifica a análise de strings inconsistentes, a extração de componentes individuais de data e hora e a execução de operações matemáticas precisas, levando em consideração anos bissextos e o horário de verão.

O knitr é um pacote que permite a geração dinâmica de relatórios em R Markdown, integrando código R, resultados e texto em um único documento reprodutível. Ele é responsável por executar os chunks de código e incorporar automaticamente tabelas, gráficos e saídas no relatório final.

O plotly permite criar gráficos de alta qualidade com poucos comandos. Diferentemente de bibliotecas estáticas como ggplot2, os gráficos gerados pelo Plotly são interativos por padrão — permitindo zoom, hover com informações detalhadas, filtros por legenda e exportação direta.

O DT é um pacote que fornece uma interface interativa para visualização de tabelas no R, baseada na biblioteca DataTables do JavaScript. Ele permite paginação, busca, ordenação e rolagem (scroll), sendo muito útil para explorar grandes bases de dados diretamente em relatórios HTML.

O crosstalk é um pacote que permite a comunicação e a interatividade entre diferentes componentes de visualização em relatórios e aplicações web desenvolvidas em R. Ele possibilita que tabelas, gráficos e outros widgets compartilhem informações, permitindo filtragem e seleção sincronizadas sem a necessidade de programação adicional em JavaScript.

O skimr é um pacote voltado para a exploração inicial de dados, fornecendo resumos estatísticos completos e organizados para variáveis numéricas, categóricas, textuais e outros tipos de dados. Ele facilita a compreensão da estrutura do conjunto de dados ao apresentar informações como valores ausentes, medidas de tendência central, dispersão e distribuição de forma clara e padronizada.

library(tidyverse)
library(lubridate)
library(knitr)
library(DT)
library(plotly)
library(skimr)
library(crosstalk)

3. Preparação dos dados

3.1 Origem

O conjunto de dados Notificações em Farmacovigilância foi disponibilizado pela Anvisa, como parte do Programa de Dados Abertos do Governo Federal. Nesta análise será utilizado o arquivo disposto no link como “VigiMed_Notificacoes.csv”.


3.2 Descrição da fonte de dados

A base de dados utilizada neste estudo é proveniente do sistema VigiMed, plataforma de farmacovigilância implementada pela ANVISA em 2018 para registrar notificações de suspeitas de eventos adversos relacionados a medicamentos e vacinas. O objetivo original desses dados é apoiar o monitoramento da segurança de medicamentos e vacinas no Brasil, permitindo identificar possíveis riscos e auxiliar ações de vigilância sanitária. A base contempla todas as notificações consideradas válidas, contendo, no mínimo, informações sobre o notificador, identificação do paciente, descrição do evento adverso e nome do medicamento suspeito. Os dados são obtidos a partir do preenchimento de formulários realizados por profissionais de saúde, pacientes, cidadãos e instituições, sendo a qualidade e completude das informações dependentes do notificador. Como apenas alguns campos são obrigatórios, a base apresenta muitos valores ausentes, registros incompletos ou campos sinalizados como “Não informados”, característica importante que deve ser considerada durante o tratamento e análise dos dados.

3.3 Etapas de importação e limpeza de dados

Os dados são lidos a partir de um arquivo CSV com separador ; e codificação Latin1. Em seguida, o dataset é filtrado para manter apenas as notificações dos nove estados do Nordeste.

df <- read.csv(
  "C:/Workspace/CPAD_PROJ_02/VigiMed_Notificacoes.csv",
  sep          = ";",
  fileEncoding = "Latin1",
  header       = TRUE
)

estados_nordeste <- c("AL", "MA", "PI", "PE", "PB", "RN", "CE", "BA", "SE")

df <- df %>%
  filter(UF %in% estados_nordeste)

3.3.1 Verificação de Valores Ausentes

Verificando a ocorrência de valores ausentes em todo o conjunto de dados.

colMeans(is.na(df))
#>                               UF             TIPO_ENTRADA_VIGIMED 
#>                                0                                0 
#>                      RECEBIDO_DE        IDENTIFICACAO_NOTIFICACAO 
#>                                0                                0 
#>            DATA_INCLUSAO_SISTEMA          DATA_ULTIMA_ATUALIZACAO 
#>                                0                                0 
#>                 DATA_NOTIFICACAO                 TIPO_NOTIFICACAO 
#>                                0                                0 
#>         NOTIFICACAO_PARENT_CHILD                  DATA_NASCIMENTO 
#>                                0                                0 
#>             IDADE_MOMENTO_REACAO                      GRUPO_IDADE 
#>                                0                                0 
#> IDADE_GESTACIONAL_MOMENTO_REACAO                             SEXO 
#>                                0                                0 
#>                         GESTANTE                         LACTANTE 
#>                                0                                0 
#>                          PESO_KG                        ALTURA_CM 
#>                                0                                0 
#>     REACAO_EVENTO_ADVERSO_MEDDRA                            GRAVE 
#>                                0                                0 
#>                        GRAVIDADE                         DESFECHO 
#>                                0                                0 
#>                 DATA_INICIO_HORA                  DATA_FINAL_HORA 
#>                                0                                0 
#>                          DURACAO       RELACAO_MEDICAMENTO_EVENTO 
#>                                0                                0 
#>         NOME_MEDICAMENTO_WHODRUG                     ACAO_ADOTADA 
#>                                0                                0 
#>                      NOTIFICADOR 
#>                                0

Não foram encontrados NAs nativos. Entretanto, diversas colunas possuem strings vazias ("") ou o literal "None", que serão tratados nas etapas seguintes.


3.3.2 Tratanto Variáveis Categóricas

Tipo de Entrada VigiMed

Tratamento das variáveis categoricas: TIPO_ENTRADA_VIGIMED, RECEBIDO_DE e IDENTIFICACAO_NOTIFICACAO, esta última é o identificador da notificação então é esperado que não haja nenhum valor faltante.

table(df$TIPO_ENTRADA_VIGIMED)
#> 
#>                        Empresas Farmacêuticas      Serviços de Saúde 
#>                      4                      2                  30810 
#>  Serviços de Vacinação             VigiMobile 
#>                     93                   1162
df$TIPO_ENTRADA_VIGIMED <- dplyr::if_else(
  df$TIPO_ENTRADA_VIGIMED == "",
  "Não informado",
  df$TIPO_ENTRADA_VIGIMED
)

Recebido De

table(df$RECEBIDO_DE)
#> 
#>                                                                                                                                    
#>                                                                                                                                717 
#>                                                                                                              Autoridade Reguladora 
#>                                                                                                                                  1 
#>                                                                                               Centro Regional de Farmacovigilância 
#>                                                                                                                               6666 
#>                                                                                                               Empresa Farmacêutica 
#>                                                                                                                                120 
#>                                                                                                                               None 
#>                                                                                                                               2287 
#> Outro (p.ex. Distribuidora, Financiador de Estudo, Organização Representativa para Pesquisa Clínica, ou Organização não-Comercial) 
#>                                                                                                                                 21 
#>                                                                                                                Paciente/Consumidor 
#>                                                                                                                                162 
#>                                                                                                              Profissional de Saúde 
#>                                                                                                                              22097
df$RECEBIDO_DE <- dplyr::if_else(
  df$RECEBIDO_DE == "" | df$RECEBIDO_DE == "None",
  "Não informado",
  df$RECEBIDO_DE
)

Identificação da Notificação

any(!nzchar(df$IDENTIFICACAO_NOTIFICACAO))
#> [1] FALSE

Sem strings vazias — coluna íntegra.

Não houve necessidade de tratamento na coluna IDENTIFICACAO_NOTIFICACAO, já para as demais, strings vazias e as contendo “None”, foi necessária substituição pelo termo “Não informado”.


3.3.3 Padronização de Datas

Todas as colunas de data no formato AAAAMMDD são convertidas para o tipo Date do R após remoção de espaços.

converter_data <- function(x) {
  as.Date(trimws(x), format = "%Y%m%d")
}

df$DATA_INCLUSAO_SISTEMA   <- converter_data(df$DATA_INCLUSAO_SISTEMA)
df$DATA_ULTIMA_ATUALIZACAO <- converter_data(df$DATA_ULTIMA_ATUALIZACAO)
df$DATA_NOTIFICACAO        <- converter_data(df$DATA_NOTIFICACAO)
df$DATA_NASCIMENTO         <- converter_data(df$DATA_NASCIMENTO)

# Verificando os valores:
summary(df$DATA_INCLUSAO_SISTEMA)
#>         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
#> "2019-05-14" "2022-03-25" "2023-10-16" "2023-08-04" "2025-01-31" "2026-04-19"
summary(df$DATA_ULTIMA_ATUALIZACAO)
#>         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
#> "2019-01-03" "2022-03-25" "2023-10-16" "2023-08-04" "2025-01-31" "2026-04-19" 
#>         NA's 
#>         "16"
summary(df$DATA_NOTIFICACAO)
#>         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
#> "0020-09-17" "2021-12-18" "2023-07-12" "2022-12-03" "2024-10-28" "2026-04-19" 
#>         NA's 
#>       "3571"
summary(df$DATA_NASCIMENTO)
#>         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
#> "0049-03-10" "1959-08-24" "1979-03-03" "1979-02-04" "1997-07-04" "2026-04-05" 
#>         NA's 
#>       "5498"
df <- df %>%
  mutate(
    DATA_NOTIFICACAO = if_else(
      year(DATA_NOTIFICACAO) < 2018,
      as.Date(NA),
      DATA_NOTIFICACAO
    ),
    
    DATA_NASCIMENTO = if_else(
      year(DATA_NASCIMENTO) < 1900,
      as.Date(NA),
      DATA_NASCIMENTO
    )
  )

Foi verificado que DATA_NOTIFICACAO e DATA_NOTIFICACAO tem valores mínimos impossíveis, portanto foi necesária correção, utilizado como parâmetro o anos abaixo 1900 para nascimento e abaixo de 2018 (ano que o uso do sistema foi iniciado) para notificação.

3.3.4 Colunas com Múltiplos Formatos de Data/Hora

As colunas DATA_INICIO_HORA e DATA_FINAL_HORA apresentam registros em diferentes formatos. A função parse_date_time() do lubridate lida com essa heterogeneidade.

parsear_datetime <- function(coluna) {
  x <- trimws(coluna)
  x[x == ""] <- NA
  parse_date_time(x, orders = c("Ymd HMS", "Ymd", "HMS", "Ym", "Y"))
}

df$DATA_INICIO_HORA <- parsear_datetime(df$DATA_INICIO_HORA)
df$DATA_FINAL_HORA  <- parsear_datetime(df$DATA_FINAL_HORA)

summary(df$DATA_INICIO_HORA)
#>                  Min.               1st Qu.                Median 
#> "0000-01-01 14:30:00" "2022-03-27 00:00:00" "2023-09-14 00:00:00" 
#>                  Mean               3rd Qu.                  Max. 
#> "2022-09-08 16:57:15" "2025-01-22 00:00:00" "2026-04-19 00:00:00" 
#>                  NA's 
#>               "10886"
summary(df$DATA_FINAL_HORA)
#>                  Min.               1st Qu.                Median 
#> "0000-01-01 08:45:00" "2022-04-20 00:00:00" "2023-10-13 00:00:00" 
#>                  Mean               3rd Qu.                  Max. 
#> "2022-02-24 09:29:15" "2025-02-26 04:42:30" "2026-04-19 00:00:00" 
#>                  NA's 
#>               "16175"
df <- df %>%
  mutate(
    DATA_INICIO_HORA = if_else(
      year(DATA_INICIO_HORA) < 1900,
      as.POSIXct(NA),
      DATA_INICIO_HORA
    ),
    
    DATA_FINAL_HORA = if_else(
      year(DATA_FINAL_HORA) < 1900,
      as.POSIXct(NA),
      DATA_FINAL_HORA
    )
  )

Aqui também vemos que existem valores mínimos irreais que precisam ser corrigidos.


3.3.5 Tipo de Notificação

table(df$TIPO_NOTIFICACAO)
#> 
#> Não disponível pelo notificador (desconhecido) 
#>                                             65 
#>                          Notificação de estudo 
#>                                            467 
#>                         Notificação espontânea 
#>                                          29035 
#>                                          Outro 
#>                                           2504

Sem tratativa necessária.


3.3.6 Coluna Parent/Child

Esta variável indica notificações relacionadas ao uso de medicamento pelo pai/mãe cujo evento ocorreu no filho/feto. Devido à baixíssima representatividade dos casos positivos, a coluna foi excluída.

table(df$NOTIFICACAO_PARENT_CHILD)
#> 
#>        None   Sim 
#> 24951  7086    34
df <- df %>%
  select(-NOTIFICACAO_PARENT_CHILD)

3.3.7 Idade em Anos

A coluna IDADE_MOMENTO_REACAO armazena a idade de forma textual (ex.: "32 anos", "6 meses"). O processo abaixo extrai o valor numérico e a unidade, convertendo tudo para anos decimais.

df <- df %>%
  mutate(
    idade_valor   = as.numeric(str_extract(IDADE_MOMENTO_REACAO, "\\d+")),
    idade_unidade = str_extract(
      IDADE_MOMENTO_REACAO,
      "ano|década|mês|semana|dia|hora"
    )
  )
df <- df %>%
  mutate(
    IDADE_FINAL = case_when(
      idade_unidade == "ano"     ~ idade_valor,
      idade_unidade == "mês"    ~ idade_valor / 12,
      idade_unidade == "semana"  ~ idade_valor / 52.25,
      idade_unidade == "dia"    ~ idade_valor / 365.25,
      idade_unidade == "hora"   ~ idade_valor / (24 * 365.25),
      idade_unidade == "década"  ~ idade_valor * 10,
      TRUE                      ~ NA_real_
    ),
    # Valores >= 120 anos removidos (entrada provavelmente incorreta)
    IDADE_FINAL = if_else(IDADE_FINAL >= 120, NA_real_, IDADE_FINAL)
  )

A coluna apresentava os valores de idade em diferentes unidades (ano,década,mês,semana,dia,hora), fazendo-se necessária a conversão e padronização em anos. Além disso, valores biológicamente impossíveis foram excluídos.

3.3.8 Grupo Idade

df$GRUPO_IDADE <- dplyr::if_else(
  df$GRUPO_IDADE == "" | df$GRUPO_IDADE == "None",
  "Não informado",
  df$GRUPO_IDADE
)

table(df$GRUPO_IDADE)
#> 
#>   Adolescente        Adulto       Criança          Feto         Idoso 
#>           734         11681          1068             3          6367 
#>      Infantil Não informado       Neonato 
#>           743         11118           357

3.3.8.1 Consistência entre Grupo e Idade

df %>%
  group_by(GRUPO_IDADE) %>%
  summarise(
    maxima = max(IDADE_FINAL, na.rm = TRUE),
    minima = min(IDADE_FINAL, na.rm = TRUE)
  )

Foram identificadas inconsistências entre a faixa etária declarada e a idade calculada.

df %>%
  summarise(
    total = n(),
    idade_disponivel = sum(!is.na(IDADE_FINAL)),
    apenas_grupo = sum(is.na(IDADE_FINAL) &
                         !is.na(GRUPO_IDADE) &
                         GRUPO_IDADE != "" &
                         GRUPO_IDADE != "None")
  )

Com o resultado acima, é prudente adotar a variável IDADE_FINAL para análises etárias, enquanto a variável GRUPO_IDADE será tratata como complementar, porque recupera informação para 17,4% da base que não têm idade exata.


3.3.9 Coluna Idade gestacional

Apresenta quase exclusivamente valores ausentes ou "None", portanto foi excluída da análise.

table(df$IDADE_GESTACIONAL_MOMENTO_REACAO)
#> 
#>        None 
#> 12671 19400
df <- df %>%
  select(-IDADE_GESTACIONAL_MOMENTO_REACAO)

3.3.10 Gestante e Lactante

Verificação de valores únicos.

#GESTANTE
table(df$GESTANTE)
#> 
#>        Não    Sim 
#> 12591 19161   319
unique(df$GESTANTE)
#> [1] ""     "Sim"  "Não "
df$GESTANTE <- trimws(df$GESTANTE)
df$GESTANTE[df$GESTANTE==""] <- "Não informado"

#LACTANTE
table(df$LACTANTE)
#> 
#>        Não    Sim 
#> 12499 18979   593
unique(df$LACTANTE)
#> [1] ""     "Sim"  "Não "
df$LACTANTE <- trimws(df$LACTANTE)
df$LACTANTE[df$LACTANTE==""] <- "Não informado"

Apenas necessária a substituição das strings vazias, e remoção do espaço no final da string do valor nominal “Não”.


3.3.11 Medidas - Peso e Altura

Strings inválidas são convertidas para NA e outliers extremos são removidos.

Foram considerados valores extremos aqueles acima de Q3 + 3×IQR, critério comumente uttilizado para identificar erros de registro ou observações biologicamente implausíveis.

Peso (kg)

df <- df %>%
  mutate(
    PESO_KG = case_when(
      PESO_KG %in% c("", "None") ~ NA_character_,
      TRUE ~ PESO_KG
    ),
    PESO_KG = as.numeric(PESO_KG)
  )

Q3_peso <- quantile(df$PESO_KG, 0.75, na.rm = TRUE)
IQR_peso <- IQR(df$PESO_KG, na.rm = TRUE)

df <- df %>%
  filter(PESO_KG <= Q3_peso + 3 * IQR_peso | is.na(PESO_KG))

summary(df$PESO_KG)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
#>   0.264  51.000  63.000  61.225  75.225 150.000   22068

Inicialmente, os valores da coluna PESO_KG estavam no formato ‘chr’, sendo necessária conversão para o tipo numérico. Os valores faltantes, bem como os irreais, foram substituídos por NA.

df %>%
  ggplot(aes(x = GRUPO_IDADE, y = PESO_KG)) +
  geom_boxplot(outlier.shape = NA, fill = "#4E91C0", alpha = 0.6) +
  labs(title = "Peso (kg) por Grupo de Idade", x = "Grupo de Idade", y = "Peso (kg)") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 30, hjust = 1))
Distribuição do Peso por Grupo de Idade

Distribuição do Peso por Grupo de Idade

Altura (cm)

df <- df %>%
  mutate(
    ALTURA_CM = case_when(
      ALTURA_CM %in% c("", "None") ~ NA_character_,
      TRUE ~ ALTURA_CM
    ),
    ALTURA_CM = as.numeric(ALTURA_CM)
  )

Q3_alt  <- quantile(df$ALTURA_CM, 0.75, na.rm = TRUE)
IQR_alt <- IQR(df$ALTURA_CM, na.rm = TRUE)

df <- df %>%
  filter(ALTURA_CM <= Q3_alt + 3 * IQR_alt | is.na(ALTURA_CM))

summary(df$ALTURA_CM)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
#>     0.0   154.0   160.0   157.7   168.0   197.0   25201

Os valores de altura seguiram o mesmo tratamento da coluna PESO_KG.

df %>%
  ggplot(aes(x = GRUPO_IDADE, y = ALTURA_CM)) +
  geom_boxplot(outlier.shape = NA, fill = "#6BBF8E", alpha = 0.6) +
  labs(title = "Altura (cm) por Grupo de Idade", x = "Grupo de Idade", y = "Altura (cm)") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 30, hjust = 1))
Distribuição da Altura por Grupo de Idade

Distribuição da Altura por Grupo de Idade

Mesmo após os ajustes utilizando o método IQR alguns valores continuaram fora da realidade, pesos <1 ou altura = 0 por isso precisamos ajustar:

df <- df %>%
  mutate(
    PESO_KG = if_else(
      PESO_KG < 1,
      NA_real_,
      PESO_KG
    ),
    
    ALTURA_CM = if_else(
      ALTURA_CM < 30,
      NA_real_,
      ALTURA_CM
    )
  )

3.3.12 Reação / Evento Adverso (MedDRA)

sum(df$REACAO_EVENTO_ADVERSO_MEDDRA == "", na.rm = TRUE)
#> [1] 18
sum(df$REACAO_EVENTO_ADVERSO_MEDDRA == "None", na.rm = TRUE)
#> [1] 4
df %>%
  count(REACAO_EVENTO_ADVERSO_MEDDRA, sort = TRUE) %>%
  slice_head(n = 10)
df$REACAO_EVENTO_ADVERSO_MEDDRA <- dplyr::if_else(
  df$REACAO_EVENTO_ADVERSO_MEDDRA == "" | df$REACAO_EVENTO_ADVERSO_MEDDRA == "None",
  "Não informado",
  df$REACAO_EVENTO_ADVERSO_MEDDRA
)

Esta coluna possui um total de 2297 valores únicos. Será necessário tratar apenas os valores ausentes e aqueles correspondentes à “None”, que são poucos.


3.3.13 Gravidade e Desfecho

df %>%
  select(GRAVE, DESFECHO, GRAVIDADE, SEXO) %>%
  head(10) %>%
  kable()
GRAVE DESFECHO GRAVIDADE SEXO
Não Recuperado Feminino
Sim Recuperado Ameaça à vida Feminino
Não Recuperado Masculino
Não Recuperado Feminino
Não Recuperado Masculino
Não Recuperado Feminino
Não Em recuperação Masculino
Sim Recuperado Hospitalização Masculino
Sim Em recuperação Hospitalização Masculino
Não Recuperado Feminino
table(df$GRAVE)
#> 
#>         Não  None   Sim 
#>  9185 15996  1605  5142
df$GRAVE <- dplyr::if_else(
  df$GRAVE == "" | df$GRAVE == "None",
  "Não informado", df$GRAVE
)

table(df$GRAVIDADE)
#> 
#>                                             
#>                                       23275 
#>                               Ameaça à vida 
#>                                        1409 
#> Anomalia congênita ou malformação ao nascer 
#>                                           2 
#>                              Hospitalização 
#>                                        1423 
#>   Incapacidade persistente ou significativa 
#>                                         194 
#>                                        None 
#>                                        3444 
#>     Outro efeito clinicamente significativo 
#>                                        2029 
#>                           Resultou em óbito 
#>                                         152
df$GRAVIDADE <- dplyr::if_else(
  df$GRAVIDADE == "" | df$GRAVIDADE == "None",
  "Não informado", df$GRAVIDADE
)

table(df$DESFECHO)
#> 
#>                  Desconhecido Em recuperação          Fatal Não Recuperado 
#>           7596            841           2919            222            625 
#>           None     Recuperado 
#>           1354          18371
df$DESFECHO <- dplyr::if_else(
  df$DESFECHO == "" | df$DESFECHO == "None",
  "Desconhecido", df$DESFECHO
)

table(df$SEXO)
#> 
#>              Desconhecido     Feminino    Masculino         None 
#>          456          101        19314        11384          673
df$SEXO <- dplyr::if_else(
  df$SEXO == "" | df$SEXO == "None",
  "Desconhecido", df$SEXO
)

Para as colunas SEXO, DESFECHO, GRAVIDADE e GRAVE é necessário tratar apenas os valores ausentes e aqueles correspondentes à “None”.


3.3.14 Duração em Dias

A coluna DURACAO armazena valores com unidades variadas (ex.: "3 dias", "2 semanas"). Todas as durações precisam ser convertidas para dias e valores impossíveis são removidos, como os casos em que a duração do efeito é superior a própria idade do paciente.

df <- df %>%
  mutate(
    DURACAO         = tolower(trimws(DURACAO)),
    duracao_valor   = as.numeric(str_extract(DURACAO, "\\d+\\.?\\d*")),
    duracao_unidade = str_extract(DURACAO, "segundo|minuto|hora|dia|semana|mês|mes|ano")
  )

df <- df %>%
  mutate(
    DURACAO_DIA = case_when(
      duracao_unidade == "segundo"           ~ duracao_valor / 86400,
      duracao_unidade == "minuto"            ~ duracao_valor / 1440,
      duracao_unidade == "hora"              ~ duracao_valor / 24,
      duracao_unidade == "dia"               ~ duracao_valor,
      duracao_unidade == "semana"            ~ duracao_valor * 7,
      duracao_unidade %in% c("mês", "mes")  ~ duracao_valor * 30,
      duracao_unidade == "ano"               ~ duracao_valor * 365,
      TRUE                                   ~ NA_real_
    ),
    DURACAO_DIA = if_else(
      DURACAO_DIA <= 0 |
        (!is.na(IDADE_FINAL) & DURACAO_DIA / 365 > IDADE_FINAL),
      NA_real_,
      DURACAO_DIA
    )
  )

3.3.15 Demais Variáveis

df %>%
  select(NOME_MEDICAMENTO_WHODRUG, ACAO_ADOTADA, NOTIFICADOR) %>%
  head(15) %>%
  kable()
NOME_MEDICAMENTO_WHODRUG ACAO_ADOTADA NOTIFICADOR
Acido acetilsalicilico Redução da dose Farmacêutico
Clozapin Retirada do medicamento Farmacêutico
Metoclopramida Retirada do medicamento Farmacêutico
Prednisona Sem alteração da dose Farmacêutico
Vancomicina Retirada do medicamento Farmacêutico
Fitomenadiona Retirada do medicamento Farmacêutico
Rifampicin Farmacêutico
Penicilina g potassica Retirada do medicamento Farmacêutico
Dapsona Retirada do medicamento Farmacêutico
Metilprednisolona Farmacêutico
Polimixina B Farmacêutico
Neocaina Aumento da dose Médico
Enoxaparina Sodica Retirada do medicamento Farmacêutico
Soro fisiologico Outro profissional de saúde
Ocitocina Outro profissional de saúde
df$NOME_MEDICAMENTO_WHODRUG <- dplyr::if_else(
  df$NOME_MEDICAMENTO_WHODRUG == "" | df$NOME_MEDICAMENTO_WHODRUG == "None",
  "Não informado", df$NOME_MEDICAMENTO_WHODRUG
)

df$ACAO_ADOTADA <- dplyr::if_else(
  df$ACAO_ADOTADA == "" | df$ACAO_ADOTADA == "None",
  "Não informado", df$ACAO_ADOTADA
)

df$NOTIFICADOR <- dplyr::if_else(
  df$NOTIFICADOR == "" | df$NOTIFICADOR == "None",
  "Não informado", df$NOTIFICADOR
)

Para as colunas NOME_MEDICAMENTO_WHODRUG, ACAO_ADOTADA e NOTIFICADOR é necessário tratar apenas os valores ausentes e aqueles correspondentes à “None”.


3.3.16 Exclusão de colunas temporárias

As colunas idade_valor, ìdade_unidade, duracao_valor e duracao_unidade foram criadas como intermediários para obtenção dos valores das colunas IDADE_FINAL e DURACAO_DIA, e como já cumpriram seu propósito, serão excluídas.

df <- df %>%
  select(-idade_valor, -idade_unidade,
         -duracao_valor, -duracao_unidade)

3.3.17 Conversão para factors

df <- df %>%
  mutate(
    UF = as.factor(UF),
    RECEBIDO_DE = as.factor(RECEBIDO_DE),
    TIPO_ENTRADA_VIGIMED = as.factor(TIPO_ENTRADA_VIGIMED),
    TIPO_NOTIFICACAO = as.factor(TIPO_NOTIFICACAO),
    GRUPO_IDADE = as.factor(GRUPO_IDADE),
    SEXO = as.factor(SEXO),
    GRAVE = as.factor(GRAVE),
    GRAVIDADE = as.factor(GRAVIDADE),
    DESFECHO = as.factor(DESFECHO),
    RELACAO_MEDICAMENTO_EVENTO = as.factor(RELACAO_MEDICAMENTO_EVENTO),
    ACAO_ADOTADA = as.factor(ACAO_ADOTADA),
    NOTIFICADOR = as.factor(NOTIFICADOR)
  )

3.4 Conjunto de dados final

Resumo das Transformações

Etapa Ação
Filtragem regional Mantidos apenas os 9 estados do Nordeste
Strings inválidas "" e "None" substituídos por "Não informado" / "Desconhecido"
Datas simples Convertidas de AAAAMMDD para Date
Datas heterogêneas Parseadas com parse_date_time() (múltiplos formatos)
NOTIFICACAO_PARENT_CHILD Excluída (baixa representatividade)
IDADE_GESTACIONAL_MOMENTO_REACAO Excluída (>95% ausente)
Idade Extraída e convertida para anos decimais; valores ≥ 120 anos removidos
Peso e Altura Outliers removidos pelo critério Q3 + 3×IQR
Duração Unidades diversas convertidas para dias; valores impossíveis removidos

Dataframe compacto:

datatable(
  df,
  options = list(
    scrollX = TRUE,
    scrollY = "350px",
    pageLength = 5,
    lengthChange = TRUE
  ),
  class = "compact stripe"
)

3.5 Dicionário de dados

Variável Descrição
UF Unidade da Federação responsável pela notificação do evento adverso
TIPO_ENTRADA_VIGIMED Interface de entrada da informação no VigiMed
RECEBIDO_DE Caracterização do notificador do caso
IDENTIFICACAO_NOTIFICACAO Número de identificação da notificação
DATA_INCLUSAO_SISTEMA Data em que a notificação foi registrada no sistema VigiMed pela primeira vez
DATA_ULTIMA_ATUALIZACAO Data da última atualização da notificação no sistema
DATA_NOTIFICACAO Data da notificação
TIPO_NOTIFICACAO Tipo de notificação
NOTIFICACAO_PARENT_CHILD Notificação relacionada ao uso de medicamento pelo pai/mãe, cujo evento ocorreu no filho/feto
DATA_NASCIMENTO Data de nascimento do paciente
IDADE_MOMENTO_REACAO Idade do paciente no momento da reação
GRUPO_IDADE Grupo etário do paciente
IDADE_GESTACIONAL_MOMENTO_REACAO Idade gestacional no momento do evento (dias, semanas, meses ou trimestre, se aplicável)
SEXO Sexo do paciente
GESTANTE Indica se a reação ocorreu em gestante (sim/não)
LACTANTE Indica se a reação ocorreu em lactante (sim/não)
PESO_KG Peso declarado do paciente
ALTURA_CM Altura declarada do paciente
REACAO_EVENTO_ADVERSO_MEDDRA Reação evento adverso codificada no MedDRA
GRAVE Indica se o evento adverso é grave ou não grave
GRAVIDADE Critério de gravidade do evento adverso
DESFECHO Desfecho do evento adverso
DATA_INICIO_HORA Data de início do evento adverso
DATA_FINAL_HORA Data de término do evento adverso
DURACAO Duração do evento adverso
RELACAO_MEDICAMENTO_EVENTO Relação do medicamento com o evento (suspeito, concomitante, interação, não administrado)
NOME_MEDICAMENTO_WHODRUG Nome do medicamento codificado no WHODrug
ACAO_ADOTADA Medida adotada após ocorrência do evento adverso
NOTIFICADOR Grupo do notificador
IDADE_FINAL Idade final do paciente, obtida a partir da conversão dos valores e unidades da coluna IDADE_MOMENTO_REACAO
DURACAO_DIA Duração do evento adverso em dias, obtida a partir da conversão dos valores e unidades da coluna DURACAO

4. Análise Exploratória

Dados

Os dados utilizados nesta etapa são carregados a partir do arquivo .rds gerado ao final do pré-processamento, o que preserva os tipos de variáveis definidos anteriormente.

df <- readRDS("C:/Workspace/CPAD_PROJ_02/df_tratado.rds")

1. Visão Geral

glimpse(df)
#> Rows: 31,928
#> Columns: 29
#> $ UF                           <fct> BA, BA, BA, BA, BA, BA, BA, BA, BA, BA, B…
#> $ TIPO_ENTRADA_VIGIMED         <fct> Serviços de Saúde, Serviços de Saúde, Ser…
#> $ RECEBIDO_DE                  <fct> "Centro Regional de Farmacovigilância", "…
#> $ IDENTIFICACAO_NOTIFICACAO    <chr> "BR-ANVISA-300001736", "BR-ANVISA-3000017…
#> $ DATA_INCLUSAO_SISTEMA        <date> 2019-05-14, 2019-05-15, 2019-05-15, 2019…
#> $ DATA_ULTIMA_ATUALIZACAO      <date> 2019-05-14, 2019-05-15, 2019-05-15, 2019…
#> $ DATA_NOTIFICACAO             <date> 2019-04-03, 2019-03-26, NA, NA, 2018-07-…
#> $ TIPO_NOTIFICACAO             <fct> Notificação espontânea, Notificação espon…
#> $ DATA_NASCIMENTO              <date> 1945-06-08, 1970-06-24, 2003-03-24, 1985…
#> $ IDADE_MOMENTO_REACAO         <chr> "73 ano", "48 ano", "14 ano", "30 ano", "…
#> $ GRUPO_IDADE                  <fct> Idoso, Adulto, Adolescente, Adulto, Não i…
#> $ SEXO                         <fct> Feminino, Feminino, Masculino, Feminino, …
#> $ GESTANTE                     <chr> "Não informado", "Não informado", "Não in…
#> $ LACTANTE                     <chr> "Não informado", "Não informado", "Não in…
#> $ PESO_KG                      <dbl> 94.0, 83.0, 35.4, 86.0, 61.0, 66.1, NA, 7…
#> $ ALTURA_CM                    <dbl> 148, NA, 147, 169, 176, NA, NA, 168, NA, …
#> $ REACAO_EVENTO_ADVERSO_MEDDRA <chr> "Erro de monitoramento de medicação", "Se…
#> $ GRAVE                        <fct> Não, Sim, Não, Não, Não, Não, Não, Sim, S…
#> $ GRAVIDADE                    <fct> Não informado, Ameaça à vida, Não informa…
#> $ DESFECHO                     <fct> Recuperado, Recuperado, Recuperado, Recup…
#> $ DATA_INICIO_HORA             <dttm> 2019-03-30, 2019-03-05, 2017-11-16, 2016…
#> $ DATA_FINAL_HORA              <dttm> 2019-04-02, 2019-03-28, 2017-11-18, 2016…
#> $ DURACAO                      <chr> "3 dia", "24 dia", "48 hora", "1 dia", "2…
#> $ RELACAO_MEDICAMENTO_EVENTO   <fct> Concomitante, Suspeito, Suspeito, Suspeit…
#> $ NOME_MEDICAMENTO_WHODRUG     <chr> "Acido acetilsalicilico", "Clozapin", "Me…
#> $ ACAO_ADOTADA                 <fct> Redução da dose, Retirada do medicamento,…
#> $ NOTIFICADOR                  <fct> Farmacêutico, Farmacêutico, Farmacêutico,…
#> $ IDADE_FINAL                  <dbl> 73, 48, 14, 30, 16, 60, 72, 37, 76, 46, 6…
#> $ DURACAO_DIA                  <dbl> 3.00000000, 24.00000000, 2.00000000, 1.00…

As colunas numéricas e de data apresentam NAs, enquanto nas demais os valores faltantes foram substituídos por "Não informado" ou "Desconhecido" na etapa de pré-processamento.

skim(df)
Data summary
Name df
Number of rows 31928
Number of columns 29
_______________________
Column type frequency:
character 7
Date 4
factor 12
numeric 4
POSIXct 2
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
IDENTIFICACAO_NOTIFICACAO 0 1 19 19 0 31928 0
IDADE_MOMENTO_REACAO 0 1 0 9 2442 289 0
GESTANTE 0 1 3 13 0 3 0
LACTANTE 0 1 3 13 0 3 0
REACAO_EVENTO_ADVERSO_MEDDRA 0 1 3 82 0 2297 0
DURACAO 0 1 0 10 21400 307 0
NOME_MEDICAMENTO_WHODRUG 0 1 2 69 0 2759 0

Variable type: Date

skim_variable n_missing complete_rate min max median n_unique
DATA_INCLUSAO_SISTEMA 0 1.00 2019-05-14 2026-04-19 2023-10-16 2191
DATA_ULTIMA_ATUALIZACAO 16 1.00 2019-01-03 2026-04-19 2023-10-16 2189
DATA_NOTIFICACAO 3643 0.89 2018-01-11 2026-04-19 2023-07-17 2612
DATA_NASCIMENTO 5496 0.83 1911-04-25 2026-04-05 1979-02-19 16476

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
UF 0 1 FALSE 9 BA: 12338, CE: 8469, RN: 4284, PI: 1820
TIPO_ENTRADA_VIGIMED 0 1 FALSE 5 Ser: 30676, Vig: 1153, Ser: 93, Não: 4
RECEBIDO_DE 0 1 FALSE 6 Pro: 21994, Cen: 6653, Não: 2981, Pac: 162
TIPO_NOTIFICACAO 0 1 FALSE 4 Not: 28910, Out: 2490, Not: 463, Não: 65
GRUPO_IDADE 0 1 FALSE 8 Adu: 11638, Não: 11084, Ido: 6336, Cri: 1065
SEXO 0 1 FALSE 3 Fem: 19314, Mas: 11384, Des: 1230
GRAVE 0 1 FALSE 3 Não: 15996, Não: 10790, Sim: 5142
GRAVIDADE 0 1 FALSE 7 Não: 26719, Out: 2029, Hos: 1423, Ame: 1409
DESFECHO 0 1 FALSE 5 Rec: 18371, Des: 9791, Em : 2919, Não: 625
RELACAO_MEDICAMENTO_EVENTO 0 1 FALSE 4 Sus: 29673, Med: 1952, Con: 169, Int: 134
ACAO_ADOTADA 0 1 FALSE 7 Não: 18543, Ret: 7651, Sem: 2826, Não: 1502
NOTIFICADOR 0 1 FALSE 6 Far: 18562, Out: 8635, Não: 4142, Méd: 361

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
PESO_KG 22083 0.31 61.32 23.31 1 51.00 63 75.4 150 ▂▆▇▁▁
ALTURA_CM 25210 0.21 157.86 19.48 32 154.00 160 168.0 197 ▁▁▁▇▅
IDADE_FINAL 5566 0.83 46.84 23.56 0 30.00 48 66.0 105 ▅▇▇▆▁
DURACAO_DIA 21465 0.33 14.79 324.70 0 0.12 1 6.0 20075 ▇▁▁▁▁

Variable type: POSIXct

skim_variable n_missing complete_rate min max median n_unique
DATA_INICIO_HORA 10863 0.66 1916-11-01 2026-04-19 2023-09-14 10:45:00 6000
DATA_FINAL_HORA 16124 0.49 1971-01-29 2026-04-19 2023-10-13 00:00:00 5139

Destaques da saída do skim():

  • Os estados do Nordeste registraram juntos 31.928 notificações, entre 2019 e 2026.
  • O dataset possui 29 variáveis cobrindo dados demográficos, temporais, clínicos, de medicamentos e desfechos — o que justifica análises temporais, epidemiológicas, de farmacovigilância e de perfil dos pacientes.
  • REACAO_EVENTO_ADVERSO_MEDDRA apresenta 2.297 categorias e NOME_MEDICAMENTO_WHODRUG, 2.759, evidenciando grande diversidade de reações e medicamentos notificados.

Completude das principais variáveis:

Variável % ausente
Peso ~69%
Altura ~79%
Duração ~67%
Data início ~34%
Data fim ~50%

A elevada incompletude é esperada, pois o formulário de notificação do VigiMed possui apenas alguns campos obrigatórios. Soma-se a isso a heterogeneidade do perfil dos notificadores — pacientes, cidadãos, profissionais de saúde, serviços de saúde e detentores de registro de medicamentos.

A variável DURACAO apresentou forte assimetria (média = 14,8 dias; mediana = 1 dia; máximo = 20.075 dias), reflexo de outliers mantidos no pré-processamento por serem valores possíveis, embora provavelmente originados de erros de digitação ou preenchimento inadequado.

# Verificação de duplicidade
sum(duplicated(df$IDENTIFICACAO_NOTIFICACAO))
#> [1] 0

Os 31.928 identificadores únicos confirmam ausência de notificações duplicadas.


2. Análise Univariada

Variáveis de interesse

2.1 Unidade Federativa

df %>%
  count(UF) %>%
  mutate(percentage = round(n / sum(n) * 100, 1)) %>%
  plot_ly(x = ~UF, y = ~percentage, type = "bar") %>%
  layout(
    title  = "Proporção de Notificações por Unidade Federativa do Nordeste",
    xaxis  = list(title = "Estado"),
    yaxis  = list(title = "Percentual de notificações (%)")
  )

Bahia, Ceará e Rio Grande do Norte concentram mais de 75% das notificações. Já Alagoas, Sergipe e Paraíba somadas representam pouco mais de 5%.


2.2 Tipo de Entrada VigiMed

df %>%
  count(TIPO_ENTRADA_VIGIMED) %>%
  mutate(percentage = round(n / sum(n) * 100, 1)) %>%
  arrange(desc(n)) %>%
  knitr::kable(col.names = c("Tipo de Entrada", "n", "%"))
Tipo de Entrada n %
Serviços de Saúde 30676 96.1
VigiMobile 1153 3.6
Serviços de Vacinação 93 0.3
Não informado 4 0.0
Empresas Farmacêuticas 2 0.0

As notificações são predominantemente enviadas por serviços de saúde. Em segundo lugar aparecem os registros via VigiMobile, que pode ser utilizado por diferentes perfis de notificadores.


2.3 Recebido De

df %>%
  count(RECEBIDO_DE) %>%
  mutate(percentage = round(n / sum(n) * 100, 1)) %>%
  arrange(desc(n)) %>%
  knitr::kable(col.names = c("Recebido do Notificador", "n", "%"))
Recebido do Notificador n %
Profissional de Saúde 21994 68.9
Centro Regional de Farmacovigilância 6653 20.8
Não informado 2981 9.3
Paciente/Consumidor 162 0.5
Empresa Farmacêutica 117 0.4
Outro (p.ex. Distribuidora, Financiador de Estudo, Organização Representativa para Pesquisa Clínica, ou Organização não-Comercial) 21 0.1

Profissionais de saúde lideram as notificações, seguidos pelos centros de farmacovigilância.


2.4 Tipo de Notificação

df %>%
  count(TIPO_NOTIFICACAO) %>%
  mutate(percentage = round(n / sum(n) * 100, 1)) %>%
  plot_ly(
    y             = ~TIPO_NOTIFICACAO,
    x             = ~percentage,
    type          = "bar",
    orientation   = "h",
    text          = ~TIPO_NOTIFICACAO,
    textposition  = "outside",
    cliponaxis    = FALSE
  ) %>%
  layout(
    title  = "Proporção de Notificações por Tipo",
    yaxis  = list(title = "Tipo de notificação", showticklabels = FALSE),
    xaxis  = list(title = "Percentual de notificações (%)"),
    margin = list(r = 200)
  )

Aproximadamente 90% das notificações são espontâneas, o que pode parecer surpreendente dado que parte delas é de caráter compulsório.


2.5 Reação / Evento Adverso (MedDRA)

df %>%
  count(REACAO_EVENTO_ADVERSO_MEDDRA) %>%
  mutate(
    percentage                    = round(n / sum(n) * 100, 1),
    REACAO_EVENTO_ADVERSO_MEDDRA  = fct_reorder(REACAO_EVENTO_ADVERSO_MEDDRA, n),
    label                         = str_wrap(REACAO_EVENTO_ADVERSO_MEDDRA, width = 25)
  ) %>%
  arrange(desc(n)) %>%
  slice(1:10) %>%
  plot_ly(
    y                  = ~REACAO_EVENTO_ADVERSO_MEDDRA,
    x                  = ~n,
    type               = "bar",
    orientation        = "h",
    text               = ~label,
    textposition       = "inside",
    insidetextanchor   = "middle",
    insidetextfont     = list(size = 11, color = "white")
  ) %>%
  layout(
    title  = "Reações adversas mais frequentes (Top 10)",
    yaxis  = list(
      title          = "Reação / Evento",
      showticklabels = FALSE,
      categoryorder  = "total ascending"
    ),
    xaxis  = list(title = "Quantidade")
  )

Dada a amplitude de 2.297 valores únicos, o gráfico exibe apenas os 10 mais frequentes. Prurido e coceira — essencialmente a mesma manifestação — lideram. Destaque também para erros de administração e de prescrição, que somados totalizam cerca de 1.120 notificações.


2.6 Evento Adverso Grave

df %>%
  count(GRAVE) %>%
  mutate(percentage = round(n / sum(n) * 100, 1)) %>%
  plot_ly(x = ~GRAVE, y = ~percentage, type = "bar") %>%
  layout(
    title  = "Evento Adverso Grave ou Não Grave",
    yaxis  = list(title = "Percentual (%)"),
    xaxis  = list(title = "Evento Adverso Grave (Sim/Não)")
  )

Cerca de metade dos eventos não foi considerada grave. Aproximadamente 34% não tiveram classificação definida, e apenas 16% foram classificados como graves.


2.7 Gravidade

df %>%
  count(GRAVIDADE) %>%
  mutate(percentage = round(n / sum(n) * 100, 1)) %>%
  arrange(desc(n)) %>%
  knitr::kable(col.names = c("Categoria", "n", "%"))
Categoria n %
Não informado 26719 83.7
Outro efeito clinicamente significativo 2029 6.4
Hospitalização 1423 4.5
Ameaça à vida 1409 4.4
Incapacidade persistente ou significativa 194 0.6
Resultou em óbito 152 0.5
Anomalia congênita ou malformação ao nascer 2 0.0

Coerente com a distribuição anterior, 83,7% dos rótulos de gravidade estão em branco. Entre os classificados, predominam Outro efeito clinicamente significativo, hospitalização e ameaça à vida. Desfechos mais severos — incapacidade persistente, óbito e anomalia congênita — são mais raros.


2.8 Medicamentos

df %>%
  count(NOME_MEDICAMENTO_WHODRUG) %>%
  arrange(desc(n)) %>%
  slice(1:10) %>%
  mutate(
    NOME_MEDICAMENTO_WHODRUG = fct_reorder(NOME_MEDICAMENTO_WHODRUG, n),
    label                    = str_wrap(NOME_MEDICAMENTO_WHODRUG, width = 25)
  ) %>%
  plot_ly(
    y                  = ~NOME_MEDICAMENTO_WHODRUG,
    x                  = ~n,
    type               = "bar",
    orientation        = "h",
    text               = ~label,
    textposition       = "inside",
    insidetextanchor   = "middle",
    insidetextfont     = list(size = 11, color = "white")
  ) %>%
  layout(
    title  = "Medicamentos mais associados a eventos adversos (Top 10)",
    yaxis  = list(
      title          = "Medicamento",
      showticklabels = FALSE,
      categoryorder  = "total ascending"
    ),
    xaxis  = list(title = "Quantidade")
  )

Entre os 2.759 medicamentos únicos, a vancomicina lidera as notificações. O top 4 reúne classes farmacológicas distintas — antibiótico, analgésico/antipirético, quimioterápico e opioide —, padrão que se mantém ao longo do top 10.


3. Perfil Demográfico

df %>%
  filter(SEXO != "Desconhecido") %>%
  plot_ly(
    x     = ~SEXO,
    y     = ~IDADE_FINAL,
    type  = "box",
    color = ~SEXO
  ) %>%
  layout(
    title  = "Distribuição da Idade por Sexo",
    xaxis  = list(title = "Sexo"),
    yaxis  = list(title = "Idade (anos)")
  )
  • Mediana de idade semelhante entre os sexos: 48 anos (feminino) e 49 anos (masculino).
  • A base contempla pacientes desde neonatos até idosos (0–105 anos), gerando ampla dispersão.
  • O sexo masculino apresenta IQR ligeiramente mais amplo (Q1 = 25; Q3 = 67) em relação ao feminino (Q1 = 32; Q3 = 65), indicando maior variabilidade etária entre os homens.
  • Outliers em ambos os grupos refletem notificações em faixas etárias extremas.

Obs.: registros com sexo "Desconhecido" foram excluídos desta visualização (< 3% do total).


4. Análise Temporal

df_plot <- df %>%
  filter(!is.na(DATA_NOTIFICACAO)) %>%
  mutate(ANO = year(DATA_NOTIFICACAO)) %>%
  count(ANO, GRAVIDADE)

shared_df <- SharedData$new(df_plot)

filter_checkbox(
  id         = "gravidade_filter",
  label      = "Selecione a gravidade:",
  sharedData = shared_df,
  group      = ~GRAVIDADE,
  inline     = FALSE
)
plot_ly(
  shared_df,
  x     = ~ANO,
  y     = ~n,
  color = ~GRAVIDADE,
  type  = "scatter",
  mode  = "lines+markers"
) %>%
  layout(
    title  = "Número de notificações por ano e gravidade",
    xaxis  = list(title = "Ano", dtick = 1),
    yaxis  = list(title = "Número de notificações")
  )

O gráfico interativo mostra a evolução da quantidade de notificações, classificadas por gravidade, ao longo dos últimos 8 anos. Com exceção da categoria Anomalia congênita ou malformação ao nascer — que conta com apenas duas observações —, as demais seguem um padrão relativamente homogêneo entre si. O comportamento mais distinto pertence à classe Outro efeito clinicamente significativo, que registrou um aumento expressivo em 2020, ano que coincide com a pandemia de COVID-19, período em que diversos medicamentos passaram a ser utilizados em caráter off-label ou em doses atípicas.

Verificando o número de notificações por mês:

df_mes <- df %>%
  filter(!is.na(DATA_NOTIFICACAO)) %>%
  mutate(ANO_MES = floor_date(DATA_NOTIFICACAO, "month")) %>%
  count(ANO_MES)

plot_ly(
  data = df_mes,
  x    = ~ANO_MES,
  y    = ~n,
  type = "scatter",
  mode = "lines"
) %>%
  layout(
    title  = "Número de notificações por mês",
    xaxis  = list(title = "Ano/Mês"),
    yaxis  = list(title = "Número de notificações")
  )

É possível verificar uma tendência de crescimento no número de notificações ao longo do tempo. Isso pode ser explicado pela implantação progressiva dos módulos do VigiMed: o Módulo Cidadão e Profissional de Saúde Liberal (eReporting) foi disponibilizado inicialmente; em março de 2019, foi implementado o Módulo VISAS e Serviços de Saúde (VigiFlow); e, por fim, o Módulo Empresa (eReporting Industry) foi disponibilizado em outubro de 2020, conforme a documentação oficial do conjunto de dados. Embora tenha sido verificado anteriormente que as notificações são feitas predominantemente por profissionais de saúde, a incorporação gradual ao novo sistema e as novas adesões institucionais podem justificar essa tendência de crescimento.


5. Análise de Gravidade de Reação e Medicamento

df_plot <- df %>%
  filter(GRAVE == "Sim") %>%
  count(GRAVIDADE, DESFECHO) %>%
  group_by(GRAVIDADE) %>%
  mutate(prop = n / sum(n))

plot_ly(
  data  = df_plot,
  x     = ~GRAVIDADE,
  y     = ~prop,
  color = ~DESFECHO,
  type  = "bar"
) %>%
  layout(
    title   = "Proporção de desfechos por gravidade",
    xaxis   = list(title = "Gravidade"),
    yaxis   = list(title = "Proporção", tickformat = ".0%"),
    barmode = "stack"
  )

Chama atenção a incoerência observada na categoria Resultou em óbito, que apresenta desfechos como Em recuperação e Recuperado — logicamente incompatíveis com um paciente que foi a óbito. Essa inconsistência provavelmente decorre de erros de preenchimento no momento da notificação. A categoria Anomalia congênita ou malformação ao nascer, como já observado, conta com apenas duas notificações, o que limita qualquer interpretação. Com exceção dessas duas classes, as demais apresentam proporções de desfecho relativamente semelhantes entre si.

Análise aprofundada da categoria Hospitalização:

df_plot <- df %>%
  filter(GRAVIDADE == "Hospitalização") %>%
  count(REACAO_EVENTO_ADVERSO_MEDDRA, sort = TRUE) %>%
  slice_head(n = 20)

plot_ly(
  data        = df_plot,
  x           = ~n,
  y           = ~reorder(REACAO_EVENTO_ADVERSO_MEDDRA, n),
  type        = "bar",
  orientation = "h"
) %>%
  layout(
    title  = "20 reações adversas mais frequentes em casos de hospitalização",
    xaxis  = list(title = "Número de notificações"),
    yaxis  = list(title = "Reação adversa")
  )

A reação adversa mais frequente nos casos de hospitalização é a neutropenia febril, complicação comumente associada ao uso de quimioterápicos. Dado que os pacientes oncológicos já apresentam estado de saúde debilitado, é esperado que essa reação seja a principal causa de internação no conjunto de dados.

Seguindo essa linha de raciocínio, podemos listar os medicamentos que mais aparecem nos casos de hospitalização:

df %>%
  filter(GRAVIDADE == "Hospitalização") %>%
  count(NOME_MEDICAMENTO_WHODRUG, sort = TRUE) %>%
  slice_head(n = 10)

Corroborando com a discussão anterior, seis dos 10 medicamentos mais citados nas reações que resultam em hospitalização são utilizados no tratamento do câncer.

Uma análise adicional que podemos explorar é a relação entre reação adversa e medicamento:

df_bubble <- df %>%
  filter(GRAVIDADE == "Hospitalização") %>%
  count(
    NOME_MEDICAMENTO_WHODRUG,
    REACAO_EVENTO_ADVERSO_MEDDRA,
    sort = TRUE
  ) %>%
  slice_head(n = 30)

plot_ly(
  data          = df_bubble,
  x             = ~NOME_MEDICAMENTO_WHODRUG,
  y             = ~REACAO_EVENTO_ADVERSO_MEDDRA,
  size          = ~n,
  color         = ~n,
  type          = "scatter",
  mode          = "markers",
  hovertemplate = ~paste0(
    NOME_MEDICAMENTO_WHODRUG, ", ",
    REACAO_EVENTO_ADVERSO_MEDDRA, ", ",
    n,
    "<extra></extra>"
  )
) %>%
  layout(
    title  = "Associação entre medicamentos e reações adversas (Hospitalização)",
    xaxis  = list(title = "Medicamento"),
    yaxis  = list(title = "Reação adversa")
  )

Apesar de o gráfico apresentar limitações visuais decorrentes da granularidade elevada de ambas as variáveis, é possível identificar algumas associações de destaque, como capecitabina e diarreia, fenitoína e síndrome de DRESS, e pantoprazol e colite pseudomembranosa. Além disso, a neutropenia febril foi a reação que apresentou associação suspeita com a maior quantidade de medicamentos — 10 dos 15 primeiros.

Estendendo a comparação sem o filtro de hospitalização:

df_bubble <- df %>%
  count(
    NOME_MEDICAMENTO_WHODRUG,
    REACAO_EVENTO_ADVERSO_MEDDRA,
    sort = TRUE
  ) %>%
  slice_head(n = 10)

plot_ly(
  data          = df_bubble,
  x             = ~NOME_MEDICAMENTO_WHODRUG,
  y             = ~REACAO_EVENTO_ADVERSO_MEDDRA,
  size          = ~n,
  color         = ~n,
  type          = "scatter",
  mode          = "markers",
  hovertemplate = ~paste0(
    NOME_MEDICAMENTO_WHODRUG, ", ",
    REACAO_EVENTO_ADVERSO_MEDDRA, ", ",
    n,
    "<extra></extra>"
  )
) %>%
  layout(
    title  = "Associação entre medicamentos e reações adversas",
    xaxis  = list(title = "Medicamento"),
    yaxis  = list(title = "Reação adversa")
  )

Sem o filtro de hospitalização, é possível associar os dois primeiros lugares tanto de reação adversa quanto de medicamento: prurido e vancomicina, com 121 notificações relacionando os dois. Vale ressaltar também a associação suspeita entre morfina e prurido, além de docetaxel e rubor facial.


5. Conclusão

A análise exploratória das notificações do VigiMed para a Região Nordeste revelou um panorama relevante para a farmacovigilância regional. Ao longo das seções, foi possível caracterizar o perfil das notificações sob diferentes perspectivas, portanto atende ao que foi proposto na definição do problema, que consiste no potencial de identificação de padrões e tendências relacionados às notificações, no intuito de melhorar o entendimento do cenário da farmacovigilância da região Nordeste.

A concentração geográfica das notificações aponta para estados com maior capacidade instalada de farmacovigilância, o que pode guiar estratégias de expansão e qualificação de notificadores em estados subrepresentados.

Do ponto de vista epidemiológico, as notificações concentram-se desproporcionalmente em três estados — Bahia, Ceará e Rio Grande do Norte —, o que pode refletir tanto diferenças reais na ocorrência de eventos adversos quanto desigualdades na capacidade de notificação entre os estados. A maioria das notificações é de origem espontânea e realizada por profissionais de saúde, perfil consistente com sistemas de farmacovigilância passiva.

Em relação ao perfil dos pacientes, a distribuição etária é ampla, contemplando desde neonatos até idosos, sem diferenças relevantes entre os sexos. Isso reforça a importância de uma vigilância abrangente, que considere as particularidades farmacocinéticas de cada faixa etária.

No que diz respeito às reações adversas, prurido e coceira lideram as notificações gerais, enquanto a neutropenia febril se destaca como a principal reação associada à hospitalização, predominantemente relacionada ao uso de quimioterápicos em pacientes oncológicos. A presença expressiva de notificações de erros de administração e de prescrição — cerca de 1.120 casos — aponta para uma dimensão de segurança do paciente que merece atenção específica.

A análise das associações entre medicamentos e reações adversas identificou pares de interesse, como vancomicina e prurido, capecitabina e diarreia, fenitoína e síndrome de DRESS, e pantoprazol e colite pseudomembranosa. É importante destacar, no entanto, que todas essas associações têm caráter suspeito e exploratório. A confirmação ou refutação de uma relação causal entre um medicamento e um evento adverso é atribuição da área técnica competente da Anvisa, que dispõe de metodologias específicas de avaliação de causalidade e acesso a informações complementares não presentes neste conjunto de dados.

Por fim, a análise temporal evidenciou crescimento consistente no volume de notificações ao longo do período, impulsionado pela implantação progressiva dos módulos do VigiMed e pela adesão crescente de notificadores. O pico observado em 2020 na categoria Outro efeito clinicamente significativo merece investigação adicional no contexto do uso de medicamentos durante a pandemia de COVID-19.

Em conjunto, esses achados demonstram o potencial do VigiMed como fonte de dados para a vigilância de eventos adversos a medicamentos na Região Nordeste, ao mesmo tempo em que evidenciam limitações importantes — como a elevada incompletude de variáveis clínicas e inconsistências no preenchimento — que devem ser consideradas na interpretação de qualquer análise derivada desta base. Adicionalmente, considerando as inconsistências no preenchimento de campos de texto livre, que dificultam a padronização e a categorização automatizada, recomenda-se, para análises futuras, o uso de técnicas de processamento de linguagem natural para harmonização dos campos descritivos