required_packages <- c("dplyr", "ggplot2", "knitr", "maps", "tidyr")
missing_packages <- required_packages[!vapply(required_packages, requireNamespace, logical(1), quietly = TRUE)]

if (length(missing_packages) > 0) {
  stop(
    "Install the missing packages before knitting: ",
    paste(missing_packages, collapse = ", ")
  )
}

library(dplyr)
library(ggplot2)
library(knitr)
library(maps)
library(tidyr)

knitr::opts_chunk$set(
  echo = TRUE,
  message = FALSE,
  warning = FALSE,
  fig.width = 11,
  fig.height = 7,
  fig.align = "center"
)

dir.create("visuals", showWarnings = FALSE, recursive = TRUE)
dir.create("data/processed", showWarnings = FALSE, recursive = TRUE)

theme_set(theme_minimal(base_size = 12))

1 1. Project overview

1.1 1.1 Background

Childhood and adolescent obesity is a major public-health challenge because it affects physical and mental health, quality of life and later noncommunicable-disease risk. The sixth round of the WHO European Childhood Obesity Surveillance Initiative (COSI) found substantial differences between countries and renewed concern about the lack of progress after the COVID-19 period. The 2021/2022 Health Behaviour in School-aged Children (HBSC) report additionally shows that weight status, diet and physical activity are socially patterned.

The 18th European Public Health Conference 2025 abstract supplement sharpened the policy context for this project. Its childhood-obesity contributions highlighted four connected challenges: interventions limited to one behaviour or setting; unequal food and activity environments; weak protection from unhealthy food marketing; and the need for standardized, measurement-based surveillance. The report therefore treats obesity as a population and systems problem rather than an individual failure.

The present dataset can quantify differences by country, sex and time. It cannot directly measure family affluence, food marketing, school meals or policy implementation. Those determinants are used to interpret the findings and define future data extensions, not as causes tested in this analysis.

1.2 1.2 Primary research question

How large and persistent are inequalities in modelled obesity prevalence among children and adolescents aged 5–19 years across the WHO European Region from 1990 to 2022, considering country, sex, change over time and uncertainty?

1.3 1.3 Secondary research questions

  1. Which countries have the highest latest prevalence, and how wide are their confidence intervals?
  2. Which countries experienced the largest absolute and annualized changes between their first and latest estimates?
  3. Did the spread between lower- and higher-prevalence countries narrow or widen over time?
  4. How large is the male–female prevalence gap in each country, and did the average absolute gap change over time?
  5. How should the patterns be interpreted alongside current European concerns about socioeconomic disadvantage, unhealthy food environments, physical inactivity and food marketing?

The first four questions are answered quantitatively. The fifth is a contextual interpretation and does not claim causal effects.

1.4 1.4 Objectives

  1. Validate and clean the WHO-derived dataset.
  2. Describe its geographic and temporal coverage.
  3. Compare the latest country estimates with confidence intervals.
  4. Quantify absolute, relative and annualized country-level change.
  5. Measure between-country inequality using the interquartile range and P90–P10 gap.
  6. Quantify the direction and magnitude of sex differences over time.
  7. Separate observed inequalities from unmeasured explanations and policy determinants.
  8. Export an analysis-ready file for Tableau.

1.5 1.5 Unit of analysis and terminology

Each row represents one country–year–sex–age-group estimate. Results are ecological country-level estimates and must not be interpreted as individual-level risk.

In this report, inequality means a measurable difference between countries or sex categories. Inequity implies that a difference is avoidable and unfair. Establishing inequity requires information on social position and structural determinants that is not present in the core dataset.

1.6 1.6 Analytical framework

analytical_framework <- data.frame(
  Domain = c(
    "Current burden", "Long-term change", "Geographic inequality",
    "Sex inequality", "Estimate uncertainty", "Systems context"
  ),
  Measure = c(
    "Latest prevalence and rank", "Absolute, relative and annualized change",
    "IQR and P90–P10 gap", "Male minus female gap and absolute gap",
    "95% confidence interval width", "Narrative triangulation only"
  ),
  Interpretation = c(
    "Where modelled prevalence is highest", "Direction and speed of change",
    "Whether country distributions converge or diverge",
    "Direction and magnitude of sex difference",
    "Precision of each modelled estimate",
    "Food, activity, school, community and policy environment"
  )
)

kable(analytical_framework, caption = "Conference-informed analytical framework")
Conference-informed analytical framework
Domain Measure Interpretation
Current burden Latest prevalence and rank Where modelled prevalence is highest
Long-term change Absolute, relative and annualized change Direction and speed of change
Geographic inequality IQR and P90–P10 gap Whether country distributions converge or diverge
Sex inequality Male minus female gap and absolute gap Direction and magnitude of sex difference
Estimate uncertainty 95% confidence interval width Precision of each modelled estimate
Systems context Narrative triangulation only Food, activity, school, community and policy environment

2 2. Data source and variables

The raw file is stored at data/raw/childhood_obesity.csv. It contains modelled estimates from the WHO indicator Prevalence of obesity among children and adolescents aged 5–19 years. The source URL is retained in every row for provenance.

data_dictionary <- data.frame(
  Variable = c(
    "Country", "Country_code", "Year", "Sex", "Age_group",
    "Obesity_prevalence", "Lower_CI", "Upper_CI", "Source"
  ),
  Meaning = c(
    "Country name", "ISO alpha-3 country code", "Estimate year",
    "Female, Male or Total", "WHO age-group code",
    "Modelled obesity prevalence (%)", "Lower confidence limit (%)",
    "Upper confidence limit (%)", "WHO indicator URL"
  )
)

kable(data_dictionary, caption = "Data dictionary")
Data dictionary
Variable Meaning
Country Country name
Country_code ISO alpha-3 country code
Year Estimate year
Sex Female, Male or Total
Age_group WHO age-group code
Obesity_prevalence Modelled obesity prevalence (%)
Lower_CI Lower confidence limit (%)
Upper_CI Upper confidence limit (%)
Source WHO indicator URL

3 3. Import and validation

3.1 3.1 Read the CSV

Using a relative path makes the project reproducible on another computer after cloning the repository.

data_path <- "/Users/test/Desktop/Research with Codex/childhood_obesity.csv"

if (!file.exists(data_path)) {
  stop("Data file not found: ", data_path)
}

obesity_raw <- read.csv(
  data_path,
  na.strings = c("", "NA"),
  check.names = FALSE,
  stringsAsFactors = FALSE
)

required_columns <- c(
  "Country", "Country_code", "Year", "Sex", "Age_group",
  "Obesity_prevalence", "Lower_CI", "Upper_CI", "Source"
)

missing_columns <- setdiff(required_columns, names(obesity_raw))
if (length(missing_columns) > 0) {
  stop("Missing required columns: ", paste(missing_columns, collapse = ", "))
}

3.2 3.2 Clean and derive analysis variables

This step converts numeric fields, creates readable labels and removes rows that cannot contribute to the analysis. The original raw CSV remains unchanged.

obesity <- obesity_raw |>
  transmute(
    Country = trimws(Country),
    Country_code = toupper(trimws(Country_code)),
    Year = as.integer(Year),
    Sex = trimws(Sex),
    Age_group = trimws(Age_group),
    Age_group_label = recode(
      Age_group,
      "Y5T9" = "5–9 years",
      "Y10T19" = "10–19 years",
      "Y5T19" = "5–19 years",
      .default = Age_group
    ),
    Obesity_prevalence = as.numeric(Obesity_prevalence),
    Lower_CI = as.numeric(Lower_CI),
    Upper_CI = as.numeric(Upper_CI),
    Source = trimws(Source)
  ) |>
  filter(
    Country != "",
    !is.na(Year),
    !is.na(Obesity_prevalence)
  ) |>
  arrange(Country, Year, Sex, Age_group)

kable(
  head(obesity, 10),
  digits = 2,
  caption = "First ten cleaned observations"
)
First ten cleaned observations
Country Country_code Year Sex Age_group Age_group_label Obesity_prevalence Lower_CI Upper_CI Source
Albania ALB 1990 Female Y10T19 10–19 years 1.28 0.35 3.06 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1990 Female Y5T19 5–19 years 1.69 0.49 3.89 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1990 Female Y5T9 5–9 years 2.45 0.74 5.54 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1990 Male Y10T19 10–19 years 2.24 0.66 5.24 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1990 Male Y5T19 5–19 years 3.32 1.09 7.49 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1990 Male Y5T9 5–9 years 5.29 1.78 11.46 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1990 Total Y10T19 10–19 years 1.77 0.75 3.49 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1990 Total Y5T19 5–19 years 2.52 1.10 4.79 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1990 Total Y5T9 5–9 years 3.91 1.76 7.26 https://data.who.int/indicators/i/C6262EC/EF93DDB
Albania ALB 1991 Female Y10T19 10–19 years 1.32 0.39 3.05 https://data.who.int/indicators/i/C6262EC/EF93DDB

4 4. Data-quality assessment

4.1 4.1 Coverage summary

coverage_summary <- data.frame(
  Measure = c(
    "Rows", "Countries", "First year", "Latest year",
    "Sex categories", "Age groups", "Duplicate keys"
  ),
  Value = c(
    nrow(obesity),
    n_distinct(obesity$Country),
    min(obesity$Year),
    max(obesity$Year),
    paste(sort(unique(obesity$Sex)), collapse = ", "),
    paste(sort(unique(obesity$Age_group_label)), collapse = ", "),
    obesity |>
      count(Country, Year, Sex, Age_group) |>
      filter(n > 1) |>
      nrow()
  )
)

kable(coverage_summary, caption = "Dataset coverage and uniqueness checks")
Dataset coverage and uniqueness checks
Measure Value
Rows 15444
Countries 52
First year 1990
Latest year 2022
Sex categories Female, Male, Total
Age groups 10–19 years, 5–19 years, 5–9 years
Duplicate keys 0

4.2 4.2 Missingness and logical checks

Confidence limits should contain the point estimate. Any violations are counted so that they are visible rather than silently ignored.

quality_checks <- data.frame(
  Check = c(
    "Missing prevalence", "Missing lower confidence limit",
    "Missing upper confidence limit", "Prevalence outside 0–100%",
    "Lower limit above estimate", "Upper limit below estimate"
  ),
  Count = c(
    sum(is.na(obesity$Obesity_prevalence)),
    sum(is.na(obesity$Lower_CI)),
    sum(is.na(obesity$Upper_CI)),
    sum(obesity$Obesity_prevalence < 0 | obesity$Obesity_prevalence > 100, na.rm = TRUE),
    sum(obesity$Lower_CI > obesity$Obesity_prevalence, na.rm = TRUE),
    sum(obesity$Upper_CI < obesity$Obesity_prevalence, na.rm = TRUE)
  )
)

kable(quality_checks, caption = "Missingness and logical validation")
Missingness and logical validation
Check Count
Missing prevalence 0
Missing lower confidence limit 0
Missing upper confidence limit 0
Prevalence outside 0–100% 0
Lower limit above estimate 0
Upper limit below estimate 0

5 5. Descriptive analysis

5.1 5.1 Define the latest comparable population

For a fair country comparison, the analysis uses the latest year shared by the data and restricts results to total-sex estimates for ages 5–19 years.

latest_year <- max(obesity$Year, na.rm = TRUE)

latest_total <- obesity |>
  filter(
    Year == latest_year,
    Sex == "Total",
    Age_group == "Y5T19"
  ) |>
  mutate(
    Burden_quartile = factor(
      ntile(Obesity_prevalence, 4),
      levels = 1:4,
      labels = c("Low", "Moderate", "High", "Very high")
    )
  )

latest_summary <- latest_total |>
  summarise(
    Year = latest_year,
    Countries = n_distinct(Country),
    Mean = mean(Obesity_prevalence, na.rm = TRUE),
    Median = median(Obesity_prevalence, na.rm = TRUE),
    Minimum = min(Obesity_prevalence, na.rm = TRUE),
    Maximum = max(Obesity_prevalence, na.rm = TRUE)
  )

kable(latest_summary, digits = 1, caption = "Latest comparable prevalence summary")
Latest comparable prevalence summary
Year Countries Mean Median Minimum Maximum
2022 52 8.4 8.6 1.6 14.8

5.2 5.2 Highest-prevalence countries

highest_prevalence <- latest_total |>
  slice_max(Obesity_prevalence, n = 15, with_ties = FALSE) |>
  arrange(desc(Obesity_prevalence))

kable(
  highest_prevalence |>
    select(Country, Year, Obesity_prevalence, Lower_CI, Upper_CI, Burden_quartile),
  digits = 1,
  caption = "Fifteen highest latest estimates, total population aged 5–19"
)
Fifteen highest latest estimates, total population aged 5–19
Country Year Obesity_prevalence Lower_CI Upper_CI Burden_quartile
Hungary 2022 14.8 12.4 17.4 Very high
Cyprus 2022 14.1 10.5 18.0 Very high
Finland 2022 12.6 10.1 15.5 Very high
Malta 2022 12.1 8.8 15.8 Very high
Bulgaria 2022 12.1 9.5 14.7 Very high
Bosnia and Herzegovina 2022 12.0 6.7 18.6 Very high
Türkiye 2022 11.7 7.9 16.0 Very high
Greece 2022 11.6 9.2 14.1 Very high
North Macedonia 2022 11.3 8.2 15.1 Very high
United Kingdom of Great Britain and Northern Ireland 2022 11.3 9.4 13.4 Very high
Andorra 2022 11.2 3.9 21.3 Very high
Serbia 2022 10.9 8.2 13.9 Very high
Slovenia 2022 10.8 9.8 12.0 Very high
Estonia 2022 10.7 8.9 12.6 High
Georgia 2022 10.6 7.3 14.7 High
ranking_plot <- highest_prevalence |>
  mutate(Country = reorder(Country, Obesity_prevalence)) |>
  ggplot(aes(x = Obesity_prevalence, y = Country)) +
  geom_col(fill = "#C94C4C", width = 0.72) +
  geom_errorbar(
    aes(xmin = Lower_CI, xmax = Upper_CI),
    orientation = "y",
    width = 0.22,
    colour = "#333333"
  ) +
  labs(
    title = paste("Highest modelled obesity prevalence in", latest_year),
    subtitle = "Total population aged 5–19; lines show confidence intervals",
    x = "Obesity prevalence (%)",
    y = NULL,
    caption = "Source: WHO"
  )

ranking_plot

ggsave("visuals/latest_obesity_prevalence.png", ranking_plot, width = 11, height = 7, dpi = 200)

6 6. Inequality analysis by sex

The sex gap is defined as male prevalence minus female prevalence. Positive values indicate a higher estimate among males; negative values indicate a higher estimate among females.

sex_gap_all <- obesity |>
  filter(
    Age_group == "Y5T19",
    Sex %in% c("Female", "Male")
  ) |>
  select(Country, Country_code, Year, Sex, Obesity_prevalence) |>
  pivot_wider(names_from = Sex, values_from = Obesity_prevalence) |>
  mutate(
    Sex_gap = Male - Female,
    Absolute_sex_gap = abs(Sex_gap)
  ) |>
  filter(!is.na(Sex_gap)) |>
  arrange(Country, Year)

sex_gap <- sex_gap_all |>
  filter(Year == latest_year) |>
  arrange(desc(abs(Sex_gap)))

kable(
  head(sex_gap, 15),
  digits = 1,
  caption = "Largest absolute sex gaps in the latest year"
)
Largest absolute sex gaps in the latest year
Country Country_code Year Female Male Sex_gap Absolute_sex_gap
Serbia SRB 2022 7.4 14.3 6.9 6.9
Montenegro MNE 2022 5.2 12.0 6.8 6.8
Georgia GEO 2022 7.6 13.4 5.8 5.8
Cyprus CYP 2022 11.2 16.8 5.7 5.7
Finland FIN 2022 10.0 15.2 5.3 5.3
Estonia EST 2022 8.1 13.2 5.1 5.1
Greece GRC 2022 9.0 14.0 5.0 5.0
Bosnia and Herzegovina BIH 2022 9.5 14.5 4.9 4.9
Hungary HUN 2022 12.3 17.2 4.9 4.9
Malta MLT 2022 9.6 14.4 4.8 4.8
Romania ROU 2022 6.5 11.1 4.6 4.6
Slovenia SVN 2022 8.5 13.1 4.6 4.6
Sweden SWE 2022 7.8 12.3 4.5 4.5
Italy ITA 2022 7.4 11.7 4.3 4.3
North Macedonia MKD 2022 9.1 13.4 4.3 4.3
sex_gap_plot <- sex_gap |>
  slice_max(abs(Sex_gap), n = 15, with_ties = FALSE) |>
  mutate(Country = reorder(Country, Sex_gap)) |>
  ggplot(aes(x = Sex_gap, y = Country, fill = Sex_gap > 0)) +
  geom_col(show.legend = FALSE) +
  geom_vline(xintercept = 0, colour = "#333333") +
  scale_fill_manual(values = c("TRUE" = "#3B78A8", "FALSE" = "#D9795B")) +
  labs(
    title = paste("Largest sex differences in", latest_year),
    subtitle = "Male prevalence minus female prevalence, ages 5–19",
    x = "Percentage-point difference",
    y = NULL
  )

sex_gap_plot

ggsave("visuals/latest_sex_gap.png", sex_gap_plot, width = 11, height = 7, dpi = 200)

6.1 6.1 Change in the regional sex gap

The mean absolute gap describes the typical size of the country-level difference regardless of which sex has the higher estimate. The mean signed gap retains direction.

sex_gap_trend <- sex_gap_all |>
  group_by(Year) |>
  summarise(
    Mean_signed_gap = mean(Sex_gap, na.rm = TRUE),
    Mean_absolute_gap = mean(Absolute_sex_gap, na.rm = TRUE),
    Countries = n_distinct(Country),
    .groups = "drop"
  )

sex_gap_trend_plot <- sex_gap_trend |>
  select(Year, `Signed gap` = Mean_signed_gap, `Absolute gap` = Mean_absolute_gap) |>
  pivot_longer(-Year, names_to = "Measure", values_to = "Gap") |>
  ggplot(aes(Year, Gap, colour = Measure)) +
  geom_hline(yintercept = 0, colour = "grey65") +
  geom_line(linewidth = 1) +
  scale_colour_manual(values = c("Signed gap" = "#3B78A8", "Absolute gap" = "#C94C4C")) +
  labs(
    title = "Country-level sex inequality over time",
    subtitle = "Unweighted mean across countries, ages 5–19",
    x = "Year", y = "Percentage-point gap", colour = NULL
  )

sex_gap_trend_plot

ggsave("visuals/sex_gap_trend.png", sex_gap_trend_plot, width = 11, height = 7, dpi = 200)

8 8. Uncertainty and ranking caution

Confidence-interval width is used as a transparent indicator of estimate precision. It is not a data-quality score. Countries with overlapping intervals should not be described as definitively different based on rank alone.

latest_uncertainty <- latest_total |>
  mutate(CI_width = Upper_CI - Lower_CI) |>
  arrange(desc(CI_width))

kable(
  latest_uncertainty |>
    select(Country, Obesity_prevalence, Lower_CI, Upper_CI, CI_width) |>
    head(15),
  digits = 1,
  caption = "Latest estimates with the widest confidence intervals"
)
Latest estimates with the widest confidence intervals
Country Obesity_prevalence Lower_CI Upper_CI CI_width
Andorra 11.2 3.9 21.3 17.4
Iceland 8.7 2.8 17.8 15.1
Luxembourg 7.4 2.2 15.7 13.5
Azerbaijan 8.6 3.5 16.1 12.6
Bosnia and Herzegovina 12.0 6.7 18.6 11.9
Belarus 7.9 3.3 14.2 10.8
Uzbekistan 6.4 2.3 12.8 10.4
Türkiye 11.7 7.9 16.0 8.2
Cyprus 14.1 10.5 18.0 7.5
Greenland 4.7 1.8 9.2 7.5
Georgia 10.6 7.3 14.7 7.4
Malta 12.1 8.8 15.8 6.9
North Macedonia 11.3 8.2 15.1 6.9
Armenia 9.6 6.7 13.0 6.4
Germany 8.5 5.7 11.9 6.3

9 9. Geographic patterns

Maps help reveal geographic clustering that is difficult to see in a ranked table. The map frame covers Europe and central Asia. Greenland remains in the analytical dataset but falls outside this focused display.

Country names in the WHO file are matched to the names used by the maps package through one explicit lookup table. This avoids fragile automatic text matching and creates a reusable Map_region field for Tableau.

9.1 9.1 Latest obesity prevalence across the Region

prevalence_map <- ggplot(
  prevalence_map_data,
  aes(long, lat, group = group, fill = Obesity_prevalence)
) +
  geom_polygon(colour = "white", linewidth = 0.18) +
  coord_quickmap(xlim = c(-30, 90), ylim = c(25, 75), expand = FALSE) +
  scale_fill_viridis_c(
    option = "C",
    direction = -1,
    na.value = "#E6E6E6",
    name = "Prevalence (%)"
  ) +
  labs(
    title = paste("Childhood and adolescent obesity prevalence across Europe in", latest_year),
    subtitle = "WHO modelled estimates for ages 5–19, total population",
    x = NULL,
    y = NULL,
    caption = "Darker colours indicate higher prevalence; grey indicates no matched estimate. Source: WHO"
  ) +
  theme_void(base_size = 12) +
  theme(
    legend.position = "right",
    plot.title = element_text(face = "bold"),
    plot.caption = element_text(hjust = 0)
  )

prevalence_map

ggsave(
  "visuals/latest_obesity_prevalence_map.png",
  prevalence_map,
  width = 12,
  height = 7,
  dpi = 220,
  bg = "white"
)

How to read the map: darker countries have higher modelled prevalence in 2022; grey countries are outside the matched analytical data.

Easy interpretation: Hungary has the highest point estimate at 14.8%, followed by Cyprus and Finland. The map helps identify geographic patterning, but colour differences do not by themselves prove statistically significant or causal differences.

9.2 9.2 Geographic pattern of the sex gap

max_absolute_sex_gap <- max(abs(sex_gap$Sex_gap), na.rm = TRUE)

sex_gap_map <- ggplot(
  sex_gap_map_data,
  aes(long, lat, group = group, fill = Sex_gap)
) +
  geom_polygon(colour = "white", linewidth = 0.18) +
  coord_quickmap(xlim = c(-30, 90), ylim = c(25, 75), expand = FALSE) +
  scale_fill_gradient2(
    low = "#3B78A8",
    mid = "#F7F7F7",
    high = "#C94C4C",
    midpoint = 0,
    limits = c(-max_absolute_sex_gap, max_absolute_sex_gap),
    na.value = "#E6E6E6",
    name = "Male − female\n(percentage points)"
  ) +
  labs(
    title = paste("Sex differences in obesity prevalence across Europe in", latest_year),
    subtitle = "Ages 5–19; red indicates higher prevalence among males and blue among females",
    x = NULL,
    y = NULL,
    caption = "Near-white values indicate a small sex gap; grey indicates no matched estimate. Source: WHO"
  ) +
  theme_void(base_size = 12) +
  theme(
    legend.position = "right",
    plot.title = element_text(face = "bold"),
    plot.caption = element_text(hjust = 0)
  )

sex_gap_map

ggsave(
  "visuals/latest_sex_gap_map.png",
  sex_gap_map,
  width = 12,
  height = 7,
  dpi = 220,
  bg = "white"
)

How to read the map: red means the male estimate is higher, blue means the female estimate is higher, and colours close to white indicate a small difference.

Easy interpretation: Serbia has the largest absolute sex gap (6.9 percentage points). This is a descriptive difference between modelled estimates; it does not explain whether biology, behaviour, social conditions or data inputs produced the gap.

10 10. Tableau-ready export

The export contains cleaned fields plus latest-year flags, quartiles and sex-gap values. These derived variables simplify Tableau dashboard construction.

tableau_export <- obesity |>
  left_join(map_name_lookup, by = "Country") |>
  left_join(
    sex_gap |>
      select(Country_code, Sex_gap),
    by = "Country_code"
  ) |>
  left_join(
    latest_total |>
      select(Country_code, Burden_quartile),
    by = "Country_code"
  ) |>
  left_join(
    country_change |>
      select(Country_code, Absolute_change, Relative_change_percent, Annualized_change),
    by = "Country_code"
  ) |>
  mutate(
    CI_width = Upper_CI - Lower_CI,
    Latest_year = latest_year,
    Is_latest_year = Year == latest_year,
    Latest_burden_quartile = ifelse(
      Year == latest_year & Sex == "Total" & Age_group == "Y5T19",
      as.character(Burden_quartile),
      NA_character_
    )
  ) |>
  select(-Burden_quartile)

write.csv(
  tableau_export,
  "data/processed/tableau_childhood_obesity.csv",
  row.names = FALSE,
  na = ""
)

kable(
  head(tableau_export, 8),
  digits = 2,
  caption = "Preview of the Tableau-ready dataset"
)
Preview of the Tableau-ready dataset
Country Country_code Year Sex Age_group Age_group_label Obesity_prevalence Lower_CI Upper_CI Source Map_region Sex_gap Absolute_change Relative_change_percent Annualized_change CI_width Latest_year Is_latest_year Latest_burden_quartile
Albania ALB 1990 Female Y10T19 10–19 years 1.28 0.35 3.06 https://data.who.int/indicators/i/C6262EC/EF93DDB Albania 4.27 5 198.31 0.16 2.71 2022 FALSE NA
Albania ALB 1990 Female Y5T19 5–19 years 1.69 0.49 3.89 https://data.who.int/indicators/i/C6262EC/EF93DDB Albania 4.27 5 198.31 0.16 3.39 2022 FALSE NA
Albania ALB 1990 Female Y5T9 5–9 years 2.45 0.74 5.54 https://data.who.int/indicators/i/C6262EC/EF93DDB Albania 4.27 5 198.31 0.16 4.80 2022 FALSE NA
Albania ALB 1990 Male Y10T19 10–19 years 2.24 0.66 5.24 https://data.who.int/indicators/i/C6262EC/EF93DDB Albania 4.27 5 198.31 0.16 4.58 2022 FALSE NA
Albania ALB 1990 Male Y5T19 5–19 years 3.32 1.09 7.49 https://data.who.int/indicators/i/C6262EC/EF93DDB Albania 4.27 5 198.31 0.16 6.40 2022 FALSE NA
Albania ALB 1990 Male Y5T9 5–9 years 5.29 1.78 11.46 https://data.who.int/indicators/i/C6262EC/EF93DDB Albania 4.27 5 198.31 0.16 9.68 2022 FALSE NA
Albania ALB 1990 Total Y10T19 10–19 years 1.77 0.75 3.49 https://data.who.int/indicators/i/C6262EC/EF93DDB Albania 4.27 5 198.31 0.16 2.74 2022 FALSE NA
Albania ALB 1990 Total Y5T19 5–19 years 2.52 1.10 4.79 https://data.who.int/indicators/i/C6262EC/EF93DDB Albania 4.27 5 198.31 0.16 3.69 2022 FALSE NA

11 11. Interpretation and public-health relevance

This is a surveillance and inequality-screening analysis. A high or rapidly increasing estimate identifies where closer investigation may be warranted; it does not identify why prevalence is high. Confidence intervals and changes in the country distribution are more informative than a league table alone.

The conference report and WHO evidence suggest a practical interpretation pathway:

  1. Surveillance: compare modelled patterns with standardized measured systems such as COSI and national child-growth registers.
  2. Equity diagnosis: add HBSC family-affluence, diet and physical-activity indicators before making claims about socioeconomic inequity.
  3. Systems response: examine school meals, active travel, physical education, food retail and digital marketing together rather than treating behaviour as an isolated choice.
  4. Policy audit: link country trends to comparable policy-implementation data before evaluating marketing restrictions or other interventions.

These steps convert descriptive findings into hypotheses for further study; they are not causal conclusions from the current data.

12 12. Limitations

  1. The values are modelled estimates rather than a single harmonised survey.
  2. Country comparisons may reflect differences in source data and uncertainty.
  3. The regional trend is not population weighted.
  4. Country-level associations cannot be applied to individuals.
  5. Prevalence alone does not measure access to prevention or treatment.
  6. Statistical overlap between confidence intervals should be considered before describing differences as meaningful.
  7. The data end in 2022 and therefore do not measure subsequent changes reported by COSI 2022–2024.
  8. Sex is represented as Female, Male and Total; gender identity and intersectional inequalities cannot be assessed.
  9. P90–P10 and IQR summarize between-country dispersion but do not measure within-country social inequality.
  10. No correction is made for multiple comparisons, and ranks are descriptive.

14 14. Reproducibility checklist

  • Keep the repository folder structure unchanged.
  • Do not edit the raw CSV manually.
  • Install the packages listed in required_packages.
  • Open childhood-obesity-analysis.Rmd in RStudio.
  • Set the repository root as the working project folder.
  • Click Knit to create the HTML report.
  • Confirm that visuals/ and data/processed/ are regenerated.

15 15. Session information

sessionInfo()
## R version 4.5.2 (2025-10-31)
## Platform: aarch64-apple-darwin20
## Running under: macOS Tahoe 26.5.2
## 
## Matrix products: default
## BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: Europe/Helsinki
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] tidyr_1.3.2   maps_3.4.3    knitr_1.51    ggplot2_4.0.3 dplyr_1.2.1  
## 
## loaded via a namespace (and not attached):
##  [1] gtable_0.3.6       jsonlite_2.0.0     compiler_4.5.2     tidyselect_1.2.1  
##  [5] jquerylib_0.1.4    scales_1.4.0       yaml_2.3.12        fastmap_1.2.0     
##  [9] R6_2.6.1           labeling_0.4.3     generics_0.1.4     tibble_3.3.1      
## [13] bslib_0.12.0       pillar_1.11.1      RColorBrewer_1.1-3 rlang_1.3.0       
## [17] cachem_1.1.0       xfun_0.60          sass_0.4.10        S7_0.2.2          
## [21] otel_0.2.0         viridisLite_0.4.3  cli_3.6.6          withr_3.0.3       
## [25] magrittr_2.0.5     digest_0.6.39      grid_4.5.2         lifecycle_1.0.5   
## [29] vctrs_0.7.3        evaluate_1.0.5     glue_1.8.1         farver_2.1.2      
## [33] rmarkdown_2.31     purrr_1.2.2        tools_4.5.2        pkgconfig_2.0.3   
## [37] htmltools_0.5.9