Knowledge, Attitudes, and Perceived Barriers Toward Pediatric Preventive Dentistry

Demetrio Data

Published

March 7, 2026

Modified

March 23, 2026

1 Setup

1.1 Load packages

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)

1.2 Read and clean data

Code
# 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.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

2 Demographics

2.1 Demographics table (Table 1)

Code
# 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,497 (42%) 172 (45%) 438 (59%) 147 (41%) 128 (40%) 82 (82%) 152 (18%) 350 (49%) 28 (25%)
    Pediatric Dentistry 410 (11%) 98 (26%) 140 (19%) 19 (5.3%) 13 (4.0%) 11 (11%) 44 (5.2%) 64 (9.0%) 21 (19%)
    Orthodontics 251 (7.0%) 32 (8.4%) 29 (3.9%) 9 (2.5%) 36 (11%) 1 (1.0%) 55 (6.5%) 75 (11%) 14 (12%)
    Oral Surgery 247 (6.9%) 21 (5.5%) 25 (3.3%) 22 (6.1%) 39 (12%) 0 (0%) 40 (4.7%) 96 (14%) 4 (3.6%)
    Restorative/Endo 428 (12%) 42 (11%) 34 (4.5%) 146 (41%) 45 (14%) 2 (2.0%) 54 (6.4%) 79 (11%) 26 (23%)
    Prosthodontics 212 (5.9%) 12 (3.1%) 78 (10%) 9 (2.5%) 41 (13%) 2 (2.0%) 20 (2.4%) 38 (5.4%) 12 (11%)
    Periodontics 532 (15%) 5 (1.3%) 4 (0.5%) 8 (2.2%) 20 (6.2%) 2 (2.0%) 480 (57%) 6 (0.8%) 7 (6.2%)
1 n (%)
2 Pearson's Chi-squared test

3 Knowledge

3.1 Descriptives by country

Code
# 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-value
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 29 (0.8%) 1 (0.3%) 0 (0%) 19 (5.3%) 1 (0.3%) 1 (1.0%) 3 (0.4%) 1 (0.1%) 3 (2.7%)
    2 83 (2.3%) 16 (4.2%) 1 (0.1%) 16 (4.4%) 23 (7.1%) 4 (4.0%) 8 (0.9%) 14 (2.0%) 1 (0.9%)
    3 301 (8.4%) 30 (7.9%) 4 (0.5%) 52 (14%) 74 (23%) 6 (6.0%) 64 (7.6%) 55 (7.8%) 16 (14%)
    4 622 (17%) 82 (21%) 16 (2.1%) 49 (14%) 102 (32%) 19 (19%) 197 (23%) 134 (19%) 23 (21%)
    5 909 (25%) 122 (32%) 91 (12%) 93 (26%) 85 (26%) 43 (43%) 228 (27%) 220 (31%) 27 (24%)
    6 959 (27%) 99 (26%) 252 (34%) 89 (25%) 37 (11%) 27 (27%) 240 (28%) 191 (27%) 24 (21%)
    7 667 (19%) 32 (8.4%) 384 (51%) 39 (11%) 0 (0%) 0 (0%) 105 (12%) 90 (13%) 17 (15%)
1 n (%)

3.2 Item-level correct response rate

Code
# 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,587 (72%) 344 (91%) 731 (98%) 262 (73%) 0 (0%) 95 (95%) 637 (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

3.3 Knowledge by specialty

Code
# 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,497 (42%) 172 (45%) 438 (59%) 147 (41%) 128 (40%) 82 (82%) 152 (18%) 350 (49%) 28 (25%)
    Pediatric Dentistry 410 (11%) 98 (26%) 140 (19%) 19 (5.3%) 13 (4.0%) 11 (11%) 44 (5.2%) 64 (9.0%) 21 (19%)
    Orthodontics 251 (7.0%) 32 (8.4%) 29 (3.9%) 9 (2.5%) 36 (11%) 1 (1.0%) 55 (6.5%) 75 (11%) 14 (12%)
    Oral Surgery 247 (6.9%) 21 (5.5%) 25 (3.3%) 22 (6.1%) 39 (12%) 0 (0%) 40 (4.7%) 96 (14%) 4 (3.6%)
    Restorative/Endo 428 (12%) 42 (11%) 34 (4.5%) 146 (41%) 45 (14%) 2 (2.0%) 54 (6.4%) 79 (11%) 26 (23%)
    Prosthodontics 212 (5.9%) 12 (3.1%) 78 (10%) 9 (2.5%) 41 (13%) 2 (2.0%) 20 (2.4%) 38 (5.4%) 12 (11%)
    Periodontics 532 (15%) 5 (1.3%) 4 (0.5%) 8 (2.2%) 20 (6.2%) 2 (2.0%) 480 (57%) 6 (0.8%) 7 (6.2%)
1 n (%)
2 Pearson's Chi-squared test

3.4 Kruskal-Wallis: knowledge by country

Code
# 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 = 884.07, df = 7, p-value < 2.2e-16

3.5 Kruskal-Wallis: knowledge by specialty

Code
# 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 = 171.42, df = 6, p-value < 2.2e-16

3.6 Knowledge by gender

Code
# 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 12 (1.1%) 17 (0.7%)
    2 36 (3.2%) 47 (1.9%)
    3 115 (10%) 186 (7.6%)
    4 213 (19%) 409 (17%)
    5 285 (25%) 623 (25%)
    6 264 (24%) 694 (28%)
    7 188 (17%) 477 (19%)
1 n (%)

4 Attitudes

Likert scale: 1 = Strongly agree, 2 = Agree, 3 = Disagree, 4 = Strongly disagree.

4.1 Attitude items by country

Code
# 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 2,061 (60%) 320 (84%) 579 (77%) 240 (67%) 170 (53%) 15 (15%) 129 (16%) 608 (86%) 0 (NA%)
    Agree 615 (18%) 0 (0%) 134 (18%) 88 (25%) 116 (36%) 29 (29%) 248 (30%) 0 (0%) 0 (NA%)
    Disagree 708 (21%) 54 (14%) 31 (4.1%) 28 (7.9%) 33 (10%) 30 (30%) 444 (54%) 88 (12%) 0 (NA%)
    Strongly disagree 51 (1.5%) 8 (2.1%) 4 (0.5%) 0 (0%) 3 (0.9%) 26 (26%) 0 (0%) 10 (1.4%) 0 (NA%)
Preventive activities: paediatric dentistry <0.001
    Strongly agree 2,371 (67%) 374 (98%) 704 (94%) 230 (65%) 250 (78%) 13 (13%) 32 (3.8%) 695 (99%) 73 (65%)
    Agree 350 (9.8%) 0 (0%) 38 (5.1%) 101 (29%) 61 (19%) 2 (2.0%) 118 (14%) 0 (0%) 30 (27%)
    Disagree 766 (21%) 5 (1.3%) 3 (0.4%) 23 (6.5%) 9 (2.8%) 17 (17%) 692 (82%) 9 (1.3%) 8 (7.1%)
    Strongly disagree 76 (2.1%) 3 (0.8%) 3 (0.4%) 0 (0%) 0 (0%) 68 (68%) 0 (0%) 1 (0.1%) 1 (0.9%)
Well equipped for preventive care <0.001
    Strongly agree 1,642 (47%) 260 (68%) 311 (42%) 181 (51%) 71 (22%) 10 (10%) 272 (37%) 505 (72%) 32 (29%)
    Agree 747 (22%) 0 (0%) 260 (35%) 96 (27%) 90 (28%) 11 (11%) 234 (32%) 0 (0%) 56 (50%)
    Disagree 932 (27%) 104 (27%) 163 (22%) 78 (22%) 131 (41%) 29 (29%) 231 (31%) 173 (25%) 23 (21%)
    Strongly disagree 137 (4.0%) 18 (4.7%) 14 (1.9%) 0 (0%) 27 (8.5%) 50 (50%) 0 (0%) 27 (3.8%) 1 (0.9%)
1 n (%)
2 Pearson's Chi-squared test

5 Practice

Likert scale: 1 = Always, 2 = Often, 3 = Sometimes, 4 = Never.

5.1 Practice items by country

Code
# 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,156 (33%) 133 (35%) 343 (46%) 148 (42%) 154 (48%) 23 (23%) 68 (8.3%) 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 939 (27%) 56 (15%) 81 (11%) 45 (13%) 32 (10%) 20 (20%) 514 (62%) 166 (24%) 25 (23%)
    Never 178 (5.0%) 17 (4.5%) 42 (5.6%) 7 (2.0%) 13 (4.1%) 13 (13%) 0 (0%) 67 (9.6%) 19 (17%)
Apply fluoride varnish <0.001
    Always 1,493 (44%) 156 (41%) 587 (79%) 124 (35%) 71 (22%) 25 (25%) 270 (40%) 219 (31%) 41 (37%)
    Often 1,110 (33%) 140 (37%) 139 (19%) 148 (42%) 108 (34%) 45 (45%) 240 (36%) 254 (36%) 36 (32%)
    Sometimes 620 (18%) 63 (16%) 14 (1.9%) 74 (21%) 86 (27%) 27 (27%) 161 (24%) 178 (25%) 17 (15%)
    Never 161 (4.8%) 23 (6.0%) 7 (0.9%) 8 (2.3%) 55 (17%) 3 (3.0%) 0 (0%) 48 (6.9%) 17 (15%)
Give oral hygiene instructions <0.001
    Always 2,268 (64%) 353 (92%) 608 (82%) 261 (73%) 269 (85%) 75 (75%) 4 (0.5%) 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 737 (21%) 1 (0.3%) 14 (1.9%) 21 (5.9%) 4 (1.3%) 0 (0%) 695 (82%) 1 (0.1%) 1 (0.9%)
    Never 7 (0.2%) 0 (0%) 0 (0%) 5 (1.4%) 1 (0.3%) 0 (0%) 0 (0%) 0 (0%) 1 (0.9%)
Provide dietary advice <0.001
    Always 1,553 (44%) 253 (67%) 254 (34%) 215 (61%) 156 (49%) 32 (32%) 175 (21%) 409 (58%) 59 (54%)
    Often 1,289 (36%) 95 (25%) 303 (41%) 109 (31%) 124 (39%) 54 (54%) 338 (41%) 223 (32%) 43 (39%)
    Sometimes 649 (18%) 28 (7.4%) 162 (22%) 25 (7.1%) 34 (11%) 14 (14%) 311 (38%) 68 (9.6%) 7 (6.4%)
    Never 47 (1.3%) 4 (1.1%) 27 (3.6%) 3 (0.9%) 6 (1.9%) 0 (0%) 0 (0%) 6 (0.8%) 1 (0.9%)
1 n (%)
2 Pearson's Chi-squared test

5.2 Practice by age group

Code
# 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 376 (35%) 300 (31%) 220 (30%) 173 (34%) 80 (40%) 7 (33%)
    Often 454 (42%) 326 (33%) 242 (33%) 169 (33%) 65 (32%) 8 (38%)
    Sometimes 225 (21%) 285 (29%) 237 (32%) 144 (28%) 44 (22%) 4 (19%)
    Never 32 (2.9%) 64 (6.6%) 42 (5.7%) 27 (5.3%) 11 (5.5%) 2 (9.5%)
Apply fluoride varnish <0.001
    Always 533 (51%) 436 (47%) 284 (41%) 172 (35%) 62 (31%) 6 (29%)
    Often 330 (31%) 309 (33%) 221 (32%) 168 (34%) 73 (37%) 9 (43%)
    Sometimes 154 (15%) 146 (16%) 148 (21%) 115 (23%) 51 (26%) 6 (29%)
    Never 35 (3.3%) 38 (4.1%) 42 (6.0%) 35 (7.1%) 11 (5.6%) 0 (0%)
Give oral hygiene instructions <0.001
    Always 718 (66%) 579 (59%) 442 (59%) 355 (68%) 157 (79%) 17 (77%)
    Often 209 (19%) 176 (18%) 98 (13%) 47 (9.0%) 18 (9.0%) 2 (9.1%)
    Sometimes 160 (15%) 225 (23%) 205 (27%) 121 (23%) 24 (12%) 2 (9.1%)
    Never 4 (0.4%) 0 (0%) 1 (0.1%) 1 (0.2%) 0 (0%) 1 (4.5%)
Provide dietary advice 0.001
    Always 432 (40%) 408 (42%) 351 (47%) 244 (46%) 110 (55%) 8 (38%)
    Often 425 (39%) 361 (37%) 252 (34%) 172 (33%) 67 (34%) 12 (57%)
    Sometimes 201 (19%) 185 (19%) 134 (18%) 106 (20%) 22 (11%) 1 (4.8%)
    Never 19 (1.8%) 16 (1.6%) 7 (0.9%) 4 (0.8%) 1 (0.5%) 0 (0%)
1 n (%)
2 Pearson's Chi-squared test

6 Barriers

Likert scale: 1 = Strongly disagree, 2 = Disagree, 3 = Agree, 4 = Strongly agree.

6.1 Barrier items by country

Code
# 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,459 (44%) 164 (43%) 402 (54%) 134 (38%) 29 (9.1%) 23 (23%) 283 (49%) 409 (58%) 15 (13%)
    Disagree 693 (21%) 0 (0%) 239 (32%) 104 (29%) 75 (24%) 17 (17%) 201 (35%) 0 (0%) 57 (51%)
    Agree 727 (22%) 102 (27%) 92 (12%) 118 (33%) 109 (34%) 37 (37%) 94 (16%) 151 (21%) 24 (21%)
    Strongly agree 421 (13%) 116 (30%) 14 (1.9%) 0 (0%) 106 (33%) 23 (23%) 0 (0%) 146 (21%) 16 (14%)
Lack of remuneration <0.001
    Strongly disagree 1,140 (36%) 207 (54%) 114 (15%) 112 (32%) 40 (12%) 16 (16%) 233 (47%) 407 (58%) 11 (9.8%)
    Disagree 584 (18%) 0 (0%) 192 (26%) 104 (30%) 75 (23%) 26 (26%) 160 (33%) 0 (0%) 27 (24%)
    Agree 967 (30%) 82 (22%) 310 (42%) 130 (38%) 109 (34%) 35 (35%) 99 (20%) 152 (22%) 50 (45%)
    Strongly agree 511 (16%) 92 (24%) 130 (17%) 0 (0%) 96 (30%) 23 (23%) 0 (0%) 146 (21%) 24 (21%)
Lack of motivation <0.001
    Strongly disagree 1,400 (43%) 197 (52%) 444 (60%) 116 (33%) 20 (6.3%) 24 (24%) 232 (43%) 364 (52%) 3 (2.7%)
    Disagree 649 (20%) 0 (0%) 209 (28%) 122 (34%) 48 (15%) 26 (26%) 209 (39%) 0 (0%) 35 (32%)
    Agree 760 (23%) 100 (26%) 78 (10%) 117 (33%) 100 (31%) 30 (30%) 99 (18%) 191 (27%) 45 (41%)
    Strongly agree 445 (14%) 84 (22%) 15 (2.0%) 0 (0%) 151 (47%) 20 (20%) 0 (0%) 148 (21%) 27 (25%)
Poor patient compliance <0.001
    Strongly disagree 1,235 (35%) 195 (51%) 214 (29%) 117 (33%) 39 (12%) 8 (8.0%) 211 (27%) 438 (62%) 13 (12%)
    Disagree 830 (24%) 0 (0%) 178 (24%) 137 (39%) 106 (33%) 12 (12%) 348 (45%) 0 (0%) 49 (44%)
    Agree 977 (28%) 118 (31%) 149 (20%) 101 (28%) 125 (39%) 32 (32%) 223 (29%) 194 (27%) 35 (31%)
    Strongly agree 462 (13%) 68 (18%) 207 (28%) 0 (0%) 50 (16%) 48 (48%) 0 (0%) 74 (10%) 15 (13%)
Lack of knowledge <0.001
    Strongly disagree 1,057 (32%) 197 (52%) 170 (23%) 115 (32%) 35 (11%) 21 (21%) 261 (46%) 252 (36%) 6 (5.4%)
    Disagree 597 (18%) 0 (0%) 134 (18%) 102 (29%) 80 (25%) 29 (29%) 211 (37%) 0 (0%) 41 (37%)
    Agree 928 (28%) 97 (26%) 172 (23%) 138 (39%) 117 (37%) 30 (30%) 100 (17%) 229 (32%) 45 (40%)
    Strongly agree 710 (22%) 85 (22%) 272 (36%) 0 (0%) 88 (28%) 20 (20%) 0 (0%) 225 (32%) 20 (18%)
Lack of training <0.001
    Strongly disagree 1,111 (34%) 215 (56%) 188 (25%) 114 (32%) 24 (7.5%) 23 (23%) 253 (47%) 291 (41%) 3 (2.7%)
    Disagree 658 (20%) 0 (0%) 252 (34%) 96 (27%) 55 (17%) 35 (35%) 192 (36%) 0 (0%) 28 (25%)
    Agree 858 (26%) 79 (21%) 155 (21%) 145 (41%) 110 (34%) 23 (23%) 93 (17%) 204 (29%) 49 (44%)
    Strongly agree 630 (19%) 87 (23%) 153 (20%) 0 (0%) 131 (41%) 19 (19%) 0 (0%) 209 (30%) 31 (28%)
1 n (%)
2 Pearson's Chi-squared test

6.2 Barrier items by specialty

Code
# 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,4971 Pediatric Dentistry, N = 4101 Orthodontics, N = 2511 Oral Surgery, N = 2471 Restorative/Endo, N = 4281 Prosthodontics, N = 2121 Periodontics, N = 5321 p-value2
Lack of time <0.001
    Strongly disagree 651 (45%) 185 (47%) 101 (44%) 115 (49%) 155 (38%) 74 (36%) 178 (47%)
    Disagree 278 (19%) 51 (13%) 43 (19%) 30 (13%) 96 (24%) 59 (29%) 136 (36%)
    Agree 323 (22%) 87 (22%) 50 (22%) 57 (24%) 108 (27%) 44 (22%) 58 (15%)
    Strongly agree 193 (13%) 72 (18%) 38 (16%) 35 (15%) 47 (12%) 27 (13%) 9 (2.4%)
Lack of remuneration <0.001
    Strongly disagree 477 (34%) 132 (34%) 81 (34%) 104 (45%) 152 (38%) 50 (25%) 144 (44%)
    Disagree 224 (16%) 67 (17%) 40 (17%) 35 (15%) 82 (20%) 34 (17%) 102 (31%)
    Agree 456 (32%) 125 (32%) 71 (30%) 56 (24%) 112 (28%) 72 (36%) 75 (23%)
    Strongly agree 255 (18%) 68 (17%) 43 (18%) 35 (15%) 57 (14%) 44 (22%) 9 (2.7%)
Lack of motivation <0.001
    Strongly disagree 652 (45%) 211 (55%) 78 (33%) 95 (41%) 155 (38%) 69 (34%) 140 (40%)
    Disagree 279 (19%) 44 (11%) 39 (17%) 33 (14%) 81 (20%) 54 (27%) 119 (34%)
    Agree 324 (22%) 76 (20%) 65 (28%) 52 (22%) 123 (30%) 45 (22%) 75 (22%)
    Strongly agree 185 (13%) 56 (14%) 54 (23%) 54 (23%) 50 (12%) 33 (16%) 13 (3.7%)
Poor patient compliance <0.001
    Strongly disagree 570 (39%) 130 (32%) 88 (36%) 115 (47%) 156 (37%) 70 (33%) 106 (21%)
    Disagree 283 (19%) 71 (18%) 50 (20%) 46 (19%) 108 (25%) 40 (19%) 232 (47%)
    Agree 404 (27%) 108 (27%) 69 (28%) 64 (26%) 124 (29%) 58 (28%) 150 (30%)
    Strongly agree 217 (15%) 96 (24%) 40 (16%) 21 (8.5%) 39 (9.1%) 41 (20%) 8 (1.6%)
Lack of knowledge <0.001
    Strongly disagree 398 (28%) 135 (35%) 84 (36%) 73 (31%) 127 (31%) 71 (34%) 169 (46%)
    Disagree 236 (16%) 49 (13%) 23 (9.7%) 31 (13%) 94 (23%) 43 (21%) 121 (33%)
    Agree 425 (30%) 100 (26%) 75 (32%) 79 (33%) 128 (31%) 52 (25%) 69 (19%)
    Strongly agree 380 (26%) 107 (27%) 54 (23%) 55 (23%) 60 (15%) 42 (20%) 12 (3.2%)
Lack of training <0.001
    Strongly disagree 460 (32%) 145 (37%) 77 (33%) 85 (36%) 141 (35%) 53 (26%) 150 (42%)
    Disagree 275 (19%) 74 (19%) 26 (11%) 31 (13%) 72 (18%) 55 (27%) 125 (35%)
    Agree 375 (26%) 94 (24%) 71 (31%) 63 (27%) 126 (31%) 61 (30%) 68 (19%)
    Strongly agree 318 (22%) 84 (21%) 56 (24%) 55 (24%) 67 (17%) 35 (17%) 15 (4.2%)
1 n (%)
2 Pearson's Chi-squared test

7 Visualizations

7.1 Knowledge score by country

Code
# 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)
Figure 1: Knowledge score distribution by country

7.2 Knowledge item heatmap

Code
# 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)
Figure 2: Correct response rate by knowledge item and country

7.3 Knowledge by specialty

Code
# 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)
Figure 3: Knowledge score distribution by dental specialty
Code
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")

Code
ggsave(here("output", "fig6_1_knowledge_by_specialty_by_country.png"),
       width = 9, height = 5, dpi = 300)

7.4 Barriers: stacked bar chart

Code
# 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)
Figure 4: Perceived barriers to preventive dentistry (overall)

7.5 Barriers heatmap by country

Code
# 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)
Figure 5: Perceived barriers by country (% Agree or Strongly Agree)

7.6 Practice frequency by country

Code
# 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)
Figure 6: Preventive practice frequency by country (% Always or Often)

7.7 Overall ranking of preventive practices

Code
# 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)
Figure 7: Overall ranking of preventive practices (% Always or Often)

7.8 Overall ranking of preventive practices by country

Code
# 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)
Figure 8: Ranking of preventive practices by country (% Always or Often)

7.9 Distribution of preventive practice score

Code
# 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)
Figure 9: Distribution of composite preventive practice score

8 Summary table

Code
# 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.3% 1.0% 0.4% 0.1% 2.7%
    2 2.3% 4.2% 0.1% 4.4% 7.1% 4.0% 0.9% 2.0% 0.9%
    3 8.4% 7.9% 0.5% 14% 23% 6.0% 7.6% 7.8% 14%
    4 17% 21% 2.1% 14% 32% 19% 23% 19% 21%
    5 25% 32% 12% 26% 26% 43% 27% 31% 24%
    6 27% 26% 34% 25% 11% 27% 28% 27% 21%
    7 19% 8.4% 51% 11% 0% 0% 12% 13% 15%
Sealants (Always/Often) 69% 81% 84% 85% 86% 67% 39% 67% 60%
Fluoride varnish (Always/Often) 78% 77% 97% 77% 56% 70% 81% 68% 69%
OHI (Always/Often) 79% 100% 98% 93% 98% 100% 18% 100% 98%
Dietary advice (Always/Often) 80% 92% 75% 92% 88% 86% 63% 90% 93%
Barrier: lack of time 32% 57% 14% 33% 67% 60% 11% 42% 36%
Barrier: lack of remuneration 42% 46% 59% 38% 64% 58% 12% 42% 66%
Barrier: lack of motivation 34% 48% 12% 33% 79% 50% 12% 48% 65%
Barrier: poor compliance 40% 49% 48% 28% 55% 80% 26% 38% 45%
Barrier: lack of knowledge 46% 48% 59% 39% 64% 50% 12% 64% 58%
Barrier: lack of training 42% 44% 41% 41% 75% 42% 11% 59% 72%
1 %