Background: 10 patients’ cardiovascular profiles
(Age, Gender, Blood Pressure, Cholesterol, Diagnosis).
Task: Recreate patients_data and inspect
it with View().
patients_data <- data.frame(
ID = 1:10,
Name = c("Ayesha", "Rahim", "Karim", "Nusrat", "Sadia",
"Tanvir", "Farhana", "Imran", "Mim", "Jamal"),
Age = c(45, 52, 38, 29, 41, 34, 60, 47, 36, 25),
Gender = c("F", "M", "M", "F", "F", "M", "F", "M", "F", "M"),
BP_Sys = c(145, 150, 130, 118, 138, 122, 155, 142, 128, 115),
BP_Dia = c(95, 98, 85, 76, 88, 80, 100, 92, 84, 74),
Cholesterol = c(240, 260, 210, 180, 225, 195, 270, 235, 205, 175),
Diagnosis = c("Hypertension", "Hypertension", "Borderline", "Normal",
"Borderline", "Normal", "Hypertension", "Hypertension",
"Borderline", "Normal"),
stringsAsFactors = FALSE
)
# Rendered inspection of the full data frame
knitr::kable(patients_data, caption = "Table 1. patients_data — 10 cardiovascular profiles")| ID | Name | Age | Gender | BP_Sys | BP_Dia | Cholesterol | Diagnosis |
|---|---|---|---|---|---|---|---|
| 1 | Ayesha | 45 | F | 145 | 95 | 240 | Hypertension |
| 2 | Rahim | 52 | M | 150 | 98 | 260 | Hypertension |
| 3 | Karim | 38 | M | 130 | 85 | 210 | Borderline |
| 4 | Nusrat | 29 | F | 118 | 76 | 180 | Normal |
| 5 | Sadia | 41 | F | 138 | 88 | 225 | Borderline |
| 6 | Tanvir | 34 | M | 122 | 80 | 195 | Normal |
| 7 | Farhana | 60 | F | 155 | 100 | 270 | Hypertension |
| 8 | Imran | 47 | M | 142 | 92 | 235 | Hypertension |
| 9 | Mim | 36 | F | 128 | 84 | 205 | Borderline |
| 10 | Jamal | 25 | M | 115 | 74 | 175 | Normal |
## Rows: 10
## Columns: 8
## $ ID <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
## $ Name <chr> "Ayesha", "Rahim", "Karim", "Nusrat", "Sadia", "Tanvir", "…
## $ Age <dbl> 45, 52, 38, 29, 41, 34, 60, 47, 36, 25
## $ Gender <chr> "F", "M", "M", "F", "F", "M", "F", "M", "F", "M"
## $ BP_Sys <dbl> 145, 150, 130, 118, 138, 122, 155, 142, 128, 115
## $ BP_Dia <dbl> 95, 98, 85, 76, 88, 80, 100, 92, 84, 74
## $ Cholesterol <dbl> 240, 260, 210, 180, 225, 195, 270, 235, 205, 175
## $ Diagnosis <chr> "Hypertension", "Hypertension", "Borderline", "Normal", "B…
View() opens the interactive RStudio spreadsheet viewer.
It works in the Console but cannot be embedded in a knitted HTML page,
so the chunk below is set to eval = FALSE (run it manually
with Ctrl+Enter):
Result: 10 patients × 8 variables.
Task: Keep only Name, Age,
Cholesterol, Diagnosis for the report.
## Name Age Cholesterol Diagnosis
## 1 Ayesha 45 240 Hypertension
## 2 Rahim 52 260 Hypertension
## 3 Karim 38 210 Borderline
## 4 Nusrat 29 180 Normal
## 5 Sadia 41 225 Borderline
## 6 Tanvir 34 195 Normal
## 7 Farhana 60 270 Hypertension
## 8 Imran 47 235 Hypertension
## 9 Mim 36 205 Borderline
## 10 Jamal 25 175 Normal
Task: Female patients older than 35 (follow-up consultation).
## ID Name Age Gender BP_Sys BP_Dia Cholesterol Diagnosis
## 1 1 Ayesha 45 F 145 95 240 Hypertension
## 2 5 Sadia 41 F 138 88 225 Borderline
## 3 7 Farhana 60 F 155 100 270 Hypertension
## 4 9 Mim 36 F 128 84 205 Borderline
Result: 4 female patients over 35: Ayesha, Sadia, Farhana, Mim.
Task: Gender == "M"
and Diagnosis == "Hypertension"
(drug-trial candidates).
hypertensive_males <- patients_data %>%
filter(Gender == "M", Diagnosis == "Hypertension")
hypertensive_males## ID Name Age Gender BP_Sys BP_Dia Cholesterol Diagnosis
## 1 2 Rahim 52 M 150 98 260 Hypertension
## 2 8 Imran 47 M 142 92 235 Hypertension
Result: 2 eligible males — Rahim, Imran.
Task: Gender == "F"
and Diagnosis == "Borderline" (lifestyle
counselling).
borderline_females <- patients_data %>%
filter(Gender == "F", Diagnosis == "Borderline")
borderline_females## ID Name Age Gender BP_Sys BP_Dia Cholesterol Diagnosis
## 1 5 Sadia 41 F 138 88 225 Borderline
## 2 9 Mim 36 F 128 84 205 Borderline
Result: 2 patients — Sadia, Mim.
Task: Rank patients lowest → highest, then highest → lowest.
## ID Name Age Gender BP_Sys BP_Dia Cholesterol Diagnosis
## 1 10 Jamal 25 M 115 74 175 Normal
## 2 4 Nusrat 29 F 118 76 180 Normal
## 3 6 Tanvir 34 M 122 80 195 Normal
## 4 9 Mim 36 F 128 84 205 Borderline
## 5 3 Karim 38 M 130 85 210 Borderline
## 6 5 Sadia 41 F 138 88 225 Borderline
## 7 8 Imran 47 M 142 92 235 Hypertension
## 8 1 Ayesha 45 F 145 95 240 Hypertension
## 9 2 Rahim 52 M 150 98 260 Hypertension
## 10 7 Farhana 60 F 155 100 270 Hypertension
## ID Name Age Gender BP_Sys BP_Dia Cholesterol Diagnosis
## 1 7 Farhana 60 F 155 100 270 Hypertension
## 2 2 Rahim 52 M 150 98 260 Hypertension
## 3 1 Ayesha 45 F 145 95 240 Hypertension
## 4 8 Imran 47 M 142 92 235 Hypertension
## 5 5 Sadia 41 F 138 88 225 Borderline
## 6 3 Karim 38 M 130 85 210 Borderline
## 7 9 Mim 36 F 128 84 205 Borderline
## 8 6 Tanvir 34 M 122 80 195 Normal
## 9 4 Nusrat 29 F 118 76 180 Normal
## 10 10 Jamal 25 M 115 74 175 Normal
Task: mutate() a new column
BP_Sum = BP_Sys + BP_Dia.
## ID Name Age Gender BP_Sys BP_Dia Cholesterol Diagnosis BP_Sum
## 1 1 Ayesha 45 F 145 95 240 Hypertension 240
## 2 2 Rahim 52 M 150 98 260 Hypertension 248
## 3 3 Karim 38 M 130 85 210 Borderline 215
## 4 4 Nusrat 29 F 118 76 180 Normal 194
## 5 5 Sadia 41 F 138 88 225 Borderline 226
## 6 6 Tanvir 34 M 122 80 195 Normal 202
## 7 7 Farhana 60 F 155 100 270 Hypertension 255
## 8 8 Imran 47 M 142 92 235 Hypertension 234
## 9 9 Mim 36 F 128 84 205 Borderline 212
## 10 10 Jamal 25 M 115 74 175 Normal 189
# Focused view of the derived variable
patients_bp %>%
select(ID, Name, BP_Sys, BP_Dia, BP_Sum) %>%
arrange(desc(BP_Sum))## ID Name BP_Sys BP_Dia BP_Sum
## 1 7 Farhana 155 100 255
## 2 2 Rahim 150 98 248
## 3 1 Ayesha 145 95 240
## 4 8 Imran 142 92 234
## 5 5 Sadia 138 88 226
## 6 3 Karim 130 85 215
## 7 9 Mim 128 84 212
## 8 6 Tanvir 122 80 202
## 9 4 Nusrat 118 76 194
## 10 10 Jamal 115 74 189
Task: Overall mean_age and
mean_chol.
overall_summary <- patients_data %>%
summarise(
mean_age = mean(Age),
mean_chol = mean(Cholesterol)
)
overall_summary## mean_age mean_chol
## 1 40.7 219.5
Result: mean age = 40.7 years; mean cholesterol = 219.5 mg/dL.
Task: group_by(Diagnosis) +
summarise() → mean age, mean cholesterol, count
n.
group_stats <- patients_data %>%
group_by(Diagnosis) %>%
summarise(
mean_age = mean(Age),
mean_chol = mean(Cholesterol),
n = n(),
.groups = "drop"
)
group_stats## # A tibble: 3 × 4
## Diagnosis mean_age mean_chol n
## <chr> <dbl> <dbl> <int>
## 1 Borderline 38.3 213. 3
## 2 Hypertension 51 251. 4
## 3 Normal 29.3 183. 3
Task: Rank the grouped summary with
arrange(desc(mean_chol)).
## # A tibble: 3 × 4
## Diagnosis mean_age mean_chol n
## <chr> <dbl> <dbl> <int>
## 1 Hypertension 51 251. 4
## 2 Borderline 38.3 213. 3
## 3 Normal 29.3 183. 3
Result: The Hypertension group has the highest average cholesterol (251.25 mg/dL), making it the highest-risk category for lipid-lowering intervention.
Task: filter(Gender == "F") →
group_by(Diagnosis) → summarise() →
arrange(mean_chol).
female_summary <- patients_data %>%
filter(Gender == "F") %>%
group_by(Diagnosis) %>%
summarise(
mean_age = mean(Age),
mean_chol = mean(Cholesterol),
n = n(),
.groups = "drop"
) %>%
arrange(mean_chol)
female_summary## # A tibble: 3 × 4
## Diagnosis mean_age mean_chol n
## <chr> <dbl> <dbl> <int>
## 1 Normal 29 180 1
## 2 Borderline 38.5 215 2
## 3 Hypertension 52.5 255 2
Result: Among females, the Hypertension group shows the highest mean cholesterol (255 mg/dL).
Task: Keep only the original 8 columns before export.
# Simulate the "mistakenly added" derived columns
patients_extra <- patients_bp %>%
mutate(
Chol_mmol = round(Cholesterol / 38.67, 2),
Age_Group = ifelse(Age >= 50, "Senior", "Adult")
)
names(patients_extra) # 11 columns: 8 original + 3 derived## [1] "ID" "Name" "Age" "Gender" "BP_Sys"
## [6] "BP_Dia" "Cholesterol" "Diagnosis" "BP_Sum" "Chol_mmol"
## [11] "Age_Group"
patients_clean <- patients_extra %>%
select(ID, Name, Age, Gender, BP_Sys, BP_Dia, Cholesterol, Diagnosis)
names(patients_clean) # back to the original 8## [1] "ID" "Name" "Age" "Gender" "BP_Sys"
## [6] "BP_Dia" "Cholesterol" "Diagnosis"
## ID Name Age Gender BP_Sys BP_Dia Cholesterol Diagnosis
## 1 1 Ayesha 45 F 145 95 240 Hypertension
## 2 2 Rahim 52 M 150 98 260 Hypertension
## 3 3 Karim 38 M 130 85 210 Borderline
## 4 4 Nusrat 29 F 118 76 180 Normal
## 5 5 Sadia 41 F 138 88 225 Borderline
## 6 6 Tanvir 34 M 122 80 195 Normal
## 7 7 Farhana 60 F 155 100 270 Hypertension
## 8 8 Imran 47 M 142 92 235 Hypertension
## 9 9 Mim 36 F 128 84 205 Borderline
## 10 10 Jamal 25 M 115 74 175 Normal
## [1] TRUE
Task: pivot_longer() on
BP_Sys and BP_Dia → BP_Type,
Value.
bp_long <- patients_clean %>%
pivot_longer(
cols = c(BP_Sys, BP_Dia),
names_to = "BP_Type",
values_to = "Value"
)
bp_long## # A tibble: 20 × 8
## ID Name Age Gender Cholesterol Diagnosis BP_Type Value
## <int> <chr> <dbl> <chr> <dbl> <chr> <chr> <dbl>
## 1 1 Ayesha 45 F 240 Hypertension BP_Sys 145
## 2 1 Ayesha 45 F 240 Hypertension BP_Dia 95
## 3 2 Rahim 52 M 260 Hypertension BP_Sys 150
## 4 2 Rahim 52 M 260 Hypertension BP_Dia 98
## 5 3 Karim 38 M 210 Borderline BP_Sys 130
## 6 3 Karim 38 M 210 Borderline BP_Dia 85
## 7 4 Nusrat 29 F 180 Normal BP_Sys 118
## 8 4 Nusrat 29 F 180 Normal BP_Dia 76
## 9 5 Sadia 41 F 225 Borderline BP_Sys 138
## 10 5 Sadia 41 F 225 Borderline BP_Dia 88
## 11 6 Tanvir 34 M 195 Normal BP_Sys 122
## 12 6 Tanvir 34 M 195 Normal BP_Dia 80
## 13 7 Farhana 60 F 270 Hypertension BP_Sys 155
## 14 7 Farhana 60 F 270 Hypertension BP_Dia 100
## 15 8 Imran 47 M 235 Hypertension BP_Sys 142
## 16 8 Imran 47 M 235 Hypertension BP_Dia 92
## 17 9 Mim 36 F 205 Borderline BP_Sys 128
## 18 9 Mim 36 F 205 Borderline BP_Dia 84
## 19 10 Jamal 25 M 175 Normal BP_Sys 115
## 20 10 Jamal 25 M 175 Normal BP_Dia 74
## rows_before rows_after
## 10 20
Each patient now contributes two rows (one systolic,
one diastolic), which is the tidy format required by
ggplot2:
ggplot(bp_long, aes(x = factor(ID), y = Value, fill = BP_Type)) +
geom_col(position = "dodge") +
labs(title = "Systolic vs Diastolic BP per Patient",
x = "Patient ID", y = "Blood Pressure (mmHg)", fill = "BP Type") +
theme_minimal()Task: pivot_wider() to restore separate
BP_Sys and BP_Dia columns.
bp_wide <- bp_long %>%
pivot_wider(
names_from = BP_Type,
values_from = Value
) %>%
select(all_of(names(patients_data))) # restore original column order
bp_wide## # A tibble: 10 × 8
## ID Name Age Gender BP_Sys BP_Dia Cholesterol Diagnosis
## <int> <chr> <dbl> <chr> <dbl> <dbl> <dbl> <chr>
## 1 1 Ayesha 45 F 145 95 240 Hypertension
## 2 2 Rahim 52 M 150 98 260 Hypertension
## 3 3 Karim 38 M 130 85 210 Borderline
## 4 4 Nusrat 29 F 118 76 180 Normal
## 5 5 Sadia 41 F 138 88 225 Borderline
## 6 6 Tanvir 34 M 122 80 195 Normal
## 7 7 Farhana 60 F 155 100 270 Hypertension
## 8 8 Imran 47 M 142 92 235 Hypertension
## 9 9 Mim 36 F 128 84 205 Borderline
## 10 10 Jamal 25 M 115 74 175 Normal
# Round-trip check: wide data identical to the original?
all.equal(as.data.frame(bp_wide), patients_data)## [1] TRUE
Task: intersect() and
setdiff() on the two ID vectors, print both, and
interpret.
cohort_A <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) # current study
cohort_B <- c(2, 3, 7, 10, 11, 12) # follow-up study
common_patients <- base::intersect(cohort_A, cohort_B)
only_in_A <- base::setdiff(cohort_A, cohort_B)
only_in_B <- base::setdiff(cohort_B, cohort_A) # bonus: new recruits
cat("Cohort A:", cohort_A, "\n")## Cohort A: 1 2 3 4 5 6 7 8 9 10
## Cohort B: 2 3 7 10 11 12
## Common to both cohorts (intersect): 2 3 7 10
## Unique to Cohort A (setdiff A - B): 1 4 5 6 8 9
## New in Cohort B (setdiff B - A): 11 12
tibble(
Set_operation = c("intersect(A, B)", "setdiff(A, B)", "setdiff(B, A)"),
IDs = c(paste(common_patients, collapse = ", "),
paste(only_in_A, collapse = ", "),
paste(only_in_B, collapse = ", ")),
Count = c(length(common_patients), length(only_in_A), length(only_in_B)),
Meaning = c("Retained / followed-up patients",
"Lost to follow-up (dropouts)",
"Newly enrolled patients")
) %>% knitr::kable(caption = "Table 2. Cohort overlap summary")| Set_operation | IDs | Count | Meaning |
|---|---|---|---|
| intersect(A, B) | 2, 3, 7, 10 | 4 | Retained / followed-up patients |
| setdiff(A, B) | 1, 4, 5, 6, 8, 9 | 6 | Lost to follow-up (dropouts) |
| setdiff(B, A) | 11, 12 | 2 | Newly enrolled patients |
# Who are the retained patients, clinically?
patients_data %>%
filter(ID %in% common_patients) %>%
select(ID, Name, Age, Gender, Cholesterol, Diagnosis)## ID Name Age Gender Cholesterol Diagnosis
## 1 2 Rahim 52 M 260 Hypertension
## 2 3 Karim 38 M 210 Borderline
## 3 7 Farhana 60 F 270 Hypertension
## 4 10 Jamal 25 M 175 Normal
Biomedical interpretation
intersect(A, B) = 2, 3, 7, 10 — the 4
patients enrolled in the baseline study and returning for
follow-up. These are the only subjects with paired longitudinal
measurements, so they form the valid analysis set for
before/after or treatment-effect comparisons (retention rate =
40%).setdiff(A, B) = 1, 4, 5, 6, 8, 9 — the
6 baseline patients missing from the follow-up: loss to
follow-up / attrition. They must be checked for dropout bias
(e.g. if sicker or older patients preferentially disappear, outcome
estimates become over-optimistic).setdiff(B, A) = 11, 12 — IDs appearing
only at follow-up: new recruits (or ID/data-entry
errors that need verification), since they have no baseline record.tibble(
Metric = c("Total patients", "Female patients", "Male patients",
"Mean age (years)", "Mean cholesterol (mg/dL)",
"Highest-risk diagnosis"),
Value = c(nrow(patients_data),
sum(patients_data$Gender == "F"),
sum(patients_data$Gender == "M"),
round(mean(patients_data$Age), 2),
round(mean(patients_data$Cholesterol), 2),
risk_ranked$Diagnosis[1])
) %>% knitr::kable(caption = "Table 3. Key takeaways")| Metric | Value |
|---|---|
| Total patients | 10 |
| Female patients | 5 |
| Male patients | 5 |
| Mean age (years) | 40.7 |
| Mean cholesterol (mg/dL) | 219.5 |
| Highest-risk diagnosis | Hypertension |
All 15 tasks were completed with the tidyverse
grammar: select() / filter() for subsetting,
arrange() for ranking, mutate() for derived
variables, group_by() + summarise() for
aggregation, pivot_longer() / pivot_wider()
for reshaping, and base set operations (intersect(),
setdiff()) for cohort comparison.
## 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] lubridate_1.9.5 forcats_1.0.1 stringr_1.6.0 dplyr_1.2.1
## [5] purrr_1.2.2 readr_2.2.0 tidyr_1.3.2 tibble_3.3.1
## [9] ggplot2_4.0.4 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 jsonlite_2.0.0 compiler_4.4.3 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 knitr_1.51
## [13] bslib_0.11.0 pillar_1.11.1 RColorBrewer_1.1-3 tzdb_0.5.0
## [17] rlang_1.3.0 utf8_1.2.6 stringi_1.8.7 cachem_1.1.0
## [21] xfun_0.59 sass_0.4.10 S7_0.2.2 timechange_0.4.0
## [25] cli_3.6.6 withr_3.0.3 magrittr_2.0.5 digest_0.6.39
## [29] grid_4.4.3 rstudioapi_0.19.0 hms_1.1.4 lifecycle_1.0.5
## [33] vctrs_0.7.3 evaluate_1.0.5 glue_1.8.1 farver_2.1.2
## [37] rmarkdown_2.31 tools_4.4.3 pkgconfig_2.0.3 htmltools_0.5.9