Capstone: A Data Analysis Report

Reviewed & published by Brayan K

A capstone is a project that ties everything together — here you'll run a complete data analysis in R, from importing and cleaning a dataset to summarising, modelling, and reporting the results.

Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll have walked the full analysis pipeline — read.csv and tidyr to clean, dplyr to aggregate, lm to model, and a written summary — using every skill from the course in one flow.

What You'll Learn in This Lesson

1️⃣ Import and Clean

Every analysis starts by getting data in and tidying it. Here we read CSV text, drop a row with a missing value using drop_na(), and add a computed revenue column with mutate().

library(dplyr)
library(tidyr)

# STEP 1 — IMPORT & CLEAN
# Imagine a CSV of monthly sales; here we read it from inline text.
raw <- "region,month,units,price
North,Jan,120,5
North,Feb,150,5
South,Jan,90,8
South,Feb,NA,8
East,Jan,200,4"

sales <- read.csv(text = raw)

clean <- sales |>
  drop_na(units) |>                  # remove the missing-units row
  mutate(revenue = units * price)    # add a revenue column

head(clean)

2️⃣ Aggregate and Summarise

With clean data, we answer the business question: revenue and average units per region. group_by() + summarise() + arrange() produce a tidy report.

library(dplyr)

clean <- data.frame(
  region = c("North","North","South","East"),
  units = c(120, 150, 90, 200),
  price = c(5, 5, 8, 4),
  revenue = c(600, 750, 720, 800)
)

# STEP 2 — ANALYSE: revenue and average units per region.
report <- clean |>
  group_by(region) |>
  summarise(
    total_rev = sum(revenue),
    avg_units = mean(units),
    n = n(),
    .groups = "drop"
  ) |>
  arrange(desc(total_rev))

report

3️⃣ Model and Communicate

Finally we model the relationship between price and units with lm(), read the slope and R-squared, and (in RStudio) draw a ggplot2 chart to communicate the finding.

# STEP 3 — MODEL & VISUALISE
# A linear model relating price to units, plus a plot.
# library(ggplot2)

data <- data.frame(
  price = c(4, 5, 8, 10, 12),
  units = c(200, 150, 90, 70, 50)
)

model <- lm(units ~ price, data = data)
round(coef(model), 1)
# (Intercept)       price
#       250.9       -17.8   (each +1 price -> ~18 fewer units)

cat("R-squared:", round(summary(model)$r.squared, 3), "\n")

# The plot (run in RStudio) would be:
# ggplot(data, aes(price, units)) + geom_point() +
#   geom_smooth(method = "lm") + theme_minimal()

Your turn. Fill in the ___ blank, run it, and compare with the expected output.

library(dplyr)

# 🎯 YOUR TURN — replace each ___ then run it.

orders <- data.frame(
  customer = c("A", "B", "A", "C", "B"),
  amount = c(50, 80, 30, 120, 60)
)

# Build a per-customer report: total spend and number of orders,
# sorted by total spend descending.
report <- orders |>
  group_by(customer) |>
  summarise(
    total = sum(amount),
    orders = n(),
    .groups = "drop"
  ) |>
  ___   # 👉 replace ___ with arrange(desc(total))

print(report)

# ✅ Expected output:
# # A tibble: 3 × 3
#   customer total orders
#   <chr>    <dbl>  <int>
# 1 B          140      2
# 2 C          120      1
# 3 A           80      2

Mini-Challenge: A Complete Mini-Report

Run the entire pipeline yourself, from raw text to a written conclusion. This is the whole course in one exercise — import, clean, aggregate, sort, and report.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Analysis Pipeline

StageKey tools
Importread.csv()
Cleandrop_na() ; mutate()
Explorestr() ; summary()
Aggregategroup_by() ; summarise()
Modellm() ; predict()
Communicateggplot() ; R Markdown

🎉 Course Complete!

Practice quiz

Which function reads a CSV file into a data frame in base R?

  • load.csv()
  • import.csv()
  • read.csv()
  • csv.read()

Answer: read.csv(). read.csv() reads comma-separated data into a data frame.

In dplyr, which pair produces a grouped summary?

  • group_by() then summarise()
  • filter() then arrange()
  • mutate() then select()
  • join() then rename()

Answer: group_by() then summarise(). group_by() defines groups and summarise() reduces each to summary rows.

What does mutate(revenue = units * price) do?

  • Removes the units column
  • Filters rows where units > price
  • Sorts by price
  • Adds a new revenue column computed per row

Answer: Adds a new revenue column computed per row. mutate() adds or changes columns; the calculation is vectorized per row.

Which dplyr verb sorts rows from highest to lowest revenue?

  • sort(revenue)
  • arrange(desc(revenue))
  • order(-revenue)
  • rank(revenue)

Answer: arrange(desc(revenue)). arrange(desc(...)) sorts in descending order in a dplyr pipeline.

What does sum(revenue) compute inside summarise()?

  • The total of the revenue values in each group
  • The number of rows
  • The average revenue
  • The largest revenue

Answer: The total of the revenue values in each group. sum() adds the values; n() would count rows instead.

Which function fits a simple linear model of units on price?

  • glm.fit(units, price)
  • model(units, price)
  • lm(units ~ price, data = data)
  • regress(units ~ price)

Answer: lm(units ~ price, data = data). lm() fits linear models using the y ~ x formula interface.

In a typical analysis, when should cleaning (handling NAs) happen?

  • After producing the final report
  • Before aggregating or modelling
  • Only inside the plot
  • Never, R handles it silently

Answer: Before aggregating or modelling. Clean first so totals and models aren't distorted by missing or bad values.

What does n() return inside summarise()?

  • The sum of a column
  • The mean of a column
  • The number of columns
  • The number of rows in the current group

Answer: The number of rows in the current group. n() counts rows per group; useful alongside sum() and mean().

Why finish a grouped summary with .groups = "drop"?

  • To delete the data frame
  • So later steps operate on ungrouped data
  • To sort the result
  • To round the numbers

Answer: So later steps operate on ungrouped data. Dropping groups (or ungroup()) prevents surprises in later pipeline steps.

Why pull report numbers from code rather than typing them by hand?

  • To make the file larger
  • Because typing is disallowed in R
  • So the report can never drift out of sync with the data
  • To avoid using dplyr

Answer: So the report can never drift out of sync with the data. Generating numbers from live code keeps the report reproducible and accurate.

Continue this course

Frequently asked questions

What are the typical steps of a data analysis in R?

A standard analysis flows through clear stages: import the data (read.csv), clean it (handle missing values, fix types, reshape with tidyr), explore it (str, summary, plots), transform and aggregate it (dplyr verbs, group_by/summarise, joins), model it if needed (lm and statistical tests), and finally communicate the results (ggplot2 charts and a written report). This capstone walks through that whole pipeline so you can see how the lessons fit together into one workflow.

How do I structure a reproducible analysis project?

Keep everything in one project folder (an RStudio Project sets the working directory automatically). Put raw data in a data/ folder and never edit it by hand; do all changes in code so they're repeatable. Write your analysis as a script or, better, an R Markdown / Quarto document that mixes code, output, and narrative. Set.seed() before any randomness. The goal is that someone can re-run your code from scratch and get exactly the same results.

What is R Markdown and why use it for reports?

R Markdown (and its successor Quarto) lets you weave R code, its output (tables and plots), and explanatory text into a single document that renders to HTML, PDF, or Word. Because the figures and numbers are generated from live code, your report can never drift out of sync with your analysis — re-knit it and everything updates. It's the standard way R users produce shareable, reproducible reports, and it's the natural next step after this course.

Where should I go after finishing this course?

You now have the full toolkit: data structures, the tidyverse, visualization, and modeling. Good next steps are: deepen ggplot2 for richer visuals, learn R Markdown/Quarto for reporting, explore the broader tidyverse (purrr, stringr, lubridate, forcats), and try a real dataset end to end — find one that interests you, ask a question, and use the pipeline from this capstone to answer it. Building real projects is the fastest way to consolidate everything you've learned.