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
- The full data-analysis pipeline end to end
- Importing and cleaning data with read.csv and tidyr
- Aggregating with dplyr group_by and summarise
- Fitting and reading a linear model
- Turning results into a clear report
- Where to go next on your R journey
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))
report3️⃣ 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 2Mini-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)
- Skipping the cleaning step — analysing before handling NAs and types gives wrong totals. Always clean first, then summarise.
- Packages not loaded — load dplyr, tidyr, and ggplot2 before using their functions.
- Still grouped at the end — finish summaries with .groups = "drop" or ungroup() so later steps behave.
- Reporting numbers by hand — pull values from your code (e.g. with cat()) so the report can never drift from the data.
Pro Tips
- 💡 Write it in R Markdown: combine code, charts, and narrative into one reproducible report that re-knits on demand.
- 💡 Comment your intent: note WHY each step exists, not just what it does — future-you will thank you.
- 💡 Keep raw data untouched: do all transformations in code so the whole analysis is repeatable from scratch.
📋 Quick Reference — Analysis Pipeline
| Stage | Key tools |
|---|---|
| Import | read.csv() |
| Clean | drop_na() ; mutate() |
| Explore | str() ; summary() |
| Aggregate | group_by() ; summarise() |
| Model | lm() ; predict() |
| Communicate | ggplot() ; R Markdown |
🎉 Course Complete!
- ✅ You ran a full analysis: import → clean → aggregate → model → report
- ✅ read.csv() and tidyr get data ready
- ✅ dplyr filters, mutates, groups, and summarises
- ✅ lm() models relationships; ggplot2 communicates them
- ✅ You now have the complete R data-science toolkit 🎓
- ✅ Next steps: R Markdown reports and a real dataset of your own
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
- Previous: Checkpoint: Statistical Modeling
- Next: Machine Learning with caret — Train, tune and evaluate models with caret's unified train() interface
- Quick reference: R cheat sheet
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.