Statistical Analysis and Reporting with R
Run hypothesis tests and build a reproducible R Markdown report.
Introduction
30 Second Summary
Every business team has dashboards showing what happened last quarter. Fewer teams can answer why it happened or whether the pattern is real or just noise.
In this project, you will build a reproducible statistical analysis report in R that answers real business questions about e-commerce customer behavior using hypothesis tests and professional visualizations.
What You'll Build
A complete R Markdown report that generates a 500-row e-commerce dataset, runs three hypothesis tests, and knits into a polished standalone HTML document with executive summary, methodology, results, and recommendations.
By the end of this project, you'll have:
- A reproducible .Rmd file combining narrative, code, and visualizations in one document that auto-updates when data changes.
- Three hypothesis tests (t-test, ANOVA, chi-squared) answering business questions with p-values interpreted in plain language.
- Four publication-quality ggplot2 visualizations including histograms with density overlays, boxplots, scatter plots with trend lines, and a correlation heatmap.
- Secret Mission: Add multiple linear regression to predict purchase amount and translate coefficients into dollar-value business recommendations.
Are there any prerequisites?
This project assumes you have R and RStudio already installed on Windows. Step 1 walks you through installing the one additional package you need (tidyverse).
Set Up Your Project
Before you can analyze data, you need the right tools loaded and a clean workspace to keep everything organized.
In this step, you will install the tidyverse package (the Swiss Army knife of R data science), create a dedicated project folder, and set up your R Markdown report file.
In this step, get ready to:
- Install and verify the tidyverse package.
- Create an RStudio Project for your analysis.
- Create and configure your R Markdown report file.
Install the tidyverse package
The tidyverse package is actually a collection of R packages that work together for data manipulation, visualization, and import. Installing it gives you ggplot2, dplyr, tidyr, readr, and more in a single command.
- Install tidyverse by typing the following command into the Console pane at the bottom of RStudio and pressing Enter:
install.packages("tidyverse")
What does this command do?
install.packages() downloads and installs a package from CRAN (R's central package repository) onto your machine.
This download takes 2-3 minutes because tidyverse bundles nine core packages plus their dependencies.
You will see red text scrolling in the Console as packages download and compile. This is normal.
- Once the install finishes and you see the > prompt again, verify the install by running:
library(tidyverse)
You should see a message listing the attached packages: ggplot2, dplyr, tidyr, readr, purrr, tibble, stringr, forcats, and lubridate.
Seeing errors during install?
- If you see a "package is not available" error, check your internet connection. RStudio needs to reach CRAN to download packages.
- If you see a "cannot remove prior installation" warning on Windows, close and reopen RStudio, then run install.packages("tidyverse") again.
- help me troubleshoot my tidyverse installation error
Create an RStudio Project
An RStudio Project gives your analysis a self-contained working directory. Every file path in your code will be relative to this folder, making your work portable and reproducible.
- Click File in the top menu bar.
- Click New Project.
- Select New Directory.
- Select New Project.
- Type ecommerce-analysis in the Directory name field.
- Click Create Project.
RStudio restarts with a fresh session inside your new ecommerce-analysis folder. You should see the project name in the top-right corner of the RStudio window.
Why use an RStudio Project?
Without a project, R's working directory defaults to your home folder. File paths break when you share your code or move folders. A project locks the working directory to the project folder automatically.
Don't see the project name in the top-right?
- Make sure you clicked Create Project (not Cancel). The top-right corner should show ecommerce-analysis instead of "Project: (None)".
- help me set up my RStudio Project
Create your R Markdown file
R Markdown files (.Rmd) let you combine prose, code, and visualizations in a single document. When you "knit" the file, R runs the code and produces a polished HTML report.
- Click File in the top menu bar.
- Click New File.
- Click R Markdown....
What is this dialog?
RStudio opens a wizard that builds the YAML header for your file. YAML is a simple key-value format that tells R Markdown how to render your document (title, author, output type).
- In the Title field, type E-Commerce Customer Analysis Report.
- In the Author field, type your name.
- Make sure HTML is selected as the default output format (it should already be selected).
- Click OK.
RStudio opens a new .Rmd file with a YAML header at the top and boilerplate template content below it.
- Select all the content below the closing --- line of the YAML header and delete it. Leave only the YAML header (the block between the two --- lines).
- Save the file by pressing Ctrl+S.
- In the Save dialog, name the file analysis_report.Rmd and click Save.
Your file should now contain only the YAML header with title, author, date, and output fields.
✔️ Awesome, I've got everything!
Great. Double check you have saved analysis_report.Rmd and that only the YAML header remains.
ⓧ I'd like to double check the full code
Your analysis_report.Rmd should look exactly like this:
---
title: "E-Commerce Customer Analysis Report"
author: "Your Name"
date: "`r Sys.Date()`"
output: html_document
---
File looks different from the example?
- If you see extra content below the second ---, you did not delete all the boilerplate. Select everything after the closing --- and press Delete.
- The author field should show your actual name (not "Your Name"). The example uses a placeholder.
- The date field with `r Sys.Date()` is auto-generated by RStudio. It will insert today's date when you knit the report later.
- help me fix my R Markdown YAML header
Your workspace is ready. Next up, you will generate a realistic e-commerce dataset and wrangle it into shape for analysis.
Generate and Wrangle Data
In the previous step, you set up your project environment with tidyverse installed and a blank analysis_report.Rmd file ready to go.
Now you need data to analyze. Instead of downloading a dataset from a website, you will generate a synthetic dataset directly in R. This keeps the project self-contained and gives every learner identical results for easy validation.
In this step, get ready to:
- Create a 500-row simulated e-commerce dataset with realistic distributions.
- Add derived features using mutate() and case_when().
- Summarize spending patterns by category and season using the pipe operator.
Add the setup chunk and generate your dataset
Every R Markdown file starts with a setup chunk that loads packages and sets default behavior for all code chunks in the document.
- In analysis_report.Rmd, place your cursor on the blank line below the closing --- of the YAML header.
- Add this setup chunk by typing it directly (or press Ctrl+Alt+I to insert an empty chunk, then fill in the details):
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
library(tidyverse)
```
What does the setup chunk do?
- include=FALSE means this chunk runs silently. Its output never appears in the final report.
- opts_chunk$set(...) sets defaults for every chunk in the document. message = FALSE and warning = FALSE suppress noisy package messages.
- library(tidyverse) loads all the core packages (dplyr, ggplot2, tidyr, readr, and more) in one call.
Now for the main event. You will generate a 500-row dataset simulating e-commerce customer transactions with controlled randomness.
- Below the setup chunk, add the following data generation chunk:
```{r generate-data}
set.seed(123)
n <- 500
shopping <- tibble(
customer_id = 1:n,
age = sample(18:70, n, replace = TRUE),
gender = sample(c("Male", "Female", "Non-binary"), n, replace = TRUE, prob = c(0.45, 0.45, 0.10)),
category = sample(c("Clothing", "Footwear", "Accessories", "Electronics"), n, replace = TRUE, prob = c(0.35, 0.25, 0.20, 0.20)),
season = sample(c("Spring", "Summer", "Fall", "Winter"), n, replace = TRUE),
previous_purchases = sample(0:50, n, replace = TRUE),
review_rating = round(runif(n, min = 1, max = 5), 1),
discount_applied = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.40, 0.60)),
subscription_status = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.35, 0.65))
)
shopping <- shopping %>%
mutate(
purchase_amount = round(
50 + (age * 0.8) + (previous_purchases * 1.2) +
if_else(discount_applied == "Yes", rnorm(n, mean = 15, sd = 10), 0) +
rnorm(n, mean = 0, sd = 30),
2
),
purchase_amount = pmax(purchase_amount, 5)
)
glimpse(shopping)
```
What does this code do?
- set.seed(123) locks the random number generator so every learner gets identical data. Reproducibility is critical for statistical analysis.
- tibble() creates a modern data frame with 500 rows. Each column uses sample() for categorical data and runif() for continuous data.
- The purchase_amount formula builds realistic spending: a base of $50 plus contributions from age, purchase history, and a discount uplift (mean $15 extra for discount users). Random noise from rnorm() adds natural variation.
- pmax(purchase_amount, 5) ensures no purchase goes below $5, preventing unrealistic negative values.
- glimpse() prints a compact summary of all columns and their types so you can verify the dataset looks correct.
- Save analysis_report.Rmd with Ctrl+S.
- Run the generate-data chunk by clicking the green Run arrow at the top-right corner of the chunk (or press Ctrl+Shift+Enter).
- You should see output from glimpse() showing 500 rows and 10 columns, with column names and sample values listed.
Not seeing the glimpse output?
- Make sure you ran the setup chunk first. The generate-data chunk depends on tidyverse being loaded. Click the green Run arrow on the setup chunk, then run generate-data again.
- If you see an error about tibble not being found, confirm that library(tidyverse) is inside the setup chunk and runs without errors.
- Check that the chunk fences use exactly three backticks followed by {r generate-data} on the opening line and three backticks on the closing line.
help me fix my generate-data chunk error
Create derived features with case_when
Raw data often needs enrichment before analysis. You will add two new columns that categorize customers by spending level and age group, making it easier to compare segments later.
- Below the generate-data chunk, add this wrangling chunk:
```{r wrangle}
shopping <- shopping %>%
mutate(
spend_category = case_when(
purchase_amount < 50 ~ "Low",
purchase_amount < 150 ~ "Medium",
TRUE ~ "High"
),
age_group = case_when(
age < 30 ~ "Young",
age < 50 ~ "Middle",
TRUE ~ "Senior"
)
)
```
What does case_when do?
case_when() works like a series of if/else checks. It evaluates conditions from top to bottom and assigns the first matching value. The TRUE ~ at the end acts as a catch-all for anything that did not match earlier conditions.
This pattern scales cleanly to complex business rules. If you later need to add a "Very High" tier above $300, you just insert one line.
After the wrangle chunk, add this line of R Markdown prose (plain text, not inside a code chunk):
The dataset contains `r nrow(shopping)` customer records with `r ncol(shopping)` features after wrangling.
What is inline R code?
The backtick-r syntax (`r expression`) lets you embed computed values directly in your narrative text. When the report knits, R evaluates the expression and inserts the result. This keeps your prose in sync with your data automatically.
- Save analysis_report.Rmd.
- Run the wrangle chunk by clicking its green Run arrow.
- In the console, type ncol(shopping) and press Enter. You should see 12 (the original 10 columns plus spend_category and age_group).
Still seeing 10 columns?
- Confirm you ran the wrangle chunk after the generate-data chunk. Chunks must run in order because each depends on the previous one.
- Check that shopping <- shopping %>% reassigns the result back to shopping. Without the assignment, the new columns are computed but not saved.
help me debug why my wrangle chunk did not add new columns
Summarize spending by category and season
Before diving into hypothesis tests, you need a quick overview of how spending breaks down across product categories and seasons. Grouped summaries reveal patterns worth investigating further.
- Below the inline text you just added, insert the summary statistics chunk:
```{r summary-stats}
shopping %>%
group_by(category) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
shopping %>%
group_by(season) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
```
What does this code do?
- The %>% (pipe operator) passes the result of one function into the next, reading left to right like a sentence: "take shopping, group it, then summarize it."
- group_by(category) splits the data into subgroups. summarize() then computes metrics within each group independently.
- n() counts rows per group, so you can see how many customers fall into each category or season.
- Save analysis_report.Rmd.
- Run the summary-stats chunk. You should see two tibbles printed: one with 4 rows (one per product category) and one with 4 rows (one per season), each showing average spend, median spend, and count.
Seeing an object not found error?
- This means a previous chunk did not run. Click the downward arrow next to the Run button and select Run All Chunks Above to execute all preceding chunks in order.
- Verify that you typed summarize correctly. Both summarize and summarise work in dplyr, but make sure the spelling matches what you typed in the chunk.
help me fix my summary-stats chunk error
✔️ Awesome, I've got everything!
Great. Double check you have saved analysis_report.Rmd and that all four chunks (setup, generate-data, wrangle, summary-stats) run without errors.
ⓧ I'd like to double check the full code
Here is the complete analysis_report.Rmd file at this point. Compare yours against it:
---
title: "E-Commerce Customer Analysis Report"
author: "Your Name"
date: "`r Sys.Date()`"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
library(tidyverse)
```
```{r generate-data}
set.seed(123)
n <- 500
shopping <- tibble(
customer_id = 1:n,
age = sample(18:70, n, replace = TRUE),
gender = sample(c("Male", "Female", "Non-binary"), n, replace = TRUE, prob = c(0.45, 0.45, 0.10)),
category = sample(c("Clothing", "Footwear", "Accessories", "Electronics"), n, replace = TRUE, prob = c(0.35, 0.25, 0.20, 0.20)),
season = sample(c("Spring", "Summer", "Fall", "Winter"), n, replace = TRUE),
previous_purchases = sample(0:50, n, replace = TRUE),
review_rating = round(runif(n, min = 1, max = 5), 1),
discount_applied = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.40, 0.60)),
subscription_status = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.35, 0.65))
)
shopping <- shopping %>%
mutate(
purchase_amount = round(
50 + (age * 0.8) + (previous_purchases * 1.2) +
if_else(discount_applied == "Yes", rnorm(n, mean = 15, sd = 10), 0) +
rnorm(n, mean = 0, sd = 30),
2
),
purchase_amount = pmax(purchase_amount, 5)
)
glimpse(shopping)
```
```{r wrangle}
shopping <- shopping %>%
mutate(
spend_category = case_when(
purchase_amount < 50 ~ "Low",
purchase_amount < 150 ~ "Medium",
TRUE ~ "High"
),
age_group = case_when(
age < 30 ~ "Young",
age < 50 ~ "Middle",
TRUE ~ "Senior"
)
)
```
The dataset contains `r nrow(shopping)` customer records with `r ncol(shopping)` features after wrangling.
```{r summary-stats}
shopping %>%
group_by(category) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
shopping %>%
group_by(season) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
```
Your data foundation is solid. You have 500 customer records with 12 features, plus grouped summaries showing how spending varies across categories and seasons. Next up, you will turn these numbers into publication-quality visualizations with ggplot2.
Visualize Data with ggplot2
Your dataset is ready with 500 rows of wrangled data and summary statistics computed for every product category and season. Now it's time to see what stories the data tells visually.
In this step, you'll build four publication-quality visualizations using ggplot2. Each chart answers a different question about customer spending patterns.
In this step, get ready to:
- Build a histogram with a density curve showing how purchase amounts are distributed.
- Create a boxplot and scatter plot comparing spending across categories and purchase history.
- Build a correlation heatmap identifying relationships between all numeric variables.
Build a histogram with density overlay
A histogram shows how data is distributed across a range. Layering a density curve on top smooths out the bins, making the overall shape easier to interpret.
- In analysis_report.Rmd, scroll to the bottom of your file (below the summary-stats chunk's closing backticks).
- Add the following histogram code chunk:
```{r histogram, fig.cap="Distribution of purchase amounts with density overlay"}
ggplot(shopping, aes(x = purchase_amount)) +
geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "steelblue", alpha = 0.7) +
geom_density(color = "red", linewidth = 1) +
labs(
title = "Distribution of Purchase Amounts",
x = "Purchase Amount ($)",
y = "Density"
) +
theme_minimal()
```
What does this code do?
This chunk creates a layered visualization combining a histogram and a density curve.
- geom_histogram(aes(y = after_stat(density))) rescales the histogram bars so their area integrates to 1, making the bars compatible with the density overlay.
- geom_density(color = "red") adds a smooth curve that reveals the overall shape of the distribution without the choppiness of individual bins.
- theme_minimal() strips away visual clutter (grey background, gridlines) for a cleaner look.
- Save analysis_report.Rmd.
- Click the green play button on the histogram chunk header to run it. You should see a blue histogram with a red density curve overlaid.
Don't see a plot?
- Make sure you ran the generate-data and wrangle chunks first. The shopping tibble must exist in your environment.
- Check that the chunk starts with ```{r histogram and ends with ``` on its own line.
Why isn't my ggplot2 histogram rendering in RStudio?
Create a boxplot and scatter plot
Boxplots reveal how spending varies across categories. A scatter plot with a trend line shows whether past purchasing behavior predicts current spending.
- Below the histogram chunk, add this boxplot chunk:
```{r boxplot, fig.cap="Purchase amount distribution across product categories"}
ggplot(shopping, aes(x = category, y = purchase_amount, fill = category)) +
geom_boxplot() +
labs(
title = "Purchase Amount by Product Category",
x = "Category",
y = "Purchase Amount ($)"
) +
theme_minimal() +
theme(legend.position = "none")
```
What does this code do?
- fill = category assigns a distinct color to each product category's box.
- geom_boxplot() draws the median, interquartile range, and outliers for each category so you can quickly compare their distributions.
- theme(legend.position = "none") hides the legend because the x-axis labels already name each category.
- Run the boxplot chunk by clicking its green play button. You should see four colored boxes, one per product category.
Seeing overlapping labels?
If category names overlap on the x-axis, widen your Plots pane by dragging its border. The labels need horizontal space to render without collision.
Help me fix overlapping axis labels on my ggplot2 boxplot.
- Below the boxplot chunk, add this scatter plot chunk:
```{r scatter, fig.cap="Relationship between previous purchases and current purchase amount"}
ggplot(shopping, aes(x = previous_purchases, y = purchase_amount)) +
geom_point(alpha = 0.3, color = "steelblue") +
geom_smooth(method = "lm", color = "red", se = TRUE) +
labs(
title = "Previous Purchases vs. Purchase Amount",
x = "Number of Previous Purchases",
y = "Purchase Amount ($)"
) +
theme_minimal()
```
What does this code do?
- geom_point(alpha = 0.3) makes points semi-transparent so you can see where data clusters densely.
- geom_smooth(method = "lm") fits a linear regression line through the points, with a grey confidence band (se = TRUE) showing the uncertainty around that trend.
- Run the scatter chunk. You should see a cloud of blue points with a red upward trend line and a grey confidence band.
- Save analysis_report.Rmd.
Seeing a warning about the smoothing method?
If you see a message about the formula used for smoothing, this is informational and does not affect your plot. The visualization still renders correctly.
Why is RStudio showing me a formula message when I use geom_smooth?
Build a correlation heatmap
A correlation heatmap shows how strongly each numeric variable relates to every other one. This helps identify which variables might be useful predictors for regression analysis later.
- Below the scatter chunk, add this heatmap chunk:
```{r heatmap, fig.cap="Correlation matrix of numeric variables"}
numeric_data <- shopping %>%
select(where(is.numeric))
cor_matrix <- cor(numeric_data, use = "complete.obs")
cor_df <- as.data.frame(cor_matrix) %>%
mutate(var1 = rownames(cor_matrix)) %>%
pivot_longer(cols = -var1, names_to = "var2", values_to = "correlation")
ggplot(cor_df, aes(x = var1, y = var2, fill = correlation)) +
geom_tile() +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
labs(title = "Correlation Heatmap of Numeric Variables") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
```
What does this code do?
This chunk takes three steps to produce the heatmap.
- select(where(is.numeric)) pulls only the numeric columns from shopping, ignoring text-based columns like gender and category.
- cor() computes a correlation matrix, and pivot_longer() reshapes it from a wide table into a long format that ggplot2 can map to tile positions.
- scale_fill_gradient2(low = "blue", mid = "white", high = "red") creates a diverging color scale where blue means negative correlation, white means no correlation, and red means positive correlation.
- Save analysis_report.Rmd and run the heatmap chunk.
- You should see a grid of colored tiles with variable names on both axes, angled at 45 degrees on the x-axis.
Heatmap labels cut off?
If axis labels are clipped, widen your Plots pane or click the **Zoom** button above the plot to open it in a larger window.
Help me fix my correlation heatmap axis labels being cut off.
✔️ Awesome, I've got everything!
Great. Double check you've saved analysis_report.Rmd with all four visualization chunks.
ⓧ I'd like to double check the full code
Here is the complete analysis_report.Rmd file as it should look at the end of this step:
---
title: "E-Commerce Customer Analysis Report"
author: "Your Name"
date: "`r Sys.Date()`"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
library(tidyverse)
```
```{r generate-data}
set.seed(123)
n <- 500
shopping <- tibble(
customer_id = 1:n,
age = sample(18:70, n, replace = TRUE),
gender = sample(c("Male", "Female", "Non-binary"), n, replace = TRUE, prob = c(0.45, 0.45, 0.10)),
category = sample(c("Clothing", "Footwear", "Accessories", "Electronics"), n, replace = TRUE, prob = c(0.35, 0.25, 0.20, 0.20)),
season = sample(c("Spring", "Summer", "Fall", "Winter"), n, replace = TRUE),
previous_purchases = sample(0:50, n, replace = TRUE),
review_rating = round(runif(n, min = 1, max = 5), 1),
discount_applied = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.40, 0.60)),
subscription_status = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.35, 0.65))
)
shopping <- shopping %>%
mutate(
purchase_amount = round(
50 + (age * 0.8) + (previous_purchases * 1.2) +
if_else(discount_applied == "Yes", rnorm(n, mean = 15, sd = 10), 0) +
rnorm(n, mean = 0, sd = 30),
2
),
purchase_amount = pmax(purchase_amount, 5)
)
glimpse(shopping)
```
```{r wrangle}
shopping <- shopping %>%
mutate(
spend_category = case_when(
purchase_amount < 50 ~ "Low",
purchase_amount < 150 ~ "Medium",
TRUE ~ "High"
),
age_group = case_when(
age < 30 ~ "Young",
age < 50 ~ "Middle",
TRUE ~ "Senior"
)
)
```
The dataset contains `r nrow(shopping)` customer records with `r ncol(shopping)` features after wrangling.
```{r summary-stats}
shopping %>%
group_by(category) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
shopping %>%
group_by(season) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
```
```{r histogram, fig.cap="Distribution of purchase amounts with density overlay"}
ggplot(shopping, aes(x = purchase_amount)) +
geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "steelblue", alpha = 0.7) +
geom_density(color = "red", linewidth = 1) +
labs(
title = "Distribution of Purchase Amounts",
x = "Purchase Amount ($)",
y = "Density"
) +
theme_minimal()
```
```{r boxplot, fig.cap="Purchase amount distribution across product categories"}
ggplot(shopping, aes(x = category, y = purchase_amount, fill = category)) +
geom_boxplot() +
labs(
title = "Purchase Amount by Product Category",
x = "Category",
y = "Purchase Amount ($)"
) +
theme_minimal() +
theme(legend.position = "none")
```
```{r scatter, fig.cap="Relationship between previous purchases and current purchase amount"}
ggplot(shopping, aes(x = previous_purchases, y = purchase_amount)) +
geom_point(alpha = 0.3, color = "steelblue") +
geom_smooth(method = "lm", color = "red", se = TRUE) +
labs(
title = "Previous Purchases vs. Purchase Amount",
x = "Number of Previous Purchases",
y = "Purchase Amount ($)"
) +
theme_minimal()
```
```{r heatmap, fig.cap="Correlation matrix of numeric variables"}
numeric_data <- shopping %>%
select(where(is.numeric))
cor_matrix <- cor(numeric_data, use = "complete.obs")
cor_df <- as.data.frame(cor_matrix) %>%
mutate(var1 = rownames(cor_matrix)) %>%
pivot_longer(cols = -var1, names_to = "var2", values_to = "correlation")
ggplot(cor_df, aes(x = var1, y = var2, fill = correlation)) +
geom_tile() +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
labs(title = "Correlation Heatmap of Numeric Variables") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
```
You now have four visualizations that reveal spending patterns, category differences, and variable relationships. Next up, you'll run hypothesis tests that answer specific business questions with statistical confidence.
Run Hypothesis Tests
Your visualizations tell a story about what the data looks like. Now you need to answer a harder question: are the patterns you see real, or just noise?
In this step, you will run three hypothesis tests that answer specific business questions with statistical confidence. Each test produces a p-value that you will interpret in plain language a stakeholder can act on.
In this step, get ready to:
- Run a two-sample t-test comparing discount vs. non-discount spending.
- Run an ANOVA and a chi-squared test to answer two more business questions.
- Write a Recommendations section that auto-updates when data changes.
Test discount spending with a t-test
The first business question is: do customers who use discounts spend significantly more than those who don't? A two-sample t-test compares the average spending of two groups and tells you whether the difference is large enough to be real.
- In analysis_report.Rmd, scroll to the bottom of the file (below the heatmap code chunk).
- Add the following t-test section by pasting this code at the end of your file:
```{r ttest}
# Split purchase amounts into two groups based on discount usage
discount_yes <- shopping %>%
filter(discount_applied == "Yes") %>%
pull(purchase_amount)
discount_no <- shopping %>%
filter(discount_applied == "No") %>%
pull(purchase_amount)
# Run a two-sample t-test comparing the two groups
t_test_result <- t.test(discount_yes, discount_no)
```
The two-sample t-test yields t = `r round(t_test_result$statistic, 2)`, p-value = `r round(t_test_result$p.value, 4)`. The mean spending for discount users is $`r round(mean(discount_yes), 2)` compared to $`r round(mean(discount_no), 2)` for non-discount users.
**Business interpretation:** `r if(t_test_result$p.value < 0.05) {"The difference is statistically significant at the 0.05 level, suggesting discounts are associated with higher spending per transaction."} else {"The difference is NOT statistically significant at the 0.05 level. Discount users do not spend meaningfully more per transaction than non-discount users."}`
What does this code do?
The R code chunk splits your data into two vectors using filter() and pull(). One vector holds purchase amounts for discount users. The other holds purchase amounts for non-discount users.
The t.test() function compares the means and returns a t-statistic, p-value, and confidence interval. The narrative below the chunk uses inline R (backtick-r syntax) to pull these values directly into the report text so they update automatically if the data changes.
The inline if/else block writes a different interpretation depending on whether the p-value is below 0.05. This makes the conclusion update itself when the underlying data changes.
- Save analysis_report.Rmd.
- Click the green play button on the ttest chunk (or place your cursor inside and press Ctrl+Shift+Enter). You should see the t-test output in the console, including a t-statistic and p-value.
Seeing 'object not found' errors?
The ttest chunk depends on the shopping tibble from earlier chunks. Run all chunks above this one first (click the downward arrow icon at the top-left of the ttest chunk, or use Ctrl+Alt+P to run all previous chunks).
help me debug my t-test chunk in R Markdown
What is a p-value?
A p-value answers: "If there were truly no difference between these groups, how likely would I be to see a gap this large just by chance?" A p-value below 0.05 means there is less than a 5% probability the difference is due to random variation.
This threshold (0.05) is a convention, not a law. It gives you a principled way to say "this pattern is probably real" vs. "this could just be noise."
Run ANOVA and chi-squared tests
The t-test compared two groups. Your next two tests handle different shapes of questions. ANOVA compares means across more than two groups (four seasons). A chi-squared test checks whether two categorical variables are associated.
- Below the t-test business interpretation, add the ANOVA section by pasting this code:
```{r anova}
# Test whether purchase amounts differ across seasons
anova_result <- aov(purchase_amount ~ season, data = shopping)
anova_summary <- summary(anova_result)
# Extract the F-statistic and p-value from the summary table
f_stat <- anova_summary[[1]]$`F value`[1]
p_val_anova <- anova_summary[[1]]$`Pr(>F)`[1]
```
The one-way ANOVA yields F = `r round(f_stat, 2)`, p-value = `r round(p_val_anova, 4)`.
**Business interpretation:** `r if(p_val_anova < 0.05) {"Spending differs significantly across seasons. Consider seasonal pricing or promotional strategies."} else {"There is no statistically significant difference in spending across seasons. Customers spend roughly the same regardless of time of year."}`
What does the ANOVA do?
The aov() function tests whether the mean purchase amount differs significantly across all four seasons at once. The formula purchase_amount ~ season reads as "purchase amount predicted by season."
The F-statistic measures how much the group means differ relative to the variation within each group. A large F means the seasons genuinely differ. A small F (with a high p-value) means seasonal differences are indistinguishable from random noise.
- Below the ANOVA business interpretation, add the chi-squared test section by pasting this code:
```{r chisq}
# Build a contingency table of discount usage vs. subscription status
contingency <- table(shopping$discount_applied, shopping$subscription_status)
# Run the chi-squared test for independence
chi_result <- chisq.test(contingency)
```
The chi-squared test yields X-squared = `r round(chi_result$statistic, 2)`, p-value = `r round(chi_result$p.value, 4)`.
**Business interpretation:** `r if(chi_result$p.value < 0.05) {"There IS a statistically significant association between discount usage and subscription status. This has implications for how we target promotions."} else {"There is NO statistically significant association between discount usage and subscription status. Subscribers and non-subscribers use discounts at similar rates."}`
What does the chi-squared test check?
Unlike the t-test and ANOVA (which compare numeric means), the chi-squared test checks whether two categorical variables are independent. Here it asks: "Does knowing whether a customer uses discounts tell you anything about whether they are a subscriber?"
The table() function creates a 2x2 contingency table counting how many customers fall into each combination of discount/no-discount and subscriber/non-subscriber. chisq.test() compares the observed counts to what you would expect if the two variables had no relationship.
- Save analysis_report.Rmd.
- Run the anova and chisq chunks by clicking each chunk's green play button. You should see F-statistic output from the ANOVA and an X-squared value from the chi-squared test.
Chi-squared test throwing a warning?
With 500 rows and only four cells in the contingency table, expected counts should be well above 5. If you see a warning about expected frequencies, double-check that you are using the full shopping tibble (500 rows) and that the earlier chunks ran successfully.
help me debug my chi-squared test chunk
Write business recommendations with inline R
Statistical findings only matter if someone acts on them. Your final section translates each p-value into a concrete business recommendation that updates automatically when the data changes.
- Below the chi-squared business interpretation, add the Recommendations section by pasting this code:
# Recommendations
1. **Discount Strategy:** `r if(t_test_result$p.value < 0.05) {"Discounts are driving higher spending. Continue the program but monitor margins."} else {"Discounts are not driving higher per-transaction spending. Consider whether they serve other purposes (acquisition, retention) or need restructuring."}`
2. **Seasonal Planning:** `r if(p_val_anova < 0.05) {"Allocate marketing budgets to peak spending seasons."} else {"Spending is consistent year-round. Distribute marketing budgets evenly rather than concentrating in specific seasons."}`
3. **Subscription Targeting:** `r if(chi_result$p.value < 0.05) {"Tailor promotional offers based on subscription status."} else {"A universal promotion strategy is appropriate regardless of subscription status."}`
Why use inline R in the recommendations?
Each recommendation uses an inline if/else that checks the p-value from the corresponding test. If the p-value is below 0.05, the report prints an action-oriented recommendation. If not, it prints an alternative recommendation.
This is the power of R Markdown reproducibility. If you swap in a new dataset tomorrow, every number and every recommendation in the report updates when you knit. No manual edits needed.
- Save analysis_report.Rmd.
- Knit the report by pressing Ctrl+Shift+K. The HTML output should open and display all three hypothesis test results with their interpretations plus the Recommendations section at the bottom.
Knit fails with inline code errors?
Inline R expressions must be on a single line with no line breaks inside the backtick-r syntax. If you accidentally split an inline expression across two lines, the knit will fail. Check that each backtick-r block sits entirely on one line.
Also confirm that curly braces and quotes are balanced in each inline if/else expression. A missing closing brace or mismatched quote will stop the knit.
help me fix my R Markdown knit error
✔️ Awesome, I've got everything!
Great. Double check you have saved your file and that the knitted HTML shows all three test results with business interpretations.
ⓧ I'd like to double check the full code
Here is the complete analysis_report.Rmd file as it should look at this point:
---
title: "E-Commerce Customer Analysis Report"
author: "Your Name"
date: "`r Sys.Date()`"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
library(tidyverse)
```
```{r generate-data}
set.seed(123)
n <- 500
shopping <- tibble(
customer_id = 1:n,
age = sample(18:70, n, replace = TRUE),
gender = sample(c("Male", "Female", "Non-binary"), n, replace = TRUE, prob = c(0.45, 0.45, 0.10)),
category = sample(c("Clothing", "Footwear", "Accessories", "Electronics"), n, replace = TRUE, prob = c(0.35, 0.25, 0.20, 0.20)),
season = sample(c("Spring", "Summer", "Fall", "Winter"), n, replace = TRUE),
previous_purchases = sample(0:50, n, replace = TRUE),
review_rating = round(runif(n, min = 1, max = 5), 1),
discount_applied = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.40, 0.60)),
subscription_status = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.35, 0.65))
)
shopping <- shopping %>%
mutate(
purchase_amount = round(
50 + (age * 0.8) + (previous_purchases * 1.2) +
if_else(discount_applied == "Yes", rnorm(n, mean = 15, sd = 10), 0) +
rnorm(n, mean = 0, sd = 30),
2
),
purchase_amount = pmax(purchase_amount, 5)
)
glimpse(shopping)
```
```{r wrangle}
shopping <- shopping %>%
mutate(
spend_category = case_when(
purchase_amount < 50 ~ "Low",
purchase_amount < 150 ~ "Medium",
TRUE ~ "High"
),
age_group = case_when(
age < 30 ~ "Young",
age < 50 ~ "Middle",
TRUE ~ "Senior"
)
)
```
The dataset contains `r nrow(shopping)` customer records with `r ncol(shopping)` features after wrangling.
```{r summary-stats}
shopping %>%
group_by(category) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
shopping %>%
group_by(season) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
```
```{r histogram, fig.cap="Distribution of purchase amounts with density overlay"}
ggplot(shopping, aes(x = purchase_amount)) +
geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "steelblue", alpha = 0.7) +
geom_density(color = "red", linewidth = 1) +
labs(
title = "Distribution of Purchase Amounts",
x = "Purchase Amount ($)",
y = "Density"
) +
theme_minimal()
```
```{r boxplot, fig.cap="Purchase amount distribution across product categories"}
ggplot(shopping, aes(x = category, y = purchase_amount, fill = category)) +
geom_boxplot() +
labs(
title = "Purchase Amount by Product Category",
x = "Category",
y = "Purchase Amount ($)"
) +
theme_minimal() +
theme(legend.position = "none")
```
```{r scatter, fig.cap="Relationship between previous purchases and current purchase amount"}
ggplot(shopping, aes(x = previous_purchases, y = purchase_amount)) +
geom_point(alpha = 0.3, color = "steelblue") +
geom_smooth(method = "lm", color = "red", se = TRUE) +
labs(
title = "Previous Purchases vs. Purchase Amount",
x = "Number of Previous Purchases",
y = "Purchase Amount ($)"
) +
theme_minimal()
```
```{r heatmap, fig.cap="Correlation matrix of numeric variables"}
numeric_data <- shopping %>%
select(where(is.numeric))
cor_matrix <- cor(numeric_data, use = "complete.obs")
cor_df <- as.data.frame(cor_matrix) %>%
mutate(var1 = rownames(cor_matrix)) %>%
pivot_longer(cols = -var1, names_to = "var2", values_to = "correlation")
ggplot(cor_df, aes(x = var1, y = var2, fill = correlation)) +
geom_tile() +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
labs(title = "Correlation Heatmap of Numeric Variables") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
```
```{r ttest}
discount_yes <- shopping %>%
filter(discount_applied == "Yes") %>%
pull(purchase_amount)
discount_no <- shopping %>%
filter(discount_applied == "No") %>%
pull(purchase_amount)
t_test_result <- t.test(discount_yes, discount_no)
```
The two-sample t-test yields t = `r round(t_test_result$statistic, 2)`, p-value = `r round(t_test_result$p.value, 4)`. The mean spending for discount users is $`r round(mean(discount_yes), 2)` compared to $`r round(mean(discount_no), 2)` for non-discount users.
**Business interpretation:** `r if(t_test_result$p.value < 0.05) {"The difference is statistically significant at the 0.05 level, suggesting discounts are associated with higher spending per transaction."} else {"The difference is NOT statistically significant at the 0.05 level. Discount users do not spend meaningfully more per transaction than non-discount users."}`
```{r anova}
anova_result <- aov(purchase_amount ~ season, data = shopping)
anova_summary <- summary(anova_result)
f_stat <- anova_summary[[1]]$`F value`[1]
p_val_anova <- anova_summary[[1]]$`Pr(>F)`[1]
```
The one-way ANOVA yields F = `r round(f_stat, 2)`, p-value = `r round(p_val_anova, 4)`.
**Business interpretation:** `r if(p_val_anova < 0.05) {"Spending differs significantly across seasons. Consider seasonal pricing or promotional strategies."} else {"There is no statistically significant difference in spending across seasons. Customers spend roughly the same regardless of time of year."}`
```{r chisq}
contingency <- table(shopping$discount_applied, shopping$subscription_status)
chi_result <- chisq.test(contingency)
```
The chi-squared test yields X-squared = `r round(chi_result$statistic, 2)`, p-value = `r round(chi_result$p.value, 4)`.
**Business interpretation:** `r if(chi_result$p.value < 0.05) {"There IS a statistically significant association between discount usage and subscription status. This has implications for how we target promotions."} else {"There is NO statistically significant association between discount usage and subscription status. Subscribers and non-subscribers use discounts at similar rates."}`
# Recommendations
1. **Discount Strategy:** `r if(t_test_result$p.value < 0.05) {"Discounts are driving higher spending. Continue the program but monitor margins."} else {"Discounts are not driving higher per-transaction spending. Consider whether they serve other purposes (acquisition, retention) or need restructuring."}`
2. **Seasonal Planning:** `r if(p_val_anova < 0.05) {"Allocate marketing budgets to peak spending seasons."} else {"Spending is consistent year-round. Distribute marketing budgets evenly rather than concentrating in specific seasons."}`
3. **Subscription Targeting:** `r if(chi_result$p.value < 0.05) {"Tailor promotional offers based on subscription status."} else {"A universal promotion strategy is appropriate regardless of subscription status."}`
Your report now answers three real business questions with statistical evidence. In the next step, you will assemble the full report with professional formatting and knit it into a polished HTML document ready to share.
Assemble and Knit the Report
Your analysis is complete. You have visualizations, hypothesis tests, and business interpretations all living in your .Rmd file. The missing piece is structure.
A professional report guides the reader from summary to evidence to recommendations. In this step, you will organize your file into named sections, hide the raw code from the final output, and knit the whole thing into a polished HTML document.
In this step, get ready to:
- Organize your .Rmd into Executive Summary, Methodology, Results, and Recommendations sections.
- Configure YAML and chunk options so the final report hides code and shows a table of contents.
- Knit the report to a standalone HTML file.
Add the Executive Summary and Methodology headers
A report reader wants the punchline first, then the details. You will add an Executive Summary at the top and group your data generation and wrangling under a Methodology section.
- In analysis_report.Rmd, find the blank line between your setup chunk (the one ending with library(tidyverse)) and your generate-data chunk.
- Insert the following markdown text in that gap:
# Executive Summary
This report analyzes 500 e-commerce customer transactions to answer three business questions using statistical tests. Key findings are presented with visualizations and actionable recommendations.
# Methodology
## Data Generation
We simulate a realistic e-commerce dataset with 500 customers to demonstrate a reproducible analysis workflow.
What does this structure do?
The Executive Summary gives a busy reader the key takeaway without scrolling. The Methodology section groups your data generation and wrangling so reviewers can audit your approach separately from the findings.
Add section headers throughout the document
Each code chunk and interpretation block needs a heading so the table of contents can link to it. You will add markdown headers above each chunk.
- Above your wrangle chunk, add:
- ## Data Wrangling (on its own line, with a blank line above and below).
- Above your summary-stats chunk, add:
- ## Summary Statistics (same pattern: blank line above and below).
- Now add the Results section. Above your histogram chunk, insert these two headers:
# Results
## Spending Distribution
- Above your boxplot chunk, add: ## Spending by Category
- Above your scatter chunk, add: ## Previous Purchases vs. Spending
- Above your heatmap chunk, add: ## Correlation Heatmap
- Above your ttest chunk, add the following heading and question line:
## Hypothesis Test 1: Discount Impact on Spending
**Question:** Do customers who use discounts spend significantly more than those who don't?
- Above your anova chunk, add:
## Hypothesis Test 2: Seasonal Spending Differences
**Question:** Does average purchase amount differ significantly across seasons?
- Above your chisq chunk, add:
## Hypothesis Test 3: Discount and Subscription Association
**Question:** Is there a statistically significant association between discount usage and subscription status?
- Finally, find the # Recommendations header at the bottom.
- Add the line Based on the statistical evidence: between the header and the numbered list.
Why add question lines above each test?
Each hypothesis test section now opens with the business question it answers. A reader scanning the table of contents can jump straight to the question they care about without reading every section in order.
- Save analysis_report.Rmd.
Not sure where a header goes?
Each header sits on its own line with a blank line above and below it. If you place a header directly against a code chunk fence (the triple backticks), the markdown parser may not recognize it.
help me check my section headers are in the right places in my .Rmd
Configure the report to hide code and show a table of contents
Your report is for a business audience. They want findings and recommendations, not raw R code. You will update two areas: the YAML header (to add navigation) and the setup chunk (to hide code globally).
- At the top of analysis_report.Rmd, find this line in the YAML header:
output: html_document
- Replace it with this multi-line YAML block:
output:
html_document:
toc: true
toc_float: true
theme: cosmo
What do these YAML options do?
- toc: true generates a table of contents from your markdown headers.
- toc_float: true pins the table of contents to the left side so it stays visible as the reader scrolls.
- theme: cosmo applies a clean, modern visual theme to the HTML output.
- Now find this line in your setup chunk:
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
- Change echo = TRUE to echo = FALSE so the line reads:
knitr::opts_chunk$set(echo = FALSE, message = FALSE, warning = FALSE)
What does echo = FALSE do?
Setting echo = FALSE globally hides your R source code from the rendered HTML. The code still runs and its output (plots, tables, inline values) still appears. Your audience sees results without having to scroll past code they don't need.
- Save analysis_report.Rmd.
YAML indentation matters
YAML uses spaces (not tabs) for indentation. If you see a knitting error mentioning the YAML header, check that html_document: is indented two spaces under output:, and toc: is indented four spaces total.
help me fix a YAML parsing error in my R Markdown file
Knit the report to HTML
Knitting runs every code chunk in sequence, evaluates inline R expressions, and passes the result through Pandoc to produce the final HTML file.
- Click the Knit button in the toolbar above your editor pane.
- Alternatively, press Ctrl+Shift+K to knit.
What happens when you knit?
RStudio calls rmarkdown::render() under the hood. This feeds your .Rmd to knitr (which executes the R code), then Pandoc converts the result to HTML.
After a few seconds, a browser window opens with your finished report. You should see a floating table of contents on the left, an Executive Summary at the top, four visualizations with captions, three hypothesis test results with inline p-values, and no visible R code anywhere.
- Confirm that analysis_report.html now appears in your ecommerce-analysis project folder (visible in the Files pane in RStudio).
Seeing a knitting error?
The R Markdown tab at the bottom of RStudio shows the exact line where knitting failed. Common causes:
- A section header placed inside a code chunk fence. Headers must sit between chunks, not inside them.
- A missing blank line between a header and a code chunk fence. R Markdown needs a blank line to separate markdown from chunk boundaries.
- The YAML block has a tab character instead of spaces. Delete the indentation and re-type it with the spacebar.
help me debug my R Markdown knitting error
✔️ Awesome, I've got everything!
Great. Double check you have saved your file and that the HTML report is visible in your Files pane.
ⓧ I'd like to double check the full code
Here is the complete analysis_report.Rmd file as it should look after this step:
---
title: "E-Commerce Customer Analysis Report"
author: "Your Name"
date: "`r Sys.Date()`"
output:
html_document:
toc: true
toc_float: true
theme: cosmo
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, message = FALSE, warning = FALSE)
library(tidyverse)
```
# Executive Summary
This report analyzes 500 e-commerce customer transactions to answer three business questions using statistical tests. Key findings are presented with visualizations and actionable recommendations.
# Methodology
## Data Generation
We simulate a realistic e-commerce dataset with 500 customers to demonstrate a reproducible analysis workflow.
```{r generate-data}
set.seed(123)
n <- 500
shopping <- tibble(
customer_id = 1:n,
age = sample(18:70, n, replace = TRUE),
gender = sample(c("Male", "Female", "Non-binary"), n, replace = TRUE, prob = c(0.45, 0.45, 0.10)),
category = sample(c("Clothing", "Footwear", "Accessories", "Electronics"), n, replace = TRUE, prob = c(0.35, 0.25, 0.20, 0.20)),
season = sample(c("Spring", "Summer", "Fall", "Winter"), n, replace = TRUE),
previous_purchases = sample(0:50, n, replace = TRUE),
review_rating = round(runif(n, min = 1, max = 5), 1),
discount_applied = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.40, 0.60)),
subscription_status = sample(c("Yes", "No"), n, replace = TRUE, prob = c(0.35, 0.65))
)
shopping <- shopping %>%
mutate(
purchase_amount = round(
50 + (age * 0.8) + (previous_purchases * 1.2) +
if_else(discount_applied == "Yes", rnorm(n, mean = 15, sd = 10), 0) +
rnorm(n, mean = 0, sd = 30),
2
),
purchase_amount = pmax(purchase_amount, 5)
)
glimpse(shopping)
```
## Data Wrangling
```{r wrangle}
shopping <- shopping %>%
mutate(
spend_category = case_when(
purchase_amount < 50 ~ "Low",
purchase_amount < 150 ~ "Medium",
TRUE ~ "High"
),
age_group = case_when(
age < 30 ~ "Young",
age < 50 ~ "Middle",
TRUE ~ "Senior"
)
)
```
The dataset contains `r nrow(shopping)` customer records with `r ncol(shopping)` features after wrangling.
## Summary Statistics
```{r summary-stats}
shopping %>%
group_by(category) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
shopping %>%
group_by(season) %>%
summarize(
avg_spend = round(mean(purchase_amount), 2),
median_spend = round(median(purchase_amount), 2),
n = n()
)
```
# Results
## Spending Distribution
```{r histogram, fig.cap="Distribution of purchase amounts with density overlay"}
ggplot(shopping, aes(x = purchase_amount)) +
geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "steelblue", alpha = 0.7) +
geom_density(color = "red", linewidth = 1) +
labs(
title = "Distribution of Purchase Amounts",
x = "Purchase Amount ($)",
y = "Density"
) +
theme_minimal()
```
## Spending by Category
```{r boxplot, fig.cap="Purchase amount distribution across product categories"}
ggplot(shopping, aes(x = category, y = purchase_amount, fill = category)) +
geom_boxplot() +
labs(
title = "Purchase Amount by Product Category",
x = "Category",
y = "Purchase Amount ($)"
) +
theme_minimal() +
theme(legend.position = "none")
```
## Previous Purchases vs. Spending
```{r scatter, fig.cap="Relationship between previous purchases and current purchase amount"}
ggplot(shopping, aes(x = previous_purchases, y = purchase_amount)) +
geom_point(alpha = 0.3, color = "steelblue") +
geom_smooth(method = "lm", color = "red", se = TRUE) +
labs(
title = "Previous Purchases vs. Purchase Amount",
x = "Number of Previous Purchases",
y = "Purchase Amount ($)"
) +
theme_minimal()
```
## Correlation Heatmap
```{r heatmap, fig.cap="Correlation matrix of numeric variables"}
numeric_data <- shopping %>%
select(where(is.numeric))
cor_matrix <- cor(numeric_data, use = "complete.obs")
cor_df <- as.data.frame(cor_matrix) %>%
mutate(var1 = rownames(cor_matrix)) %>%
pivot_longer(cols = -var1, names_to = "var2", values_to = "correlation")
ggplot(cor_df, aes(x = var1, y = var2, fill = correlation)) +
geom_tile() +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
labs(title = "Correlation Heatmap of Numeric Variables") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
```
## Hypothesis Test 1: Discount Impact on Spending
**Question:** Do customers who use discounts spend significantly more than those who don't?
```{r ttest}
discount_yes <- shopping %>%
filter(discount_applied == "Yes") %>%
pull(purchase_amount)
discount_no <- shopping %>%
filter(discount_applied == "No") %>%
pull(purchase_amount)
t_test_result <- t.test(discount_yes, discount_no)
```
The two-sample t-test yields t = `r round(t_test_result$statistic, 2)`, p-value = `r round(t_test_result$p.value, 4)`. The mean spending for discount users is $`r round(mean(discount_yes), 2)` compared to $`r round(mean(discount_no), 2)` for non-discount users.
**Business interpretation:** `r if(t_test_result$p.value < 0.05) {"The difference is statistically significant at the 0.05 level, suggesting discounts are associated with higher spending per transaction."} else {"The difference is NOT statistically significant at the 0.05 level. Discount users do not spend meaningfully more per transaction than non-discount users."}`
## Hypothesis Test 2: Seasonal Spending Differences
**Question:** Does average purchase amount differ significantly across seasons?
```{r anova}
anova_result <- aov(purchase_amount ~ season, data = shopping)
anova_summary <- summary(anova_result)
f_stat <- anova_summary[[1]]$`F value`[1]
p_val_anova <- anova_summary[[1]]$`Pr(>F)`[1]
```
The one-way ANOVA yields F = `r round(f_stat, 2)`, p-value = `r round(p_val_anova, 4)`.
**Business interpretation:** `r if(p_val_anova < 0.05) {"Spending differs significantly across seasons. Consider seasonal pricing or promotional strategies."} else {"There is no statistically significant difference in spending across seasons. Customers spend roughly the same regardless of time of year."}`
## Hypothesis Test 3: Discount and Subscription Association
**Question:** Is there a statistically significant association between discount usage and subscription status?
```{r chisq}
contingency <- table(shopping$discount_applied, shopping$subscription_status)
chi_result <- chisq.test(contingency)
```
The chi-squared test yields X-squared = `r round(chi_result$statistic, 2)`, p-value = `r round(chi_result$p.value, 4)`.
**Business interpretation:** `r if(chi_result$p.value < 0.05) {"There IS a statistically significant association between discount usage and subscription status. This has implications for how we target promotions."} else {"There is NO statistically significant association between discount usage and subscription status. Subscribers and non-subscribers use discounts at similar rates."}`
# Recommendations
Based on the statistical evidence:
1. **Discount Strategy:** `r if(t_test_result$p.value < 0.05) {"Discounts are driving higher spending. Continue the program but monitor margins."} else {"Discounts are not driving higher per-transaction spending. Consider whether they serve other purposes (acquisition, retention) or need restructuring."}`
2. **Seasonal Planning:** `r if(p_val_anova < 0.05) {"Allocate marketing budgets to peak spending seasons."} else {"Spending is consistent year-round. Distribute marketing budgets evenly rather than concentrating in specific seasons."}`
3. **Subscription Targeting:** `r if(chi_result$p.value < 0.05) {"Tailor promotional offers based on subscription status."} else {"A universal promotion strategy is appropriate regardless of subscription status."}`
Your report is now a standalone HTML file that anyone can open in a browser. It tells a complete story from data to conclusions, and if the underlying data ever changes, you just click Knit again to regenerate everything. Next up, you will tackle the Secret Mission and extend this report with predictive modeling.
Secret mission
Add Multiple Linear Regression
Your hypothesis tests answer "is there a difference?" but they can't answer "by how much?" or "what drives it?" In this extension, you'll build a predictive model that translates statistical coefficients into concrete dollar-value recommendations a business leader can act on.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- The ecommerce-analysis project folder (contains analysis_report.Rmd, analysis_report.html, and the .Rproj file).
- The tidyverse R package (installed locally in your R library).
Keep everything running
No action needed. Choose this if you're still actively building or want to extend the analysis further.
- Your ecommerce-analysis folder stays on your machine ready to reopen anytime.
- The tidyverse package remains installed and available for future R projects.
- Everything is local, so nothing is costing you money while it sits.
Pause - I'll come back to this later
Since this project runs entirely locally with no background processes, there is nothing to pause. Your files sit on disk until you need them again.
- Close RStudio to free up memory.
- When you're ready to return, open RStudio and go to File > Open Project. Navigate to your ecommerce-analysis folder and select the .Rproj file.
Delete - I don't want to use this again
Remove all project files and start fresh. This cannot be undone.
- Close RStudio.
- Open File Explorer and navigate to the location where you created the ecommerce-analysis folder.
- Right-click the ecommerce-analysis folder and select Delete.
- To remove the tidyverse package, open RStudio (or an R console) and run:
remove.packages("tidyverse")
What about tidyverse's dependencies?
Running remove.packages("tidyverse") removes the tidyverse meta-package but leaves its individual component packages (ggplot2, dplyr, tidyr, etc.) installed.
If you want to remove everything tidyverse installed, you would need to uninstall each package individually. However, these packages are widely useful for any future R work, so keeping them is a reasonable choice.
Nice Work!
Nice Work!
You just built a complete statistical analysis report that goes beyond dashboards into inferential reasoning. Your R Markdown file combines code, visualizations, and plain-language conclusions in one reproducible document.
You've learned how to:
- Build four publication-quality ggplot2 visualizations (histogram with density overlay, boxplot, scatter plot with trend line, and correlation heatmap) to explore data visually.
- Run three hypothesis tests (t-test, ANOVA, chi-squared) and interpret p-values in plain business language with actionable recommendations.
- Assemble a reproducible R Markdown report that knits to a polished HTML document with an executive summary, methodology, results, and recommendations.
- Secret Mission: Added multiple linear regression with train-test validation, RMSE evaluation, and coefficient interpretation as dollar-value business recommendations.
Ready to quiz yourself?