Ain Shams University · Faculty of Computer and Information Sciences Academic Year 2025–2026 · Big Data Analytics Course
- Project Overview
- Dataset
- Project Pipeline
- Part 1 — Preprocessing & EDA
- Part 2 — Statistical Analysis
- Part 3 — Tree-Based ML Models
- Part 4 — Linear & Distance Models
- Part 5 — Clustering & Integration
- Model Comparison
- Key Findings
- How to Run
- Requirements
- Results Summary
This project applies the full Big Data Analytics lifecycle to a real-world physical fitness dataset. The goal is to classify individuals into one of four fitness performance grades (A, B, C, D) based on measurable body composition and physical performance attributes, using a combination of statistical analysis, supervised machine learning, and unsupervised clustering — all implemented in R.
Research Question:
"Can we accurately predict an individual's performance class (A/B/C/D) from measurable physical and physiological attributes?"
Secondary Questions Explored:
- Does gender significantly affect grip strength?
- Does body fat percentage differ across performance classes?
- Which physical features are the most predictive of performance class?
- Do natural clusters in the data align with the labelled fitness grades?
| Property | Value |
|---|---|
| Name | Body Performance Data |
| Source | Kaggle — Body Performance Dataset |
| Origin | Korean Sports Promotion Foundation fitness assessments |
| Records | 13,393 individuals |
| Attributes | 12 (11 features + 1 target) |
| Class Balance | Perfectly balanced (~3,348 per class) |
| Variable | Type | Range | Description |
|---|---|---|---|
age |
Numeric | 20–64 yrs | Age of the individual |
gender |
Categorical | M / F | Biological sex |
height_cm |
Numeric | 125–193.8 cm | Standing height |
weight_kg |
Numeric | 26.3–138.1 kg | Body weight |
body fat_% |
Numeric | 3.0–78.4 % | Body fat percentage (BIA) |
diastolic |
Numeric | 0–121 mmHg | Diastolic blood pressure |
systolic |
Numeric | 0–201 mmHg | Systolic blood pressure |
gripForce |
Numeric | 0–70.5 kg | Hand grip strength |
sit and bend forward_cm |
Numeric | -25–139 cm | Sit-and-reach flexibility test |
sit-ups counts |
Numeric | 0–80 | Sit-up count in fixed time |
broad jump_cm |
Numeric | 0–303 cm | Standing broad jump distance |
class |
Categorical | A / B / C / D | 🎯 Target — Fitness grade (A = best, D = lowest) |
flowchart TD
RAW["📂 Raw Data\nbodyPerformance.csv\n13,393 records · 12 columns"]
P1["🧹 PART 1\nPreprocessing & EDA\n→ Outputs: cleaned_bodyPerformance.csv + 16 charts"]
P2["📊 PART 2\nStatistical Analysis & Hypothesis Testing\nT-Test · ANOVA · Correlation · Chi-Square"]
P3["🌳 PART 3\nTree-Based ML Models\nDecision Tree · Random Forest · XGBoost · Naive Bayes"]
P4["📐 PART 4\nLinear & Distance Models\nLogistic Regression · KNN"]
P5["🔵 PART 5\nK-Means Clustering + Final Integration\nElbow Method · Cluster Analysis · Report Assembly"]
RESULT["📈 Model Comparison & Final Report\nSections A–J · Best model: XGBoost / KNN (~74–76%)"]
RAW --> P1
P1 --> P2
P1 --> P3
P3 --> P4
P3 --> P5
P2 --> RESULT
P4 --> RESULT
P5 --> RESULT
Documentation: Sections B, C, D, E
# Load and inspect
data <- read.csv("bodyPerformance.csv")
str(data); summary(data); dim(data)
# Missing values
colSums(is.na(data)) # → all zeros
# Remove duplicates
data <- unique(data)
# Encode categoricals
data$gender <- as.factor(data$gender)
data$class <- as.factor(data$class)
# Remove physiologically impossible values
data <- subset(data, systolic > 0)
data <- subset(data, diastolic > 0)
data <- subset(data, gripForce > 0)
# Min-Max Normalization
normalize <- function(x) (x - min(x)) / (max(x) - min(x))
data$age_norm <- normalize(data$age)
data$weight_norm <- normalize(data$weight_kg)
data$bodyfat_norm <- normalize(data$body.fat_.)
data$grip_norm <- normalize(data$gripForce)
# Export
write.csv(data, "cleaned_bodyPerformance.csv", row.names = FALSE)| # | Chart | Type | Key Insight |
|---|---|---|---|
| 1 | Class Distribution | Bar chart | Perfectly balanced — ~3,348 per class |
| 2 | Gender Distribution | Pie chart | 63% Male (8,465) · 37% Female (4,923) |
| 3 | Age Distribution | Histogram | Right-skewed — majority aged 20–30 |
| 4 | Weight Distribution | Histogram | Bell-shaped, peak at 60–80 kg |
| 5 | Body Fat Distribution | Density histogram | Near-normal, slight right tail (outlier ~78%) |
| 6 | Grip Force Distribution | Histogram | Bimodal — male/female subpopulations |
| 7 | Combined Boxplots | 2×2 Boxplot | Outliers identified in weight & grip force |
| 8 | Body Fat by Class | Boxplot | Clear monotonic increase A→D |
| 9 | Grip Force by Gender | Boxplot | Males ~43 kg vs females ~25 kg (70% gap) |
| 10 | Sit-Ups by Class | Boxplot | A median ~50, D median ~30 |
| 11 | Age vs Sit-Ups | Scatter | Negative trend — fitness declines with age |
| 12 | Grip Force vs Broad Jump | Scatter | Strong positive correlation (r ≈ 0.747) |
| 13 | Height vs Weight | Scatter | Expected positive correlation |
| 14 | Systolic vs Diastolic BP | Scatter | Moderate correlation, hypertensive outliers visible |
| 15 | Grip Force Dot Plot | Dot chart | Mass between 20–60 kg; invalid cluster near 0 |
| 16 | Correlation Heatmap | Heatmap | sit-ups & broad jump strongest positive (r ≈ 0.75) |
Documentation: Section F
| Variable | Mean | Median | Std. Deviation |
|---|---|---|---|
| Grip Force | 36.96 kg | 37.9 kg | 10.62 |
| Body Fat % | 23.24 % | 22.8 % | 7.26 |
| Sit-Ups | 39.77 | 41.0 | 14.28 |
| Broad Jump | 190.13 cm | 193.0 cm | 39.87 |
t_test_result <- t.test(gripForce ~ gender, data = data)| H₀ | No significant difference in grip force between males and females |
| H₁ | Significant difference exists |
| T-Statistic | 154.379 |
| P-Value | < 2.2 × 10⁻¹⁶ (≈ 0) |
| Decision | ✅ Reject H₀ — Males have significantly higher grip force |
correlation_result <- cor.test(data$gripForce, data$broad.jump_cm)| H₀ | No relationship between grip force and broad jump |
| H₁ | Significant positive relationship exists |
| r | 0.747 |
| P-Value | < 2.2 × 10⁻¹⁶ (≈ 0) |
| Decision | ✅ Reject H₀ — Strong positive linear relationship confirmed |
anova_result <- aov(sit.ups.counts ~ class, data = data)
summary(anova_result)| H₀ | Mean sit-up counts are equal across all classes (A, B, C, D) |
| H₁ | At least one class has a significantly different mean |
| F-Statistic | 1,196.528 |
| P-Value | < 2.2 × 10⁻¹⁶ (≈ 0) |
| Decision | ✅ Reject H₀ — Sit-up performance differs significantly across all classes |
chi_square_result <- chisq.test(table(data$gender, data$class))| H₀ | Gender and performance class are independent |
| H₁ | Gender and class are associated |
| χ² | 112.394 (df = 3) |
| P-Value | < 2.2 × 10⁻¹⁶ (≈ 0) |
| Decision | ✅ Reject H₀ — Gender and class are significantly associated |
All four tests reject the null hypothesis at α = 0.05. Gender, grip force, sit-up performance, and body fat are all statistically validated as significant factors in fitness classification.
Documentation: Section G
# 70/30 random split
ind <- sample(2, nrow(bodyData), prob = c(0.7, 0.3), replace = TRUE)
train.data <- bodyData[ind == 1, ]
test.data <- bodyData[ind == 2, ]
# → ~9,374 training | ~4,018 testlibrary(party)
body.tree <- ctree(class ~ ., data = train.data)
plot(body.tree, type = "simple")- Root split on
sit.and.bend.forward_cm(p < 0.001) - Subsequent splits:
sit.ups.counts,body.fat_.,age,weight_kg - Accuracy: 68.3%
library(randomForest)
rf.model <- randomForest(class ~ ., data = train.data, ntree = 500,
mtry = round(sqrt(ncol(train.data) - 1)), importance = TRUE)
varImpPlot(rf.model)- 500 trees · mtry = 3
- Top features:
sit.and.bend.forward_cm,sit.ups.counts,body.fat_.,broad.jump_cm - Accuracy: 72.5% (+4.2% over Decision Tree)
library(xgboost)
xgb.model <- xgboost(data = dtrain, nrounds = 100, max_depth = 6,
eta = 0.1, objective = "multi:softmax", num_class = 4)- Sequential boosting — 100 rounds · learning rate 0.1
- Accuracy: ~74% — best tree-based model
library(e1071)
classifier <- naiveBayes(class ~ ., data = train.data)- Assumes feature independence (violated: r = 0.747 between grip and broad jump)
- Accuracy: 54.6% — useful as probabilistic baseline only
Documentation: Section H
# 80/20 stratified split
set.seed(123)
trainIndex <- createDataPartition(data$class, p = 0.8, list = FALSE)
# → ~10,714 training | ~2,678 test
# Features: z-score scaled + one-hot encoded via dummyVars()library(nnet)
model <- multinom(class ~ ., data = trainScaled)
predictions <- predict(model, newdata = testScaled)
confusionMatrix(predictions, testScaled$class)- Fits K–1 log-odds equations for 4-class problem
- Key finding:
sit.and.bend.forward_cm,sit.ups.counts,broad.jump_cm→ strongest coefficients for Class A - Accuracy: ~66–68%
library(class)
# Grid search for optimal K
k_values <- seq(1, 31, 2)
# Best K found empirically (typically k = 7–9)
final_predictions <- knn(train = trainX_scaled, test = testX_scaled, cl = trainY, k = best_k)
confusionMatrix(final_predictions, testY)- Euclidean distance on z-score scaled features
- Optimal K selected via accuracy-vs-K loop (k = 1 to 31, odd values)
- Accuracy peaks at k ≈ 7–9, declines beyond k = 15
- Accuracy: ~73–76% — competitive with XGBoost
Documentation: Section I (clustering eval) · Section J (conclusions)
# Features used
cluster_data <- data[, c("gripForce", "sit.ups.counts", "broad.jump_cm", "body.fat_.")]
# Elbow method
wss <- sapply(1:10, function(k) kmeans(cluster_data, k, nstart = 10)$tot.withinss)
plot(1:10, wss, type = "b", main = "Elbow Method — Optimal K")
# Apply K-Means with k = 4
set.seed(42)
kc <- kmeans(cluster_data, centers = 4, nstart = 20)
# Validate against class labels
table(data$class, kc$cluster)| Cluster | Interpretation | Corresponds To |
|---|---|---|
| Cluster 1 | Low grip, low sit-ups, high body fat | Class D (Lowest) |
| Cluster 2 | Moderate performance across features | Class C |
| Cluster 3 | Above-average performance | Class B |
| Cluster 4 | High grip, high sit-ups, low body fat | Class A (Best) |
| Metric | Value |
|---|---|
| Explained Variance (Between-SS / Total-SS) | ~65–70% |
| Elbow inflection | k = 4 (matches 4 labelled classes) |
| Cluster-Class alignment | Strong for A & D; moderate overlap for B & C |
The K-Means clustering independently recovers a 4-group structure consistent with the labelled classes — validating the dataset's intrinsic quality without using any class labels.
| Rank | Model | Accuracy | Precision | Recall | F1-Score | Type |
|---|---|---|---|---|---|---|
| 🥇 1 | KNN (best k) | ~73–76% | ~0.74 | ~0.74 | ~0.74 | Distance-based |
| 🥇 1 | XGBoost | ~74% | ~0.74 | ~0.74 | ~0.74 | Ensemble (boosting) |
| 🥈 3 | Random Forest | 72.5% | ~0.73 | ~0.73 | ~0.73 | Ensemble (bagging) |
| 🥉 4 | Decision Tree | 68.3% | ~0.68 | ~0.68 | ~0.68 | Single tree |
| 5 | Logistic Regression | ~66–68% | ~0.67 | ~0.67 | ~0.67 | Linear |
| 6 | Naive Bayes | 54.6% | ~0.55 | ~0.55 | ~0.55 | Probabilistic (baseline) |
Baseline (random): 25% · All models significantly exceed chance level.
Class A ────────────────────── Easiest to classify (extreme high performance)
Class D ────────────────────── Easy to classify (extreme low performance)
Class B ↔ Class C ─────────── Hardest boundary — overlapping feature distributions
All six models surpass the 25% random baseline by a large margin. XGBoost and KNN achieve ~74–76% accuracy, correctly classifying nearly 3 in 4 individuals using only physical measurements.
Across statistical tests, tree variable importance, model coefficients, and cluster centers:
- 🤸
sit.and.bend.forward_cm— Top-ranked in RF & XGBoost importance; root split of Decision Tree - 💪
sit.ups.counts— ANOVA F = 1,196.53; Class A mean (~47) vs Class D mean (~30) - 🦵
broad.jump_cm— Class A ~210 cm vs Class D ~170 cm (23% gap); r = 0.747 with grip force
- T-statistic = 154.38, p ≈ 0
- Males: median grip ~43 kg · Females: median grip ~25 kg (70% gap)
- Gender significantly associated with class distribution (χ² = 112.39, p ≈ 0)
- Negative correlation between age and sit-ups, broad jump, and grip force
- Maximum achievable sit-ups declines from ~80 at age 21 to ~60 at age 50+
- Class D median body fat (~27%) is ~5–7 percentage points above Classes A–C (~20–22%)
- Confirmed by ANOVA, boxplots, cluster centers, and Logistic Regression coefficients
- Every model struggles most at the Class B / Class C boundary
- These classes share substantially overlapping feature distributions
- Reflects the continuous nature of fitness performance within discrete class labels
Install R (version ≥ 4.0) from https://cran.r-project.org
install.packages(c(
"corrplot", # Correlation heatmap
"party", # Conditional inference tree (ctree)
"randomForest", # Random Forest
"e1071", # Naive Bayes & SVM
"xgboost", # XGBoost gradient boosting
"caret", # Train/test split & evaluation
"class", # KNN
"MLmetrics", # Precision, Recall, F1
"nnet" # Multinomial Logistic Regression
))Before running, update the CSV path in Part 1 to match your local directory:
# Part 1 — Line 1
data <- read.csv("path/to/bodyPerformance.csv")
# Part 3 — Line 1
bodyData <- read.csv("path/to/cleaned_bodyPerformance.csv")
# Parts 4 & 5 — Line 1
data <- read.csv("path/to/cleaned_bodyPerformance.csv")Open BodyPerformance_FullProject.R in RStudio and run sequentially:
Part 1 → Produces: cleaned_bodyPerformance.csv + 16 EDA plots
Part 2 → Produces: statistical test outputs in console
Part 3 → Produces: 8 model/EDA plots + accuracy figures (saved as PNG)
Part 4 → Produces: confusion matrices + accuracy/F1 in console
Part 5 → Produces: elbow plot + cluster plots + final_bodyPerformance_with_clusters.csv
⚠️ Run parts in order — Parts 3, 4, and 5 depend oncleaned_bodyPerformance.csvgenerated by Part 1.
| Package | Version | Purpose |
|---|---|---|
corrplot |
≥ 0.92 | Correlation matrix heatmap |
party |
≥ 1.3 | Conditional inference tree |
randomForest |
≥ 4.7 | Random Forest classifier |
e1071 |
≥ 1.7 | Naive Bayes |
xgboost |
≥ 1.7 | Gradient boosting |
caret |
≥ 6.0 | ML workflow & evaluation |
class |
≥ 7.3 | K-Nearest Neighbours |
nnet |
≥ 7.3 | Multinomial Logistic Regression |
MLmetrics |
≥ 1.1 | Precision, Recall, F1-Score |
╔══════════════════════════════════════════════════════════════════╗
║ BODY PERFORMANCE PROJECT — RESULTS SUMMARY ║
╠══════════════════════════════════════════════════════════════════╣
║ Dataset │ 13,393 records · 12 attributes · 0 missing ║
║ Classes │ A (best) · B · C · D (lowest) — balanced ║
║ │ ║
║ HYPOTHESIS TESTS (all p ≈ 0, α = 0.05) ║
║ T-Test │ Grip Force by Gender → t = 154.38 ✅ ║
║ Correlation │ Grip Force & Broad Jump → r = 0.747 ✅ ║
║ ANOVA │ Sit-Ups across Classes → F = 1196.5 ✅ ║
║ Chi-Square │ Gender vs Class → χ² = 112.4 ✅ ║
║ │ ║
║ ML MODEL ACCURACY ║
║ XGBoost │ ~74.0% 🥇 Best tree-based ║
║ KNN │ ~73–76% 🥇 Best overall ║
║ Random Forest │ 72.5% ║
║ Decision Tree │ 68.3% (most interpretable) ║
║ Logistic Reg. │ ~66–68% ║
║ Naive Bayes │ 54.6% (baseline) ║
║ │ ║
║ K-MEANS CLUSTERING ║
║ Optimal k │ 4 (confirmed by elbow method) ║
║ Explained Var. │ ~65–70% (Between-SS / Total-SS) ║
║ Validation │ Clusters align with A/D labels ✅ ║
╚══════════════════════════════════════════════════════════════════╝
This project was developed for academic purposes as part of the Big Data Analytics course at Ain Shams University / University of East London. The dataset is publicly available on Kaggle.
Ain Shams University · Faculty of Computer and Information Sciences
Big Data Analytics Project — Academic Year 2025–2026