Introduction

In this assignment, We assume the role of a Data Analyst tasked with analyzing historical healthcare data. The objective of this analysis is to explore a heart disease dataset using R programming, identify patterns, and derive meaningful insights that can support better understanding of cardiovascular risk factors.

1- Loading Libraries and Heart-Disease Dataset:

library(tidyverse)

The tidyverse library is a comprehensive collection of R packages for data science that includes tools for data manipulation (dplyr), reshaping (tidyr), visualization (ggplot2), reading data (readr), and more. By loading tidyverse, all these packages become available at once, providing everything needed to clean, transform, analyze, and visualize data efficiently in a single workflow.

heart <- read.csv("heart.csv")

2. Printing the Structure of the Dataset

# Inspecting the Dataset
head(heart)
##   age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal
## 1  52   1  0      125  212   0       1     168     0     1.0     2  2    3
## 2  53   1  0      140  203   1       0     155     1     3.1     0  0    3
## 3  70   1  0      145  174   0       1     125     1     2.6     0  0    3
## 4  61   1  0      148  203   0       1     161     0     0.0     2  1    3
## 5  62   0  0      138  294   1       1     106     0     1.9     1  3    2
## 6  58   0  0      100  248   0       0     122     0     1.0     1  0    2
##   target
## 1      0
## 2      0
## 3      0
## 4      0
## 5      0
## 6      1
str(heart)
## 'data.frame':    1025 obs. of  14 variables:
##  $ age     : int  52 53 70 61 62 58 58 55 46 54 ...
##  $ sex     : int  1 1 1 1 0 0 1 1 1 1 ...
##  $ cp      : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ trestbps: int  125 140 145 148 138 100 114 160 120 122 ...
##  $ chol    : int  212 203 174 203 294 248 318 289 249 286 ...
##  $ fbs     : int  0 1 0 0 1 0 0 0 0 0 ...
##  $ restecg : int  1 0 1 1 1 0 2 0 0 0 ...
##  $ thalach : int  168 155 125 161 106 122 140 145 144 116 ...
##  $ exang   : int  0 1 1 0 0 0 0 1 0 1 ...
##  $ oldpeak : num  1 3.1 2.6 0 1.9 1 4.4 0.8 0.8 3.2 ...
##  $ slope   : int  2 0 0 2 1 1 0 1 2 1 ...
##  $ ca      : int  2 0 0 1 3 0 3 1 0 2 ...
##  $ thal    : int  3 3 3 3 2 2 1 3 3 2 ...
##  $ target  : int  0 0 0 0 0 1 0 0 0 0 ...

The str() function shows the dataset’s structure, including 1025 observations and 14 variables. Most are numeric integers

3. Listing the Variables in the Dataset

names(heart)
##  [1] "age"      "sex"      "cp"       "trestbps" "chol"     "fbs"     
##  [7] "restecg"  "thalach"  "exang"    "oldpeak"  "slope"    "ca"      
## [13] "thal"     "target"

The names() function lists all 14 column names in the dataset, representing the clinical attributes recorded during patients’ cardiac assessments.

4. Printing the Top 15 Rows of the Dataset

head(heart, 15)
##    age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal
## 1   52   1  0      125  212   0       1     168     0     1.0     2  2    3
## 2   53   1  0      140  203   1       0     155     1     3.1     0  0    3
## 3   70   1  0      145  174   0       1     125     1     2.6     0  0    3
## 4   61   1  0      148  203   0       1     161     0     0.0     2  1    3
## 5   62   0  0      138  294   1       1     106     0     1.9     1  3    2
## 6   58   0  0      100  248   0       0     122     0     1.0     1  0    2
## 7   58   1  0      114  318   0       2     140     0     4.4     0  3    1
## 8   55   1  0      160  289   0       0     145     1     0.8     1  1    3
## 9   46   1  0      120  249   0       0     144     0     0.8     2  0    3
## 10  54   1  0      122  286   0       0     116     1     3.2     1  2    2
## 11  71   0  0      112  149   0       1     125     0     1.6     1  0    2
## 12  43   0  0      132  341   1       0     136     1     3.0     1  0    3
## 13  34   0  1      118  210   0       1     192     0     0.7     2  0    2
## 14  51   1  0      140  298   0       1     122     1     4.2     1  3    3
## 15  52   1  0      128  204   1       1     156     1     1.0     1  0    0
##    target
## 1       0
## 2       0
## 3       0
## 4       0
## 5       0
## 6       1
## 7       0
## 8       0
## 9       0
## 10      0
## 11      1
## 12      0
## 13      1
## 14      0
## 15      0

The head() function displays the first 15 rows of the heart dataset, allowing a quick preview of the data and any recent changes made to the columns.

5. User Defined Function

# Creating Age Groups with a Custom Function
age_group <- function(age)
        {
                 if (age < 40) { return("Young") }
         else if (age < 60) { return("Middle-aged") }   
         else { return("Senior") }
        }
# Apply function
heart$Age_Group <- sapply(heart$age, age_group)
head(heart)
##   age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal
## 1  52   1  0      125  212   0       1     168     0     1.0     2  2    3
## 2  53   1  0      140  203   1       0     155     1     3.1     0  0    3
## 3  70   1  0      145  174   0       1     125     1     2.6     0  0    3
## 4  61   1  0      148  203   0       1     161     0     0.0     2  1    3
## 5  62   0  0      138  294   1       1     106     0     1.9     1  3    2
## 6  58   0  0      100  248   0       0     122     0     1.0     1  0    2
##   target   Age_Group
## 1      0 Middle-aged
## 2      0 Middle-aged
## 3      0      Senior
## 4      0      Senior
## 5      0      Senior
## 6      1 Middle-aged

A custom age_group() function is defined to categorize patients into age groups: “Young” (<40), “Middle-aged” (40–59), and “Senior” (60+). The function is then applied to the age column using sapply(), creating a new Age_Group column. The head() function displays the first few rows of the updated dataset to verify the new categorization.

6. Data Manipulation: Filtering Rows Based on Logical Criteria

# Filtering High-Risk Male Patients
high_risk_males <- heart %>% filter(sex == 1, chol > 240, age > 55)
cat("Number of high-risk male patients:", nrow(high_risk_males), "\n")
## Number of high-risk male patients: 164
head(high_risk_males, 10)
##    age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal
## 1   58   1  0      114  318   0       2     140     0     4.4     0  3    1
## 2   56   1  2      130  256   1       0     142     1     0.6     1  1    1
## 3   70   1  2      160  269   0       1     112     1     2.9     1  1    3
## 4   59   1  0      138  271   0       0     182     0     0.0     2  0    2
## 5   64   1  0      128  263   0       1     105     1     0.2     1  1    3
## 6   67   1  0      100  299   0       0     125     1     0.9     1  2    2
## 7   59   1  3      170  288   0       0     159     0     0.2     1  0    3
## 8   59   1  0      170  326   0       0     140     1     3.4     0  0    3
## 9   56   1  0      125  249   1       0     144     1     1.2     1  1    2
## 10  65   1  0      110  248   0       0     158     0     0.6     2  2    1
##    target   Age_Group
## 1       0 Middle-aged
## 2       0 Middle-aged
## 3       0      Senior
## 4       1 Middle-aged
## 5       1      Senior
## 6       0      Senior
## 7       0 Middle-aged
## 8       0 Middle-aged
## 9       0 Middle-aged
## 10      0      Senior

This code identifies high-risk male patients by filtering for males (sex == 1) who have cholesterol above 240 and are older than 55. The nrow() function displays the number of patients meeting these criteria, and head() shows the first 10 rows of this subset for inspection.

7. Identifying Dependent & Independent Variables and Reshape

# Select variables
vars <- heart %>%
  select(age, thalach, oldpeak, exang, target)

# Reshape from wide to long
heart_long <- vars %>%
  pivot_longer(
    cols = c(thalach, oldpeak, exang),
    names_to = "variable",
    values_to = "value"
  )

# Preview first 15 rows
head(heart_long, 15)
## # A tibble: 15 × 4
##      age target variable value
##    <int>  <int> <chr>    <dbl>
##  1    52      0 thalach  168  
##  2    52      0 oldpeak    1  
##  3    52      0 exang      0  
##  4    53      0 thalach  155  
##  5    53      0 oldpeak    3.1
##  6    53      0 exang      1  
##  7    70      0 thalach  125  
##  8    70      0 oldpeak    2.6
##  9    70      0 exang      1  
## 10    61      0 thalach  161  
## 11    61      0 oldpeak    0  
## 12    61      0 exang      0  
## 13    62      0 thalach  106  
## 14    62      0 oldpeak    1.9
## 15    62      0 exang      0

8. Removing Missing Values

# Handling Missing Values
cat("The missing valuse:\n")
## The missing valuse:
colSums(is.na(heart))
##       age       sex        cp  trestbps      chol       fbs   restecg   thalach 
##         0         0         0         0         0         0         0         0 
##     exang   oldpeak     slope        ca      thal    target Age_Group 
##         0         0         0         0         0         0         0
heart_clean <- na.omit(heart)
cat("Row-Numbers before:" , nrow(heart),"| Row-Numbers after:", nrow(heart_clean))
## Row-Numbers before: 1025 | Row-Numbers after: 1025

The colSums(is.na()) function counts missing values in each column. The na.omit() function removes any rows containing missing data. In this dataset there are no missing values, so the row count remains unchanged. This is still an important validation step in any data analysis workflow.

9. Identifying and Removing Duplicated Data

# Identifying Duplicates
cat("Number of duplicated rows:", sum(duplicated(heart_clean)),"\n")
## Number of duplicated rows: 723
# Removing Duplicates
heart_clean <- heart_clean[!duplicated(heart_clean), ]
cat("Number after removing duplicates:", nrow(heart_clean))
## Number after removing duplicates: 302

The duplicated() function detects rows that are exact copies of previous entries. The Kaggle version of the UCI Heart Disease dataset contains duplicate records, and removing them ensures that each patient is counted only once, preventing skewed statistics and biased predictive models.

10. Reorder Multiple Rows in Descending Order

# Sorting the Dataset
heart_sorted <- heart_clean %>%
  arrange(desc(chol), desc(age), desc(trestbps))
head(heart_sorted, 10)
##    age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal
## 1   67   0  2      115  564   0       0     160     0     1.6     1  0    3
## 2   65   0  2      140  417   1       0     157     0     0.8     2  1    2
## 3   56   0  0      134  409   0       0     150     1     1.9     1  2    3
## 4   63   0  0      150  407   0       0     154     0     4.0     1  3    3
## 5   62   0  0      140  394   0       0     157     0     1.2     1  0    2
## 6   65   0  2      160  360   0       0     151     0     0.8     2  0    2
## 7   57   0  0      120  354   0       1     163     1     0.6     2  0    2
## 8   55   1  0      132  353   0       1     132     1     1.2     1  1    3
## 9   55   0  1      132  342   0       1     166     0     1.2     2  0    2
## 10  43   0  0      132  341   1       0     136     1     3.0     1  0    3
##    target   Age_Group
## 1       1      Senior
## 2       1      Senior
## 3       0 Middle-aged
## 4       0      Senior
## 5       1      Senior
## 6       1      Senior
## 7       1 Middle-aged
## 8       0 Middle-aged
## 9       1 Middle-aged
## 10      0 Middle-aged

The dataset is sorted by risk-related variables such as cholesterol, age, and blood pressure to identify patients with higher cardiovascular risk. Sorting helps prioritize extreme cases and supports better interpretation of high-risk groups.

11. Renaming Column Names

# Renaming Columns for Clarity
heart_renamed <- heart_clean %>% rename( 
                          Age = age,
                          Sex = sex,
                          ChestPainType = cp,
                          RestingBP = trestbps,
                          Cholesterol = chol,
                          FastingBS = fbs,
                          RestECG = restecg,
                          MaxHR = thalach,
                          ExerciseAngina = exang,
                          Oldpeak = oldpeak,
                          ST_Slope = slope,
                          NumVessels = ca,
                          Thalassemia = thal,
                          Target = target )

names(heart_renamed)
##  [1] "Age"            "Sex"            "ChestPainType"  "RestingBP"     
##  [5] "Cholesterol"    "FastingBS"      "RestECG"        "MaxHR"         
##  [9] "ExerciseAngina" "Oldpeak"        "ST_Slope"       "NumVessels"    
## [13] "Thalassemia"    "Target"         "Age_Group"

The rename() function replaces the dataset’s abbreviated column names with descriptive clinical labels, improving readability for anyone reviewing the analysis who may not be familiar with the original UCI dataset codes.

12. Adding New Variables Using a Mathematical Function

# Create a composite risk score
heart_clean$risk_score <- 0.3 * heart_clean$age + 
                          0.4 * heart_clean$chol + 
                          0.3 * heart_clean$trestbps

head(heart_clean[, c("age", "chol", "trestbps", "risk_score")], 10)
##    age chol trestbps risk_score
## 1   52  212      125      137.9
## 2   53  203      140      139.1
## 3   70  174      145      134.1
## 4   61  203      148      143.9
## 5   62  294      138      177.6
## 6   58  248      100      146.6
## 7   58  318      114      178.8
## 8   55  289      160      180.1
## 9   46  249      120      149.4
## 10  54  286      122      167.2

A composite risk score is calculated by combining age, cholesterol, and resting blood pressure into a single metric using weighted contributions (0.3, 0.4, 0.3 respectively). This score provides a simplified measure of overall cardiovascular risk, with higher scores indicating higher potential risk for heart disease. Displaying the first few rows helps verify that the calculation has been applied correctly.

13. Creating a Training Set Using Random Number Generator

# Splitting Training and Testing Sets
set.seed(1234)
train_index <- sample(1:nrow(heart_clean), size = 0.70 * nrow(heart_clean))
TrainingSet <- heart_clean[train_index, ]
TestingSet  <- heart_clean[-train_index, ]

cat("Training set rows : ", nrow(TrainingSet), "\n")
## Training set rows :  211
cat("Testing set rows : ", nrow(TestingSet), "\n")
## Testing set rows :  91

This code splits the heart dataset into a 70% training set and a 30% testing set. sample() randomly selects row indices for the training set, while the remaining rows form the testing set. Setting set.seed(1234) ensures the split is reproducible. The cat() functions display the number of rows in each subset to confirm the split.

14. Printing the Summary Statistics of the Health Disease Dataset

# Summary Statistics
summary(heart_clean)
##       age             sex               cp            trestbps    
##  Min.   :29.00   Min.   :0.0000   Min.   :0.0000   Min.   : 94.0  
##  1st Qu.:48.00   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:120.0  
##  Median :55.50   Median :1.0000   Median :1.0000   Median :130.0  
##  Mean   :54.42   Mean   :0.6821   Mean   :0.9636   Mean   :131.6  
##  3rd Qu.:61.00   3rd Qu.:1.0000   3rd Qu.:2.0000   3rd Qu.:140.0  
##  Max.   :77.00   Max.   :1.0000   Max.   :3.0000   Max.   :200.0  
##       chol            fbs           restecg          thalach     
##  Min.   :126.0   Min.   :0.000   Min.   :0.0000   Min.   : 71.0  
##  1st Qu.:211.0   1st Qu.:0.000   1st Qu.:0.0000   1st Qu.:133.2  
##  Median :240.5   Median :0.000   Median :1.0000   Median :152.5  
##  Mean   :246.5   Mean   :0.149   Mean   :0.5265   Mean   :149.6  
##  3rd Qu.:274.8   3rd Qu.:0.000   3rd Qu.:1.0000   3rd Qu.:166.0  
##  Max.   :564.0   Max.   :1.000   Max.   :2.0000   Max.   :202.0  
##      exang           oldpeak          slope             ca        
##  Min.   :0.0000   Min.   :0.000   Min.   :0.000   Min.   :0.0000  
##  1st Qu.:0.0000   1st Qu.:0.000   1st Qu.:1.000   1st Qu.:0.0000  
##  Median :0.0000   Median :0.800   Median :1.000   Median :0.0000  
##  Mean   :0.3278   Mean   :1.043   Mean   :1.397   Mean   :0.7185  
##  3rd Qu.:1.0000   3rd Qu.:1.600   3rd Qu.:2.000   3rd Qu.:1.0000  
##  Max.   :1.0000   Max.   :6.200   Max.   :2.000   Max.   :4.0000  
##       thal           target       Age_Group           risk_score   
##  Min.   :0.000   Min.   :0.000   Length:302         Min.   :102.0  
##  1st Qu.:2.000   1st Qu.:0.000   Class :character   1st Qu.:139.0  
##  Median :2.000   Median :1.000   Mode  :character   Median :152.1  
##  Mean   :2.315   Mean   :0.543                      Mean   :154.4  
##  3rd Qu.:3.000   3rd Qu.:1.000                      3rd Qu.:167.4  
##  Max.   :3.000   Max.   :1.000                      Max.   :280.2

The summary() function provides the five-number summary (minimum, 1st quartile, median, 3rd quartile, maximum) plus the mean for each numeric variable. Key observations: the median age is 54, median cholesterol is 223 mg/dL, and median maximum heart rate is 153 bpm. The target variable has a mean that indicates the proportion of patients diagnosed with heart disease in this cleaned dataset.

15. Statistical Functions: Mean, Median, Mode, Range

# Creating Get_mode Fun
get_mode <- function(x) {
  ux <- unique(x)
  ux[which.max(tabulate(match(x, ux)))]
}
# Applying Statistical Functions: Mean, Median, Mode, Range
cholmean = mean(heart_clean$chol)
cat("Chol-Mean : ", cholmean, "\n")
## Chol-Mean :  246.5
cholmedian = median(heart_clean$chol)
cat("Chol-Median : ", cholmedian, "\n")
## Chol-Median :  240.5
cholrange = range(heart_clean$chol)
cat("Chol-Range : ", cholrange, "\n")
## Chol-Range :  126 564
cholmode = get_mode(heart_clean$chol)
cat("Chol-Mode : ", cholmode, "\n")
## Chol-Mode :  204

A custom get_mode() function is defined because R does not have a built-in mode function for numeric data. Using this function, along with standard functions, we compute summary statistics for cholesterol in the dataset. The mean, median, mode, and range are calculated to describe the distribution. The mean is higher than the median, suggesting a right-skewed distribution, indicating that some patients have unusually high cholesterol values. The mode identifies the most frequently occurring cholesterol value in the dataset.

16. Deriving Scatter Plot: Age vs Cholesterol

ggplot(heart_clean, aes(x = age, y = chol, color = as.factor(target))) +
  geom_point(alpha = 0.6, size = 2) +
  labs(title = "Scatter Plot: Age vs Cholesterol",
       x = "Age",
       y = "Cholesterol Level",
       color = "Heart Disease") +
  scale_color_manual(values = c("0" = "#0000FF", "1" = "#00FF00"),
                     labels = c("No Disease", "Disease")) +
  theme_minimal()

This scatter plot shows the relationship between Age and Cholesterol levels, with points colored by heart disease status (No Disease vs Disease). The x-axis represents age, while the y-axis shows cholesterol levels. This visualization helps identify patterns or trends, such as whether higher age or cholesterol is associated with heart disease, and highlights differences between the two groups.

17. Ploting Bar chart: Heart Disease Across Sexes

ggplot(heart_clean, aes(x = as.factor(sex), fill = as.factor(target))) +
  geom_bar(position = "dodge") +
  labs(title = "Heart Disease by Sex",
       x = "Sex",
       y = "Count",
       fill = "Heart Disease") +
  scale_fill_manual(values = c("0" = "#1ABC9C", "1" = "#FF00FF"),
                    labels = c("No Disease", "Disease")) +
  scale_x_discrete(labels = c("0" = "Female", "1" = "Male")) +
  theme_minimal()

This bar plot visualizes the distribution of heart disease across sexes. The x-axis represents Sex (Female vs Male), and the y-axis shows the count of patients. Bars are filled by heart disease status (No Disease vs Disease), with contrasting colors for clarity. The plot highlights differences in heart disease prevalence between males and females, making it easier to compare groups visually.

18. Pearson Correlation: Cholesterol vs Resting Blood Pressure

cor_value <- cor(heart_clean$chol, heart_clean$trestbps, method = "pearson")
cat("Pearson Correlation (Cholesterol vs Resting BP):", cor_value, "\n")
## Pearson Correlation (Cholesterol vs Resting BP): 0.1252563
cor.test(heart_clean$chol, heart_clean$trestbps, method = "pearson")
## 
##  Pearson's product-moment correlation
## 
## data:  heart_clean$chol and heart_clean$trestbps
## t = 2.1867, df = 300, p-value = 0.02953
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.01256927 0.23480160
## sample estimates:
##       cor 
## 0.1252563

The Pearson correlation measure the relationship between cholesterol and resting blood pressure is 0.125, indicating a weak positive relationship. This means that as cholesterol increases, resting blood pressure tends to increase slightly, but the relationship is not strong.

The cor.test() results show a p-value of 0.029, which is less than 0.05, indicating that the relationship is statistically significant and unlikely to be due to random chance. The 95% confidence interval (0.013 to 0.235) further supports a positive but weak association between the two variables.