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.
Esses dados têm como objetivo ampliar o acesso público às informações relacionadas a eventos adversos de medicamentos e vacinas relatados espontaneamente ou de forma compulsória à Anvisa por pacientes, cidadãos, profissionais de saúde, serviços de saúde e detentores de registro de medicamentos. Atualmente, a base contempla apenas os registros do VigiMed.
Este projeto tem como objetivo analisar as notificações de eventos adversos relacionadas a medicamentos e vacinas registradas no sistema VigiMed, mais específicamente nos estados do Nordeste, justificado pelo fato da região apresentar características demográficas, socioeconômicas e estruturais específicas, incluindo desigualdades no acesso aos serviços de saúde, e particularidades epidemiológicas que podem influenciar tanto a ocorrência quanto a notificação de eventos adversos. A análise busca identificar padrões associados aos tipos de medicamentos e vacinas notificados, características dos eventos adversos, perfil das notificações, distribuição temporal e possíveis tendências observadas nos registros.
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 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.
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)#> 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.
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.
#>
#> Empresas Farmacêuticas Serviços de Saúde
#> 4 2 30810
#> Serviços de Vacinação VigiMobile
#> 93 1162
#>
#>
#> 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
#> [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”.
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)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)#>
#> Não disponível pelo notificador (desconhecido)
#> 65
#> Notificação de estudo
#> 467
#> Notificação espontânea
#> 29035
#> Outro
#> 2504
Sem tratativa necessária.
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.
#>
#> None Sim
#> 24951 7086 34
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.
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
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.
Apresenta quase exclusivamente valores ausentes ou
"None" — excluída da análise.
#>
#> None
#> 12671 19400
#>
#> Não Sim
#> 12591 19161 319
#> [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
#> [1] "" "Sim" "Não "
Apenas necessária a substituição das strings vazias, e remoção do espaço no final da string do valor nominal “Não”.
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.
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
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
#> [1] 18
#> [1] 4
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.
| GRAVE | DESFECHO | GRAVIDADE |
|---|---|---|
| Não | Recuperado | |
| Sim | Recuperado | Ameaça à vida |
| Não | Recuperado | |
| Não | Recuperado | |
| Não | Recuperado | |
| Não | Recuperado | |
| Não | Em recuperação | |
| Sim | Recuperado | Hospitalização |
| Sim | Em recuperação | Hospitalização |
| Não | Recuperado |
#>
#> 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
)Para as colunas DESFECHO, GRAVIDADE e GRAVE é necessário tratar apenas os valores ausentes e aqueles correspondentes à “None”.
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
)
)| 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”.
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.
| 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: