Objective

In Assignment 1, you practiced the basics of using R and Posit Cloud. You learned how to run code, create objects, use functions, and knit an R Markdown file.

In this assignment, we will use R for a real biostatistics task: describing one variable at a time. This is called univariate exploratory data analysis, or univariate EDA.

The statistics ideas in this assignment should look familiar from lecture. The main purpose of this lab is to learn how to make R do the work: import a dataset, inspect variables, calculate summaries, make graphs, and write short interpretations based on the output.

By the end of this assignment, you should be able to:

This assignment uses the dataset:

patient_descriptive_data.csv

Each row in the dataset represents one fictional participant.


Why We Are Using the tidyverse

In Assignment 1, you used basic R commands. In this assignment, we will use a collection of R packages called the tidyverse.

A package is an add-on for R. Packages give R extra tools. The tidyverse is a collection of packages that are commonly used for data analysis. In this assignment, tidyverse tools will help us read the dataset, summarize variables, and make graphs.

You will see code that uses the pipe symbol:

%>%

You can read the pipe as:

and then

For example, this code:

patient_data %>%
  count(SmokingStatus)

can be read as:

Start with patient_data, and then count the values of SmokingStatus.

You do not need to master every detail of the tidyverse right now. The goal is to use a small number of R tools to describe data clearly.


The Analyst Workflow for One Variable

When describing one variable, a useful workflow is:

  1. Identify the variable.
  2. Decide whether the variable is numeric or categorical.
  3. Check whether any values are missing.
  4. Choose appropriate summaries.
  5. Choose an appropriate graph.
  6. Interpret the results in context.

The type of variable determines the tools we use.

Variable Type Useful Summaries Useful Graphs
Numeric Mean, median, standard deviation, IQR Histogram, boxplot
Categorical Counts, percentages Bar chart

This assignment will walk you through that workflow.


Instructions

For each task, first run the worked example in Part A: I Do. Then complete the similar practice task in Part B: You Do.

Use the output from the Part B chunks to answer the quiz questions in eLC. You should not need to write additional code for the quiz beyond what the assignment already asks you to run.

When you are finished, click Knit to create your HTML file.


Part 1: Load the tidyverse and the Dataset

Before we can analyze data, we need to load the tools and the dataset.

Part A: I Do

Run the code below.

library(tidyverse)

patient_data <- read_csv("patient_descriptive_data.csv")

The first line loads the tidyverse:

library(tidyverse)

The second line reads the CSV file and stores it in an object called patient_data:

patient_data <- read_csv("patient_descriptive_data.csv")

The arrow <- means that we are saving something. In this case, we are saving the dataset into an object called patient_data.

Now print the dataset.

patient_data
## # A tibble: 30 × 12
##    ParticipantID   Age Sex    Race     WeightKg HeightCm   BMI SystolicBP
##            <dbl> <dbl> <chr>  <chr>       <dbl>    <dbl> <dbl>      <dbl>
##  1          1001    24 Female White        66.2     165.  24.3        118
##  2          1002    31 Male   Black        88.5     178.  28          132
##  3          1003    45 Female White        71.4     163.  27          126
##  4          1004    52 Male   Hispanic     95.1     180.  29.3        145
##  5          1005    37 Female Asian        58.9     159.  23.4        112
##  6          1006    60 Female Black        82.7     168.  NA          150
##  7          1007    29 Male   White        79.3     175.  25.8        124
##  8          1008    41 Female Hispanic     69.8     160   27.3        136
##  9          1009    55 Male   White       102.      183.  30.6        158
## 10          1010    34 Female White        64.5     164   24          120
## # ℹ 20 more rows
## # ℹ 4 more variables: DiastolicBP <dbl>, HeartRate <dbl>, SmokingStatus <chr>,
## #   ExerciseDays <dbl>

When R prints this dataset, it appears as a tibble. A tibble is a tidyverse version of a data table. It shows the variable names, variable types, and the first few rows of the dataset.

Part B: You Do

Print the first few rows of the dataset using head().

# Print the first few rows of patient_data.
head(patient_data, n=3)
## # A tibble: 3 × 12
##   ParticipantID   Age Sex   Race  WeightKg HeightCm   BMI SystolicBP DiastolicBP
##           <dbl> <dbl> <chr> <chr>    <dbl>    <dbl> <dbl>      <dbl>       <dbl>
## 1          1001    24 Fema… White     66.2     165.  24.3        118          76
## 2          1002    31 Male  Black     88.5     178.  28          132          84
## 3          1003    45 Fema… White     71.4     163.  27          126          82
## # ℹ 3 more variables: HeartRate <dbl>, SmokingStatus <chr>, ExerciseDays <dbl>

The function head() shows the first few rows of a dataset. This is a quick way to make sure the data loaded correctly.


Part 2: Inspect the Variables

Before summarizing data, we should inspect the dataset.

The glimpse() function gives a quick overview of the variables.

Part A: I Do

Run the code below.

glimpse(patient_data)
## Rows: 30
## Columns: 12
## $ ParticipantID <dbl> 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 10…
## $ Age           <dbl> 24, 31, 45, 52, 37, 60, 29, 41, 55, 34, 48, 27, 39, 63, …
## $ Sex           <chr> "Female", "Male", "Female", "Male", "Female", "Female", …
## $ Race          <chr> "White", "Black", "White", "Hispanic", "Asian", "Black",…
## $ WeightKg      <dbl> 66.2, 88.5, 71.4, 95.1, 58.9, 82.7, 79.3, 69.8, 102.4, 6…
## $ HeightCm      <dbl> 165.1, 177.8, 162.6, 180.3, 158.8, 167.6, 175.3, 160.0, …
## $ BMI           <dbl> 24.3, 28.0, 27.0, 29.3, 23.4, NA, 25.8, 27.3, 30.6, 24.0…
## $ SystolicBP    <dbl> 118, 132, 126, 145, 112, 150, 124, 136, 158, 120, NA, 11…
## $ DiastolicBP   <dbl> 76, 84, 82, 92, 70, 95, 78, 86, 96, 78, 88, 68, 82, 90, …
## $ HeartRate     <dbl> 72, 78, 74, 86, 68, 88, 70, 76, 90, 72, 84, 66, 80, 82, …
## $ SmokingStatus <chr> "Never", "Former", "Never", "Current", "Never", "Former"…
## $ ExerciseDays  <dbl> 3, 1, 4, 0, 5, 1, 2, 3, 0, 4, 1, 6, 2, 1, 2, 0, 5, 3, 4,…

The glimpse() function prints each variable name, shows the variable type, and previews some of the values.

In the output, you may see types such as:

  • <dbl>: numeric variable
  • <chr>: character variable, often used for categories

For example, Age is numeric because it contains numbers. SmokingStatus is categorical because it contains labels such as "Never", "Former", and "Current".

Part B: You Do

Use the glimpse() output to answer the questions below.

Is BMI numeric or categorical?

Your answer:

glimpse(patient_data["BMI"])
## Rows: 30
## Columns: 1
## $ BMI <dbl> 24.3, 28.0, 27.0, 29.3, 23.4, NA, 25.8, 27.3, 30.6, 24.0, 30.2, 22…

BMI is numeric

Is Race numeric or categorical?

Your answer:

glimpse(patient_data["Race"])
## Rows: 30
## Columns: 1
## $ Race <chr> "White", "Black", "White", "Hispanic", "Asian", "Black", "White",…

Race is categorical


Part 3: Summarize One Numeric Variable

Numeric variables can be summarized using measures of center and spread. In this section, we will use R to calculate the mean, median, standard deviation, and IQR.

Part A: I Do

Summarize BMI.

patient_data %>%
  summarize(
    mean_bmi = mean(BMI, na.rm = TRUE),
    median_bmi = median(BMI, na.rm = TRUE),
    sd_bmi = sd(BMI, na.rm = TRUE),
    iqr_bmi = IQR(BMI, na.rm = TRUE)
  )
## # A tibble: 1 × 4
##   mean_bmi median_bmi sd_bmi iqr_bmi
##      <dbl>      <dbl>  <dbl>   <dbl>
## 1     27.2       27.7   2.52    3.60

Let’s read this code carefully.

The first line starts with the dataset:

patient_data %>%

This says:

Start with patient_data, and then…

The next line sends the dataset into summarize():

summarize(

The summarize() function creates a small summary table. Each line inside summarize() creates one summary value.

For example:

mean_bmi = mean(BMI, na.rm = TRUE)

This line creates a summary value called mean_bmi.

The part on the left side of the equals sign names the output:

mean_bmi =

The part on the right side of the equals sign calculates the value:

mean(BMI, na.rm = TRUE)

The argument na.rm = TRUE tells R to remove missing values before calculating the summary. This is important because some variables in this dataset have missing values. If you forget na.rm = TRUE, R may return NA instead of the summary statistic.

Part B: You Do

Summarize WeightKg.

Calculate:

  • mean weight
  • median weight
  • standard deviation of weight
  • IQR of weight

Use the names:

  • mean_weight
  • median_weight
  • sd_weight
  • iqr_weight
# Summarize WeightKg.
patient_data %>%
  summarize(
    mean_weight = mean(WeightKg, na.rm = TRUE),
    median_weight = median(WeightKg, na.rm = TRUE),
    sd_weight = sd(WeightKg, na.rm = TRUE),
    iqr_weight = IQR(WeightKg, na.rm = TRUE)
  )
## # A tibble: 1 × 4
##   mean_weight median_weight sd_weight iqr_weight
##         <dbl>         <dbl>     <dbl>      <dbl>
## 1        78.7          78.4      12.0       18.7

Part 4: Add More Numeric Summaries

Sometimes we also want the minimum, maximum, and quartiles. These help us understand the overall range and the middle part of the distribution.

Part A: I Do

Calculate several summaries for BMI.

patient_data %>%
  summarize(
    min_bmi = min(BMI, na.rm = TRUE),
    q1_bmi = quantile(BMI, 0.25, na.rm = TRUE),
    median_bmi = median(BMI, na.rm = TRUE),
    q3_bmi = quantile(BMI, 0.75, na.rm = TRUE),
    max_bmi = max(BMI, na.rm = TRUE)
  )
## # A tibble: 1 × 5
##   min_bmi q1_bmi median_bmi q3_bmi max_bmi
##     <dbl>  <dbl>      <dbl>  <dbl>   <dbl>
## 1    22.4   25.6       27.7   29.2    30.8

This code uses the same summarize() structure as Part 3. The only difference is that we are asking R to calculate different summaries.

The function min() finds the smallest value.

The function max() finds the largest value.

The function quantile() finds a percentile. For example:

quantile(BMI, 0.25, na.rm = TRUE)

calculates the first quartile, or Q1, for BMI.

Part B: You Do

Calculate the minimum, Q1, median, Q3, and maximum for SystolicBP.

Use the names:

  • min_sbp
  • q1_sbp
  • median_sbp
  • q3_sbp
  • max_sbp
# Calculate min, Q1, median, Q3, and max for SystolicBP.
patient_data %>%
  summarize(
    min_sbp = min(SystolicBP, na.rm = TRUE),
    q1_sbp = quantile(SystolicBP, 0.25, na.rm = TRUE),
    median_sbp = median(SystolicBP, na.rm = TRUE),
    q3_sbp = quantile(SystolicBP, 0.75, na.rm = TRUE),
    max_sbp = max(SystolicBP, na.rm = TRUE)
  )
## # A tibble: 1 × 5
##   min_sbp q1_sbp median_sbp q3_sbp max_sbp
##     <dbl>  <dbl>      <dbl>  <dbl>   <dbl>
## 1     110    124        134    145     158

Part 5: Create a Histogram

A histogram is useful for graphing a numeric variable. In this section, the main goal is to learn the R code pattern for creating one with ggplot().

Part A: I Do

Create a histogram of BMI.

ggplot(data = patient_data, aes(x = BMI)) +
  geom_histogram(bins = 8)

Let’s read the code.

The ggplot() function starts the graph:

ggplot(data = patient_data, aes(x = BMI))

The argument data = patient_data tells R which dataset to use.

The part aes(x = BMI) tells R to put BMI on the x-axis. The aes() function is where we tell ggplot which variables to use in the graph.

The next line adds the histogram:

geom_histogram(bins = 8)

The geom_histogram() function tells R to create a histogram. The argument bins = 8 tells R to use 8 bars.

The plus sign + connects the pieces of the graph. In ggplot, we build graphs by starting with ggplot() and then adding layers with +.

Part B: You Do

Create a histogram of WeightKg.

Use 8 bins.

# Create a histogram of WeightKg using 8 bins.
ggplot(data = patient_data, aes(x = WeightKg)) +
  geom_histogram(bins = 8)


Part 6: Add Labels to a Histogram

A graph should have clear labels so the reader knows what is being shown.

We add labels using labs().

Part A: I Do

Create a labeled histogram of BMI.

ggplot(data = patient_data, aes(x = BMI)) +
  geom_histogram(bins = 8) +
  labs(
    title = "Distribution of BMI",
    x = "Body Mass Index",
    y = "Number of Participants"
  )

This code has three pieces.

The first piece starts the graph and chooses the variable:

ggplot(data = patient_data, aes(x = BMI))

The second piece adds the histogram:

geom_histogram(bins = 8)

The third piece adds labels:

labs(
  title = "Distribution of BMI",
  x = "Body Mass Index",
  y = "Number of Participants"
)

Inside labs(), the words in quotation marks are the labels that appear on the graph.

Part B: You Do

Create a labeled histogram of WeightKg.

Use the following labels:

  • Title: "Distribution of Weight"
  • x-axis: "Weight in Kilograms"
  • y-axis: "Number of Participants"
# Create a labeled histogram of WeightKg.
ggplot(data = patient_data, aes(x = WeightKg)) +
  geom_histogram(bins = 8) +
  labs(
    title = "Distribution of Weight",
    x = "Weight in Kilograms",
    y = "Number of Participants"
  )


Part 7: Create a Boxplot

A boxplot is another useful graph for a numeric variable. It gives a compact picture of the median, spread, and possible unusual values.

Part A: I Do

Create a boxplot of BMI.

ggplot(data = patient_data, aes(x = BMI)) +
  geom_boxplot()

This code uses the same ggplot structure.

The first line starts the graph and puts BMI on the x-axis.

The second line adds the boxplot layer:

geom_boxplot()

Part B: You Do

Create a boxplot of HeartRate.

# Create a boxplot of HeartRate.
ggplot(data = patient_data, aes(x = HeartRate)) +
  geom_boxplot()


Part 8: Interpret a Numeric Variable

Now we will combine the numeric summaries and graphs.

A good interpretation should mention a typical value, the amount of spread, and one pattern from the graph. You do not need to write a long paragraph. Two or three clear sentences are enough.

Part A: I Do

A reasonable interpretation of BMI might be:

The typical BMI in this sample is in the upper 20s, based on the mean and median. The BMI values show moderate spread, with most values falling from the low 20s to around 30. The histogram and boxplot suggest that BMI is concentrated mostly in the mid-to-upper 20s, with a few higher values.

This interpretation uses both numbers and graphs. It does not overstate the result.

Part B: You Do

Using your summaries, histogram, and boxplot, write two or three sentences describing WeightKg.

Your answer: > The typical WeightKg in this sample is above 65kgs, based on the mean and median. The WeightKg values show moderate spread, with most values falling from around 65kg to 95kg. The histogram suggest that WeightKg is concentrated mostly in around 65kg to 75kg, with fewer in higher values.


Part 9: Summarize One Categorical Variable

Categorical variables are summarized using counts and percentages.

Part A: I Do

Count the number of participants in each SmokingStatus category.

patient_data %>%
  count(SmokingStatus)
## # A tibble: 3 × 2
##   SmokingStatus     n
##   <chr>         <int>
## 1 Current           4
## 2 Former            8
## 3 Never            18

The code starts with the dataset:

patient_data %>%

Then the pipe sends the dataset into count():

count(SmokingStatus)

The count() function counts how many observations are in each category of SmokingStatus.

Now add percentages.

patient_data %>%
  count(SmokingStatus) %>%
  mutate(percent = n / sum(n) * 100)
## # A tibble: 3 × 3
##   SmokingStatus     n percent
##   <chr>         <int>   <dbl>
## 1 Current           4    13.3
## 2 Former            8    26.7
## 3 Never            18    60

This code has one additional step:

mutate(percent = n / sum(n) * 100)

The mutate() function creates a new column.

Here, the new column is called percent.

The expression n / sum(n) * 100 calculates the percentage in each category. The column n was created by count(), and sum(n) gives the total number of participants counted across all categories.

Part B: You Do

Count the number of participants in each Race category and add percentages.

# Count Race categories and add percentages.
patient_data %>%
  count(Race) %>%
  mutate(percent = n / sum(n) * 100)
## # A tibble: 4 × 3
##   Race         n percent
##   <chr>    <int>   <dbl>
## 1 Asian        4    13.3
## 2 Black        7    23.3
## 3 Hispanic     6    20  
## 4 White       13    43.3

Part 10: Create a Bar Chart

A bar chart is useful for graphing a categorical variable.

Part A: I Do

Create a bar chart of SmokingStatus.

ggplot(data = patient_data, aes(x = SmokingStatus)) +
  geom_bar()

This code follows the same ggplot pattern as the histogram and boxplot.

The first line starts the graph and puts SmokingStatus on the x-axis.

The second line adds the bar chart:

geom_bar()

The geom_bar() function counts how many observations are in each category and creates the bars.

Part B: You Do

Create a bar chart of Race.

# Create a bar chart of Race.
ggplot(data = patient_data, aes(x = Race)) +
  geom_bar()


Part 11: Add Labels to a Bar Chart

As with histograms, bar charts should have clear labels.

Part A: I Do

Create a labeled bar chart of SmokingStatus.

ggplot(data = patient_data, aes(x = SmokingStatus)) +
  geom_bar() +
  labs(
    title = "Smoking Status of Participants",
    x = "Smoking Status",
    y = "Number of Participants"
  )

The graph starts with ggplot(), adds the bar chart with geom_bar(), and adds the title and axis labels with labs().

Part B: You Do

Create a labeled bar chart of Race.

Use the following labels:

  • Title: "Race Categories of Participants"
  • x-axis: "Race"
  • y-axis: "Number of Participants"
# Create a labeled bar chart of Race.
ggplot(data = patient_data, aes(x = Race)) +
  geom_bar() +
  labs(
    title = "Race Categories of Participants",
    x = "Race",
    y = "Number of Participants"
  )


Part 12: Interpret a Categorical Variable

A good interpretation of a categorical variable should mention the most common category, the count or percentage in that category, and any categories that are less common.

Part A: I Do

A reasonable interpretation of SmokingStatus might be:

The most common smoking status in this sample is never smoker. There are 18 never smokers, which is 60% of the sample. Former and current smokers are less common in this dataset.

This interpretation uses counts and percentages.

Part B: You Do

Using your count table, percentage table, and bar chart, write two or three sentences describing Race.

Your answer: > The most common race category in this sample is white. There are 13 white people, which is 43.33% of the sample. Asian, Hispanic, and Black in total are contributing to more than half of the population in this dataset.


Part 13: Mini Univariate EDA Report

Now put the pieces together.

Question A: Numeric Variable

Write a short paragraph describing WeightKg.

Include:

  • mean
  • median
  • standard deviation or IQR
  • one observation from the histogram or boxplot

Your answer: > The typical WeightKg in this sample is above 65kgs, based on the mean and median. The WeightKg values show moderate spread, with most values falling from around 65kg to 95kg. The histogram suggest that WeightKg is concentrated mostly in around 65kg to 75kg, with fewer in higher values.

Question B: Categorical Variable

Write a short paragraph describing Race.

Include:

  • the most common category
  • the count for that category
  • the percentage for that category
  • one observation from the bar chart

Your answer: > The most common race category in this sample is white. There are 13 white people, which is 43.33% of the sample. Asian, Hispanic, and Black in total are contributing to more than half of the population in this dataset.

Question C: Method Choice

For each variable below, identify an appropriate summary and graph.

  1. Age: | Numeric | Mean, median, standard deviation, IQR | Histogram, boxplot |

  2. SmokingStatus: | Categorical | Counts, percentages | Bar chart |

  3. SystolicBP: | Numeric | Mean, median, standard deviation, IQR | Histogram, boxplot |

  4. Sex: | Categorical | Counts, percentages | Bar chart |


Quiz Questions

Use your output from the code chunks above to answer the quiz questions in eLC. You should not need to write any new code to answer these questions.

Question 1

From Part 2B, is BMI numeric or categorical?

A. Numeric
B. Categorical
C. Both numeric and categorical
D. Neither numeric nor categorical

Answer:A

Question 2

From Part 2B, is Race numeric or categorical?

A. Numeric
B. Categorical
C. Both numeric and categorical
D. Neither numeric nor categorical

Answer:B

Question 3

In the Part 3A example, what does na.rm = TRUE tell R to do?

A. Rename the variable before calculating the summary.
B. Remove missing values before calculating the summary.
C. Remove duplicate rows before calculating the summary.
D. Round the answer to two decimal places.

Answer:B

Question 4

From Part 3B, approximately what is the mean of WeightKg?

A. 69.80
B. 78.69
C. 78.40
D. 88.50

Answer:B

Question 5

From Part 3B, what is the median of WeightKg?

A. 69.80
B. 78.40
C. 78.69
D. 88.50

Answer:B

Question 6

From Part 3B, approximately what is the standard deviation of WeightKg?

A. 2.52
B. 11.96
C. 18.70
D. 78.69

Answer:B

Question 7

From Part 3B, what is the IQR of WeightKg?

A. 11.96
B. 18.70
C. 69.80
D. 88.50

Answer:B

Question 8

From Part 4B, what is the median of SystolicBP?

A. 124
B. 134
C. 145
D. 158

Answer:B

Question 9

From Part 4B, what is the third quartile, or Q3, of SystolicBP?

A. 110
B. 124
C. 134
D. 145

Answer:D

Question 10

Which summary-and-graph combination is appropriate for a numeric variable such as WeightKg?

A. Counts, percentages, and a bar chart
B. Mean, median, standard deviation, IQR, histogram, and boxplot
C. Counts, percentages, histogram, and boxplot
D. Mean, median, standard deviation, IQR, and bar chart

Answer:B

Question 11

Which ggplot function creates a histogram?

A. geom_bar()
B. geom_boxplot()
C. geom_histogram()
D. geom_hist()

Answer:C

Question 12

Which ggplot function creates a boxplot?

A. geom_bar()
B. geom_boxplot()
C. geom_histogram()
D. geom_box()

Answer:B

Question 13

From Part 9B, which Race category has the largest count?

A. Asian
B. Black
C. Hispanic
D. White

Answer:D

Question 14

From Part 9B, how many participants are in the largest Race category?

A. 4
B. 6
C. 7
D. 13

Answer:D

Question 15

From Part 9B, approximately what percentage of participants are in the largest Race category?

A. 13.3%
B. 20.0%
C. 23.3%
D. 43.3%

Answer:D

Question 16

Which ggplot function creates a bar chart?

A. geom_bar()
B. geom_boxplot()
C. geom_histogram()
D. geom_barchart()

Answer:A

Question 17

In the code mutate(percent = n / sum(n) * 100), what new column is being created?

A. n
B. Race
C. percent
D. sum

Answer:C

Question 18

In this assignment, how should you read the pipe symbol %>%?

A. equals
B. greater than
C. and then
D. divided by

Answer:C

Question 19

Which summary-and-graph combination is appropriate for a categorical variable such as SmokingStatus?

A. Mean, median, standard deviation, and histogram
B. Mean, median, IQR, and boxplot
C. Counts, percentages, and a bar chart
D. Standard deviation, IQR, and a bar chart

Answer:C

Question 20

Which interpretation is most consistent with the Race summary from Part 9B?

A. White is the most common race category in the sample, and Asian is the least common.
B. Asian is the most common race category in the sample, and White is the least common.
C. All race categories have exactly the same number of participants.
D. Race should be summarized using a mean and standard deviation.

Answer:A


Final Step: Knit Your Assignment

Click the Knit button to create your HTML file.

Before submitting, check that:

Error messages are part of learning R. Read them carefully. Check the named code chunk. Revise your code one step at a time.

The goal is not to memorize every command immediately. The goal is to learn how R can help you describe data clearly.

In this assignment, you described one variable at a time using numerical summaries, histograms, boxplots, counts, percentages, and bar charts.