Code
# Load all required packages via pacman
if (!requireNamespace("pacman", quietly = TRUE)) install.packages("pacman")
pacman::p_load(tidyverse, here, janitor, viridis, scales, MASS, gtsummary)Demetrio Data
# Load all required packages via pacman
if (!requireNamespace("pacman", quietly = TRUE)) install.packages("pacman")
pacman::p_load(tidyverse, here, janitor, viridis, scales, MASS, gtsummary)# Read the raw CSV, normalize column names with janitor, then rename
# to short workable aliases. This is the only persistent object we keep
# thorughout the analysis
df <- read_csv(here("data", "data_2026_09_28.csv"),
show_col_types = FALSE) |>
clean_names() |>
# Rename the long janitor names to short aliases
rename(
specialty = dental_specialty,
q1_sugar = x1_what_is_the_maximum_recommended_sugar_consumption_during_the_day,
q2_first_visit = x2_at_what_age_is_the_first_visit_recommended,
q3_fluoride = x3_which_of_the_following_statements_about_fluoride_is_correct,
q4_sealants = x4_which_of_the_following_questions_about_sealants_is_correct,
q5_ecc = x5_which_of_the_following_statements_about_early_childhood_caries_ecc_is_correct,
q6_brushing = x6_which_of_the_following_statements_about_brushing_is_correct,
q7_risk = x7_which_of_the_following_statements_about_oral_health_risk_factors_is_wrong,
q8_smoking = x8_dentists_should_carry_out_preventive_activities_for_smoking_cessation,
q9_paed_prev = x9_dentists_should_carry_out_preventive_activities_in_paediatric_dentistry,
q10_equipped = x10_dentists_are_well_equipped_in_carrying_out_preventive_care_in_paediatric_dentistry,
q11_sealants = x11_when_needed_do_you_apply_dental_sealants,
q12_varnish = x12_when_needed_do_you_apply_fluoride_varnish,
q13_ohi = x13_when_needed_do_you_give_oral_hygiene_instructions,
q14_diet = x14_when_needed_do_you_provide_any_dietary_advice,
q15_time = x15_lack_of_time,
q16_remuneration = x16_lack_of_remuneration,
q17_motivation = x17_lack_of_motivation_in_implementing_preventive_care,
q18_compliance = x18_poor_patient_compliance,
q19_knowledge_b = x19_lack_of_knowledge,
q20_training = x20_lack_of_training
) |>
# Recode categorical variables
mutate(
age_group = factor(age,
levels = 1:6,
labels = c("25-34", "35-44", "45-54",
"55-64", "65-74", "75+")),
gender_label = factor(gender,
levels = c(0, 1),
labels = c("Male", "Female")),
specialty_label = factor(specialty,
levels = 1:7,
labels = c("General Dentistry", "Pediatric Dentistry",
"Orthodontics", "Oral Surgery",
"Restorative/Endo", "Prosthodontics",
"Periodontics")),
country = factor(country)
) -> df# Table 1: sample characteristics stratified by country using gtsummary
df |>
select(country, age_group, gender_label, specialty_label) |>
tbl_summary(
by = country,
label = list(
age_group ~ "Age group",
gender_label ~ "Gender",
specialty_label ~ "Dental specialty"
),
missing = "ifany",
missing_text = "Missing"
) |>
add_overall() |>
add_p(test = list(all_categorical() ~ "chisq.test")) |>
modify_header(label ~ "**Characteristic**") |>
bold_labels()| Characteristic | Overall N = 3,5771 |
Argentina N = 3821 |
Chile N = 7481 |
India N = 3601 |
Italy N = 3221 |
Latvia N = 1001 |
Serbia N = 8451 |
Spain N = 7081 |
Switzerland N = 1121 |
p-value2 |
|---|---|---|---|---|---|---|---|---|---|---|
| Age group | <0.001 | |||||||||
| 25-34 | 1,098 (31%) | 86 (23%) | 280 (37%) | 318 (88%) | 51 (16%) | 27 (27%) | 183 (22%) | 130 (18%) | 23 (21%) | |
| 35-44 | 981 (27%) | 82 (21%) | 279 (37%) | 28 (7.8%) | 47 (15%) | 33 (33%) | 268 (32%) | 208 (29%) | 36 (32%) | |
| 45-54 | 749 (21%) | 122 (32%) | 106 (14%) | 13 (3.6%) | 56 (17%) | 23 (23%) | 232 (27%) | 178 (25%) | 19 (17%) | |
| 55-64 | 526 (15%) | 73 (19%) | 58 (7.8%) | 1 (0.3%) | 105 (33%) | 16 (16%) | 128 (15%) | 118 (17%) | 27 (24%) | |
| 65-74 | 201 (5.6%) | 15 (3.9%) | 24 (3.2%) | 0 (0%) | 58 (18%) | 1 (1.0%) | 31 (3.7%) | 67 (9.5%) | 5 (4.5%) | |
| 75+ | 22 (0.6%) | 4 (1.0%) | 1 (0.1%) | 0 (0%) | 5 (1.6%) | 0 (0%) | 3 (0.4%) | 7 (1.0%) | 2 (1.8%) | |
| Gender | <0.001 | |||||||||
| Male | 1,118 (31%) | 77 (20%) | 237 (32%) | 108 (30%) | 212 (66%) | 5 (5.0%) | 196 (23%) | 236 (33%) | 47 (43%) | |
| Female | 2,455 (69%) | 305 (80%) | 511 (68%) | 252 (70%) | 110 (34%) | 95 (95%) | 647 (77%) | 472 (67%) | 63 (57%) | |
| Missing | 4 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 2 | |
| Dental specialty | <0.001 | |||||||||
| General Dentistry | 1,825 (51%) | 172 (45%) | 438 (59%) | 147 (41%) | 128 (40%) | 82 (82%) | 480 (57%) | 350 (49%) | 28 (25%) | |
| Pediatric Dentistry | 518 (14%) | 98 (26%) | 140 (19%) | 19 (5.3%) | 13 (4.0%) | 11 (11%) | 152 (18%) | 64 (9.0%) | 21 (19%) | |
| Orthodontics | 240 (6.7%) | 32 (8.4%) | 29 (3.9%) | 9 (2.5%) | 36 (11%) | 1 (1.0%) | 44 (5.2%) | 75 (11%) | 14 (13%) | |
| Oral Surgery | 262 (7.3%) | 21 (5.5%) | 25 (3.3%) | 22 (6.1%) | 39 (12%) | 0 (0%) | 55 (6.5%) | 96 (14%) | 4 (3.6%) | |
| Restorative/Endo | 414 (12%) | 42 (11%) | 34 (4.5%) | 146 (41%) | 45 (14%) | 2 (2.0%) | 40 (4.7%) | 79 (11%) | 26 (23%) | |
| Prosthodontics | 246 (6.9%) | 12 (3.1%) | 78 (10%) | 9 (2.5%) | 41 (13%) | 2 (2.0%) | 54 (6.4%) | 38 (5.4%) | 12 (11%) | |
| Periodontics | 72 (2.0%) | 5 (1.3%) | 4 (0.5%) | 8 (2.2%) | 20 (6.2%) | 2 (2.0%) | 20 (2.4%) | 6 (0.8%) | 7 (6.3%) | |
| 1 n (%) | ||||||||||
| 2 Pearson’s Chi-squared test | ||||||||||
# Knowledge score summary stratified by country
df |>
select(country, score_knowledge) |>
tbl_summary(
by = country,
label = list(score_knowledge ~ "Knowledge score (0-7)"),
statistic = list(all_continuous() ~ "{mean} ({sd}) | {median} [{p25}, {p75}]"),
missing = "no"
) |>
add_overall() |>
add_p(test = list(all_continuous() ~ "kruskal.test")) |>
bold_labels()| Characteristic | Overall N = 3,5771 |
Argentina N = 3821 |
Chile N = 7481 |
India N = 3601 |
Italy N = 3221 |
Latvia N = 1001 |
Serbia N = 8451 |
Spain N = 7081 |
Switzerland N = 1121 |
p-value2 |
|---|---|---|---|---|---|---|---|---|---|---|
| Knowledge score (0-7) | ||||||||||
| 0 | 7 (0.2%) | 0 (0%) | 0 (0%) | 3 (0.8%) | 0 (0%) | 0 (0%) | 0 (0%) | 3 (0.4%) | 1 (0.9%) | |
| 1 | 28 (0.8%) | 1 (0.3%) | 0 (0%) | 19 (5.3%) | 0 (0%) | 1 (1.0%) | 3 (0.4%) | 1 (0.1%) | 3 (2.7%) | |
| 2 | 68 (1.9%) | 16 (4.2%) | 1 (0.1%) | 16 (4.4%) | 8 (2.5%) | 4 (4.0%) | 8 (0.9%) | 14 (2.0%) | 1 (0.9%) | |
| 3 | 264 (7.4%) | 30 (7.9%) | 4 (0.5%) | 52 (14%) | 35 (11%) | 8 (8.0%) | 64 (7.6%) | 55 (7.8%) | 16 (14%) | |
| 4 | 611 (17%) | 82 (21%) | 16 (2.1%) | 49 (14%) | 82 (25%) | 28 (28%) | 197 (23%) | 134 (19%) | 23 (21%) | |
| 5 | 925 (26%) | 122 (32%) | 91 (12%) | 93 (26%) | 103 (32%) | 41 (41%) | 228 (27%) | 220 (31%) | 27 (24%) | |
| 6 | 982 (27%) | 99 (26%) | 252 (34%) | 89 (25%) | 69 (21%) | 18 (18%) | 240 (28%) | 191 (27%) | 24 (21%) | |
| 7 | 692 (19%) | 32 (8.4%) | 384 (51%) | 39 (11%) | 25 (7.8%) | 0 (0%) | 105 (12%) | 90 (13%) | 17 (15%) | |
| 1 n (%) | ||||||||||
| 2 NA | ||||||||||
# Proportion correct for each knowledge item, stratified by country
df |>
select(country, q1_sugar:q7_risk) |>
# Convert to factors so gtsummary shows proportions
mutate(across(q1_sugar:q7_risk, ~ factor(.x, levels = c(1, 0),
labels = c("Correct", "Incorrect")))) |>
tbl_summary(
by = country,
label = list(
q1_sugar ~ "Sugar intake",
q2_first_visit ~ "First visit age",
q3_fluoride ~ "Fluoride",
q4_sealants ~ "Sealants",
q5_ecc ~ "ECC",
q6_brushing ~ "Brushing",
q7_risk ~ "Risk factors"
),
value = list(q1_sugar ~ "Correct", q2_first_visit ~ "Correct",
q3_fluoride ~ "Correct", q4_sealants ~ "Correct",
q5_ecc ~ "Correct", q6_brushing ~ "Correct",
q7_risk ~ "Correct"),
missing = "no"
) |>
add_overall() |>
add_p(test = list(all_categorical() ~ "chisq.test")) |>
bold_labels()| Characteristic | Overall N = 3,5771 |
Argentina N = 3821 |
Chile N = 7481 |
India N = 3601 |
Italy N = 3221 |
Latvia N = 1001 |
Serbia N = 8451 |
Spain N = 7081 |
Switzerland N = 1121 |
p-value2 |
|---|---|---|---|---|---|---|---|---|---|---|
| Sugar intake | 2,428 (68%) | 219 (58%) | 593 (79%) | 212 (59%) | 174 (54%) | 77 (77%) | 551 (65%) | 516 (74%) | 86 (77%) | <0.001 |
| First visit age | 2,788 (78%) | 344 (91%) | 731 (98%) | 262 (73%) | 229 (71%) | 73 (73%) | 631 (75%) | 466 (66%) | 52 (47%) | <0.001 |
| Fluoride | 2,694 (76%) | 199 (53%) | 642 (86%) | 218 (62%) | 209 (65%) | 77 (77%) | 773 (91%) | 504 (73%) | 72 (64%) | <0.001 |
| Sealants | 1,952 (55%) | 204 (54%) | 688 (92%) | 262 (73%) | 139 (44%) | 45 (45%) | 301 (36%) | 257 (37%) | 56 (50%) | <0.001 |
| ECC | 3,271 (92%) | 337 (90%) | 699 (93%) | 302 (85%) | 308 (97%) | 89 (89%) | 786 (93%) | 656 (94%) | 94 (85%) | <0.001 |
| Brushing | 3,341 (94%) | 344 (91%) | 729 (97%) | 263 (74%) | 303 (96%) | 95 (95%) | 818 (97%) | 679 (97%) | 110 (99%) | <0.001 |
| Risk factors | 2,281 (64%) | 232 (61%) | 651 (87%) | 156 (44%) | 191 (60%) | 2 (2.0%) | 448 (53%) | 528 (75%) | 73 (65%) | <0.001 |
| 1 n (%) | ||||||||||
| 2 Pearson’s Chi-squared test | ||||||||||
# Knowledge score stratified by specialty
df |>
select(country, age_group, gender_label, specialty_label) |>
tbl_summary(
by = country,
label = list(
age_group ~ "Age group",
gender_label ~ "Gender",
specialty_label ~ "Dental specialty"
),
missing = "ifany",
missing_text = "Missing"
) |>
add_overall() |>
add_p(test = list(all_categorical() ~ "chisq.test.no.correct"),
test.args = all_tests("chisq.test.no.correct") ~
list(simulate.p.value = TRUE, B = 2000)) |>
modify_header(label ~ "**Characteristic**") |>
bold_labels()| Characteristic | Overall N = 3,5771 |
Argentina N = 3821 |
Chile N = 7481 |
India N = 3601 |
Italy N = 3221 |
Latvia N = 1001 |
Serbia N = 8451 |
Spain N = 7081 |
Switzerland N = 1121 |
p-value2 |
|---|---|---|---|---|---|---|---|---|---|---|
| Age group | <0.001 | |||||||||
| 25-34 | 1,098 (31%) | 86 (23%) | 280 (37%) | 318 (88%) | 51 (16%) | 27 (27%) | 183 (22%) | 130 (18%) | 23 (21%) | |
| 35-44 | 981 (27%) | 82 (21%) | 279 (37%) | 28 (7.8%) | 47 (15%) | 33 (33%) | 268 (32%) | 208 (29%) | 36 (32%) | |
| 45-54 | 749 (21%) | 122 (32%) | 106 (14%) | 13 (3.6%) | 56 (17%) | 23 (23%) | 232 (27%) | 178 (25%) | 19 (17%) | |
| 55-64 | 526 (15%) | 73 (19%) | 58 (7.8%) | 1 (0.3%) | 105 (33%) | 16 (16%) | 128 (15%) | 118 (17%) | 27 (24%) | |
| 65-74 | 201 (5.6%) | 15 (3.9%) | 24 (3.2%) | 0 (0%) | 58 (18%) | 1 (1.0%) | 31 (3.7%) | 67 (9.5%) | 5 (4.5%) | |
| 75+ | 22 (0.6%) | 4 (1.0%) | 1 (0.1%) | 0 (0%) | 5 (1.6%) | 0 (0%) | 3 (0.4%) | 7 (1.0%) | 2 (1.8%) | |
| Gender | <0.001 | |||||||||
| Male | 1,118 (31%) | 77 (20%) | 237 (32%) | 108 (30%) | 212 (66%) | 5 (5.0%) | 196 (23%) | 236 (33%) | 47 (43%) | |
| Female | 2,455 (69%) | 305 (80%) | 511 (68%) | 252 (70%) | 110 (34%) | 95 (95%) | 647 (77%) | 472 (67%) | 63 (57%) | |
| Missing | 4 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 2 | |
| Dental specialty | <0.001 | |||||||||
| General Dentistry | 1,825 (51%) | 172 (45%) | 438 (59%) | 147 (41%) | 128 (40%) | 82 (82%) | 480 (57%) | 350 (49%) | 28 (25%) | |
| Pediatric Dentistry | 518 (14%) | 98 (26%) | 140 (19%) | 19 (5.3%) | 13 (4.0%) | 11 (11%) | 152 (18%) | 64 (9.0%) | 21 (19%) | |
| Orthodontics | 240 (6.7%) | 32 (8.4%) | 29 (3.9%) | 9 (2.5%) | 36 (11%) | 1 (1.0%) | 44 (5.2%) | 75 (11%) | 14 (13%) | |
| Oral Surgery | 262 (7.3%) | 21 (5.5%) | 25 (3.3%) | 22 (6.1%) | 39 (12%) | 0 (0%) | 55 (6.5%) | 96 (14%) | 4 (3.6%) | |
| Restorative/Endo | 414 (12%) | 42 (11%) | 34 (4.5%) | 146 (41%) | 45 (14%) | 2 (2.0%) | 40 (4.7%) | 79 (11%) | 26 (23%) | |
| Prosthodontics | 246 (6.9%) | 12 (3.1%) | 78 (10%) | 9 (2.5%) | 41 (13%) | 2 (2.0%) | 54 (6.4%) | 38 (5.4%) | 12 (11%) | |
| Periodontics | 72 (2.0%) | 5 (1.3%) | 4 (0.5%) | 8 (2.2%) | 20 (6.2%) | 2 (2.0%) | 20 (2.4%) | 6 (0.8%) | 7 (6.3%) | |
| 1 n (%) | ||||||||||
| 2 Pearson’s Chi-squared test with simulated p-value (based on 2000 replicates) | ||||||||||
# Non-parametric comparison given ordinal nature of the score
kruskal.test(score_knowledge ~ country, data = df)
Kruskal-Wallis rank sum test
data: score_knowledge by country
Kruskal-Wallis chi-squared = 737.42, df = 7, p-value < 2.2e-16
# Compare knowledge scores accross specialties
kruskal.test(score_knowledge ~ specialty_label, data = df)
Kruskal-Wallis rank sum test
data: score_knowledge by specialty_label
Kruskal-Wallis chi-squared = 150.6, df = 6, p-value < 2.2e-16
# Knowledge score comparison by gender
df |>
filter(!is.na(gender_label)) |>
select(gender_label, score_knowledge) |>
tbl_summary(
by = gender_label,
label = list(score_knowledge ~ "Knowledge score (0-7)"),
statistic = list(all_continuous() ~ "{mean} ({sd}) | {median} [{p25}, {p75}]"),
missing = "no"
) |>
bold_labels()| Characteristic | Male N = 1,1181 |
Female N = 2,4551 |
|---|---|---|
| Knowledge score (0-7) | ||
| 0 | 5 (0.4%) | 2 (<0.1%) |
| 1 | 11 (1.0%) | 17 (0.7%) |
| 2 | 25 (2.2%) | 43 (1.8%) |
| 3 | 95 (8.5%) | 169 (6.9%) |
| 4 | 197 (18%) | 414 (17%) |
| 5 | 296 (26%) | 628 (26%) |
| 6 | 286 (26%) | 695 (28%) |
| 7 | 203 (18%) | 487 (20%) |
| 1 n (%) | ||
Likert scale: 1 = Strongly agree, 2 = Agree, 3 = Disagree, 4 = Strongly disagree.
# Attitude items as ordered factors, stratified by country
df |>
select(country, q8_smoking, q9_paed_prev, q10_equipped) |>
mutate(across(q8_smoking:q10_equipped,
~ factor(.x, levels = 1:4,
labels = c("Strongly agree", "Agree",
"Disagree", "Strongly disagree")))) |>
tbl_summary(
by = country,
label = list(
q8_smoking ~ "Preventive activities: smoking cessation",
q9_paed_prev ~ "Preventive activities: paediatric dentistry",
q10_equipped ~ "Well equipped for preventive care"
),
missing = "no"
) |>
add_overall() |>
add_p(test = list(all_categorical() ~ "chisq.test")) |>
bold_labels()| Characteristic | Overall N = 3,5771 |
Argentina N = 3821 |
Chile N = 7481 |
India N = 3601 |
Italy N = 3221 |
Latvia N = 1001 |
Serbia N = 8451 |
Spain N = 7081 |
Switzerland N = 1121 |
p-value2 |
|---|---|---|---|---|---|---|---|---|---|---|
| Preventive activities: smoking cessation | <0.001 | |||||||||
| Strongly agree | 1,956 (57%) | 320 (84%) | 579 (77%) | 240 (67%) | 170 (53%) | 15 (15%) | 24 (2.8%) | 608 (86%) | 0 (NA%) | |
| Agree | 496 (14%) | 0 (0%) | 134 (18%) | 88 (25%) | 116 (36%) | 29 (29%) | 129 (15%) | 0 (0%) | 0 (NA%) | |
| Disagree | 512 (15%) | 54 (14%) | 31 (4.1%) | 28 (7.9%) | 33 (10%) | 30 (30%) | 248 (29%) | 88 (12%) | 0 (NA%) | |
| Strongly disagree | 495 (14%) | 8 (2.1%) | 4 (0.5%) | 0 (0%) | 3 (0.9%) | 26 (26%) | 444 (53%) | 10 (1.4%) | 0 (NA%) | |
| Preventive activities: paediatric dentistry | <0.001 | |||||||||
| Strongly agree | 2,342 (66%) | 374 (98%) | 704 (94%) | 230 (65%) | 250 (78%) | 13 (13%) | 3 (0.4%) | 695 (99%) | 73 (65%) | |
| Agree | 264 (7.4%) | 0 (0%) | 38 (5.1%) | 101 (29%) | 61 (19%) | 2 (2.0%) | 32 (3.8%) | 0 (0%) | 30 (27%) | |
| Disagree | 192 (5.4%) | 5 (1.3%) | 3 (0.4%) | 23 (6.5%) | 9 (2.8%) | 17 (17%) | 118 (14%) | 9 (1.3%) | 8 (7.1%) | |
| Strongly disagree | 768 (22%) | 3 (0.8%) | 3 (0.4%) | 0 (0%) | 0 (0%) | 68 (68%) | 692 (82%) | 1 (0.1%) | 1 (0.9%) | |
| Well equipped for preventive care | <0.001 | |||||||||
| Strongly agree | 1,478 (41%) | 260 (68%) | 311 (42%) | 181 (51%) | 71 (22%) | 10 (10%) | 108 (13%) | 505 (72%) | 32 (29%) | |
| Agree | 785 (22%) | 0 (0%) | 260 (35%) | 96 (27%) | 90 (28%) | 11 (11%) | 272 (32%) | 0 (0%) | 56 (50%) | |
| Disagree | 935 (26%) | 104 (27%) | 163 (22%) | 78 (22%) | 131 (41%) | 29 (29%) | 234 (28%) | 173 (25%) | 23 (21%) | |
| Strongly disagree | 368 (10%) | 18 (4.7%) | 14 (1.9%) | 0 (0%) | 27 (8.5%) | 50 (50%) | 231 (27%) | 27 (3.8%) | 1 (0.9%) | |
| 1 n (%) | ||||||||||
| 2 Pearson’s Chi-squared test | ||||||||||
Likert scale: 1 = Always, 2 = Often, 3 = Sometimes, 4 = Never.
# Practice items as ordered factors, stratified by country
df |>
select(country, q11_sealants:q14_diet) |>
mutate(across(q11_sealants:q14_diet,
~ factor(.x, levels = 1:4,
labels = c("Always", "Often",
"Sometimes", "Never")))) |>
tbl_summary(
by = country,
label = list(
q11_sealants ~ "Apply dental sealants",
q12_varnish ~ "Apply fluoride varnish",
q13_ohi ~ "Give oral hygiene instructions",
q14_diet ~ "Provide dietary advice"
),
missing = "no"
) |>
add_overall() |>
add_p(test = list(all_categorical() ~ "chisq.test")) |>
bold_labels()| Characteristic | Overall N = 3,5771 |
Argentina N = 3821 |
Chile N = 7481 |
India N = 3601 |
Italy N = 3221 |
Latvia N = 1001 |
Serbia N = 8451 |
Spain N = 7081 |
Switzerland N = 1121 |
p-value2 |
|---|---|---|---|---|---|---|---|---|---|---|
| Apply dental sealants | <0.001 | |||||||||
| Always | 1,602 (45%) | 133 (35%) | 343 (46%) | 148 (42%) | 154 (48%) | 23 (23%) | 514 (61%) | 257 (37%) | 30 (27%) | |
| Often | 1,264 (36%) | 176 (46%) | 281 (38%) | 154 (44%) | 121 (38%) | 44 (44%) | 242 (29%) | 210 (30%) | 36 (33%) | |
| Sometimes | 493 (14%) | 56 (15%) | 81 (11%) | 45 (13%) | 32 (10%) | 20 (20%) | 68 (8.0%) | 166 (24%) | 25 (23%) | |
| Never | 199 (5.6%) | 17 (4.5%) | 42 (5.6%) | 7 (2.0%) | 13 (4.1%) | 13 (13%) | 21 (2.5%) | 67 (9.6%) | 19 (17%) | |
| Apply fluoride varnish | <0.001 | |||||||||
| Always | 1,384 (39%) | 156 (41%) | 587 (79%) | 124 (35%) | 71 (22%) | 25 (25%) | 161 (19%) | 219 (31%) | 41 (37%) | |
| Often | 1,110 (31%) | 140 (37%) | 139 (19%) | 148 (42%) | 108 (34%) | 45 (45%) | 240 (28%) | 254 (36%) | 36 (32%) | |
| Sometimes | 729 (20%) | 63 (16%) | 14 (1.9%) | 74 (21%) | 86 (27%) | 27 (27%) | 270 (32%) | 178 (25%) | 17 (15%) | |
| Never | 335 (9.4%) | 23 (6.0%) | 7 (0.9%) | 8 (2.3%) | 55 (17%) | 3 (3.0%) | 174 (21%) | 48 (6.9%) | 17 (15%) | |
| Give oral hygiene instructions | <0.001 | |||||||||
| Always | 2,959 (83%) | 353 (92%) | 608 (82%) | 261 (73%) | 269 (85%) | 75 (75%) | 695 (82%) | 625 (89%) | 73 (65%) | |
| Often | 550 (15%) | 28 (7.3%) | 124 (17%) | 69 (19%) | 44 (14%) | 25 (25%) | 145 (17%) | 78 (11%) | 37 (33%) | |
| Sometimes | 46 (1.3%) | 1 (0.3%) | 14 (1.9%) | 21 (5.9%) | 4 (1.3%) | 0 (0%) | 4 (0.5%) | 1 (0.1%) | 1 (0.9%) | |
| Never | 8 (0.2%) | 0 (0%) | 0 (0%) | 5 (1.4%) | 1 (0.3%) | 0 (0%) | 1 (0.1%) | 0 (0%) | 1 (0.9%) | |
| Provide dietary advice | <0.001 | |||||||||
| Always | 1,689 (47%) | 253 (67%) | 254 (34%) | 215 (61%) | 156 (49%) | 32 (32%) | 311 (37%) | 409 (58%) | 59 (54%) | |
| Often | 1,289 (36%) | 95 (25%) | 303 (41%) | 109 (31%) | 124 (39%) | 54 (54%) | 338 (40%) | 223 (32%) | 43 (39%) | |
| Sometimes | 513 (14%) | 28 (7.4%) | 162 (22%) | 25 (7.1%) | 34 (11%) | 14 (14%) | 175 (21%) | 68 (9.6%) | 7 (6.4%) | |
| Never | 68 (1.9%) | 4 (1.1%) | 27 (3.6%) | 3 (0.9%) | 6 (1.9%) | 0 (0%) | 21 (2.5%) | 6 (0.8%) | 1 (0.9%) | |
| 1 n (%) | ||||||||||
| 2 Pearson’s Chi-squared test | ||||||||||
# Practice items stratified by age group
df |>
select(age_group, q11_sealants:q14_diet) |>
mutate(across(q11_sealants:q14_diet,
~ factor(.x, levels = 1:4,
labels = c("Always", "Often",
"Sometimes", "Never")))) |>
tbl_summary(
by = age_group,
label = list(
q11_sealants ~ "Apply dental sealants",
q12_varnish ~ "Apply fluoride varnish",
q13_ohi ~ "Give oral hygiene instructions",
q14_diet ~ "Provide dietary advice"
),
missing = "no"
) |>
add_p(test = list(all_categorical() ~ "chisq.test")) |>
bold_labels()| Characteristic | 25-34 N = 1,0981 |
35-44 N = 9811 |
45-54 N = 7491 |
55-64 N = 5261 |
65-74 N = 2011 |
75+ N = 221 |
p-value2 |
|---|---|---|---|---|---|---|---|
| Apply dental sealants | <0.001 | ||||||
| Always | 466 (43%) | 425 (43%) | 359 (48%) | 249 (48%) | 94 (47%) | 9 (43%) | |
| Often | 454 (42%) | 326 (33%) | 242 (33%) | 169 (32%) | 65 (32%) | 8 (38%) | |
| Sometimes | 135 (12%) | 160 (16%) | 98 (13%) | 68 (13%) | 30 (15%) | 2 (9.5%) | |
| Never | 36 (3.3%) | 69 (7.0%) | 44 (5.9%) | 36 (6.9%) | 12 (6.0%) | 2 (9.5%) | |
| Apply fluoride varnish | <0.001 | ||||||
| Always | 483 (44%) | 402 (41%) | 266 (36%) | 162 (31%) | 65 (33%) | 6 (27%) | |
| Often | 330 (30%) | 309 (32%) | 221 (30%) | 168 (32%) | 73 (37%) | 9 (41%) | |
| Sometimes | 204 (19%) | 180 (18%) | 166 (22%) | 125 (24%) | 48 (24%) | 6 (27%) | |
| Never | 73 (6.7%) | 89 (9.1%) | 91 (12%) | 67 (13%) | 14 (7.0%) | 1 (4.5%) | |
| Give oral hygiene instructions | <0.001 | ||||||
| Always | 853 (78%) | 793 (81%) | 641 (86%) | 472 (90%) | 181 (91%) | 19 (86%) | |
| Often | 209 (19%) | 176 (18%) | 98 (13%) | 47 (9.0%) | 18 (9.0%) | 2 (9.1%) | |
| Sometimes | 25 (2.3%) | 11 (1.1%) | 6 (0.8%) | 4 (0.8%) | 0 (0%) | 0 (0%) | |
| Never | 4 (0.4%) | 0 (0%) | 1 (0.1%) | 2 (0.4%) | 0 (0%) | 1 (4.5%) | |
| Provide dietary advice | <0.001 | ||||||
| Always | 428 (39%) | 429 (44%) | 414 (56%) | 295 (56%) | 115 (58%) | 8 (38%) | |
| Often | 425 (39%) | 361 (37%) | 252 (34%) | 172 (33%) | 67 (34%) | 12 (57%) | |
| Sometimes | 205 (19%) | 164 (17%) | 71 (9.5%) | 55 (10%) | 17 (8.5%) | 1 (4.8%) | |
| Never | 30 (2.8%) | 25 (2.6%) | 8 (1.1%) | 4 (0.8%) | 1 (0.5%) | 0 (0%) | |
| 1 n (%) | |||||||
| 2 Pearson’s Chi-squared test | |||||||
Likert scale: 1 = Strongly disagree, 2 = Disagree, 3 = Agree, 4 = Strongly agree.
# Barrier items as ordered factors, stratified by country
# Includes overall column and chi-squared p-values
df |>
select(country, q15_time:q20_training) |>
mutate(across(q15_time:q20_training,
~ factor(.x, levels = 1:4,
labels = c("Strongly disagree", "Disagree",
"Agree", "Strongly agree")))) |>
tbl_summary(
by = country,
label = list(
q15_time ~ "Lack of time",
q16_remuneration ~ "Lack of remuneration",
q17_motivation ~ "Lack of motivation",
q18_compliance ~ "Poor patient compliance",
q19_knowledge_b ~ "Lack of knowledge",
q20_training ~ "Lack of training"
),
missing = "no"
) |>
add_overall() |>
add_p(test = list(all_categorical() ~ "chisq.test")) |>
bold_labels()| Characteristic | Overall N = 3,5771 |
Argentina N = 3821 |
Chile N = 7481 |
India N = 3601 |
Italy N = 3221 |
Latvia N = 1001 |
Serbia N = 8451 |
Spain N = 7081 |
Switzerland N = 1121 |
p-value2 |
|---|---|---|---|---|---|---|---|---|---|---|
| Lack of time | <0.001 | |||||||||
| Strongly disagree | 1,443 (40%) | 164 (43%) | 402 (54%) | 134 (38%) | 29 (9.1%) | 23 (23%) | 267 (32%) | 409 (58%) | 15 (13%) | |
| Disagree | 775 (22%) | 0 (0%) | 239 (32%) | 104 (29%) | 75 (24%) | 17 (17%) | 283 (33%) | 0 (0%) | 57 (51%) | |
| Agree | 834 (23%) | 102 (27%) | 92 (12%) | 118 (33%) | 109 (34%) | 37 (37%) | 201 (24%) | 151 (21%) | 24 (21%) | |
| Strongly agree | 515 (14%) | 116 (30%) | 14 (1.9%) | 0 (0%) | 106 (33%) | 23 (23%) | 94 (11%) | 146 (21%) | 16 (14%) | |
| Lack of remuneration | <0.001 | |||||||||
| Strongly disagree | 1,260 (35%) | 207 (54%) | 114 (15%) | 112 (32%) | 40 (13%) | 16 (16%) | 353 (42%) | 407 (58%) | 11 (9.8%) | |
| Disagree | 657 (18%) | 0 (0%) | 192 (26%) | 104 (30%) | 75 (23%) | 26 (26%) | 233 (28%) | 0 (0%) | 27 (24%) | |
| Agree | 1,028 (29%) | 82 (22%) | 310 (42%) | 130 (38%) | 109 (34%) | 35 (35%) | 160 (19%) | 152 (22%) | 50 (45%) | |
| Strongly agree | 610 (17%) | 92 (24%) | 130 (17%) | 0 (0%) | 96 (30%) | 23 (23%) | 99 (12%) | 146 (21%) | 24 (21%) | |
| Lack of motivation | <0.001 | |||||||||
| Strongly disagree | 1,473 (41%) | 197 (52%) | 444 (60%) | 116 (33%) | 20 (6.3%) | 24 (24%) | 305 (36%) | 364 (52%) | 3 (2.7%) | |
| Disagree | 672 (19%) | 0 (0%) | 209 (28%) | 122 (34%) | 48 (15%) | 26 (26%) | 232 (27%) | 0 (0%) | 35 (32%) | |
| Agree | 870 (24%) | 100 (26%) | 78 (10%) | 117 (33%) | 100 (31%) | 30 (30%) | 209 (25%) | 191 (27%) | 45 (41%) | |
| Strongly agree | 544 (15%) | 84 (22%) | 15 (2.0%) | 0 (0%) | 151 (47%) | 20 (20%) | 99 (12%) | 148 (21%) | 27 (25%) | |
| Poor patient compliance | <0.001 | |||||||||
| Strongly disagree | 1,087 (30%) | 195 (51%) | 214 (29%) | 117 (33%) | 39 (12%) | 8 (8.0%) | 63 (7.5%) | 438 (62%) | 13 (12%) | |
| Disagree | 693 (19%) | 0 (0%) | 178 (24%) | 137 (39%) | 106 (33%) | 12 (12%) | 211 (25%) | 0 (0%) | 49 (44%) | |
| Agree | 1,102 (31%) | 118 (31%) | 149 (20%) | 101 (28%) | 125 (39%) | 32 (32%) | 348 (41%) | 194 (27%) | 35 (31%) | |
| Strongly agree | 685 (19%) | 68 (18%) | 207 (28%) | 0 (0%) | 50 (16%) | 48 (48%) | 223 (26%) | 74 (10%) | 15 (13%) | |
| Lack of knowledge | <0.001 | |||||||||
| Strongly disagree | 1,069 (30%) | 197 (52%) | 170 (23%) | 115 (32%) | 35 (11%) | 21 (21%) | 273 (32%) | 252 (36%) | 6 (5.4%) | |
| Disagree | 647 (18%) | 0 (0%) | 134 (18%) | 102 (29%) | 80 (25%) | 29 (29%) | 261 (31%) | 0 (0%) | 41 (37%) | |
| Agree | 1,039 (29%) | 97 (26%) | 172 (23%) | 138 (39%) | 117 (37%) | 30 (30%) | 211 (25%) | 229 (32%) | 45 (40%) | |
| Strongly agree | 810 (23%) | 85 (22%) | 272 (36%) | 0 (0%) | 88 (28%) | 20 (20%) | 100 (12%) | 225 (32%) | 20 (18%) | |
| Lack of training | <0.001 | |||||||||
| Strongly disagree | 1,165 (33%) | 215 (56%) | 188 (25%) | 114 (32%) | 24 (7.5%) | 23 (23%) | 307 (36%) | 291 (41%) | 3 (2.7%) | |
| Disagree | 719 (20%) | 0 (0%) | 252 (34%) | 96 (27%) | 55 (17%) | 35 (35%) | 253 (30%) | 0 (0%) | 28 (25%) | |
| Agree | 957 (27%) | 79 (21%) | 155 (21%) | 145 (41%) | 110 (34%) | 23 (23%) | 192 (23%) | 204 (29%) | 49 (44%) | |
| Strongly agree | 723 (20%) | 87 (23%) | 153 (20%) | 0 (0%) | 131 (41%) | 19 (19%) | 93 (11%) | 209 (30%) | 31 (28%) | |
| 1 n (%) | ||||||||||
| 2 Pearson’s Chi-squared test | ||||||||||
# Barriers stratified by speciaty
df |>
select(specialty_label, q15_time:q20_training) |>
mutate(across(q15_time:q20_training,
~ factor(.x, levels = 1:4,
labels = c("Strongly disagree", "Disagree",
"Agree", "Strongly agree")))) |>
tbl_summary(
by = specialty_label,
label = list(
q15_time ~ "Lack of time",
q16_remuneration ~ "Lack of remuneration",
q17_motivation ~ "Lack of motivation",
q18_compliance ~ "Poor patient compliance",
q19_knowledge_b ~ "Lack of knowledge",
q20_training ~ "Lack of training"
),
missing = "no"
) |>
add_p(test = list(all_categorical() ~ "chisq.test")) |>
bold_labels()| Characteristic | General Dentistry N = 1,8251 |
Pediatric Dentistry N = 5181 |
Orthodontics N = 2401 |
Oral Surgery N = 2621 |
Restorative/Endo N = 4141 |
Prosthodontics N = 2461 |
Periodontics N = 721 |
p-value2 |
|---|---|---|---|---|---|---|---|---|
| Lack of time | <0.001 | |||||||
| Strongly disagree | 750 (41%) | 218 (42%) | 100 (42%) | 117 (45%) | 149 (36%) | 90 (37%) | 19 (27%) | |
| Disagree | 407 (22%) | 93 (18%) | 43 (18%) | 36 (14%) | 104 (25%) | 67 (27%) | 25 (35%) | |
| Agree | 422 (23%) | 117 (23%) | 53 (22%) | 67 (26%) | 108 (26%) | 51 (21%) | 16 (23%) | |
| Strongly agree | 242 (13%) | 89 (17%) | 44 (18%) | 41 (16%) | 52 (13%) | 36 (15%) | 11 (15%) | |
| Lack of remuneration | 0.2 | |||||||
| Strongly disagree | 640 (35%) | 194 (38%) | 82 (34%) | 109 (42%) | 151 (37%) | 67 (27%) | 17 (24%) | |
| Disagree | 339 (19%) | 95 (18%) | 37 (15%) | 40 (15%) | 82 (20%) | 47 (19%) | 17 (24%) | |
| Agree | 528 (29%) | 142 (28%) | 73 (30%) | 69 (26%) | 113 (28%) | 79 (32%) | 24 (34%) | |
| Strongly agree | 310 (17%) | 83 (16%) | 48 (20%) | 43 (16%) | 62 (15%) | 51 (21%) | 13 (18%) | |
| Lack of motivation | <0.001 | |||||||
| Strongly disagree | 791 (44%) | 246 (48%) | 83 (35%) | 98 (38%) | 149 (36%) | 83 (34%) | 23 (32%) | |
| Disagree | 363 (20%) | 80 (16%) | 33 (14%) | 37 (14%) | 80 (19%) | 67 (28%) | 12 (17%) | |
| Agree | 419 (23%) | 115 (22%) | 67 (28%) | 65 (25%) | 128 (31%) | 55 (23%) | 21 (30%) | |
| Strongly agree | 244 (13%) | 73 (14%) | 56 (23%) | 61 (23%) | 57 (14%) | 38 (16%) | 15 (21%) | |
| Poor patient compliance | <0.001 | |||||||
| Strongly disagree | 554 (30%) | 133 (26%) | 81 (34%) | 107 (41%) | 138 (33%) | 61 (25%) | 13 (19%) | |
| Disagree | 323 (18%) | 104 (20%) | 42 (18%) | 43 (16%) | 99 (24%) | 55 (23%) | 27 (39%) | |
| Agree | 590 (32%) | 155 (30%) | 70 (29%) | 74 (28%) | 124 (30%) | 73 (30%) | 16 (23%) | |
| Strongly agree | 354 (19%) | 125 (24%) | 47 (20%) | 37 (14%) | 53 (13%) | 55 (23%) | 14 (20%) | |
| Lack of knowledge | <0.001 | |||||||
| Strongly disagree | 520 (29%) | 179 (35%) | 76 (32%) | 75 (29%) | 118 (29%) | 84 (34%) | 17 (24%) | |
| Disagree | 349 (19%) | 74 (14%) | 25 (11%) | 42 (16%) | 90 (22%) | 52 (21%) | 15 (21%) | |
| Agree | 517 (28%) | 137 (26%) | 79 (33%) | 80 (31%) | 140 (34%) | 63 (26%) | 23 (32%) | |
| Strongly agree | 434 (24%) | 127 (25%) | 58 (24%) | 64 (25%) | 66 (16%) | 45 (18%) | 16 (23%) | |
| Lack of training | <0.001 | |||||||
| Strongly disagree | 591 (32%) | 199 (38%) | 69 (29%) | 89 (34%) | 132 (32%) | 70 (29%) | 15 (21%) | |
| Disagree | 385 (21%) | 102 (20%) | 32 (13%) | 42 (16%) | 76 (18%) | 67 (27%) | 15 (21%) | |
| Agree | 475 (26%) | 119 (23%) | 76 (32%) | 64 (25%) | 132 (32%) | 69 (28%) | 22 (31%) | |
| Strongly agree | 371 (20%) | 97 (19%) | 62 (26%) | 64 (25%) | 72 (17%) | 38 (16%) | 19 (27%) | |
| 1 n (%) | ||||||||
| 2 Pearson’s Chi-squared test | ||||||||
# Boxplot ordered by median knowledge score
df |>
ggplot(aes(x = fct_reorder(country, score_knowledge, .fun = median),
y = score_knowledge, fill = country)) +
geom_boxplot(alpha = 0.8, show.legend = FALSE) +
geom_jitter(alpha = 0.05, width = 0.2) +
scale_fill_viridis_d() +
labs(x = "Country", y = "Knowledge Score (0-7)") +
coord_flip() +
theme_minimal(base_size = 13) +
theme(legend.position = "none")
ggsave(here("output", "fig1_knowledge_by_country.png"),
width = 8, height = 5, dpi = 300)# Heatmap of item-level performace across countries
df |>
select(country, q1_sugar:q7_risk) |>
pivot_longer(cols = q1_sugar:q7_risk,
names_to = "item",
values_to = "correct") |>
mutate(
item = case_when(
item == "q1_sugar" ~ "Sugar intake",
item == "q2_first_visit" ~ "First visit age",
item == "q3_fluoride" ~ "Fluoride",
item == "q4_sealants" ~ "Sealants",
item == "q5_ecc" ~ "ECC",
item == "q6_brushing" ~ "Brushing",
item == "q7_risk" ~ "Risk factors"
)
) |>
group_by(country, item) |>
summarise(pct_correct = mean(correct, na.rm = TRUE) * 100,
.groups = "drop") |>
ggplot(aes(x = item, y = country, fill = pct_correct)) +
geom_tile(color = "white", linewidth = 0.5) +
geom_text(aes(label = sprintf("%.0f%%", pct_correct)), size = 3) +
scale_fill_viridis_c(name = "% Correct", limits = c(0, 100)) +
labs(x = NULL, y = NULL) +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggsave(here("output", "fig2_knowledge_heatmap.png"),
width = 9, height = 5, dpi = 300)# Violin + boxplot combination for specialty comparisons
df |>
ggplot(aes(x = fct_reorder(specialty_label, score_knowledge, .fun = median),
y = score_knowledge, fill = specialty_label)) +
geom_violin(alpha = 0.5, show.legend = FALSE) +
geom_boxplot(width = 0.15, alpha = 0.8, show.legend = FALSE) +
scale_fill_viridis_d() +
labs(x = "Specialty", y = "Knowledge Score (0-7)") +
coord_flip() +
theme_minimal(base_size = 13) +
ggtitle("Knowledge score distribution by dental specialty")
ggsave(here("output", "fig6_knowledge_by_specialty.png"),
width = 9, height = 5, dpi = 300)df |>
ggplot(aes(x = fct_reorder(specialty_label, score_knowledge, .fun = median),
y = score_knowledge, fill = specialty_label)) +
geom_violin(alpha = 0.5, show.legend = FALSE) +
geom_boxplot(width = 0.15, alpha = 0.8, show.legend = FALSE) +
scale_fill_viridis_d() +
labs(x = "Specialty", y = "Knowledge Score (0-7)") +
coord_flip() +
theme_minimal(base_size = 13) +
facet_grid( . ~ country) +
ggtitle("Knowledge score distribution by dental specialty and country")ggsave(here("output", "fig6_1_knowledge_by_specialty_by_country.png"),
width = 9, height = 5, dpi = 300)# Stacked bar chart for barrier Likert responses
df |>
select(q15_time:q20_training) |>
pivot_longer(cols = everything(),
names_to = "barrier",
values_to = "response") |>
filter(!is.na(response)) |>
mutate(
response_label = factor(response,
levels = 1:4,
labels = c("Strongly disagree", "Disagree",
"Agree", "Strongly agree")),
barrier = case_when(
barrier == "q15_time" ~ "Lack of time",
barrier == "q16_remuneration" ~ "Lack of remuneration",
barrier == "q17_motivation" ~ "Lack of motivation",
barrier == "q18_compliance" ~ "Poor compliance",
barrier == "q19_knowledge_b" ~ "Lack of knowledge",
barrier == "q20_training" ~ "Lack of training"
)
) |>
count(barrier, response_label) |>
group_by(barrier) |>
mutate(pct = n / sum(n) * 100) |>
ungroup() |>
ggplot(aes(x = fct_rev(barrier), y = pct, fill = response_label)) +
geom_col(position = "stack", width = 0.7) +
scale_fill_viridis_d(name = "Response", option = "D") +
labs(x = NULL, y = "Percentage (%)") +
coord_flip() +
theme_minimal(base_size = 13) +
theme(legend.position = "top") +
ggtitle("Perceived barriers to preventive dentistry (overall)")
ggsave(here("output", "fig3_barriers_stacked.png"),
width = 9, height = 5, dpi = 300)# Heatmap showing barrier agreement rates across countries
df |>
select(country, q15_time:q20_training) |>
pivot_longer(cols = q15_time:q20_training,
names_to = "barrier",
values_to = "response") |>
filter(!is.na(response)) |>
mutate(
agree = if_else(response >= 3, 1, 0),
barrier = case_when(
barrier == "q15_time" ~ "Time",
barrier == "q16_remuneration" ~ "Remuneration",
barrier == "q17_motivation" ~ "Motivation",
barrier == "q18_compliance" ~ "Patient compliance",
barrier == "q19_knowledge_b" ~ "Knowledge",
barrier == "q20_training" ~ "Training"
)
) |>
group_by(country, barrier) |>
summarise(pct_agree = mean(agree) * 100, .groups = "drop") |>
ggplot(aes(x = barrier, y = country, fill = pct_agree)) +
geom_tile(color = "white", linewidth = 0.5) +
geom_text(aes(label = sprintf("%.0f%%", pct_agree)), size = 3) +
scale_fill_viridis_c(name = "% Agree", option = "D") +
labs(x = NULL, y = NULL) +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
ggtitle("Perceived barriers by country (% Agree or Strongly Agree)")
ggsave(here("output", "fig5_barriers_heatmap.png"),
width = 9, height = 5, dpi = 300)# Grouped bar chart for practice items across countries
df |>
select(country, q11_sealants:q14_diet) |>
pivot_longer(cols = q11_sealants:q14_diet,
names_to = "practice",
values_to = "response") |>
filter(!is.na(response)) |>
mutate(
frequent = if_else(response <= 2, 1, 0),
practice = case_when(
practice == "q11_sealants" ~ "Sealants",
practice == "q12_varnish" ~ "Fluoride varnish",
practice == "q13_ohi" ~ "OHI",
practice == "q14_diet" ~ "Dietary advice"
)
) |>
group_by(country, practice) |>
summarise(pct = mean(frequent) * 100, .groups = "drop") |>
ggplot(aes(x = country, y = pct, fill = practice)) +
geom_col(position = position_dodge(width = 0.8), width = 0.7) +
scale_fill_viridis_d(name = "Practice", option = "D") +
labs(x = NULL, y = "% Always or Often") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
coord_flip() +
ggtitle("Preventive practice frequency by country (% Always or Often)")
ggsave(here("output", "fig4_practice_by_country.png"),
width = 10, height = 5, dpi = 300)# Proportion reporting Always/Often for each practice item, overall
df |>
select(q11_sealants:q14_diet) |>
pivot_longer(cols = everything(),
names_to = "practice",
values_to = "response") |>
filter(!is.na(response)) |>
mutate(
frequent = if_else(response <= 2, 1, 0),
practice = case_when(
practice == "q11_sealants" ~ "Sealants",
practice == "q12_varnish" ~ "Fluoride varnish",
practice == "q13_ohi" ~ "Oral hygiene instructions",
practice == "q14_diet" ~ "Dietary advice"
)
) |>
group_by(practice) |>
summarise(pct = mean(frequent) * 100, .groups = "drop") |>
ggplot(aes(x = fct_reorder(practice, pct), y = pct, fill = pct)) +
geom_col(width = 0.6) +
geom_text(aes(label = sprintf("%.1f%%", pct)), hjust = -0.1, size = 4) +
scale_fill_viridis_c(option = "D", guide = "none") +
labs(x = NULL, y = "% Always or Often") +
coord_flip(ylim = c(0, 100)) +
theme_minimal(base_size = 13) +
ggtitle("Overall ranking of preventive practices (% Always or Often)")
ggsave(here("output", "fig7_practice_ranking_overall.png"),
width = 8, height = 4, dpi = 300)# Practice ranking faceted by country
df |>
select(country, q11_sealants:q14_diet) |>
pivot_longer(cols = q11_sealants:q14_diet,
names_to = "practice",
values_to = "response") |>
filter(!is.na(response)) |>
mutate(
frequent = if_else(response <= 2, 1, 0),
practice = case_when(
practice == "q11_sealants" ~ "Sealants",
practice == "q12_varnish" ~ "Fluoride varnish",
practice == "q13_ohi" ~ "OHI",
practice == "q14_diet" ~ "Dietary advice"
)
) |>
group_by(country, practice) |>
summarise(pct = mean(frequent) * 100, .groups = "drop") |>
ggplot(aes(x = fct_reorder(practice, pct), y = pct, fill = practice)) +
geom_col(width = 0.7) +
geom_text(aes(label = sprintf("%.0f%%", pct)), hjust = -0.1, size = 2.5) +
scale_fill_viridis_d(option = "C", guide = "none") +
labs(x = NULL, y = "% Always or Often") +
coord_flip(ylim = c(0, 105)) +
facet_wrap(~ country, ncol = 4) +
theme_minimal(base_size = 11) +
theme(strip.text = element_text(face = "bold")) +
ggtitle("Ranking of preventive practices by country (% Always or Often)")
ggsave(here("output", "fig8_practice_ranking_by_country.png"),
width = 12, height = 6, dpi = 300)# Composite score: sum of all 4 practice items (range 4-16)
# Lower score = check"more frequent preventive practice?
df |>
mutate(
practice_score = q11_sealants + q12_varnish + q13_ohi + q14_diet
) |>
filter(!is.na(practice_score)) |>
ggplot(aes(x = practice_score)) +
geom_histogram(binwidth = 1, color = "white", linewidth = 0.3) +
geom_vline(aes(xintercept = mean(practice_score, na.rm = TRUE)),
linetype = "dashed", color = "red", linewidth = 0.8) +
# scale_fill_viridis_c(option = "D", guide = "none") +
scale_x_continuous(breaks = 0:16) +
labs(
title = "Distribution of composite preventive practice score",
x = "Preventive Practice Score (0 = best, 16 = worst)",
y = "Frequency"
) +
annotate("text", x = 14, y = Inf, vjust = 2, hjust = 1, size = 3.5,
label = "Dashed line = mean") +
theme_minimal(base_size = 13)
ggsave(here("output", "fig9_practice_score_histogram.png"),
width = 8, height = 5, dpi = 300)# Recode practice and barrier items into binary for a sake of clarity
df |>
transmute(
country,
score_knowledge,
# Practice: binary always/often
sealants_frequent = if_else(q11_sealants <= 2, 1L, 0L),
varnish_frequent = if_else(q12_varnish <= 2, 1L, 0L),
ohi_frequent = if_else(q13_ohi <= 2, 1L, 0L),
diet_frequent = if_else(q14_diet <= 2, 1L, 0L),
# Barriers: binary agree/strongly agree
barrier_time = if_else(q15_time >= 3, 1L, 0L),
barrier_remuneration = if_else(q16_remuneration >= 3, 1L, 0L),
barrier_motivation = if_else(q17_motivation >= 3, 1L, 0L),
barrier_compliance = if_else(q18_compliance >= 3, 1L, 0L),
barrier_knowledge = if_else(q19_knowledge_b >= 3, 1L, 0L),
barrier_training = if_else(q20_training >= 3, 1L, 0L)
) |>
tbl_summary(
by = country,
label = list(
score_knowledge ~ "Knowledge score (0-7)",
sealants_frequent ~ "Sealants (Always/Often)",
varnish_frequent ~ "Fluoride varnish (Always/Often)",
ohi_frequent ~ "OHI (Always/Often)",
diet_frequent ~ "Dietary advice (Always/Often)",
barrier_time ~ "Barrier: lack of time",
barrier_remuneration ~ "Barrier: lack of remuneration",
barrier_motivation ~ "Barrier: lack of motivation",
barrier_compliance ~ "Barrier: poor compliance",
barrier_knowledge ~ "Barrier: lack of knowledge",
barrier_training ~ "Barrier: lack of training"
),
statistic = list(
all_continuous() ~ "{mean} ({sd})",
all_categorical() ~ "{p}%"
),
missing = "no"
) |>
add_overall() |>
bold_labels()| Characteristic | Overall N = 3,5771 |
Argentina N = 3821 |
Chile N = 7481 |
India N = 3601 |
Italy N = 3221 |
Latvia N = 1001 |
Serbia N = 8451 |
Spain N = 7081 |
Switzerland N = 1121 |
|---|---|---|---|---|---|---|---|---|---|
| Knowledge score (0-7) | |||||||||
| 0 | 0.2% | 0% | 0% | 0.8% | 0% | 0% | 0% | 0.4% | 0.9% |
| 1 | 0.8% | 0.3% | 0% | 5.3% | 0% | 1.0% | 0.4% | 0.1% | 2.7% |
| 2 | 1.9% | 4.2% | 0.1% | 4.4% | 2.5% | 4.0% | 0.9% | 2.0% | 0.9% |
| 3 | 7.4% | 7.9% | 0.5% | 14% | 11% | 8.0% | 7.6% | 7.8% | 14% |
| 4 | 17% | 21% | 2.1% | 14% | 25% | 28% | 23% | 19% | 21% |
| 5 | 26% | 32% | 12% | 26% | 32% | 41% | 27% | 31% | 24% |
| 6 | 27% | 26% | 34% | 25% | 21% | 18% | 28% | 27% | 21% |
| 7 | 19% | 8.4% | 51% | 11% | 7.8% | 0% | 12% | 13% | 15% |
| Sealants (Always/Often) | 81% | 81% | 84% | 85% | 86% | 67% | 89% | 67% | 60% |
| Fluoride varnish (Always/Often) | 70% | 77% | 97% | 77% | 56% | 70% | 47% | 68% | 69% |
| OHI (Always/Often) | 98% | 100% | 98% | 93% | 98% | 100% | 99% | 100% | 98% |
| Dietary advice (Always/Often) | 84% | 92% | 75% | 92% | 88% | 86% | 77% | 90% | 93% |
| Barrier: lack of time | 38% | 57% | 14% | 33% | 67% | 60% | 35% | 42% | 36% |
| Barrier: lack of remuneration | 46% | 46% | 59% | 38% | 64% | 58% | 31% | 42% | 66% |
| Barrier: lack of motivation | 40% | 48% | 12% | 33% | 79% | 50% | 36% | 48% | 65% |
| Barrier: poor compliance | 50% | 49% | 48% | 28% | 55% | 80% | 68% | 38% | 45% |
| Barrier: lack of knowledge | 52% | 48% | 59% | 39% | 64% | 50% | 37% | 64% | 58% |
| Barrier: lack of training | 47% | 44% | 41% | 41% | 75% | 42% | 34% | 59% | 72% |
| 1 | |||||||||