Recipe Complexity Analysis

Exploring relationships in a large recipe dataset

VAISHNAVI REDDY TUDI (s4196984)

19 October, 2025

Introduction

Introduction Cont:

Problem Statement

Plan:

Data

-Source: 1_Recipe_csv (1).csv (provided dataset).

-Observation: Each row represents one recipe.

Key variables:

-num_ingredients – number of ingredients.

-num_steps – number of preparation steps.

-category – recipe category.

#--- Load Packages and Read Dataset ---



if(!require(janitor)) install.packages("janitor")
if(!require(dplyr)) install.packages("dplyr")
if(!require(knitr)) install.packages("knitr")




library(janitor)
library(dplyr)
library(knitr)

#Read dataset



recipes <- read.csv("1_Recipe_csv (1).csv", stringsAsFactors = FALSE)

#Clean column names



recipes <- janitor::clean_names(recipes)
#--- Dataset Dimensions ---



dims <- data.frame(Rows = nrow(recipes), Columns = ncol(recipes))
knitr::kable(dims, caption = "Dataset Dimensions")
Dataset Dimensions
Rows Columns
62126 8
#--- Sample of Dataset (Compact View) ---



shorten <- function(x, n = 45){
ifelse(is.na(x), NA, ifelse(nchar(x) > n, paste0(substr(x, 1, n-3), "..."), x))
}




key_cols <- intersect(c("recipe_title","category","subcategory","num_ingredients","num_steps"),
names(recipes))
if(length(key_cols) == 0) key_cols <- names(recipes)[1(5, ncol(recipes))]




preview <- head(recipes[, key_cols, drop = FALSE], 6)
for(col in key_cols){
if(is.character(preview[[col]])) preview[[col]] <- shorten(preview[[col]], 45)
}
knitr::kable(preview, caption = "Sample of Dataset (Compact View)")
Sample of Dataset (Compact View)
recipe_title category subcategory num_ingredients num_steps
Air Fryer Potato Slices with Dipping Sauce Air Fryer Recipes Air Fryer Recipes 9 5
Gochujang Pork Belly Bites Air Fryer Recipes Air Fryer Recipes 5 4
3-Ingredient Air Fryer Everything Bagel Ch… Air Fryer Recipes Air Fryer Recipes 3 4
Air Fryer Everything Bagel Chicken Cutlets Air Fryer Recipes Air Fryer Recipes 9 9
Air Fryer Honey Sriracha Salmon Bites Air Fryer Recipes Air Fryer Recipes 5 5
Air Fryer Corn on The Cob Air Fryer Recipes Air Fryer Recipes 6 4
#--- Column Type Summary and Numeric Summary ---
#Column Type Summary



col_summary <- data.frame(
Column = names(recipes),
Type = sapply(recipes, function(x) class(x)[1])
)
knitr::kable(col_summary, caption = "Column Type Summary (Clean Output)")
Column Type Summary (Clean Output)
Column Type
recipe_title recipe_title character
category category character
subcategory subcategory character
description description character
ingredients ingredients character
directions directions character
num_ingredients num_ingredients integer
num_steps num_steps integer
#Numeric Summary



numeric_cols <- names(recipes)[sapply(recipes, is.numeric)]




if(length(numeric_cols) > 0){
num_summary <- data.frame(
Variable = numeric_cols,
Min = unname(sapply(recipes[numeric_cols], function(x) min(x, na.rm = TRUE))),
Mean = unname(sapply(recipes[numeric_cols], function(x) mean(x, na.rm = TRUE))),
Median = unname(sapply(recipes[numeric_cols], function(x) median(x, na.rm = TRUE))),
Max = unname(sapply(recipes[numeric_cols], function(x) max(x, na.rm = TRUE))),
Missing = unname(sapply(recipes[numeric_cols], function(x) sum(is.na(x))))
)
num_summary$Mean <- round(num_summary$Mean, 2)
num_summary$Median <- round(num_summary$Median, 2)
knitr::kable(num_summary, caption = "Numeric Summary of Key Variables")
} else {
cat("No numeric variables found in this dataset.")
}
Numeric Summary of Key Variables
Variable Min Mean Median Max Missing
num_ingredients 1 9.02 9 35 0
num_steps 1 4.66 4 25 0

Data Cont:

#**The dataset includes some main variables that describe each recipe.

1)recipe_title: The name of the recipe.

2)category: The main group a recipe belongs to, such as “Air Fryer”, “Desserts”, or “Breakfast”.

3)subcategory: A smaller group under each category that gives more detail about the recipe type.

4)num_ingredients: Shows how many ingredients are used in a recipe.

5)num_steps: Shows how many preparation steps are needed to complete the recipe.

-The columns category and subcategory were changed into factors, because they represent types or groups, not numbers. Each level of these factors stands for a different kind of recipe.

-The columns num_ingredients and num_steps were kept as numbers, because they show counts that can be used in calculations like averages or totals.

#**Before starting the analysis, a few cleaning steps were done to make the data ready:

1)Fixed column names so they are easier to use.

2)Changed data types to the correct formats (for example, text to factor, numbers to numeric).

3)Removed rows with missing or wrong values.

4)Checked that all numeric values are correct and make sense.

-These cleaning steps helped make sure the dataset is tidy, accurate, and ready to be used for graphs, summaries, and statistical tests.

Descriptive Statistics and Visualisation

-These plots show the overall distributions of ingredients and steps.

-A scatterplot is used to observe any visible relationship.

par(mfrow = c(1,2))
hist(recipes$num_ingredients, main = "Number of Ingredients", xlab = "Ingredients", col = "lightblue")
hist(recipes$num_steps, main = "Number of Steps", xlab = "Steps", col = "lightgreen")

par(mfrow = c(1,1))




plot(recipes$num_ingredients, recipes$num_steps,
main = "Steps vs Ingredients",
xlab = "Number of Ingredients",
ylab = "Number of Steps",
pch = 19, col = "blue")
abline(lm(num_steps ~ num_ingredients, data = recipes), col = "red", lwd = 2)

Descriptive Statistics Cont.

-Summary by category showing average ingredients and steps.

by_cat <- recipes %>%
group_by(category) %>%
summarise(
n_recipes = n(),
avg_ingredients = mean(num_ingredients, na.rm = TRUE),
avg_steps = mean(num_steps, na.rm = TRUE)
) %>%
arrange(desc(n_recipes))




knitr::kable(head(by_cat, 10), caption = "Top 10 Categories by Count and Average Complexity")
Top 10 Categories by Count and Average Complexity
category n_recipes avg_ingredients avg_steps
Main Dishes 3387 10.421022 5.496605
Healthy Recipes 2237 7.724184 4.003129
Appetizers And Snacks 2084 7.903551 4.198656
Cakes 1954 9.615148 5.618219
Cookies 1849 8.843699 5.214711
Beef Recipes 1400 10.569286 5.130000
Breads 1352 9.116124 5.714497
Desserts 1288 7.242236 5.356367
Breakfast And Brunch 1223 8.147997 4.270646
Pork 1205 10.507054 5.139419

Visualisation: Top Categories

-Bar chart of the top 10 categories by number of recipes.

#Make sure data is available



if (!exists("recipes")) {
recipes <- read.csv("1_Recipe_csv (1).csv", stringsAsFactors = FALSE)
}

#Only proceed if 'category' exists



if ("category" %in% names(recipes)) {

#Count recipes per category and take top 10



library(dplyr)
top10 <- recipes %>%
count(category, name = "n_recipes") %>%
arrange(desc(n_recipes)) %>%
slice_head(n = 10)

#Draw bar plot



barplot(height = top10$n_recipes,
names.arg = as.character(top10$category),
las = 2, cex.names = 0.8,
main = "Top 10 Categories (Count of Recipes)",
ylab = "Count")




} else {
cat("Note: 'category' column not found in the dataset; cannot plot top categories.\n")
}

Hypothesis Testing

# Safety check: reload data if missing
if (!exists("recipes")) {
  recipes <- read.csv("1_Recipe_csv (1).csv", stringsAsFactors = FALSE)
}
#Simple Linear Regression Model



model1 <- lm(num_steps ~ num_ingredients, data = recipes)

#Show model code and clean table result


summary(model1)$coefficients["num_ingredients", , drop = FALSE] |>
as.data.frame() |>
tibble::rownames_to_column("Variable") |>
knitr::kable(
caption = "T-Test for num_ingredients in Simple Linear Regression"
)
T-Test for num_ingredients in Simple Linear Regression
Variable Estimate Std. Error t value Pr(>|t|)
num_ingredients 0.1238493 0.0023683 52.29418 0
#Show key model metrics neatly


summary_table <- data.frame(
R_Squared = round(summary(model1)$r.squared, 4),
Adj_R_Squared = round(summary(model1)$adj.r.squared, 4),
F_Statistic = round(summary(model1)$fstatistic[1], 3),
P_Value = signif(pf(summary(model1)$fstatistic[1],
summary(model1)$fstatistic[2],
summary(model1)$fstatistic[3],
lower.tail = FALSE), 4)
)
knitr::kable(summary_table, caption = "Model Summary Statistics")
Model Summary Statistics
R_Squared Adj_R_Squared F_Statistic P_Value
value 0.0422 0.0421 2734.681 0

Hypothesis Testing Cont.

#Adjust for top 10 categories



top10_cats <- names(sort(table(recipes$category), decreasing = TRUE))[1:10]
recipes$category_limited <- ifelse(recipes$category %in% top10_cats, recipes$category, "Other")
recipes$category_limited <- factor(recipes$category_limited)

#Multiple Linear Regression



model2 <- lm(num_steps ~ num_ingredients + category_limited, data = recipes)

#Clean coefficient table



summary(model2)$coefficients["num_ingredients", , drop = FALSE] |>
as.data.frame() |>
tibble::rownames_to_column("Variable") |>
knitr::kable(
caption = "T-Test for num_ingredients (Adjusted for Category)"
)
T-Test for num_ingredients (Adjusted for Category)
Variable Estimate Std. Error t value Pr(>|t|)
num_ingredients 0.1172847 0.0023704 49.47811 0
#Add R-squared and F-stat summary for adjusted model


summary_table2 <- data.frame(
R_Squared = round(summary(model2)$r.squared, 4),
Adj_R_Squared = round(summary(model2)$adj.r.squared, 4),
F_Statistic = round(summary(model2)$fstatistic[1], 3),
P_Value = signif(pf(summary(model2)$fstatistic[1],
summary(model2)$fstatistic[2],
summary(model2)$fstatistic[3],
lower.tail = FALSE), 4)
)
knitr::kable(summary_table2, caption = "Adjusted Model Summary Statistics")
Adjusted Model Summary Statistics
R_Squared Adj_R_Squared F_Statistic P_Value
value 0.0667 0.0666 403.778 0

Mathematical Equations (Examples)

#Discussion

Conclusion (Take-Home Message)

References