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.
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.
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.
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).
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.
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.
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.
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.
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()## 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.
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.
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.
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.
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).
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.
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.
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.
## 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