Da heterogeneidade de registros brutos a uma leitura exploratória sobre saúde, escola, rotina e adversidades.
O Transtorno de Déficit de Atenção/Hiperatividade (TDAH) raramente aparece sozinho no cotidiano de uma criança. A pergunta que guia esta análise não é apenas “quem relatou TDAH?”, mas em quais combinações de escola, saúde, experiências adversas e rotina esse relato aparece com mais frequência?
A análise utiliza os microdados públicos de 2023 da National Survey of Children’s Health (NSCH), pesquisa norte-americana voltada a crianças e adolescentes de 0 a 17 anos. A fonte oficial disponibiliza os arquivos e a documentação das variáveis em Census Bureau - 2023 NSCH Data Release e no Topical Variable List.
O problema prático é responder: quais perfis escolares, clínicos e sociais aparecem com maior frequência entre crianças com e sem diagnóstico informado de TDAH? Os potenciais usuários são profissionais de educação, saúde infantil e gestores interessados em planejamento de apoio, sempre sem usar esta análise para diagnóstico individual.
# Não instale pacotes automaticamente. Se necessário, execute uma única vez no RStudio:
# install.packages(c("readr", "dplyr", "tidyr", "stringr", "forcats", "ggplot2",
# "scales", "DT", "plotly", "knitr"))
# Opcional para reprodutibilidade do projeto: renv::init()
required_packages <- c(
"readr", "dplyr", "tidyr", "stringr", "forcats", "ggplot2",
"scales", "DT", "plotly", "knitr"
)
missing_packages <- required_packages[!vapply(required_packages, requireNamespace, logical(1), quietly = TRUE)]
if (length(missing_packages) > 0) {
stop(
"Pacotes ausentes: ", paste(missing_packages, collapse = ", "),
". Instale-os no RStudio com install.packages(c(\"",
paste(missing_packages, collapse = "\", \""), "\"))."
)
}
invisible(lapply(required_packages, library, character.only = TRUE))
package_purpose <- tibble::tibble(
Pacote = required_packages,
Finalidade = c(
"Importação tipada de CSV",
"Transformação e agregação de dados",
"Reformatação de tabelas",
"Padronização de texto e expressões regulares",
"Ordenação explícita de fatores",
"Gráficos estáticos",
"Formatação de percentuais",
"Tabelas HTML interativas",
"Versões interativas de gráficos",
"Formatação de tabelas no relatório"
)
)
knitr::kable(package_purpose, caption = "Pacotes utilizados e sua finalidade.")| Pacote | Finalidade |
|---|---|
| readr | Importação tipada de CSV |
| dplyr | Transformação e agregação de dados |
| tidyr | Reformatação de tabelas |
| stringr | Padronização de texto e expressões regulares |
| forcats | Ordenação explícita de fatores |
| ggplot2 | Gráficos estáticos |
| scales | Formatação de percentuais |
| DT | Tabelas HTML interativas |
| plotly | Versões interativas de gráficos |
| knitr | Formatação de tabelas no relatório |
A fonte é a National Survey of Children’s Health (NSCH) 2023, uma pesquisa norte-americana com informações fornecidas por pais ou cuidadores sobre saúde, acesso a cuidados, ambiente familiar, experiências escolares e bem-estar de crianças e adolescentes de 0 a 17 anos. A edição de 2023 reúne 55.162 respostas no arquivo tópico de uso público.
O arquivo bruto recebido para este projeto contém 31 variáveis e mantém a unidade de análise da pesquisa: uma criança selecionada por domicílio. Ele reúne identificação, demografia, diagnóstico informado de TDAH, comorbidades, escola, experiências adversas, rotina e condição socioeconômica. Antes da análise, os valores precisam ser estruturados porque uma mesma variável pode conter códigos, palavras, capitalizações ou formatos numéricos diferentes. A documentação técnica usada como referência para definir os padrões de destino está no dicionário de variáveis da NSCH.
variable_dictionary <- tibble::tribble(
~Grupo, ~Variável, ~Representa,
"Identificação", "HHID", "Identificador do domicílio respondente.",
"Identificação", "FIPSST", "Código FIPS do estado de residência.",
"Identificação", "YEAR", "Ano de referência da pesquisa.",
"Demografia", "SC_AGE_YEARS", "Idade da criança selecionada em anos completos.",
"Demografia", "SC_SEX", "Sexo da criança selecionada.",
"Demografia", "SC_RACE_R", "Raça detalhada da criança selecionada.",
"TDAH", "K2Q31A", "Algum profissional já informou diagnóstico de ADD/TDAH.",
"TDAH", "K2Q31B", "A criança apresenta ADD/TDAH atualmente.",
"TDAH", "K2Q31C", "Gravidade atual informada para ADD/TDAH.",
"TDAH", "K2Q31D", "Uso atual de medicação para ADD/TDAH.",
"TDAH", "ADDTREAT", "Recebeu tratamento comportamental para ADD/TDAH nos últimos 12 meses.",
"Comorbidades", "K2Q30A", "Diagnóstico já informado de deficiência de aprendizagem.",
"Comorbidades", "K2Q32A", "Diagnóstico já informado de depressão.",
"Comorbidades", "K2Q33A", "Diagnóstico já informado de problemas de ansiedade.",
"Escola", "GRADES", "Padrão de notas recebido no ano escolar de 2022-2023.",
"Escola", "REPEATED", "Repetiu alguma série desde o início do jardim de infância.",
"Escola", "K7Q04R_R", "Número de contatos da escola com o domicílio por problemas escolares.",
"Escola", "K6Q15", "Já teve plano de educação especial ou intervenção precoce.",
"ACEs", "ACE1", "Frequência de dificuldade para cobrir necessidades básicas como alimentação ou moradia.",
"ACEs", "ACE3", "Pais ou responsáveis se divorciaram ou se separaram.",
"ACEs", "ACE4", "Morte de pai, mãe ou responsável.",
"ACEs", "ACE5", "Pai, mãe ou responsável cumpriu pena de prisão.",
"ACEs", "ACE6", "Viu ou ouviu violência física entre adultos no domicílio.",
"ACEs", "ACE7", "Foi vítima ou testemunha de violência na vizinhança.",
"ACEs", "ACE8", "Viveu com pessoa com doença mental, ideação suicida ou depressão grave.",
"ACEs", "ACE9", "Viveu com pessoa com problema de álcool ou drogas.",
"ACEs", "ACE10", "Sofreu tratamento injusto por raça ou grupo étnico.",
"ACEs", "ACE11", "Sofreu tratamento injusto por condição de saúde ou deficiência.",
"Rotina", "HOURSLEEP", "Horas médias de sono nas noites de semana.",
"Rotina", "SCREENTIME", "Tempo de tela em um dia típico de semana.",
"Socioeconômico", "FPL_I1", "Primeira imputação da razão de pobreza familiar (Federal Poverty Level)."
)
DT::datatable(
variable_dictionary,
caption = "Dicionário das 31 variáveis usadas no projeto. Use a busca para localizar um campo.",
rownames = FALSE,
filter = "top",
options = list(pageLength = 12, scrollX = TRUE)
)O arquivo bruto reúne 55.162 registros e 31 colunas relacionadas a identificação, demografia, TDAH, escola, ACEs, rotina e condição socioeconômica. Sua principal característica é a heterogeneidade de representação: respostas equivalentes aparecem como códigos numéricos e rótulos textuais, há diferenças de capitalização e espaços, números foram registrados com unidades e alguns ausentes utilizam marcadores textuais.
A estruturação transforma cada campo para um domínio consistente e verificável, sem substituir valores ausentes por zero e sem descartar registros apenas porque exigem recodificação. A variável de peso amostral não está presente no arquivo bruto; portanto, os resultados desta etapa analítica são descritivos e não ponderados, e não devem ser interpretados como estimativas nacionais.
data_candidates <- list.files(
"outputs",
pattern = "\\.csv$",
recursive = TRUE,
full.names = TRUE
)
if (length(data_candidates) != 1) {
stop(
"Esperava-se exatamente um arquivo CSV de dados dentro de 'outputs'. ",
"Abra o projeto pelo diretório raiz e confirme os arquivos disponíveis."
)
}
data_path <- data_candidates[[1]]
# Todas as colunas entram inicialmente como texto para evitar conversões silenciosas.
raw_data <- readr::read_csv(
data_path,
col_types = readr::cols(.default = readr::col_character()),
na = c("", "NA", "n/a", "."),
name_repair = "minimal",
show_col_types = FALSE
)
expected_columns <- c(
"HHID", "FIPSST", "YEAR", "SC_AGE_YEARS", "SC_SEX", "SC_RACE_R",
"K2Q31A", "K2Q31B", "K2Q31C", "K2Q31D", "ADDTREAT",
"K2Q30A", "K2Q32A", "K2Q33A", "GRADES", "REPEATED", "K7Q04R_R", "K6Q15",
"ACE1", "ACE3", "ACE4", "ACE5", "ACE6", "ACE7", "ACE8", "ACE9", "ACE10", "ACE11",
"HOURSLEEP", "SCREENTIME", "FPL_I1"
)
stopifnot(identical(names(raw_data), expected_columns))
stopifnot(nrow(raw_data) == 55162)
raw_display_data <- raw_data |>
dplyr::mutate(
.populated_fields = rowSums(!is.na(dplyr::across(dplyr::everything())))
) |>
dplyr::arrange(dplyr::desc(.populated_fields)) |>
dplyr::select(-.populated_fields)
DT::datatable(
raw_display_data |>
dplyr::slice_head(n = 10),
caption = "Tabela 1. Base bruta: todas as 31 colunas. A primeira página prioriza registros completos para que nenhum campo apareça vazio; essa ordenação é apenas visual e não altera a base. Use a barra horizontal para consultar todas as variáveis.",
options = list(pageLength = 10, scrollX = TRUE, scrollY = "420px")
)A estruturação é executada em etapas explícitas. Primeiro, a
importação preserva todos os campos como texto, evitando que o R
transforme silenciosamente códigos ou identificadores. Em seguida,
espaços são comprimidos e letras são normalizadas para permitir
comparações consistentes. Funções de recodificação convertem códigos e
rótulos equivalentes para domínios únicos, como
Yes/No, níveis de gravidade, faixas de sono e
categorias de notas. parse_number() recupera números
inseridos com unidades, por exemplo em idade e razão de pobreza
familiar. Marcadores NA, n/a, . e
células vazias passam a representar uma ausência real em R.
O destino de cada coluna segue uma regra verificável.
HHID permanece apenas como identificador.
FIPSST vira indicadores binários de estado e
YEAR vira um indicador binário do ano observado.
SC_AGE_YEARS e FPL_I1 são convertidas em
escalas min-max de 0 a 1. SC_SEX, SC_RACE_R,
K2Q31C, GRADES e K7Q04R_R passam
por codificação one-hot, criando uma coluna binária por
categoria. K2Q31A, K2Q31B,
K2Q31D, ADDTREAT, K2Q30A,
K2Q32A, K2Q33A, REPEATED,
K6Q15 e ACE3 a ACE11 tornam-se
indicadores binários de presença (1) ou ausência
(0). ACE1, HOURSLEEP e
SCREENTIME são variáveis ordinais e são convertidas em
escores normalizados de 0 a 1, preservando a ordem das respostas.
Por fim, o código FIPS é unido a uma tabela de estados antes da codificação; fatores recebem níveis ordenados quando existe uma ordem substantiva; e indicadores derivados são criados somente depois de os campos de origem estarem padronizados. A auditoria posterior mede ausências e valores que não puderam ser associados a uma regra, mantendo qualquer incerteza visível.
normalize_text <- function(x) {
x |>
stringr::str_squish() |>
stringr::str_to_upper(locale = "en")
}
clean_yes_no <- function(x) {
value <- normalize_text(x)
dplyr::case_when(
is.na(value) ~ NA_character_,
value %in% c("1", "1.0", "YES", "Y") ~ "Yes",
value %in% c("2", "2.0", "NO", "N") ~ "No",
stringr::str_detect(value, "NO BEHAVIORAL TREATMENT|BEHAVIORAL TREATMENT: NO|DEPRESSION\\s*-\\s*NO") ~ "No",
stringr::str_detect(value, "NOT TAKING|NO MEDS|NOT TREATED|TREATMENT: NO|MEDICATION: NO") ~ "No",
stringr::str_detect(value, "NO LEARNING|NO DEPRESSION|LEARNING DISABILITY: NO|DEPRESSION: NO|LD - NO") ~ "No",
stringr::str_detect(value, "^BEHAVIORAL TREATMENT$|BEHAVIORAL TREATMENT: YES|DEPRESSION\\s*-\\s*YES") ~ "Yes",
stringr::str_detect(value, "TAKING MEDICATION|ON MEDS|RECEIVED TREATMENT|TREATMENT: YES|HAS ") ~ "Yes",
stringr::str_detect(value, "MEDICATION: YES|LEARNING DISABILITY: YES|DEPRESSION: YES|LD - YES") ~ "Yes",
TRUE ~ NA_character_
)
}
clean_sex <- function(x) {
value <- normalize_text(x)
dplyr::case_when(
value %in% c("1", "1.0", "M", "MALE") ~ "Male",
value %in% c("2", "2.0", "F", "FEMALE") ~ "Female",
TRUE ~ NA_character_
)
}
clean_race <- function(x) {
value <- normalize_text(x)
dplyr::case_when(
value %in% c("1", "1.0", "WHITE", "WHITE ALONE") ~ "White alone",
value %in% c("2", "2.0", "BLACK", "BLACK ALONE", "BLACK OR AFRICAN AMERICAN") ~ "Black or African American alone",
value %in% c("3", "3.0", "AIAN", "AMERICAN INDIAN/ALASKA NATIVE", "AMERICAN INDIAN OR ALASKA NATIVE ALONE") ~ "American Indian or Alaska Native alone",
value %in% c("4", "4.0", "ASIAN", "ASIAN ALONE") ~ "Asian alone",
value %in% c("5", "5.0", "NHPI", "NATIVE HAWAIIAN OR PACIFIC ISLANDER", "NATIVE HAWAIIAN AND OTHER PACIFIC ISLANDER ALONE") ~ "Native Hawaiian or Other Pacific Islander alone",
value %in% c("7", "7.0", "TWO OR MORE RACES", "MULTIPLE RACES", "TWO+ RACES") ~ "Two or more races",
TRUE ~ NA_character_
)
}
clean_severity <- function(x) {
value <- normalize_text(x)
dplyr::case_when(
value %in% c("1", "1.0", "MILD", "LOW") ~ "Mild",
value %in% c("2", "2.0", "MODERATE", "MEDIUM") ~ "Moderate",
value %in% c("3", "3.0", "SEVERE", "HIGH") ~ "Severe",
TRUE ~ NA_character_
)
}
clean_grades <- function(x) {
value <- normalize_text(x) |> stringr::str_replace_all("'", "")
dplyr::case_when(
value %in% c("1", "1.0", "MOSTLY AS", "A", "A GRADES") ~ "Mostly A's",
value %in% c("2", "2.0", "MOSTLY AS AND BS", "A/B", "MOSTLY AS & BS") ~ "Mostly A's and B's",
value %in% c("3", "3.0", "MOSTLY BS AND CS", "B/C", "MOSTLY BS & CS") ~ "Mostly B's and C's",
value %in% c("4", "4.0", "MOSTLY CS AND DS", "C/D", "MOSTLY CS & DS") ~ "Mostly C's and D's",
value %in% c("5", "5.0", "MOSTLY DS OR LOWER", "D OR BELOW", "DS OR LOWER") ~ "Mostly D's or lower",
value %in% c("6", "6.0", "SCHOOL DOES NOT GIVE GRADES", "NO LETTER GRADES", "N/A - SCHOOL") ~ "No letter grades",
TRUE ~ NA_character_
)
}
clean_school_contact <- function(x) {
value <- normalize_text(x)
dplyr::case_when(
value %in% c("1", "1.0", "NONE", "0 CONTACTS", "NO CONTACTS") ~ "None",
value %in% c("2", "2.0", "1 TIME", "ONE CONTACT", "1X") ~ "1 time",
value %in% c("3", "3.0", "2 OR MORE TIMES", "2+ CONTACTS", "MULTIPLE CONTACTS") ~ "2 or more times",
TRUE ~ NA_character_
)
}
clean_ace1 <- function(x) {
value <- normalize_text(x)
dplyr::case_when(
value %in% c("1", "1.0", "NEVER", "0 - NEVER") ~ "Never",
value %in% c("2", "2.0", "RARELY", "INFREQUENT") ~ "Rarely",
value %in% c("3", "3.0", "SOMEWHAT OFTEN", "SOMETIMES OFTEN", "OCCASIONAL") ~ "Somewhat often",
value %in% c("4", "4.0", "VERY OFTEN", "FREQUENT") ~ "Very often",
TRUE ~ NA_character_
)
}
clean_sleep <- function(x) {
value <- normalize_text(x)
dplyr::case_when(
value %in% c("1", "1.0", "UNDER 6 HOURS", "<6H", "LESS THAN SIX HOURS") ~ "Less than 6 hours",
value %in% c("2", "2.0", "6 H", "6 HOURS", "SIX HOURS") ~ "6 hours",
value %in% c("3", "3.0", "7 H", "7 HOURS", "SEVEN HOURS") ~ "7 hours",
value %in% c("4", "4.0", "8 H", "8 HOURS", "EIGHT HOURS") ~ "8 hours",
value %in% c("5", "5.0", "9 H", "9 HOURS", "NINE HOURS") ~ "9 hours",
value %in% c("6", "6.0", "10 H", "10 HOURS", "TEN HOURS") ~ "10 hours",
value %in% c("7", "7.0", "11+ HOURS", "11 OR MORE HOURS", "11+ H") ~ "11 or more hours",
TRUE ~ NA_character_
)
}
clean_screen_time <- function(x) {
value <- normalize_text(x)
dplyr::case_when(
value %in% c("1", "1.0", "UNDER 1 HOUR", "<1H", "LESS THAN ONE HOUR") ~ "Less than 1 hour",
value %in% c("2", "2.0", "1 HOUR", "1 H", "ONE HOUR") ~ "1 hour",
value %in% c("3", "3.0", "2 HOURS", "2 H", "TWO HOURS") ~ "2 hours",
value %in% c("4", "4.0", "3 HOURS", "3 H", "THREE HOURS") ~ "3 hours",
value %in% c("5", "5.0", "4+ HOURS", "4 OR MORE HOURS", "4H+") ~ "4 or more hours",
TRUE ~ NA_character_
)
}# Tabela auxiliar para dar significado geográfico ao código FIPS.
fips_lookup <- tibble::tibble(
FIPSST = c("01", "02", "04", "05", "06", "08", "09", "10", "11", "12", "13", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "44", "45", "46", "47", "48", "49", "50", "51", "53", "54", "55", "56"),
state = c("Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "District of Columbia", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming")
)
clean_data <- raw_data |>
dplyr::transmute(
hhid = as.character(HHID),
fipsst = stringr::str_pad(stringr::str_extract(FIPSST, "\\d+"), width = 2, pad = "0"),
year = readr::parse_number(YEAR),
age_years = readr::parse_number(SC_AGE_YEARS),
sex = clean_sex(SC_SEX),
race = clean_race(SC_RACE_R),
adhd_ever = clean_yes_no(K2Q31A),
adhd_current = clean_yes_no(K2Q31B),
adhd_severity = clean_severity(K2Q31C),
adhd_medication = clean_yes_no(K2Q31D),
adhd_behavioral_treatment = clean_yes_no(ADDTREAT),
learning_disability = clean_yes_no(K2Q30A),
depression = clean_yes_no(K2Q32A),
anxiety = clean_yes_no(K2Q33A),
grades = clean_grades(GRADES),
repeated_grade = clean_yes_no(REPEATED),
school_contact = clean_school_contact(K7Q04R_R),
special_education_plan = clean_yes_no(K6Q15),
ace1_frequency = clean_ace1(ACE1),
ace3_parent_separation = clean_yes_no(ACE3),
ace4_parent_death = clean_yes_no(ACE4),
ace5_parent_incarceration = clean_yes_no(ACE5),
ace6_domestic_violence = clean_yes_no(ACE6),
ace7_neighborhood_violence = clean_yes_no(ACE7),
ace8_household_mental_illness = clean_yes_no(ACE8),
ace9_household_substance_problem = clean_yes_no(ACE9),
ace10_racial_unfair_treatment = clean_yes_no(ACE10),
ace11_health_unfair_treatment = clean_yes_no(ACE11),
sleep = clean_sleep(HOURSLEEP),
screen_time = clean_screen_time(SCREENTIME),
fpl_ratio = readr::parse_number(FPL_I1)
) |>
dplyr::left_join(fips_lookup, by = c("fipsst" = "FIPSST")) |>
dplyr::mutate(
age_group = cut(age_years, breaks = c(-Inf, 5, 11, 17), labels = c("0-5", "6-11", "12-17")),
sex = factor(sex, levels = c("Male", "Female")),
race = forcats::fct_relevel(factor(race), "White alone", "Black or African American alone"),
adhd_severity = factor(adhd_severity, levels = c("Mild", "Moderate", "Severe")),
grades = factor(grades, levels = c("Mostly A's", "Mostly A's and B's", "Mostly B's and C's", "Mostly C's and D's", "Mostly D's or lower", "No letter grades")),
school_contact = factor(school_contact, levels = c("None", "1 time", "2 or more times")),
sleep = factor(sleep, levels = c("Less than 6 hours", "6 hours", "7 hours", "8 hours", "9 hours", "10 hours", "11 or more hours"), ordered = TRUE),
screen_time = factor(screen_time, levels = c("Less than 1 hour", "1 hour", "2 hours", "3 hours", "4 or more hours"), ordered = TRUE),
ace1_hardship = dplyr::case_when(
ace1_frequency == "Never" ~ "No",
!is.na(ace1_frequency) ~ "Yes",
TRUE ~ NA_character_
),
grade_risk = dplyr::case_when(
grades %in% c("Mostly C's and D's", "Mostly D's or lower") ~ TRUE,
grades %in% c("Mostly A's", "Mostly A's and B's", "Mostly B's and C's") ~ FALSE,
TRUE ~ NA
),
school_contact_2plus = dplyr::case_when(
school_contact == "2 or more times" ~ TRUE,
school_contact %in% c("None", "1 time") ~ FALSE,
TRUE ~ NA
)
)
comorbidity_vars <- c("learning_disability", "depression", "anxiety")
school_risk_vars <- c("repeated_grade", "grade_risk", "school_contact_2plus")
ace_vars <- c(
"ace1_hardship", "ace3_parent_separation", "ace4_parent_death",
"ace5_parent_incarceration", "ace6_domestic_violence", "ace7_neighborhood_violence",
"ace8_household_mental_illness", "ace9_household_substance_problem",
"ace10_racial_unfair_treatment", "ace11_health_unfair_treatment"
)
clean_data <- clean_data |>
dplyr::mutate(
comorbidity_answered = rowSums(!is.na(dplyr::across(dplyr::all_of(comorbidity_vars)))),
comorbidity_count = dplyr::if_else(
comorbidity_answered > 0,
rowSums(dplyr::across(dplyr::all_of(comorbidity_vars)) == "Yes", na.rm = TRUE),
NA_integer_
),
ace_answered = rowSums(!is.na(dplyr::across(dplyr::all_of(ace_vars)))),
ace_exposure_count = dplyr::if_else(
ace_answered > 0,
rowSums(dplyr::across(dplyr::all_of(ace_vars)) == "Yes", na.rm = TRUE),
NA_integer_
),
ace_2plus = dplyr::case_when(
ace_exposure_count >= 2 ~ TRUE,
!is.na(ace_exposure_count) ~ FALSE,
TRUE ~ NA
),
ace_band = dplyr::case_when(
ace_exposure_count == 0 ~ "0 ACEs",
ace_exposure_count == 1 ~ "1 ACE",
ace_exposure_count == 2 ~ "2 ACEs",
ace_exposure_count >= 3 ~ "3+ ACEs",
TRUE ~ NA_character_
),
fpl_band = dplyr::case_when(
fpl_ratio <= 100 ~ "Até 100% FPL",
fpl_ratio <= 200 ~ "101-200% FPL",
fpl_ratio <= 400 ~ "201-400% FPL",
fpl_ratio > 400 ~ "Acima de 400% FPL",
TRUE ~ NA_character_
),
school_vulnerability_answered = rowSums(!is.na(dplyr::across(dplyr::all_of(school_risk_vars)))),
school_vulnerability_count = dplyr::if_else(
school_vulnerability_answered > 0,
rowSums(dplyr::across(dplyr::all_of(school_risk_vars)) == TRUE, na.rm = TRUE),
NA_integer_
)
) |>
dplyr::mutate(
ace_band = factor(ace_band, levels = c("0 ACEs", "1 ACE", "2 ACEs", "3+ ACEs"), ordered = TRUE),
fpl_band = factor(fpl_band, levels = c("Até 100% FPL", "101-200% FPL", "201-400% FPL", "Acima de 400% FPL"), ordered = TRUE)
)
# Funções para a matriz estruturada: 0/1 para categorias e escala [0, 1] para medidas ordenadas.
min_max_01 <- function(x) {
x <- as.numeric(x)
limits <- range(x, na.rm = TRUE)
if (!all(is.finite(limits))) return(rep(NA_real_, length(x)))
if (limits[1] == limits[2]) return(ifelse(is.na(x), NA_real_, 1))
(x - limits[1]) / (limits[2] - limits[1])
}
one_hot_encode <- function(x, prefix) {
values <- as.character(x)
levels <- sort(unique(values[!is.na(values)]))
encoded <- lapply(levels, function(level) {
ifelse(is.na(values), NA_real_, as.numeric(values == level))
})
names(encoded) <- paste0(prefix, "__", make.names(levels))
tibble::as_tibble(encoded)
}
yes_no_to_binary <- function(x) {
dplyr::case_when(
x == "Yes" ~ 1,
x == "No" ~ 0,
TRUE ~ NA_real_
)
}
structured_data <- tibble::tibble(
hhid = clean_data$hhid,
year_2023 = ifelse(is.na(clean_data$year), NA_real_, as.numeric(clean_data$year == 2023)),
age_years_01 = min_max_01(clean_data$age_years),
fpl_ratio_01 = min_max_01(clean_data$fpl_ratio),
ace1_frequency_01 = min_max_01(match(clean_data$ace1_frequency, c("Never", "Rarely", "Somewhat often", "Very often"))),
sleep_01 = min_max_01(as.integer(clean_data$sleep)),
screen_time_01 = min_max_01(as.integer(clean_data$screen_time)),
adhd_ever_01 = yes_no_to_binary(clean_data$adhd_ever),
adhd_current_01 = yes_no_to_binary(clean_data$adhd_current),
adhd_medication_01 = yes_no_to_binary(clean_data$adhd_medication),
adhd_behavioral_treatment_01 = yes_no_to_binary(clean_data$adhd_behavioral_treatment),
learning_disability_01 = yes_no_to_binary(clean_data$learning_disability),
depression_01 = yes_no_to_binary(clean_data$depression),
anxiety_01 = yes_no_to_binary(clean_data$anxiety),
repeated_grade_01 = yes_no_to_binary(clean_data$repeated_grade),
special_education_plan_01 = yes_no_to_binary(clean_data$special_education_plan),
ace3_parent_separation_01 = yes_no_to_binary(clean_data$ace3_parent_separation),
ace4_parent_death_01 = yes_no_to_binary(clean_data$ace4_parent_death),
ace5_parent_incarceration_01 = yes_no_to_binary(clean_data$ace5_parent_incarceration),
ace6_domestic_violence_01 = yes_no_to_binary(clean_data$ace6_domestic_violence),
ace7_neighborhood_violence_01 = yes_no_to_binary(clean_data$ace7_neighborhood_violence),
ace8_household_mental_illness_01 = yes_no_to_binary(clean_data$ace8_household_mental_illness),
ace9_household_substance_problem_01 = yes_no_to_binary(clean_data$ace9_household_substance_problem),
ace10_racial_unfair_treatment_01 = yes_no_to_binary(clean_data$ace10_racial_unfair_treatment),
ace11_health_unfair_treatment_01 = yes_no_to_binary(clean_data$ace11_health_unfair_treatment)
)
structured_data <- dplyr::bind_cols(
structured_data,
one_hot_encode(clean_data$state, "state"),
one_hot_encode(clean_data$sex, "sex"),
one_hot_encode(clean_data$race, "race"),
one_hot_encode(clean_data$adhd_severity, "adhd_severity"),
one_hot_encode(clean_data$grades, "grades"),
one_hot_encode(clean_data$school_contact, "school_contact")
)
non_identifier_columns <- setdiff(names(structured_data), "hhid")
stopifnot(all(vapply(structured_data[non_identifier_columns], function(x) all(is.na(x) | (x >= 0 & x <= 1)), logical(1))))
structured_display_data <- structured_data |>
dplyr::mutate(
.populated_features = rowSums(!is.na(dplyr::across(dplyr::all_of(non_identifier_columns))))
) |>
dplyr::arrange(dplyr::desc(.populated_features)) |>
dplyr::select(-.populated_features)
# Universo principal: crianças com idade e status de TDAH informados.
analysis_data <- clean_data |>
dplyr::filter(!is.na(age_years), !is.na(adhd_ever))
stopifnot(nrow(analysis_data) > 0)
safe_mean <- function(x) {
if (all(is.na(x))) NA_real_ else mean(x, na.rm = TRUE)
}
headline_n <- nrow(analysis_data)
headline_adhd_rate <- safe_mean(analysis_data$adhd_ever == "Yes")
headline_adhd_n <- sum(analysis_data$adhd_ever == "Yes", na.rm = TRUE)
headline_ace_2plus <- safe_mean(analysis_data$ace_2plus)quality_map <- c(
SC_AGE_YEARS = "age_years", SC_SEX = "sex", SC_RACE_R = "race",
K2Q31A = "adhd_ever", K2Q31C = "adhd_severity", K2Q31D = "adhd_medication",
ADDTREAT = "adhd_behavioral_treatment", K2Q30A = "learning_disability",
K2Q32A = "depression", GRADES = "grades", K7Q04R_R = "school_contact",
ACE1 = "ace1_frequency", ACE6 = "ace6_domestic_violence", HOURSLEEP = "sleep",
SCREENTIME = "screen_time", FPL_I1 = "fpl_ratio"
)
quality_table <- tibble::tibble(
source_variable = names(quality_map),
cleaned_variable = unname(quality_map),
missing_raw = vapply(names(quality_map), function(x) sum(is.na(raw_data[[x]])), integer(1)),
missing_after_cleaning = vapply(unname(quality_map), function(x) sum(is.na(clean_data[[x]])), integer(1)),
non_missing_not_standardized = vapply(seq_along(quality_map), function(i) {
sum(!is.na(raw_data[[names(quality_map)[i]]]) & is.na(clean_data[[quality_map[[i]]]]))
}, integer(1))
)
values_not_standardized <- sum(quality_table$non_missing_not_standardized)
join_audit <- clean_data |>
dplyr::summarise(
records = dplyr::n(),
records_without_state_match = sum(is.na(state))
)HHID,
foram verificados para conter apenas 0, 1,
valores normalizados entre 0 e 1 ou ausência
real. A auditoria encontrou 0 valores não ausentes sem regra de
padronização e 0 registros sem correspondência de estado.
DT::datatable(
structured_display_data |>
dplyr::slice_head(n = 10),
caption = "Tabela 2. Matriz estruturada: todas as variáveis transformadas em indicadores binários ou escalas normalizadas. A primeira página prioriza registros completos; essa ordenação é apenas visual. Use a barra horizontal para visualizar todas as colunas.",
options = list(pageLength = 10, scrollX = TRUE, scrollY = "420px")
)Esta descrição se refere à nova matriz exibida
acima, e não aos nomes originais da importação. Há três convenções de
leitura: em indicadores binários, 1 significa que a
característica ou categoria está presente e 0 significa que
não está; nas escalas normalizadas, 0 representa o menor
nível observado e 1 o maior; NA permanece como
resposta ausente, não aplicável ou não informada — nunca como zero.
hhid: identificador do registro. É preservado para
rastreabilidade e não entra como medida analítica.year_2023: indicador do ano de referência;
1 representa 2023.state__<nome_do_estado>: família criada a partir
de FIPSST. Cada estado possui uma coluna; por exemplo,
state__Florida = 1 indica residência na Flórida e
0 indica outro estado.age_years_01: idade em anos, normalizada por min–max
para o intervalo de 0 a 1.sex__Female e sex__Male: indicadores de
sexo criados por one-hot encoding.race__<categoria>: indicadores de raça detalhada
criados por one-hot encoding. Apenas a categoria correspondente
recebe 1 em cada registro com resposta válida.adhd_ever_01: profissional já informou diagnóstico de
TDAH/ADD (1 = sim).adhd_current_01: TDAH/ADD informado como condição atual
(1 = sim).adhd_severity__Mild,
adhd_severity__Moderate e
adhd_severity__Severe: gravidade atual relatada, convertida
em três indicadores mutuamente exclusivos.adhd_medication_01: uso atual de medicamento para
TDAH/ADD (1 = sim).adhd_behavioral_treatment_01: tratamento comportamental
para TDAH/ADD relatado (1 = sim).learning_disability_01, depression_01 e
anxiety_01: diagnóstico informado de deficiência de
aprendizagem, depressão e ansiedade, respectivamente (1 =
sim).grades__<categoria>: padrão de notas transformado
em um indicador para cada categoria, como
grades__Mostly.A.s ou
grades__Mostly.C.s.and.D.s.repeated_grade_01: repetiu alguma série desde o início
do jardim de infância (1 = sim).school_contact__None,
school_contact__X1.time e
school_contact__X2.or.more.times: quantidade de contatos da
escola com o domicílio por problemas escolares, codificada em
indicadores mutuamente exclusivos.special_education_plan_01: já teve plano de educação
especial ou intervenção precoce (1 = sim).ace1_frequency_01: frequência de dificuldade para
cobrir necessidades básicas, da menor à maior frequência, normalizada
entre 0 e 1.ace3_parent_separation_01: separação ou divórcio de
pais/responsáveis (1 = sim).ace4_parent_death_01: falecimento de pai/mãe ou
responsável (1 = sim).ace5_parent_incarceration_01: encarceramento de pai/mãe
ou responsável (1 = sim).ace6_domestic_violence_01: convivência com violência
doméstica (1 = sim).ace7_neighborhood_violence_01: vivência de violência no
bairro (1 = sim).ace8_household_mental_illness_01: convivência com
problema de saúde mental no domicílio (1 = sim).ace9_household_substance_problem_01: convivência com
problema de álcool ou outras substâncias no domicílio (1 =
sim).ace10_racial_unfair_treatment_01: tratamento injusto
por raça/etnia relatado (1 = sim).ace11_health_unfair_treatment_01: tratamento injusto em
serviços de saúde relatado (1 = sim).sleep_01: categoria de horas de sono em noites de
semana, ordenada e normalizada de 0 a 1.screen_time_01: categoria de tempo diário de tela,
ordenada e normalizada de 0 a 1.fpl_ratio_01: razão de pobreza familiar (Federal
Poverty Level), convertida para escala min–max de 0 a 1. Valores
maiores representam maior razão em relação ao limiar federal de
pobreza.state__, race__, grades__,
school_contact__ e adhd_severity__ — devem ser
lidas conjuntamente. Elas representam uma única variável categórica
distribuída em indicadores, e não várias características independentes.
HHID garante a rastreabilidade de cada registro sem
entrar como variável analítica. FIPSST localiza o estado e
será usado para uma exploração geográfica mais adiante. Aqui, o ponto de
partida é demográfico: idade, sexo e raça ajudam a evitar comparações
entre grupos com composições muito diferentes. Se o diagnóstico
informado estiver concentrado em certos perfis, essa distribuição
precisa enquadrar todas as interpretações seguintes.
adhd_by_age_sex <- analysis_data |>
dplyr::filter(!is.na(age_group), !is.na(sex)) |>
dplyr::group_by(age_group, sex) |>
dplyr::summarise(
n = dplyr::n(),
adhd_prevalence = safe_mean(adhd_ever == "Yes"),
.groups = "drop"
)
peak_adhd_group <- adhd_by_age_sex |>
dplyr::slice_max(adhd_prevalence, n = 1, with_ties = FALSE)
adhd_by_race <- analysis_data |>
dplyr::filter(!is.na(race)) |>
dplyr::group_by(race) |>
dplyr::summarise(
n = dplyr::n(),
adhd_prevalence = safe_mean(adhd_ever == "Yes"),
.groups = "drop"
) |>
dplyr::arrange(adhd_prevalence)
plot_adhd_demographics <- ggplot2::ggplot(
adhd_by_age_sex,
ggplot2::aes(x = age_group, y = adhd_prevalence, fill = sex)
) +
ggplot2::geom_col(position = ggplot2::position_dodge(width = 0.75), width = 0.65) +
ggplot2::scale_y_continuous(labels = scales::label_percent(accuracy = 1)) +
ggplot2::scale_fill_manual(values = c("Male" = "#2878B5", "Female" = "#E07A9A")) +
ggplot2::labs(
title = "Diagnóstico informado de TDAH por faixa etária e sexo",
subtitle = "Percentuais não ponderados entre registros com informação válida",
x = "Faixa etária (anos)", y = "Percentual com TDAH informado", fill = "Sexo"
) +
ggplot2::theme_minimal(base_size = 12) +
ggplot2::theme(legend.position = "top")
plot_adhd_demographicsggplot2::ggplot(adhd_by_race, ggplot2::aes(x = race, y = adhd_prevalence, color = adhd_prevalence, size = n)) +
ggplot2::geom_point(alpha = 0.9) +
ggplot2::coord_flip() +
ggplot2::scale_color_gradient(low = "#E9BD27", high = "#990000", labels = scales::label_percent(accuracy = 1)) +
ggplot2::scale_y_continuous(labels = scales::label_percent(accuracy = 1)) +
ggplot2::labs(
title = "Diagnóstico informado de TDAH por raça detalhada",
subtitle = "O tamanho do ponto representa o número de registros no grupo",
x = NULL, y = "Percentual com TDAH informado", color = "Percentual", size = "Registros"
) +
ggplot2::theme_minimal(base_size = 12)O núcleo clínico não se resume a “sim” ou “não”. K2Q31A
informa o diagnóstico alguma vez relatado; K2Q31B indica
situação atual; K2Q31C registra gravidade; e
K2Q31D e ADDTREAT descrevem duas modalidades
de cuidado. A sequência abaixo revela como essas respostas se distribuem
sem presumir que uma modalidade seja superior à outra.
adhd_clinical <- analysis_data |>
dplyr::filter(adhd_ever == "Yes")
clinical_summary <- adhd_clinical |>
dplyr::summarise(
n_diagnosed = dplyr::n(),
current_adhd = safe_mean(adhd_current == "Yes"),
medication = safe_mean(adhd_medication == "Yes"),
behavioral_treatment = safe_mean(adhd_behavioral_treatment == "Yes")
)
clinical_treatment <- adhd_clinical |>
dplyr::filter(!is.na(adhd_severity)) |>
dplyr::select(adhd_severity, adhd_medication, adhd_behavioral_treatment) |>
tidyr::pivot_longer(-adhd_severity, names_to = "care", values_to = "received") |>
dplyr::group_by(adhd_severity, care) |>
dplyr::summarise(proportion = safe_mean(received == "Yes"), n = dplyr::n(), .groups = "drop") |>
dplyr::mutate(care = dplyr::recode(care,
adhd_medication = "Medicação atual",
adhd_behavioral_treatment = "Tratamento comportamental"
))
knitr::kable(
clinical_summary |>
dplyr::mutate(dplyr::across(-n_diagnosed, scales::percent, accuracy = 0.1)),
caption = "Cascata clínica entre registros com TDAH informado."
)| n_diagnosed | current_adhd | medication | behavioral_treatment |
|---|---|---|---|
| 5338 | 94.3% | 54.5% | 45.0% |
ggplot2::ggplot(clinical_treatment, ggplot2::aes(x = adhd_severity, y = proportion, fill = care)) +
ggplot2::geom_col(position = ggplot2::position_dodge(width = 0.75), width = 0.65) +
ggplot2::scale_y_continuous(labels = scales::label_percent(accuracy = 1)) +
ggplot2::scale_fill_manual(values = c("Medicação atual" = "#990000", "Tratamento comportamental" = "#E9BD27")) +
ggplot2::labs(
title = "Modalidades de cuidado por gravidade relatada de TDAH",
subtitle = "Percentual entre casos com gravidade e modalidade de cuidado informadas",
x = "Gravidade relatada", y = "Percentual", fill = NULL
) +
ggplot2::theme_minimal(base_size = 12) +
ggplot2::theme(legend.position = "top")Deficiência de aprendizagem, depressão e ansiedade foram organizadas como três comorbidades distintas. Em vez de somá-las imediatamente, primeiro observamos a prevalência de cada uma por grupo de TDAH. Isso mostra onde se concentram os contrastes antes de usar a contagem agregada de comorbidades.
comorbidity_long <- analysis_data |>
dplyr::select(adhd_ever, learning_disability, depression, anxiety) |>
tidyr::pivot_longer(-adhd_ever, names_to = "condition", values_to = "reported") |>
dplyr::group_by(adhd_ever, condition) |>
dplyr::summarise(prevalence = safe_mean(reported == "Yes"), n = dplyr::n(), .groups = "drop") |>
dplyr::mutate(condition = dplyr::recode(condition,
learning_disability = "Deficiência de aprendizagem",
depression = "Depressão",
anxiety = "Ansiedade"
))
comorbidity_gap <- comorbidity_long |>
dplyr::select(adhd_ever, condition, prevalence) |>
tidyr::pivot_wider(names_from = adhd_ever, values_from = prevalence) |>
dplyr::mutate(gap = Yes - No) |>
dplyr::slice_max(gap, n = 1, with_ties = FALSE)
ggplot2::ggplot(comorbidity_long, ggplot2::aes(x = adhd_ever, y = condition, fill = prevalence)) +
ggplot2::geom_tile(color = "white", linewidth = 1.1) +
ggplot2::geom_text(ggplot2::aes(label = scales::percent(prevalence, accuracy = 0.1)), color = "#1c1a1a", fontface = "bold") +
ggplot2::scale_fill_gradient(low = "#FFF4C8", high = "#990000", labels = scales::label_percent(accuracy = 1)) +
ggplot2::labs(
title = "Comorbidades relatadas por status de TDAH",
x = "TDAH informado", y = NULL, fill = "Percentual"
) +
ggplot2::theme_minimal(base_size = 12)O passo seguinte testa uma interpretação plausível: se o TDAH informado aparece junto de mais necessidades educacionais e de saúde, os indicadores escolares devem revelar contrastes entre os grupos. A tabela evita reduzir a história a um único resultado; o gráfico destaca os quatro indicadores mais diretos de acompanhamento escolar.
outcomes_numeric <- analysis_data |>
dplyr::group_by(adhd_ever) |>
dplyr::summarise(
n = dplyr::n(),
mean_comorbidities = safe_mean(comorbidity_count),
repeated_grade = safe_mean(repeated_grade == "Yes"),
special_education_plan = safe_mean(special_education_plan == "Yes"),
lower_grades = safe_mean(grade_risk),
school_contact_2plus = safe_mean(school_contact_2plus),
.groups = "drop"
)
outcomes_by_adhd <- outcomes_numeric |>
dplyr::mutate(dplyr::across(c(repeated_grade, special_education_plan, lower_grades, school_contact_2plus), scales::percent, accuracy = 0.1))
school_gap <- outcomes_numeric |>
dplyr::select(adhd_ever, school_contact_2plus) |>
tidyr::pivot_wider(names_from = adhd_ever, values_from = school_contact_2plus) |>
dplyr::mutate(gap = Yes - No) |>
dplyr::pull(gap)
school_gap_direction <- ifelse(school_gap >= 0, "maior", "menor")
DT::datatable(
outcomes_by_adhd,
caption = "Indicadores escolares e comorbidades por status de TDAH informado.",
rownames = FALSE,
options = list(dom = "tip", pageLength = 10)
)outcomes_for_plot <- analysis_data |>
dplyr::group_by(adhd_ever) |>
dplyr::summarise(
`Repetiu alguma série` = safe_mean(repeated_grade == "Yes"),
`Plano de educação especial` = safe_mean(special_education_plan == "Yes"),
`Notas C/D ou inferiores` = safe_mean(grade_risk),
`2+ contatos da escola` = safe_mean(school_contact_2plus == TRUE),
.groups = "drop"
) |>
tidyr::pivot_longer(-adhd_ever, names_to = "indicator", values_to = "proportion")
plot_school_outcomes <- ggplot2::ggplot(
outcomes_for_plot,
ggplot2::aes(x = indicator, y = proportion, fill = adhd_ever,
text = paste0("Indicador: ", indicator, "<br>Status TDAH: ", adhd_ever,
"<br>Percentual: ", scales::percent(proportion, accuracy = 0.1)))
) +
ggplot2::geom_col(position = ggplot2::position_dodge(width = 0.75), width = 0.65) +
ggplot2::coord_flip() +
ggplot2::scale_y_continuous(labels = scales::label_percent(accuracy = 1)) +
ggplot2::scale_fill_manual(values = c("No" = "#9ECAE1", "Yes" = "#E6550D")) +
ggplot2::labs(
title = "Indicadores escolares por status de TDAH informado",
x = NULL, y = "Percentual não ponderado", fill = "TDAH informado"
) +
ggplot2::theme_minimal(base_size = 12) +
ggplot2::theme(legend.position = "top")
plot_school_outcomesschool_flow <- analysis_data |>
dplyr::filter(!is.na(adhd_ever), !is.na(school_contact_2plus)) |>
dplyr::count(adhd_ever, school_contact_2plus, name = "value") |>
dplyr::mutate(
source = ifelse(adhd_ever == "No", 0L, 1L),
target = ifelse(school_contact_2plus, 3L, 2L)
)
plotly::plot_ly(
type = "sankey",
orientation = "h",
node = list(
label = c("TDAH não informado", "TDAH informado", "0-1 contato da escola", "2+ contatos da escola"),
color = c("#9ECAE1", "#E6550D", "#C7DCEB", "#D9AA00"),
pad = 18,
thickness = 22
),
link = list(
source = school_flow$source,
target = school_flow$target,
value = school_flow$value,
color = ifelse(school_flow$adhd_ever == "Yes", "rgba(230,85,13,0.42)", "rgba(40,120,181,0.30)")
)
) |>
plotly::layout(title = "Fluxo interativo: status de TDAH e contatos da escola", font = list(size = 13))As ACEs registram experiências difíceis no ambiente familiar, comunitário ou social. Elas não medem toda a história de vida da criança, mas permitem investigar um ponto central do projeto: à medida que exposições adversas se acumulam, os indicadores escolares também se alteram? A resposta abaixo é associativa, não causal.
ace_by_adhd <- analysis_data |>
dplyr::group_by(adhd_ever) |>
dplyr::summarise(
n = dplyr::n(),
mean_ace_exposures = safe_mean(ace_exposure_count),
ace_2plus = safe_mean(ace_2plus),
.groups = "drop"
)
ace_gap <- ace_by_adhd |>
dplyr::select(adhd_ever, mean_ace_exposures) |>
tidyr::pivot_wider(names_from = adhd_ever, values_from = mean_ace_exposures) |>
dplyr::mutate(gap = Yes - No) |>
dplyr::pull(gap)
knitr::kable(
ace_by_adhd |>
dplyr::mutate(ace_2plus = scales::percent(ace_2plus, accuracy = 0.1)),
caption = "Número médio de ACEs e proporção com duas ou mais exposições."
)| adhd_ever | n | mean_ace_exposures | ace_2plus |
|---|---|---|---|
| No | 46160 | 0.8237252 | 17.9% |
| Yes | 5338 | 1.8337126 | 44.6% |
ace_school_summary <- analysis_data |>
dplyr::filter(!is.na(ace_band)) |>
dplyr::group_by(ace_band, adhd_ever) |>
dplyr::summarise(
n = dplyr::n(),
`2+ contatos da escola` = safe_mean(school_contact_2plus),
`Notas C/D ou inferiores` = safe_mean(grade_risk),
`Vulnerabilidades escolares médias` = safe_mean(school_vulnerability_count),
.groups = "drop"
)
knitr::kable(
ace_school_summary |>
dplyr::mutate(dplyr::across(c(`2+ contatos da escola`, `Notas C/D ou inferiores`), scales::percent, accuracy = 0.1)),
caption = "Indicadores escolares por faixa de ACEs e status de TDAH informado."
)| ace_band | adhd_ever | n | 2+ contatos da escola | Notas C/D ou inferiores | Vulnerabilidades escolares médias |
|---|---|---|---|---|---|
| 0 ACEs | No | 23670 | 5.0% | 1.2% | 0.0579414 |
| 0 ACEs | Yes | 1481 | 23.8% | 8.8% | 0.3005780 |
| 1 ACE | No | 13569 | 8.7% | 3.2% | 0.1106307 |
| 1 ACE | Yes | 1442 | 32.0% | 14.6% | 0.4274981 |
| 2 ACEs | No | 4320 | 12.4% | 5.6% | 0.1686476 |
| 2 ACEs | Yes | 916 | 38.1% | 19.0% | 0.5283237 |
| 3+ ACEs | No | 3802 | 20.9% | 10.0% | 0.2889318 |
| 3+ ACEs | Yes | 1435 | 48.2% | 25.8% | 0.6835536 |
mode_value <- function(x) names(sort(table(x), decreasing = TRUE))[1]
school_model_data <- analysis_data |>
dplyr::filter(
!is.na(school_contact_2plus), !is.na(ace_band), !is.na(adhd_ever),
!is.na(age_years), !is.na(sex), !is.na(race), !is.na(fpl_ratio)
) |>
dplyr::mutate(adhd_ever = factor(adhd_ever, levels = c("No", "Yes")))
school_contact_model <- stats::glm(
school_contact_2plus ~ ace_band * adhd_ever + age_years + sex + race + fpl_ratio,
data = school_model_data,
family = stats::binomial()
)
prediction_grid <- tidyr::crossing(
ace_band = factor(levels(analysis_data$ace_band), levels = levels(analysis_data$ace_band), ordered = TRUE),
adhd_ever = factor(c("No", "Yes"), levels = c("No", "Yes"))
) |>
dplyr::mutate(
age_years = median(school_model_data$age_years),
sex = factor(mode_value(school_model_data$sex), levels = levels(school_model_data$sex)),
race = factor(mode_value(school_model_data$race), levels = levels(school_model_data$race)),
fpl_ratio = median(school_model_data$fpl_ratio)
)
predicted_link <- stats::predict(school_contact_model, newdata = prediction_grid, type = "link", se.fit = TRUE)
prediction_grid <- prediction_grid |>
dplyr::mutate(
probability = stats::plogis(predicted_link$fit),
lower = stats::plogis(predicted_link$fit - 1.96 * predicted_link$se.fit),
upper = stats::plogis(predicted_link$fit + 1.96 * predicted_link$se.fit)
)
adjusted_ace_gap <- prediction_grid |>
dplyr::filter(ace_band == "3+ ACEs") |>
dplyr::select(adhd_ever, probability) |>
tidyr::pivot_wider(names_from = adhd_ever, values_from = probability) |>
dplyr::mutate(gap = Yes - No) |>
dplyr::pull(gap)
ggplot2::ggplot(prediction_grid, ggplot2::aes(x = ace_band, y = probability, color = adhd_ever, group = adhd_ever, fill = adhd_ever)) +
ggplot2::geom_ribbon(ggplot2::aes(ymin = lower, ymax = upper), alpha = 0.16, color = NA) +
ggplot2::geom_line(linewidth = 1.1) +
ggplot2::geom_point(size = 3) +
ggplot2::scale_y_continuous(labels = scales::label_percent(accuracy = 1)) +
ggplot2::scale_color_manual(values = c("No" = "#2878B5", "Yes" = "#990000")) +
ggplot2::scale_fill_manual(values = c("No" = "#2878B5", "Yes" = "#990000")) +
ggplot2::labs(
title = "ACEs e probabilidade ajustada de contatos escolares frequentes",
subtitle = "Modelo logístico ajustado por idade, sexo, raça e razão de pobreza familiar",
x = "Exposições adversas selecionadas", y = "Probabilidade prevista de 2+ contatos", color = "TDAH informado", fill = "TDAH informado"
) +
ggplot2::theme_minimal(base_size = 12) +
ggplot2::theme(legend.position = "top")Sono, tempo de tela e razão de pobreza familiar são dimensões de contexto, não marcadores de responsabilidade individual. Nesta etapa, verificamos como esses fatores se distribuem segundo TDAH e faixas de ACEs. A leitura conjunta evita interpretar rotina fora das condições materiais e das experiências relatadas.
routine_by_adhd <- analysis_data |>
dplyr::filter(!is.na(sleep), !is.na(screen_time)) |>
dplyr::count(adhd_ever, sleep, screen_time, name = "n") |>
dplyr::group_by(adhd_ever) |>
dplyr::mutate(proportion = n / sum(n)) |>
dplyr::ungroup()
fpl_ace_profile <- analysis_data |>
dplyr::filter(!is.na(fpl_band), !is.na(ace_band)) |>
dplyr::count(fpl_band, ace_band, adhd_ever, name = "n") |>
dplyr::group_by(fpl_band, adhd_ever) |>
dplyr::mutate(proportion = n / sum(n)) |>
dplyr::ungroup()
ggplot2::ggplot(routine_by_adhd, ggplot2::aes(x = sleep, y = proportion, fill = screen_time)) +
ggplot2::geom_col(position = "stack") +
ggplot2::facet_wrap(~ adhd_ever) +
ggplot2::scale_y_continuous(labels = scales::label_percent(accuracy = 1)) +
ggplot2::scale_fill_brewer(palette = "YlOrRd", name = "Tempo de tela") +
ggplot2::labs(
title = "Sono e tempo de tela por status de TDAH informado",
x = "Horas de sono em noites de semana", y = "Proporção dentro do grupo"
) +
ggplot2::theme_minimal(base_size = 12) +
ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 35, hjust = 1), legend.position = "bottom")plotly::ggplotly(
ggplot2::ggplot(fpl_ace_profile, ggplot2::aes(x = fpl_band, y = ace_band, fill = proportion,
text = paste0("FPL: ", fpl_band, "<br>ACEs: ", ace_band, "<br>TDAH: ", adhd_ever,
"<br>Proporção: ", scales::percent(proportion, accuracy = 0.1)))) +
ggplot2::geom_tile(color = "white") +
ggplot2::facet_wrap(~ adhd_ever) +
ggplot2::scale_fill_gradient(low = "#FFF4C8", high = "#7A0019", labels = scales::label_percent(accuracy = 1)) +
ggplot2::labs(title = "ACEs e FPL: distribuição dentro de cada faixa socioeconômica", x = "Razão de pobreza familiar", y = "Faixa de ACEs", fill = "Proporção") +
ggplot2::theme_minimal(base_size = 12),
tooltip = "text"
)Para fugir de comparações isoladas, esta etapa reúne idade, razão de pobreza familiar, comorbidades, adversidades, sono, tempo de tela e indicadores escolares. O algoritmo k-means identifica grupos de observações com perfis semelhantes; em seguida, uma PCA reduz essas dimensões para um plano visual. Os grupos são exploratórios e não equivalem a categorias clínicas ou diagnósticas.
cluster_features <- analysis_data |>
dplyr::transmute(
age_years,
fpl_ratio,
comorbidity_count,
ace_exposure_count,
sleep_score = as.integer(sleep),
screen_time_score = as.integer(screen_time),
grade_risk = as.integer(grade_risk),
school_contact_2plus = as.integer(school_contact_2plus),
adhd_ever
) |>
dplyr::filter(dplyr::if_all(-adhd_ever, ~ !is.na(.)))
if (nrow(cluster_features) < 100) {
stop("Não há registros completos suficientes para o agrupamento exploratório.")
}
feature_names <- setdiff(names(cluster_features), "adhd_ever")
feature_matrix <- scale(as.matrix(cluster_features[, feature_names]))
set.seed(2026)
cluster_fit <- stats::kmeans(feature_matrix, centers = 4, nstart = 30, iter.max = 100)
pca_fit <- stats::prcomp(feature_matrix, center = FALSE, scale. = FALSE)
cluster_data <- cluster_features |>
dplyr::mutate(
cluster = factor(cluster_fit$cluster, labels = paste("Perfil", seq_len(4))),
pc1 = pca_fit$x[, 1],
pc2 = pca_fit$x[, 2]
)
cluster_profile <- cluster_data |>
dplyr::group_by(cluster) |>
dplyr::summarise(
n = dplyr::n(),
idade_média = mean(age_years),
FPL_médio = mean(fpl_ratio),
comorbidades_médias = mean(comorbidity_count),
ACEs_médias = mean(ace_exposure_count),
TDAH_informado = safe_mean(adhd_ever == "Yes"),
.groups = "drop"
) |>
dplyr::mutate(TDAH_informado = scales::percent(TDAH_informado, accuracy = 0.1))
cluster_sample_n <- min(4500, nrow(cluster_data))
set.seed(2026)
cluster_sample <- dplyr::slice_sample(cluster_data, n = cluster_sample_n)
plotly::plot_ly(
cluster_sample,
x = ~pc1,
y = ~pc2,
color = ~cluster,
symbol = ~adhd_ever,
type = "scattergl",
mode = "markers",
colors = c("#7A0019", "#D89B00", "#2878B5", "#5F7D3A"),
marker = list(size = 7, opacity = 0.68),
text = ~paste0(
"Perfil: ", cluster,
"<br>TDAH informado: ", adhd_ever,
"<br>Idade: ", age_years,
"<br>Comorbidades: ", comorbidity_count,
"<br>ACEs: ", ace_exposure_count,
"<br>FPL: ", round(fpl_ratio, 0)
),
hoverinfo = "text"
) |>
plotly::layout(
title = "Mapa interativo de perfis: PCA + k-means",
xaxis = list(title = "Componente principal 1"),
yaxis = list(title = "Componente principal 2"),
legend = list(orientation = "h", x = 0, y = -0.15),
paper_bgcolor = "rgba(0,0,0,0)",
plot_bgcolor = "rgba(255,255,255,0.85)"
)knitr::kable(cluster_profile, digits = 2, caption = "Perfil médio dos quatro agrupamentos exploratórios.")| cluster | n | idade_média | FPL_médio | comorbidades_médias | ACEs_médias | TDAH_informado |
|---|---|---|---|---|---|---|
| Perfil 1 | 10688 | 14.58 | 281.96 | 0.40 | 1.32 | 14.2% |
| Perfil 2 | 1248 | 13.80 | 225.28 | 1.11 | 2.47 | 47.8% |
| Perfil 3 | 8647 | 9.54 | 300.52 | 0.10 | 0.63 | 7.3% |
| Perfil 4 | 2105 | 11.82 | 266.91 | 0.83 | 1.97 | 38.7% |
O código FIPS permite deslocar a pergunta do indivíduo para o território. Como o arquivo não contém pesos, a exploração por estado não serve para produzir rankings populacionais. Ela serve para identificar onde a composição observada de TDAH, ACEs e contatos escolares parece diferente e onde análises ponderadas futuras podem ser prioritárias.
state_summary_numeric <- analysis_data |>
dplyr::filter(!is.na(state)) |>
dplyr::group_by(state) |>
dplyr::summarise(
n = dplyr::n(),
adhd_prevalence = safe_mean(adhd_ever == "Yes"),
mean_comorbidities = safe_mean(comorbidity_count),
ace_2plus = safe_mean(ace_2plus),
school_contact_2plus = safe_mean(school_contact_2plus),
.groups = "drop"
)
state_summary <- state_summary_numeric |>
dplyr::arrange(dplyr::desc(adhd_prevalence)) |>
dplyr::mutate(dplyr::across(c(adhd_prevalence, ace_2plus, school_contact_2plus), scales::percent, accuracy = 0.1))
DT::datatable(
state_summary,
caption = "Exploração descritiva não ponderada por estado.",
rownames = FALSE,
filter = "top",
options = list(pageLength = 10, scrollX = TRUE)
)plotly::plot_ly(
state_summary_numeric,
x = ~ace_2plus,
y = ~school_contact_2plus,
size = ~n,
color = ~adhd_prevalence,
colors = c("#FFF4C8", "#990000"),
type = "scatter",
mode = "markers",
marker = list(opacity = 0.75),
text = ~paste0(state, "<br>Registros: ", n,
"<br>TDAH: ", scales::percent(adhd_prevalence, accuracy = 0.1),
"<br>2+ ACEs: ", scales::percent(ace_2plus, accuracy = 0.1),
"<br>2+ contatos: ", scales::percent(school_contact_2plus, accuracy = 0.1)),
hoverinfo = "text"
) |>
plotly::layout(
title = "Exploração estadual: ACEs, contatos escolares e TDAH",
xaxis = list(title = "Proporção com 2+ ACEs", tickformat = ".0%"),
yaxis = list(title = "Proporção com 2+ contatos escolares", tickformat = ".0%")
)O relatório partiu de uma pergunta deliberadamente ampla: em quais combinações de saúde, escola, adversidades e rotina o TDAH informado aparece com mais frequência? A resposta não é um único número. Após estruturar os registros, o universo analítico reuniu 51,498 crianças com idade e status de TDAH válidos; 5,338 tiveram TDAH informado (10.4%). A partir daí, cada eixo acrescentou uma peça à mesma história.
O maior percentual descritivo foi observado entre Male de 12-17 anos (21.5%). A distribuição também varia entre os grupos de raça e entre estados. Contudo, essas diferenças mostram a composição observada do arquivo, não explicam por que alguém recebeu um diagnóstico e não podem ser convertidas em estimativas nacionais sem os pesos da pesquisa.
Não é assim que ele se apresenta nesta base. Ao comparar deficiência de aprendizagem, depressão e ansiedade separadamente, o maior contraste ocorreu em Ansiedade, com diferença de 39.2% entre os grupos. Isso sustenta uma leitura de necessidades concomitantes: uma análise escolar responsável não deve resumir toda a situação da criança ao rótulo de TDAH.
No núcleo clínico, 94.3% dos registros com TDAH informado também o reportaram como atual. As modalidades de cuidado variaram segundo a gravidade relatada, mas o banco não mede qualidade, continuidade, adesão ou efetividade do tratamento. Portanto, o resultado descreve cuidado relatado; não permite eleger uma modalidade como melhor.
Há um contraste claro nos indicadores explorados. A proporção de crianças com dois ou mais contatos da escola foi 26.4% maior no grupo com TDAH informado. Repetência, plano de educação especial, notas mais baixas e contatos escolares foram analisados lado a lado justamente para evitar que um único indicador seja tratado como sinônimo de desempenho.
Sim, como contexto associado. A média de ACEs selecionadas diferiu em 1.01 entre os grupos com e sem TDAH informado. Além disso, no modelo que comparou crianças com a mesma idade, sexo, raça e FPL de referência, a faixa de 3+ ACEs manteve uma diferença prevista de 27.8% em contatos escolares frequentes entre os grupos de TDAH.
Esse é o ponto mais importante do roteiro: adversidade não é uma explicação automática para TDAH, e TDAH não é uma explicação automática para dificuldades na escola. Os resultados são compatíveis com a hipótese de que saúde, experiências adversas e escola se cruzam; o delineamento transversal não permite afirmar que uma variável causou a outra.
Não. Sono, tempo de tela e FPL ampliaram o cenário, mas os perfis exploratórios por PCA e k-means mostraram combinações diferentes dessas dimensões. Da mesma forma, a visualização estadual revelou heterogeneidade aparente, e não um ranking confiável de estados. A leitura útil é que os apoios precisam ser sensíveis ao contexto, em vez de aplicar uma única trajetória a todas as crianças.
Para educação, saúde infantil e gestão, o uso responsável do relatório é transformar os padrões em perguntas de acompanhamento: há necessidade educacional não atendida? existem comorbidades que merecem triagem? há estressores familiares ou materiais que exigem encaminhamento? A conclusão não é classificar crianças, e sim defender uma visão coordenada entre escola, família e serviços de saúde.
Uma continuação mais robusta deve recuperar a variável de peso e o
desenho complexo da NSCH, usar o pacote survey para
estimativas e intervalos de confiança, e testar se os padrões permanecem
após incorporar outras variáveis disponíveis na fonte. Comparações entre
anos também exigem conferir a compatibilidade do dicionário de variáveis
antes de serem interpretadas. Assim, esta análise encerra a exploração
com uma conclusão prudente: os dados apontam para a importância
de olhar a criança inteira e o seu contexto, sem transformar associações
em sentenças causais.