Setup and Data

library(ggplot2)
library(tidyr)
library(dplyr)

# Keep the CSV in the same folder as this .Rmd file
biomed <- read.csv("biomed_visualization_data.csv")

# Order categorical variables so legends/axes read logically
biomed$Diagnosis   <- factor(biomed$Diagnosis,   levels = c("Healthy", "Prediabetes", "Type1", "Type2"))
biomed$Gender      <- factor(biomed$Gender,      levels = c("F", "M"))
biomed$Treatment   <- factor(biomed$Treatment,   levels = c("Control", "DrugA"))
biomed$Cancer_Type <- factor(biomed$Cancer_Type, levels = c("Breast", "Colon", "Lung"))

str(biomed)
## 'data.frame':    120 obs. of  12 variables:
##  $ Patient_ID : chr  "P001" "P002" "P003" "P004" ...
##  $ BMI        : num  28 25.4 28.6 32.1 25.1 25.1 32.3 29.1 24.1 28.2 ...
##  $ BP_Sys     : num  142 116 151 109 139 163 115 122 131 122 ...
##  $ BP_Dia     : num  74 81 87 91 70 79 77 75 100 86 ...
##  $ Glucose    : num  123 148 107 120 127 ...
##  $ Cholesterol: num  206 177 154 217 184 ...
##  $ Insulin    : num  15.8 7.4 16.3 18.8 14.1 21.4 8.1 5.8 3.1 19.5 ...
##  $ Gender     : Factor w/ 2 levels "F","M": 1 2 1 1 2 2 2 2 1 1 ...
##  $ Diagnosis  : Factor w/ 4 levels "Healthy","Prediabetes",..: 3 4 2 1 3 3 3 2 1 4 ...
##  $ Treatment  : Factor w/ 2 levels "Control","DrugA": 2 2 1 1 2 2 2 1 1 1 ...
##  $ Cancer_Type: Factor w/ 3 levels "Breast","Colon",..: 3 2 3 3 3 1 2 2 3 1 ...
##  $ Stage      : int  4 3 4 1 4 2 1 2 2 1 ...

Note on axis convention: following Problem 1’s explicit wording (“BP_Sys (y-axis) vs BMI (x-axis”)), every “A vs B” plot below puts A on the y-axis and B on the x-axis. The dataset contains 120 patients.


Section A — Exploring Clinical and Metabolic Data

Problem 1: Blood Pressure vs BMI (colored by Gender)

ggplot(biomed, aes(x = BMI, y = BP_Sys, color = Gender)) +
  geom_point(size = 2.5, alpha = 0.8) +
  labs(
    title = "Systolic Blood Pressure vs BMI by Gender",
    x = "Body Mass Index (BMI, kg/m^2)",
    y = "Systolic Blood Pressure (mmHg)"
  ) +
  theme_minimal()

Answer/Interpretation: The points are widely scattered with Pearson r = 0.1, i.e., only a very weak positive BMI–SBP association in this cohort. Both genders overlap across the BMI range; a few high-SBP outliers (e.g., 188 mmHg) are visible.

Problem 2: Glucose vs Cholesterol (colored by Diagnosis)

ggplot(biomed, aes(x = Cholesterol, y = Glucose, color = Diagnosis)) +
  geom_point(size = 2.5, alpha = 0.8) +
  labs(
    title = "Glucose vs Cholesterol by Diagnostic Group",
    x = "Cholesterol (mg/dL)",
    y = "Glucose (mg/dL)"
  ) +
  theme_minimal()

Answer/Interpretation: Contrary to the suspected positive correlation, r = -0.01 — essentially no linear relationship between glucose and cholesterol here. The four diagnostic groups are intermixed rather than separated.

Problem 3: Distribution of BMI (30-bin histogram)

ggplot(biomed, aes(x = BMI)) +
  geom_histogram(bins = 30, fill = "steelblue", color = "white") +
  labs(
    title = "Distribution of BMI in the Study Population",
    x = "BMI (kg/m^2)",
    y = "Number of Patients"
  ) +
  theme_minimal()

Answer/Interpretation: BMI is roughly bell-shaped and centered near the mean of 25.7 kg/m^2 (SD = 3.7), broadly consistent with an approximately normal distribution, with mild right skew from a few high-BMI patients (up to 35.9).

Problem 4: Cholesterol Density by Gender

ggplot(biomed, aes(x = Cholesterol, color = Gender, fill = Gender)) +
  geom_density(alpha = 0.35) +
  labs(
    title = "Cholesterol Distribution by Gender",
    x = "Cholesterol (mg/dL)",
    y = "Density"
  ) +
  theme_minimal()

Answer/Interpretation: The two density curves overlap almost completely (mean cholesterol is ~205 mg/dL in females and ~205 mg/dL in males) — no meaningful gender difference in cholesterol in this cohort.

Problem 5: Glucose Across Diagnostic Groups (boxplot)

ggplot(biomed, aes(x = Diagnosis, y = Glucose, fill = Diagnosis)) +
  geom_boxplot(alpha = 0.7) +
  labs(
    title = "Fasting Glucose Across Diagnostic Groups",
    x = "Diagnosis",
    y = "Glucose (mg/dL)"
  ) +
  theme_minimal()

Answer/Interpretation: Prediabetes shows the highest median glucose and Healthy the lowest, but the boxes overlap substantially, so glucose alone does not cleanly separate the four groups in this simulated cohort.


Section B — Combining Layers and Adding Complexity

Problem 6: Glucose Boxplot + Jitter

ggplot(biomed, aes(x = Diagnosis, y = Glucose, fill = Diagnosis)) +
  geom_boxplot(alpha = 0.7, outlier.shape = NA) +   # hide outliers: jitter shows every point
  geom_jitter(width = 0.2, alpha = 0.5) +
  labs(
    title = "Glucose by Diagnosis: Boxplot with Individual Patients",
    x = "Diagnosis",
    y = "Glucose (mg/dL)"
  ) +
  theme_minimal()

Answer/Interpretation: Jittering reveals all 120 individual measurements, exposing the within-group spread (roughly 52–187 mg/dL) that the boxplot alone hides.

Problem 7: Systolic & Diastolic BP in One Violin Plot (long format)

bp_long <- biomed %>%
  pivot_longer(cols = c(BP_Sys, BP_Dia),
               names_to = "BP_Type", values_to = "Value")

head(bp_long)   # rendered output: the reshaped 'long' data
## # A tibble: 6 × 12
##   Patient_ID   BMI Glucose Cholesterol Insulin Gender Diagnosis   Treatment
##   <chr>      <dbl>   <dbl>       <dbl>   <dbl> <fct>  <fct>       <fct>    
## 1 P001        28      123         206.    15.8 F      Type1       DrugA    
## 2 P001        28      123         206.    15.8 F      Type1       DrugA    
## 3 P002        25.4    148.        177.     7.4 M      Type2       DrugA    
## 4 P002        25.4    148.        177.     7.4 M      Type2       DrugA    
## 5 P003        28.6    107.        154.    16.3 F      Prediabetes Control  
## 6 P003        28.6    107.        154.    16.3 F      Prediabetes Control  
## # ℹ 4 more variables: Cancer_Type <fct>, Stage <int>, BP_Type <chr>,
## #   Value <dbl>
ggplot(bp_long, aes(x = BP_Type, y = Value, fill = Gender)) +
  geom_violin(alpha = 0.7, position = position_dodge(0.8)) +
  scale_x_discrete(labels = c(BP_Sys = "Systolic", BP_Dia = "Diastolic")) +
  labs(
    title = "Distribution of Systolic and Diastolic BP by Gender",
    x = "Blood Pressure Type",
    y = "Blood Pressure (mmHg)"
  ) +
  theme_minimal()

Answer/Interpretation: Systolic values center around ~131 mmHg and diastolic around ~82 mmHg. Male and female violins have very similar shapes at both levels — no clear gender separation.

Problem 8: Patient Count per Diagnosis (bar chart)

ggplot(biomed, aes(x = Diagnosis)) +
  geom_bar(fill = "skyblue", color = "black") +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.4) +
  labs(
    title = "Number of Patients per Diagnostic Group",
    x = "Diagnosis",
    y = "Count"
  ) +
  theme_minimal()

biomed %>% count(Diagnosis)   # rendered numeric answer
##     Diagnosis  n
## 1     Healthy 29
## 2 Prediabetes 28
## 3       Type1 37
## 4       Type2 26

Answer/Interpretation: Type1 = 37, Healthy = 29, Prediabetes = 28, Type2 = 26 patients (total n = 120). Groups are fairly balanced.

Problem 9: Cholesterol vs Glucose Faceted by Treatment

ggplot(biomed, aes(x = Glucose, y = Cholesterol, color = Diagnosis)) +
  geom_point(size = 2.5, alpha = 0.8) +
  facet_wrap(~Treatment) +
  labs(
    title = "Cholesterol vs Glucose by Diagnosis, Faceted by Treatment Arm",
    x = "Glucose (mg/dL)",
    y = "Cholesterol (mg/dL)"
  ) +
  theme_minimal()

Answer/Interpretation: Control (n = 65) and DrugA (n = 55) panels show the same diffuse cloud — no visible treatment-arm difference in the glucose–cholesterol relationship.

Problem 10: Publication Palette (Set2) + theme_classic()

ggplot(biomed, aes(x = Glucose, y = Cholesterol, color = Diagnosis)) +
  geom_point(size = 2.5, alpha = 0.9) +
  facet_wrap(~Treatment) +
  scale_color_brewer(palette = "Set2") +
  labs(
    title = "Cholesterol vs Glucose by Diagnosis and Treatment",
    x = "Glucose (mg/dL)",
    y = "Cholesterol (mg/dL)",
    color = "Diagnosis"
  ) +
  theme_classic()

Answer/Interpretation: Same figure as Problem 9, now with the colorblind-friendly ColorBrewer Set2 palette and the clean, axis-only theme_classic() look suitable for publication.


Section C — Clinical Summaries and Patterns

Problem 11: Insulin by Diagnosis (boxplot + jitter)

ggplot(biomed, aes(x = Diagnosis, y = Insulin, fill = Diagnosis)) +
  geom_boxplot(alpha = 0.7, outlier.shape = NA) +
  geom_jitter(width = 0.2, alpha = 0.5) +
  labs(
    title = "Serum Insulin Levels Across Diagnostic Groups",
    x = "Diagnosis",
    y = "Insulin (uU/mL)"
  ) +
  theme_minimal()

Answer/Interpretation: Insulin distributions overlap heavily across groups (Type2 has the highest mean, Type1 the lowest), so the expected insulin-resistance pattern is weak in this simulated data. Data-quality note: two records (P047 = −1.5, P069 = −1.3) have negative insulin values, which is physiologically impossible — an artifact of the simulation. They are kept as-is because the assignment does not ask for cleaning, but they should be flagged/excluded in a real analysis.

Problem 12: Systolic & Diastolic BP by Gender

bp_long <- biomed %>%
  pivot_longer(cols = c(BP_Sys, BP_Dia),
               names_to = "BP_Type", values_to = "Value")

ggplot(bp_long, aes(x = Gender, y = Value, fill = BP_Type)) +
  geom_boxplot(position = position_dodge(0.8), alpha = 0.8) +
  scale_fill_manual(values = c(BP_Sys = "#E69F00", BP_Dia = "#56B4E9"),
                    labels = c(BP_Sys = "Systolic", BP_Dia = "Diastolic")) +
  labs(
    title = "Comparison of Systolic and Diastolic BP by Gender",
    x = "Gender",
    y = "Blood Pressure (mmHg)",
    fill = "BP Type"
  ) +
  theme_minimal()

bp_long %>%
  group_by(Gender, BP_Type) %>%
  summarise(Mean_BP = round(mean(Value), 1), .groups = "drop")   # rendered numeric answer
## # A tibble: 4 × 3
##   Gender BP_Type Mean_BP
##   <fct>  <chr>     <dbl>
## 1 F      BP_Dia     83.3
## 2 F      BP_Sys    131. 
## 3 M      BP_Dia     81  
## 4 M      BP_Sys    131.

Answer/Interpretation: The hypothesis that men have higher BP is not supported here: mean systolic is essentially equal (F ≈ 131.3 vs M ≈ 130.8 mmHg) and females actually have slightly higher mean diastolic BP (83.3 vs 81.0 mmHg).

Problem 13: Glucose vs Insulin by Cancer Type, Sized by Stage

ggplot(biomed, aes(x = Insulin, y = Glucose, color = Cancer_Type)) +
  geom_point(aes(size = Stage), alpha = 0.8) +
  geom_smooth(method = "lm", se = FALSE) +   # one regression line per cancer type
  labs(
    title = "Glucose vs Insulin by Cancer Type (Point Size = Tumor Stage)",
    x = "Insulin (uU/mL)",
    y = "Glucose (mg/dL)",
    color = "Cancer Type",
    size = "Stage"
  ) +
  theme_minimal()

Answer/Interpretation: The fitted trends differ by cancer type: the Breast group shows a negative glucose–insulin slope (r ≈ −0.31), while Colon and Lung show near-flat/slightly positive slopes — no consistent stage-wide biomarker correlation.


Section D — Integrative & Publication-Ready Visuals

Problem 14: Cardiometabolic Risk by Treatment Arm

ggplot(biomed, aes(x = Glucose, y = BMI, color = Diagnosis)) +
  geom_point(size = 2.5, alpha = 0.8) +
  facet_wrap(~Treatment) +
  labs(
    title = "BMI vs Glucose Across Treatment Arms",
    x = "Glucose (mg/dL)",
    y = "BMI (kg/m^2)",
    color = "Metabolic Status"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "bottom",
    plot.title = element_text(face = "bold"),
    strip.text = element_text(face = "bold")
  )

Answer/Interpretation: BMI and glucose are essentially unrelated (r = -0.04) in both arms, and diagnostic groups overlap within each facet — no treatment-specific cardiometabolic pattern emerges. The legend is titled “Metabolic Status” as required.

Problem 15: Publication-Ready Insulin Summary Figure

ggplot(biomed, aes(x = Diagnosis, y = Insulin, fill = Diagnosis)) +
  geom_boxplot(alpha = 0.9, outlier.shape = NA) +
  geom_jitter(width = 0.2, alpha = 0.5, color = "grey25") +
  scale_fill_brewer(palette = "Set3") +
  labs(
    title = "Serum Insulin Distribution Across Diagnostic Groups",
    subtitle = "Study Population: n = 120",
    caption = "Source: Simulated Biomedical Dataset",
    x = "Diagnosis",
    y = "Serum Insulin (uU/mL)"
  ) +
  theme_minimal() +
  theme(
    plot.title    = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5),
    plot.caption  = element_text(hjust = 0.5)
  )

Answer/Interpretation: Final journal-style figure: boxplot + jitter, RColorBrewer Set3 fill palette, required title/subtitle/caption, theme_minimal(), and all title text centered with hjust = 0.5. Insulin distributions are broadly similar across the four diagnostic groups.


Reproducibility

sessionInfo()
## R version 4.4.3 (2025-02-28)
## Platform: x86_64-conda-linux-gnu
## Running under: Ubuntu 24.04.5 LTS
## 
## Matrix products: default
## BLAS/LAPACK: /home/neolith/anaconda3/envs/rstudio_env/lib/libopenblasp-r0.3.33.so;  LAPACK version 3.12.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## time zone: Asia/Dhaka
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] dplyr_1.2.1   tidyr_1.3.2   ggplot2_4.0.4
## 
## loaded via a namespace (and not attached):
##  [1] vctrs_0.7.3        cli_3.6.6          knitr_1.52         rlang_1.3.0       
##  [5] xfun_0.59          purrr_1.2.2        generics_0.1.4     S7_0.2.2          
##  [9] jsonlite_2.0.0     labeling_0.4.3     glue_1.8.1         htmltools_0.5.9   
## [13] sass_0.4.10        scales_1.4.0       rmarkdown_2.32     grid_4.4.3        
## [17] tibble_3.3.1       evaluate_1.0.5     jquerylib_0.1.4    fastmap_1.2.0     
## [21] yaml_2.3.12        lifecycle_1.0.5    compiler_4.4.3     RColorBrewer_1.1-3
## [25] pkgconfig_2.0.3    rstudioapi_0.19.0  farver_2.1.2       digest_0.6.39     
## [29] R6_2.6.1           utf8_1.2.6         tidyselect_1.2.1   pillar_1.11.1     
## [33] magrittr_2.0.5     bslib_0.11.0       withr_3.0.3        tools_4.4.3       
## [37] gtable_0.3.6       cachem_1.1.0