Paper Helicopter DOE Experiment

Author

Nayeemuddin Mohammed

Show Code
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.1.0     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Show Code
library(broom)
library(GGally)
library(tinytex)
library(gt)
library(MASS)

Attaching package: 'MASS'

The following object is masked from 'package:dplyr':

    select
Show Code
library(car)
Loading required package: carData

Attaching package: 'car'

The following object is masked from 'package:dplyr':

    recode

The following object is masked from 'package:purrr':

    some
Show Code
library(knitr)
library(kableExtra)

Attaching package: 'kableExtra'

The following object is masked from 'package:dplyr':

    group_rows
Show Code
# Global chunk options (keeps HTML output tidy)
knitr::opts_chunk$set(
  echo = TRUE,
  warning = FALSE,
  message = FALSE,
  fig.width = 8,
  fig.height = 5,
  fig.align = "center",
  dpi = 150,
  tidy = TRUE
)

# Plot theme
theme_set(theme_minimal() +
          theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
                plot.subtitle = element_text(hjust = 0.5, size = 12)))

Introduction

This report documents a comprehensive 2³ factorial Design of Experiments (DOE) using a paper helicopter as the experimental subject. The primary objective is to systematically evaluate how three key design factors affect the helicopter’s flight performance, measured as flight time in seconds.

Experimental Context and Rationale

A paper helicopter provides an excellent platform for learning DOE principles because:

  • Controllable factors: Physical dimensions and weight can be precisely modified
  • Measurable response: Flight time is easily quantifiable and repeatable
  • Physical interpretability: Results have clear engineering meaning
  • Cost-effective: Requires minimal materials and equipment

Research Questions

  1. Main Effects: How does each individual factor (rotor length, rotor width, added mass) affect flight time?
  2. Interaction Effects: Do the factors work together in ways that amplify or diminish each other’s effects?
  3. Optimization: What combination of factor settings maximizes (or minimizes) flight time?
  4. Model Adequacy: Can we build a reliable mathematical model to predict flight performance?

Experimental Factors and Levels

The experiment examines three factors, each at two levels:

  • Factor A - Rotor Length: 7.5 cm (low level) vs. 8.5 cm (high level)
    • Hypothesis: Longer rotors may provide more lift, increasing flight time
  • Factor B - Rotor Width: 3.5 cm (narrow) vs. 5.0 cm (wide)
    • Hypothesis: Wider rotors may create more air resistance, affecting flight dynamics
  • Factor C - Paper Clip Mass: 0 clips vs. 2 clips
    • Hypothesis: Added weight may increase stability but also increase descent rate

Design Strategy

A full 2³ factorial design was selected because:

  • Efficiency: Examines all possible factor combinations systematically
  • Interaction Detection: Can identify how factors work together
  • Statistical Power: Provides sufficient data for reliable conclusions
  • Educational Value: Demonstrates fundamental DOE principles

Paper helicopter design

Paper helicopter diagram

Paper helicopter diagram

Figure 1: Schematic showing the paper helicopter design with labeled dimensions. The rotor length and width are the primary design variables, while paper clips are attached at the base to add mass.

Materials and Methods

Materials

  1. Paper sheets (specify paper type, e.g., A4 office paper, 80 g/m²).
  2. Ruler and scissors.
  3. Paper clips (mass per clip should be measured; here “2” denotes two clips).
  4. Measuring device (stopwatch or slow-motion video) and a fixed release height and method.

Build & Measurement protocol (step-by-step)

  1. Cut and fold the paper helicopter rotor according to the schematic. Create rotors of lengths 7.5 cm and 8.5 cm; widths 3.5 cm and 5.0 cm as experimental factor levels.
  2. Attach 0 or 2 paper clips to the hook point depending on the treatment.
  3. Release the helicopter from a fixed height (e.g., 2.0 m) using the same release method each run to minimize bias. Start/stop timing from release to first contact with the ground.
  4. Repeat runs according to the randomized run order and replicates listed in the dataset.
  5. Record times in seconds in the Time_s column.

Experimental design A full factorial 2³ DOE was employed with replication (see data table below). Randomization was applied to run order to limit systematic error.

Show Code
helicopter_data <- data.frame(
  RunID = 1:24,
  RunOrder = 1:24,  # Randomized run order as executed
  Replicate = c(2, 2, 1, 1, 2, 3, 3, 3, 3, 2, 1, 2, 1, 2, 3, 3, 2, 2, 1, 1, 1, 3, 1, 3),
  RotorLength_cm = c(7.5, 8.5, 8.5, 7.5, 8.5, 8.5, 7.5, 7.5, 8.5, 7.5, 7.5, 7.5, 7.5, 8.5, 8.5, 7.5, 8.5, 7.5, 8.5, 7.5, 8.5, 8.5, 8.5, 7.5),
  RotorWidth_cm = c(3.5, 5, 5, 3.5, 3.5, 5, 3.5, 5, 3.5, 5, 5, 5, 3.5, 3.5, 3.5, 3.5, 5, 3.5, 3.5, 5, 3.5, 5, 5, 5),
  PaperClip = c(2, 0, 0, 0, 0, 2, 0, 2, 2, 0, 2, 2, 2, 2, 0, 2, 2, 0, 2, 0, 0, 0, 2, 0),
  Time_s = c(3.03, 3.42, 3.75, 3.4, 4.12, 3.07, 3.31, 2.32, 3.4, 2.62, 2.98, 2.45, 3.03, 3.25, 4.18, 2.96, 3.07, 3.51, 3.45, 2.4, 4.14, 3.66, 3.19, 2.52)
)

# Create coded factors for DOE analysis
helicopter_coded <- helicopter_data %>%
  mutate(
    # Convert to coded factors (-1, +1)
    A_Length = ifelse(RotorLength_cm == 7.5, -1, 1),
    B_Width = ifelse(RotorWidth_cm == 3.5, -1, 1),
    C_Clip = ifelse(PaperClip == 0, -1, 1),
    
    # Create factor labels for visualization
    Length_Factor = factor(ifelse(A_Length == -1, "Short (7.5cm)", "Long (8.5cm)")),
    Width_Factor = factor(ifelse(B_Width == -1, "Narrow (3.5cm)", "Wide (5.0cm)")),
    Clip_Factor = factor(ifelse(C_Clip == -1, "No Clip (0)", "With Clip (2)")),
    
    # Create treatment combination labels
    Treatment = paste0(
      ifelse(A_Length == 1, "a", ""),
      ifelse(B_Width == 1, "b", ""),
      ifelse(C_Clip == 1, "c", "")
    ),
    Treatment = ifelse(Treatment == "", "(1)", Treatment)
  )

Variables explanations

Understanding the Dataset Structure

The following table provides a comprehensive overview of all variables collected during the experiment. Each variable serves a specific purpose in the experimental design and analysis.

Show Code
vars_table <- tibble(
  Variable = c("RunID", "RunOrder", "Replicate", "RotorLength_cm", "RotorWidth_cm", "PaperClip", "Time_s", "Treatment"),
  Description = c(
    "Sequential ID for each run (1-24)",
    "Randomized execution order used when conducting the experiment",
    "Replicate number indicating repetition index (1, 2, or 3)",
    "Rotor length in centimeters (levels: 7.5 = Short, 8.5 = Long)",
    "Rotor width in centimeters (levels: 3.5 = Narrow, 5.0 = Wide)",
    "Number of paper clips attached (0 = No clip, 2 = Two clips)",
    "Measured flight time in seconds from release to landing",
    "Treatment label summarizing factor high/low levels using standard DOE notation"
  ),
  Purpose = c(
    "Data organization and tracking",
    "Controls for time-based systematic errors",
    "Enables estimation of experimental error",
    "Primary design factor A",
    "Primary design factor B", 
    "Primary design factor C",
    "Response variable (dependent variable)",
    "Treatment identification for analysis"
  )
)

vars_table %>%
  gt() %>%
  tab_header(title = md("**Variable Definitions and Purposes**")) %>%
  cols_label(
    Variable = "Variable Name", 
    Description = "Description", 
    Purpose = "Experimental Purpose"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  )
Variable Definitions and Purposes
Variable Name Description Experimental Purpose
RunID Sequential ID for each run (1-24) Data organization and tracking
RunOrder Randomized execution order used when conducting the experiment Controls for time-based systematic errors
Replicate Replicate number indicating repetition index (1, 2, or 3) Enables estimation of experimental error
RotorLength_cm Rotor length in centimeters (levels: 7.5 = Short, 8.5 = Long) Primary design factor A
RotorWidth_cm Rotor width in centimeters (levels: 3.5 = Narrow, 5.0 = Wide) Primary design factor B
PaperClip Number of paper clips attached (0 = No clip, 2 = Two clips) Primary design factor C
Time_s Measured flight time in seconds from release to landing Response variable (dependent variable)
Treatment Treatment label summarizing factor high/low levels using standard DOE notation Treatment identification for analysis

Key Points for Table Interpretation:

  • RunID vs RunOrder: RunID is sequential (1,2,3…) while RunOrder shows the actual randomized sequence used during data collection
  • Treatment Notation: Uses standard DOE notation where lowercase letters indicate high levels (a=long rotor, b=wide rotor, c=with clips), and (1) represents all factors at low levels
  • Replication: Each of the 8 treatment combinations was repeated 3 times, providing 24 total observations

Experimental Design Summary

The table below shows how the 2³ factorial design is structured, displaying each unique treatment combination with its corresponding factor levels and summary statistics.

Show Code
design_summary <- helicopter_coded %>%
  group_by(A_Length, B_Width, C_Clip, Treatment) %>%
  summarise(
    n_runs = n(),
    mean_time = round(mean(Time_s), 3),
    sd_time = round(sd(Time_s), 3),
    min_time = round(min(Time_s), 3),
    max_time = round(max(Time_s), 3),
    .groups = 'drop'
  ) %>%
  arrange(A_Length, B_Width, C_Clip)

design_summary %>%
  gt() %>%
  tab_header(title = md("**2³ Factorial Design: Complete Treatment Structure**")) %>%
  cols_label(
    A_Length = "Factor A\n(Length)",
    B_Width = "Factor B\n(Width)", 
    C_Clip = "Factor C\n(Clips)",
    Treatment = "Treatment\nCode",
    n_runs = "Number of\nReplicates",
    mean_time = "Mean Flight\nTime (s)",
    sd_time = "Standard\nDeviation",
    min_time = "Minimum\nTime (s)",
    max_time = "Maximum\nTime (s)"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  tab_footnote(
    footnote = "Coded levels: -1 = low level, +1 = high level",
    locations = cells_column_labels(c("A_Length", "B_Width", "C_Clip"))
  ) %>%
  tab_footnote(
    footnote = "Treatment codes: (1) = all low, letters indicate high levels",
    locations = cells_column_labels("Treatment")
  )
2³ Factorial Design: Complete Treatment Structure
Factor A (Length)1 Factor B (Width)1 Factor C (Clips)1 Treatment Code2 Number of Replicates Mean Flight Time (s) Standard Deviation Minimum Time (s) Maximum Time (s)
-1 -1 -1 (1) 3 3.407 0.100 3.31 3.51
-1 -1 1 c 3 3.007 0.040 2.96 3.03
-1 1 -1 b 3 2.513 0.110 2.40 2.62
-1 1 1 bc 3 2.583 0.350 2.32 2.98
1 -1 -1 a 3 4.147 0.031 4.12 4.18
1 -1 1 ac 3 3.367 0.104 3.25 3.45
1 1 -1 ab 3 3.610 0.171 3.42 3.75
1 1 1 abc 3 3.110 0.069 3.07 3.19
1 Coded levels: -1 = low level, +1 = high level
2 Treatment codes: (1) = all low, letters indicate high levels

How to Read This Design Table:

  1. Factor Coding: The -1/+1 coding is standard in DOE:

    • Length: -1 = 7.5cm (short), +1 = 8.5cm (long)
    • Width: -1 = 3.5cm (narrow), +1 = 5.0cm (wide)
    • Clips: -1 = 0 clips, +1 = 2 clips
  2. Treatment Codes: Follow standard factorial notation:

    • (1): All factors at low level
    • a: Only length at high level
    • b: Only width at high level
    • c: Only clips at high level
    • ab, ac, bc: Two factors at high level
    • abc: All factors at high level
  3. Balance Check: Each treatment has exactly 3 replicates, confirming the design is balanced

  4. Preliminary Observations:

    • Flight times range from ~2.3 to 4.2 seconds
    • Standard deviations are relatively small, suggesting good measurement precision
    • Treatment ab (long + wide, no clips) shows the highest mean flight time

Descriptive Statistics Analysis

Understanding the Response Variable Distribution

Before conducting formal statistical analysis, it’s crucial to examine the basic properties of our response variable (flight time). This section provides comprehensive descriptive statistics to understand the data’s central tendency, variability, and distribution characteristics.

Overall Response Variable Summary

Show Code
# Overall descriptive statistics for the response variable
overall_summary <- helicopter_coded %>%
  summarise(
    n = n(),
    mean = mean(Time_s),
    median = median(Time_s),
    sd = sd(Time_s),
    variance = var(Time_s),
    IQR = IQR(Time_s),
    min = min(Time_s),
    max = max(Time_s),
    range = max(Time_s) - min(Time_s),
    cv_percent = (sd(Time_s)/mean(Time_s)) * 100
  )

overall_summary %>%
  gt() %>%
  fmt_number(columns = vars(mean, median, sd, variance, IQR, min, max, range), decimals = 3) %>%
  fmt_number(columns = vars(cv_percent), decimals = 1) %>%
  tab_header(title = md("**Overall Descriptive Statistics for Flight Time**")) %>%
  cols_label(
    n = "Sample Size",
    mean = "Mean (s)",
    median = "Median (s)", 
    sd = "Std Dev (s)",
    variance = "Variance",
    IQR = "IQR (s)",
    min = "Minimum (s)",
    max = "Maximum (s)",
    range = "Range (s)",
    cv_percent = "CV (%)"
  ) %>%
  tab_footnote(
    footnote = "CV = Coefficient of Variation (SD/Mean × 100%)",
    locations = cells_column_labels("cv_percent")
  )
Overall Descriptive Statistics for Flight Time
Sample Size Mean (s) Median (s) Std Dev (s) Variance IQR (s) Minimum (s) Maximum (s) Range (s) CV (%)1
24 3.218 3.220 0.530 0.281 0.490 2.320 4.180 1.860 16.5
1 CV = Coefficient of Variation (SD/Mean × 100%)

Interpreting the Overall Statistics:

  • Central Tendency: The mean flight time is approximately 3.2 seconds, with the median very close to the mean, suggesting a relatively symmetric distribution
  • Variability: The standard deviation of ~0.5 seconds indicates moderate variability in flight times
  • Coefficient of Variation: At ~15%, this shows reasonable precision in our measurements (values <20% are generally considered acceptable)
  • Range: Flight times span about 1.9 seconds from shortest to longest, indicating substantial differences between treatment conditions
  • Distribution Shape: Mean ≈ median suggests the data is approximately normally distributed, which is important for ANOVA validity

Treatment-Level Summary Statistics

Show Code
by_treatment <- helicopter_coded %>%
  group_by(Treatment) %>%
  summarise(
    n = n(),
    mean = mean(Time_s),
    sd = sd(Time_s),
    median = median(Time_s),
    IQR = IQR(Time_s),
    min = min(Time_s),
    max = max(Time_s),
    cv_percent = (sd(Time_s)/mean(Time_s)) * 100,
    .groups = 'drop'
  ) %>%
  arrange(desc(mean))

by_treatment %>%
  gt() %>%
  fmt_number(columns = vars(mean, sd, median, IQR, min, max), decimals = 3) %>%
  fmt_number(columns = vars(cv_percent), decimals = 1) %>%
  tab_header(title = md("**Treatment-Level Statistics (Ordered by Mean Flight Time)**")) %>%
  cols_label(
    Treatment = "Treatment",
    n = "Replicates",
    mean = "Mean (s)",
    sd = "Std Dev (s)",
    median = "Median (s)",
    IQR = "IQR (s)", 
    min = "Min (s)",
    max = "Max (s)",
    cv_percent = "CV (%)"
  ) %>%
  tab_style(
    style = cell_fill(color = "lightgreen", alpha = 0.3),
    locations = cells_body(rows = 1)  # Highlight best treatment
  ) %>%
  tab_style(
    style = cell_fill(color = "lightcoral", alpha = 0.3), 
    locations = cells_body(rows = nrow(by_treatment))  # Highlight worst treatment
  )
Treatment-Level Statistics (Ordered by Mean Flight Time)
Treatment Replicates Mean (s) Std Dev (s) Median (s) IQR (s) Min (s) Max (s) CV (%)
a 3 4.147 0.031 4.140 0.030 4.120 4.180 0.7
ab 3 3.610 0.171 3.660 0.165 3.420 3.750 4.7
(1) 3 3.407 0.100 3.400 0.100 3.310 3.510 2.9
ac 3 3.367 0.104 3.400 0.100 3.250 3.450 3.1
abc 3 3.110 0.069 3.070 0.060 3.070 3.190 2.2
c 3 3.007 0.040 3.030 0.035 2.960 3.030 1.3
bc 3 2.583 0.350 2.450 0.330 2.320 2.980 13.5
b 3 2.513 0.110 2.520 0.110 2.400 2.620 4.4

Key Insights from Treatment Comparisons:

  • Best Performing Treatment: The highest mean flight time shows which factor combination is optimal
  • Worst Performing Treatment: The lowest mean identifies the least effective configuration
  • Variability Assessment: Most treatments show similar standard deviations (~0.1-0.3s), suggesting consistent measurement precision across conditions
  • Coefficient of Variation: Most treatments have CV < 15%, indicating good experimental control
  • Treatment Differences: The range between best and worst treatments (~1.5+ seconds) is much larger than typical standard deviations, suggesting real treatment effects

Factor-Level Summary Analysis

Show Code
# Enhanced factor-level summaries with interpretation
factor_summaries <- list(
  Length = helicopter_coded %>%
    group_by(Length_Factor) %>%
    summarise(
      n = n(), 
      mean_time = round(mean(Time_s), 3), 
      sd_time = round(sd(Time_s), 3),
      median_time = round(median(Time_s), 3),
      .groups = 'drop'
    ),
  
  Width = helicopter_coded %>%
    group_by(Width_Factor) %>%
    summarise(
      n = n(), 
      mean_time = round(mean(Time_s), 3), 
      sd_time = round(sd(Time_s), 3),
      median_time = round(median(Time_s), 3),
      .groups = 'drop'
    ),
  
  Clip = helicopter_coded %>%
    group_by(Clip_Factor) %>%
    summarise(
      n = n(), 
      mean_time = round(mean(Time_s), 3), 
      sd_time = round(sd(Time_s), 3),
      median_time = round(median(Time_s), 3),
      .groups = 'drop'
    )
)

# Display each factor summary with interpretation
for(factor_name in names(factor_summaries)) {
  cat("### ", factor_name, " Factor Analysis\n\n")
  
  print(
    factor_summaries[[factor_name]] %>%
      gt() %>%
      tab_header(title = md(paste("**", factor_name, "Factor Summary Statistics**"))) %>%
      cols_label(
        contains("Factor") ~ "Level",
        n = "Observations", 
        mean_time = "Mean Time (s)",
        sd_time = "Std Dev (s)",
        median_time = "Median Time (s)"
      )
  )
  
  # Calculate and display effect size
  factor_data <- factor_summaries[[factor_name]]
  effect_size <- abs(factor_data$mean_time[1] - factor_data$mean_time[2])
  better_level <- factor_data[which.max(factor_data$mean_time), 1]
  
  cat("\n**", factor_name, " Factor Interpretation:**\n")
  cat("- **Effect Size**: ", round(effect_size, 3), " seconds difference between levels\n")
  cat("- **Better Level**: ", as.character(better_level[[1]]), " produces longer flight times\n")
  cat("- **Practical Significance**: ", 
      if(effect_size > 0.3) "Large effect - practically significant" 
      else if(effect_size > 0.1) "Moderate effect - may be significant"
      else "Small effect - limited practical importance", "\n\n")
}

Length Factor Analysis

** Length Factor Summary Statistics**
Level Observations Mean Time (s) Std Dev (s) Median Time (s)
Long (8.5cm) 12 3.558 0.410 3.435
Short (7.5cm) 12 2.878 0.409 2.970

** Length Factor Interpretation: - Effect Size: 0.68 seconds difference between levels - Better Level: Long (8.5cm) produces longer flight times - Practical Significance**: Large effect - practically significant

Width Factor Analysis

** Width Factor Summary Statistics**
Level Observations Mean Time (s) Std Dev (s) Median Time (s)
Narrow (3.5cm) 12 3.482 0.438 3.400
Wide (5.0cm) 12 2.954 0.495 3.025

** Width Factor Interpretation: - Effect Size: 0.528 seconds difference between levels - Better Level: Narrow (3.5cm) produces longer flight times - Practical Significance**: Large effect - practically significant

Clip Factor Analysis

** Clip Factor Summary Statistics**
Level Observations Mean Time (s) Std Dev (s) Median Time (s)
No Clip (0) 12 3.419 0.623 3.465
With Clip (2) 12 3.017 0.335 3.050

** Clip Factor Interpretation: - Effect Size: 0.402 seconds difference between levels - Better Level: No Clip (0) produces longer flight times - Practical Significance**: Large effect - practically significant

Summary of Factor-Level Findings:

This analysis reveals the individual contribution of each factor by comparing the average response when each factor is at its high vs. low level. The effect sizes help us understand which factors have the most substantial impact on flight performance, setting the stage for the formal ANOVA analysis that follows.

Data Visualization and Exploratory Analysis

Purpose of Visual Analysis

Visual exploration of experimental data serves several critical purposes in DOE:

  1. Pattern Recognition: Identify trends and relationships before formal statistical testing
  2. Assumption Checking: Assess normality, equal variance, and outlier detection
  3. Effect Visualization: Understand the magnitude and direction of factor effects
  4. Interaction Detection: Spot potential factor interactions through visual patterns
  5. Communication: Present findings in an accessible, interpretable format

Main Effects Visualization

The following box plots display how each individual factor affects flight time. Box plots are ideal for showing both central tendency and variability simultaneously.

Show Code
library(patchwork)

# Enhanced main effects plots with better aesthetics and information
p1 <- helicopter_coded %>%
  ggplot(aes(x = Length_Factor, y = Time_s, fill = Length_Factor)) +
  geom_boxplot(alpha = 0.7, outlier.shape = 21, outlier.size = 2) +
  geom_jitter(width = 0.2, alpha = 0.6, size = 2) +
  stat_summary(fun = mean, geom = "point", shape = 23, size = 3, 
               fill = "red", color = "darkred") +
  labs(
    title = "Effect of Rotor Length on Flight Time",
    subtitle = "Red diamonds show treatment means",
    x = "Rotor Length", 
    y = "Flight Time (seconds)"
  ) +
  scale_fill_manual(values = c("lightblue", "lightcoral")) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    panel.grid.minor = element_blank()
  )

p2 <- helicopter_coded %>%
  ggplot(aes(x = Width_Factor, y = Time_s, fill = Width_Factor)) +
  geom_boxplot(alpha = 0.7, outlier.shape = 21, outlier.size = 2) +
  geom_jitter(width = 0.2, alpha = 0.6, size = 2) +
  stat_summary(fun = mean, geom = "point", shape = 23, size = 3, 
               fill = "red", color = "darkred") +
  labs(
    title = "Effect of Rotor Width on Flight Time",
    subtitle = "Red diamonds show treatment means", 
    x = "Rotor Width", 
    y = "Flight Time (seconds)"
  ) +
  scale_fill_manual(values = c("lightgreen", "lightyellow")) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    panel.grid.minor = element_blank()
  )

p3 <- helicopter_coded %>%
  ggplot(aes(x = Clip_Factor, y = Time_s, fill = Clip_Factor)) +
  geom_boxplot(alpha = 0.7, outlier.shape = 21, outlier.size = 2) +
  geom_jitter(width = 0.2, alpha = 0.6, size = 2) +
  stat_summary(fun = mean, geom = "point", shape = 23, size = 3, 
               fill = "red", color = "darkred") +
  labs(
    title = "Effect of Paper Clips on Flight Time",
    subtitle = "Red diamonds show treatment means",
    x = "Paper Clips", 
    y = "Flight Time (seconds)"
  ) +
  scale_fill_manual(values = c("lightpink", "lightsteelblue")) +
  theme_minimal() +
  theme(
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    panel.grid.minor = element_blank()
  )

# Combine plots with enhanced layout
(p1 | p2 | p3) + 
  plot_annotation(
    title = "Main Effects Analysis: Individual Factor Impacts",
    subtitle = "Box plots show distribution, jittered points show individual observations",
    theme = theme(
      plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
      plot.subtitle = element_text(hjust = 0.5, size = 11)
    )
  )

How to Interpret Main Effects Plots:

  • Box Plot Elements:

    • Center line = median
    • Box boundaries = 25th and 75th percentiles (IQR)
    • Whiskers = extend to most extreme points within 1.5×IQR
    • Outliers = points beyond whiskers
    • Red diamonds = treatment means
  • Individual Points: Jittered to show actual data distribution and sample size

  • What to Look For:

    • Vertical separation between boxes indicates main effects
    • Box overlap suggests smaller effects
    • Similar spreads indicate equal variance assumption is reasonable
    • Outliers may indicate measurement errors or special causes

Interaction Effects Analysis

Interaction plots are crucial for understanding whether factors work together synergistically or antagonistically. These plots show if the effect of one factor depends on the level of another factor.

Show Code
library(patchwork)

# Enhanced interaction plots with better annotations
int1 <- helicopter_coded %>%
  group_by(Length_Factor, Width_Factor) %>%
  summarise(
    mean_time = mean(Time_s), 
    se_time = sd(Time_s)/sqrt(n()),
    .groups = 'drop'
  ) %>%
  ggplot(aes(x = Length_Factor, y = mean_time, color = Width_Factor, group = Width_Factor)) +
  geom_line(size = 1.5, alpha = 0.8) +
  geom_point(size = 4, alpha = 0.9) +
  geom_errorbar(aes(ymin = mean_time - se_time, ymax = mean_time + se_time), 
                width = 0.05, alpha = 0.7) +
  labs(
    title = "Length × Width Interaction",
    subtitle = "Error bars show ±1 standard error", 
    x = "Rotor Length", 
    y = "Mean Flight Time (seconds)",
    color = "Rotor Width"
  ) +
  scale_color_manual(values = c("blue", "red")) +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    panel.grid.minor = element_blank()
  )

int2 <- helicopter_coded %>%
  group_by(Length_Factor, Clip_Factor) %>%
  summarise(
    mean_time = mean(Time_s), 
    se_time = sd(Time_s)/sqrt(n()),
    .groups = 'drop'
  ) %>%
  ggplot(aes(x = Length_Factor, y = mean_time, color = Clip_Factor, group = Clip_Factor)) +
  geom_line(size = 1.5, alpha = 0.8) +
  geom_point(size = 4, alpha = 0.9) +
  geom_errorbar(aes(ymin = mean_time - se_time, ymax = mean_time + se_time), 
                width = 0.05, alpha = 0.7) +
  labs(
    title = "Length × Clip Interaction",
    subtitle = "Error bars show ±1 standard error",
    x = "Rotor Length", 
    y = "Mean Flight Time (seconds)",
    color = "Paper Clips"
  ) +
  scale_color_manual(values = c("green", "orange")) +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    panel.grid.minor = element_blank()
  )

int3 <- helicopter_coded %>%
  group_by(Width_Factor, Clip_Factor) %>%
  summarise(
    mean_time = mean(Time_s), 
    se_time = sd(Time_s)/sqrt(n()),
    .groups = 'drop'
  ) %>%
  ggplot(aes(x = Width_Factor, y = mean_time, color = Clip_Factor, group = Clip_Factor)) +
  geom_line(size = 1.5, alpha = 0.8) +
  geom_point(size = 4, alpha = 0.9) +
  geom_errorbar(aes(ymin = mean_time - se_time, ymax = mean_time + se_time), 
                width = 0.05, alpha = 0.7) +
  labs(
    title = "Width × Clip Interaction",
    subtitle = "Error bars show ±1 standard error",
    x = "Rotor Width", 
    y = "Mean Flight Time (seconds)",
    color = "Paper Clips"
  ) +
  scale_color_manual(values = c("purple", "brown")) +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    panel.grid.minor = element_blank()
  )

# Arrange interaction plots
(int1 / int2 / int3) + 
  plot_annotation(
    title = "Interaction Effects Analysis",
    subtitle = "Parallel lines = no interaction, crossing/diverging lines = potential interaction",
    theme = theme(
      plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
      plot.subtitle = element_text(hjust = 0.5, size = 11)
    )
  )

How to Interpret Interaction Plots:

  • Parallel Lines: Indicate no interaction - the effect of one factor is consistent regardless of the other factor’s level
  • Crossing Lines: Suggest strong interaction - the effect of one factor reverses depending on the other factor
  • Diverging/Converging Lines: Indicate moderate interaction - one factor’s effect is amplified or diminished by the other
  • Error Bars: Show measurement uncertainty; overlapping bars suggest differences may not be significant

Preliminary Visual Insights:

Based on these interaction plots, we can make initial observations about: - Which factor combinations appear most/least effective - Whether factors work independently or synergistically
- The relative magnitude of main effects vs. interaction effects - Potential optimization strategies

These visual patterns will be formally tested in the subsequent ANOVA analysis.

ANOVA Analysis

Understanding Analysis of Variance in Factorial Experiments

Analysis of Variance (ANOVA) is the primary statistical method for analyzing factorial experiments. It allows us to:

  1. Partition Total Variation: Separate the total variation in flight times into components attributable to each factor, their interactions, and random error
  2. Test Statistical Significance: Determine which effects are larger than what we’d expect from random variation alone
  3. Quantify Effect Sizes: Measure how much each factor contributes to the overall variation
  4. Validate Model Assumptions: Check if our data meets the requirements for valid statistical inference

The ANOVA Model

For our 2³ factorial experiment, the statistical model is:

**Y_ijkl = μ + A_i + B_j + C_k + (AB)_ij + (AC)_ik + (BC)_jk + (ABC)_ijk + ε_ijkl**

Where: - Y_ijkl = observed flight time - μ = overall mean flight time - A_i, B_j, C_k = main effects of Length, Width, and Clips - (AB), (AC), (BC) = two-factor interactions - (ABC) = three-factor interaction - ε_ijkl = random error term

Show Code
# Load required libraries for this section
library(dplyr)
library(broom) 
library(gt)
library(stringr)
library(car)

# Fit the full factorial model
model_full <- lm(Time_s ~ A_Length * B_Width * C_Clip, data = helicopter_coded)

# Generate ANOVA table
anova_table <- anova(model_full)
model_summary <- summary(model_full)

# Create enhanced ANOVA table with interpretations
anova_tidy <- tidy(anova_table) %>%
  mutate(
    term = case_when(
      term == "A_Length" ~ "A: Rotor Length",
      term == "B_Width" ~ "B: Rotor Width", 
      term == "C_Clip" ~ "C: Paper Clip",
      term == "A_Length:B_Width" ~ "AB: Length × Width",
      term == "A_Length:C_Clip" ~ "AC: Length × Clip",
      term == "B_Width:C_Clip" ~ "BC: Width × Clip",
      term == "A_Length:B_Width:C_Clip" ~ "ABC: Length × Width × Clip",
      term == "Residuals" ~ "Error",
      TRUE ~ term
    ),
    # Calculate percentage of total variation explained
    ss_percent = round((sumsq / sum(sumsq)) * 100, 1),
    # Add significance indicators
    significance = case_when(
      is.na(p.value) ~ "",
      p.value < 0.001 ~ "***",
      p.value < 0.01 ~ "**", 
      p.value < 0.05 ~ "*",
      p.value < 0.1 ~ ".",
      TRUE ~ ""
    ),
    across(where(is.numeric), ~round(.x, 4))
  ) %>%
  dplyr::select(
    Source = term, 
    DF = df, 
    SS = sumsq, 
    `SS%` = ss_percent,
    MS = meansq, 
    `F-value` = statistic, 
    `p-value` = p.value,
    Sig = significance
  )

# Display enhanced ANOVA table
anova_tidy %>%
  gt() %>%
  tab_header(title = md("**ANOVA Table for 2³ Factorial Design**")) %>%
  tab_footnote(
    footnote = "Significance codes: *** p<0.001, ** p<0.01, * p<0.05, . p<0.1",
    locations = cells_column_labels("Sig")
  ) %>%
  tab_footnote(
    footnote = "SS% = Percentage of total sum of squares explained by each source",
    locations = cells_column_labels("SS%")
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  # Highlight significant effects
  tab_style(
    style = cell_fill(color = "lightgreen", alpha = 0.3),
    locations = cells_body(
      rows = !is.na(`p-value`) & `p-value` < 0.05
    )
  )
ANOVA Table for 2³ Factorial Design
Source DF SS SS%1 MS F-value p-value Sig2
A: Rotor Length 1 2.7812 43.0 2.7812 116.0649 0.0000 ***
B: Rotor Width 1 1.6695 25.8 1.6695 69.6729 0.0000 ***
C: Paper Clip 1 0.9720 15.0 0.9720 40.5649 0.0000 ***
AB: Length × Width 1 0.1027 1.6 0.1027 4.2860 0.0550 .
AC: Length × Clip 1 0.3384 5.2 0.3384 14.1236 0.0017 **
BC: Width × Clip 1 0.2109 3.3 0.2109 8.8028 0.0091 **
ABC: Length × Width × Clip 1 0.0135 0.2 0.0135 0.5649 0.4632
Error 16 0.3834 5.9 0.0240 NA NA
1 SS% = Percentage of total sum of squares explained by each source
2 Significance codes: *** p<0.001, ** p<0.01, * p<0.05, . p<0.1

Interpreting the ANOVA Results

Overall Model Performance

R-squared (R²): 0.941

  • Interpretation: The model explains 94.1% of the total variation in flight times
  • Assessment: Excellent fit

Adjusted R-squared: 0.915

  • Purpose: Adjusts for the number of parameters, preventing overfitting
  • Comparison: Adj-R² is lower than R², which is expected

Residual Standard Error: 0.155 seconds

  • Interpretation: Average prediction error is approximately 0.155 seconds
  • Context: This represents 4.8% of the mean flight time

Factor Effect Analysis

Show Code
# Calculate effect magnitudes and practical significance
effects_data <- anova_tidy %>%
  filter(!is.na(`p-value`)) %>%
  arrange(`p-value`) %>%
  mutate(
    practical_significance = case_when(
      `SS%` >= 10 ~ "Large Effect (≥10% of variation)",
      `SS%` >= 5 ~ "Moderate Effect (5-10% of variation)", 
      `SS%` >= 1 ~ "Small Effect (1-5% of variation)",
      TRUE ~ "Negligible Effect (<1% of variation)"
    ),
    statistical_significance = case_when(
      `p-value` < 0.001 ~ "Highly Significant (p < 0.001)",
      `p-value` < 0.01 ~ "Very Significant (p < 0.01)",
      `p-value` < 0.05 ~ "Significant (p < 0.05)",
      `p-value` < 0.1 ~ "Marginally Significant (p < 0.1)",
      TRUE ~ "Not Significant (p ≥ 0.1)"
    )
  )

effects_data %>%
  dplyr::select(Source, `SS%`, `p-value`, practical_significance, statistical_significance) %>%
  gt() %>%
  tab_header(title = md("**Factor Effects: Statistical and Practical Significance**")) %>%
  cols_label(
    Source = "Effect",
    `SS%` = "% Variation Explained", 
    `p-value` = "p-value",
    practical_significance = "Practical Significance",
    statistical_significance = "Statistical Significance"
  ) %>%
  fmt_number(columns = c(`p-value`), decimals = 4) %>%
  fmt_number(columns = c(`SS%`), decimals = 1) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  )
Factor Effects: Statistical and Practical Significance
Effect % Variation Explained p-value Practical Significance Statistical Significance
A: Rotor Length 43.0 0.0000 Large Effect (≥10% of variation) Highly Significant (p < 0.001)
B: Rotor Width 25.8 0.0000 Large Effect (≥10% of variation) Highly Significant (p < 0.001)
C: Paper Clip 15.0 0.0000 Large Effect (≥10% of variation) Highly Significant (p < 0.001)
AC: Length × Clip 5.2 0.0017 Moderate Effect (5-10% of variation) Very Significant (p < 0.01)
BC: Width × Clip 3.3 0.0091 Small Effect (1-5% of variation) Very Significant (p < 0.01)
AB: Length × Width 1.6 0.0550 Small Effect (1-5% of variation) Marginally Significant (p < 0.1)
ABC: Length × Width × Clip 0.2 0.4632 Negligible Effect (<1% of variation) Not Significant (p ≥ 0.1)

Key Findings from ANOVA

Significant Main Effects

The following main effects are statistically significant:

** A: Rotor Length **

  • Explains 43 % of total variation
  • Statistical significance: Highly Significant (p < 0.001)
  • Practical significance: Large Effect (≥10% of variation)

** B: Rotor Width **

  • Explains 25.8 % of total variation
  • Statistical significance: Highly Significant (p < 0.001)
  • Practical significance: Large Effect (≥10% of variation)

** C: Paper Clip **

  • Explains 15 % of total variation
  • Statistical significance: Highly Significant (p < 0.001)
  • Practical significance: Large Effect (≥10% of variation)

Significant Interaction Effects

The following interaction effects are statistically significant:

** AC: Length × Clip **

  • Explains 5.2 % of total variation
  • Statistical significance: Very Significant (p < 0.01)
  • Interpretation: The effect of one factor depends on the level of the other factor(s)
  • Implication: Simple main effects analysis may be needed

** BC: Width × Clip **

  • Explains 3.3 % of total variation
  • Statistical significance: Very Significant (p < 0.01)
  • Interpretation: The effect of one factor depends on the level of the other factor(s)
  • Implication: Simple main effects analysis may be needed

ANOVA Assumptions Assessment

ANOVA requires three key assumptions:

1. Independence of Observations - Status: MET - Evidence: Randomized run order prevents systematic bias - Conclusion: Each helicopter was tested independently

2. Normality of Residuals - Test: Shapiro-Wilk test - p-value: 0.1117 - Status: PASSED (p > 0.05) - Conclusion: Residuals are approximately normal

3. Homogeneity of Variance (Equal Variances) - Test: Levene’s test
- p-value: 0.5286 - Status: PASSED (p > 0.05) - Conclusion: Variances are approximately equal across treatments

Overall ANOVA Validity: GOOD****

Stepwise Model Selection

Understanding Model Selection in DOE

After fitting the full factorial model, we often find that not all effects are statistically significant. Model selection helps us identify a simpler, more interpretable model that retains only the important effects. This process serves several purposes:

  1. Parsimony: Simpler models are easier to interpret and communicate
  2. Improved Precision: Removing non-significant terms can improve the precision of remaining effect estimates
  3. Better Predictions: Simpler models often predict better on new data (avoid overfitting)
  4. Focus on Key Factors: Identifies which factors truly matter for the response

Model Selection Strategies

There are several approaches to model selection:

  • Forward Selection: Start with no terms, add significant ones
  • Backward Elimination: Start with all terms, remove non-significant ones
  • Stepwise (Both Directions): Combines forward and backward at each step
  • All Subsets: Evaluate all possible model combinations

We’ll demonstrate both automated stepwise selection using statistical criteria and manual backward elimination using p-values.

Automated Stepwise Selection Using AIC

The Akaike Information Criterion (AIC) balances model fit with model complexity. Lower AIC values indicate better models.

AIC Formula: AIC = -2 × log(likelihood) + 2 × (number of parameters)

Starting Model Information

Full Factorial Model: - Formula: Time_s ~ A_Length * B_Width * C_Clip - Number of terms: 8 - Initial AIC: -13.17

Show Code
# Perform stepwise selection (suppress output for clean presentation)
step_result <- stepAIC(model_full, direction = "both", trace = FALSE)

Stepwise Selection Results

Selected Model: - Formula: Time_s ~ A_Length + B_Width + C_Clip + A_Length:B_Width + A_Length:C_Clip + , B_Width:C_Clip - Number of terms: 7 - Final AIC: -14.34 - AIC improvement: 1.17

Interpretation of AIC Selection: The stepwise algorithm evaluated adding and removing terms at each step, selecting the combination that minimized AIC. The modest AIC improvement suggests some benefit to model simplification.

Show Code
# Summary of selected model
summary(step_result)

Call:
lm(formula = Time_s ~ A_Length + B_Width + C_Clip + A_Length:B_Width + 
    A_Length:C_Clip + B_Width:C_Clip, data = helicopter_coded)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.23958 -0.07104 -0.00875  0.06125  0.42042 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)       3.21792    0.03119 103.168  < 2e-16 ***
A_Length          0.34042    0.03119  10.914 4.23e-09 ***
B_Width          -0.26375    0.03119  -8.456 1.70e-07 ***
C_Clip           -0.20125    0.03119  -6.452 5.96e-06 ***
A_Length:B_Width  0.06542    0.03119   2.097  0.05123 .  
A_Length:C_Clip  -0.11875    0.03119  -3.807  0.00141 ** 
B_Width:C_Clip    0.09375    0.03119   3.006  0.00796 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.1528 on 17 degrees of freedom
Multiple R-squared:  0.9387,    Adjusted R-squared:  0.917 
F-statistic: 43.36 on 6 and 17 DF,  p-value: 2.21e-09
Show Code
# Compare model fit statistics
model_comparison <- data.frame(
  Model = c("Full Factorial", "Stepwise Selected"),
  Terms = c(length(coef(model_full)), length(coef(step_result))),
  R_squared = c(summary(model_full)$r.squared, summary(step_result)$r.squared),
  Adj_R_squared = c(summary(model_full)$adj.r.squared, summary(step_result)$adj.r.squared),
  AIC = c(AIC(model_full), AIC(step_result)),
  Residual_SE = c(summary(model_full)$sigma, summary(step_result)$sigma),
  stringsAsFactors = FALSE
)

model_comparison %>%
  mutate(across(where(is.numeric), ~round(.x, 4))) %>%
  gt() %>%
  tab_header(title = md("**Model Comparison: Full vs. Stepwise Selected**")) %>%
  cols_label(
    Model = "Model Type",
    Terms = "Number of Terms",
    R_squared = "R²",
    Adj_R_squared = "Adj R²", 
    AIC = "AIC",
    Residual_SE = "Residual SE"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  )
Model Comparison: Full vs. Stepwise Selected
Model Type Number of Terms Adj R² AIC Residual SE
Full Factorial 8 0.9408 0.9148 -13.1725 0.1548
Stepwise Selected 7 0.9387 0.9170 -14.3397 0.1528

Manual Backward Elimination Using p-values

While AIC provides an objective criterion, many practitioners prefer p-value based elimination for its interpretability. This approach removes the least significant term at each step.

Elimination Process: 1. Start with the full model 2. Identify the term with the highest p-value > 0.05 3. Remove that term and refit the model 4. Repeat until all remaining terms have p ≤ 0.05

Manual Elimination Results

Show Code
if(nrow(elimination_steps) > 0) {
  elimination_steps %>%
    mutate(
      Elimination_Reason = paste("p-value =", sprintf("%.4f", p_value), "> 0.05"),
      F_test_Result = ifelse(F_test_p > 0.05, "Safe removal", "Marginal significance")
    ) %>%
    dplyr::select(Step, Removed_Term, p_value, F_test_p, F_test_Result, Remaining_Terms) %>%
    gt() %>%
    tab_header(title = md("**Manual Backward Elimination Steps**")) %>%
    cols_label(
      Step = "Step",
      Removed_Term = "Removed Term",
      p_value = "Original p-value",
      F_test_p = "F-test p-value", 
      F_test_Result = "Removal Assessment",
      Remaining_Terms = "Terms Remaining"
    ) %>%
    fmt_number(columns = c("p_value", "F_test_p"), decimals = 4) %>%
    tab_style(
      style = cell_text(weight = "bold"),
      locations = cells_column_labels()
    )
} else {
  # Create a note when no elimination occurred
  no_elimination <- data.frame(
    Result = "No terms eliminated",
    Reason = "All terms significant at α = 0.05"
  )
  
  no_elimination %>%
    gt() %>%
    tab_header(title = md("**Manual Elimination Result**"))
}
Manual Backward Elimination Steps
Step Removed Term Original p-value F-test p-value Removal Assessment Terms Remaining
1 A_Length:B_Width:C_Clip 0.4632 0.4632 Safe removal 6
2 A_Length:B_Width 0.0512 0.0512 Safe removal 5

Final Manual Model

The manual backward elimination process removed 2 terms, resulting in a model with 5 remaining terms.

Show Code
if(nrow(elimination_steps) > 0) {
  summary(final_manual_model)
}

Call:
lm(formula = new_formula, data = helicopter_coded)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.30500 -0.09708 -0.00167  0.06938  0.35500 

Coefficients:
                Estimate Std. Error t value Pr(>|t|)    
(Intercept)      3.21792    0.03401  94.621  < 2e-16 ***
A_Length         0.34042    0.03401  10.010 8.80e-09 ***
B_Width         -0.26375    0.03401  -7.755 3.80e-07 ***
C_Clip          -0.20125    0.03401  -5.918 1.33e-05 ***
A_Length:C_Clip -0.11875    0.03401  -3.492   0.0026 ** 
B_Width:C_Clip   0.09375    0.03401   2.757   0.0130 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.1666 on 18 degrees of freedom
Multiple R-squared:  0.9228,    Adjusted R-squared:  0.9014 
F-statistic: 43.03 on 5 and 18 DF,  p-value: 2.157e-09

Model Selection Comparison

Show Code
# Create comprehensive comparison
if(nrow(elimination_steps) > 0) {
  comparison_data <- data.frame(
    Method = c("Full Factorial", "AIC Stepwise", "p-value Backward"),
    Formula = c(
      paste(deparse(formula(model_full)), collapse = " "),
      paste(deparse(formula(step_result)), collapse = " "), 
      paste(deparse(formula(final_manual_model)), collapse = " ")
    ),
    Terms = c(
      length(coef(model_full)),
      length(coef(step_result)),
      length(coef(final_manual_model))
    ),
    R_squared = c(
      summary(model_full)$r.squared,
      summary(step_result)$r.squared,
      summary(final_manual_model)$r.squared
    ),
    Adj_R_squared = c(
      summary(model_full)$adj.r.squared,
      summary(step_result)$adj.r.squared,
      summary(final_manual_model)$adj.r.squared
    ),
    AIC = c(
      AIC(model_full),
      AIC(step_result),
      AIC(final_manual_model)
    ),
    stringsAsFactors = FALSE
  )
} else {
  comparison_data <- data.frame(
    Method = c("Full Factorial", "AIC Stepwise"),
    Formula = c(
      paste(deparse(formula(model_full)), collapse = " "),
      paste(deparse(formula(step_result)), collapse = " ")
    ),
    Terms = c(
      length(coef(model_full)),
      length(coef(step_result))
    ),
    R_squared = c(
      summary(model_full)$r.squared,
      summary(step_result)$r.squared
    ),
    Adj_R_squared = c(
      summary(model_full)$adj.r.squared,
      summary(step_result)$adj.r.squared
    ),
    AIC = c(
      AIC(model_full),
      AIC(step_result)
    ),
    stringsAsFactors = FALSE
  )
}

comparison_data %>%
  mutate(across(where(is.numeric), ~round(.x, 4))) %>%
  gt() %>%
  tab_header(title = md("**Model Selection Methods Comparison**")) %>%
  cols_label(
    Method = "Selection Method",
    Formula = "Model Formula",
    Terms = "Number of Terms",
    R_squared = "R²",
    Adj_R_squared = "Adjusted R²",
    AIC = "AIC"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  )
Model Selection Methods Comparison
Selection Method Model Formula Number of Terms Adjusted R² AIC
Full Factorial Time_s ~ A_Length * B_Width * C_Clip 8 0.9408 0.9148 -13.1725
AIC Stepwise Time_s ~ A_Length + B_Width + C_Clip + A_Length:B_Width + A_Length:C_Clip + B_Width:C_Clip 7 0.9387 0.9170 -14.3397
p-value Backward Time_s ~ A_Length + B_Width + C_Clip + A_Length:C_Clip + B_Width:C_Clip 6 0.9228 0.9014 -10.8170

Model Selection Conclusions

Recommended Model: AIC Stepwise

Final Model Formula: Time_s ~ A_Length + B_Width + C_Clip + A_Length:B_Width + A_Length:C_Clip + , B_Width:C_Clip

Selection Rationale:

  • Statistical Optimality: This model achieved the lowest AIC value (-14.34), indicating the best balance between model fit and complexity
  • Adjusted R-squared: 0.917 - retains 91.7% of explainable variation
  • Parsimony: Uses 6 terms compared to 7 in the full model
  • Interpretability: Includes only statistically meaningful effects, making results easier to understand and communicate

This recommended model will be used for final effect interpretation and optimization analysis in subsequent sections.

Results and Interpretation

Statistical Analysis Summary

This section synthesizes the key findings from our comprehensive DOE analysis, translating statistical results into practical engineering insights for paper helicopter optimization.

Effect Magnitudes and Rankings

Show Code
effects_summary %>%
  gt() %>%
  tab_header(title = md("**Factorial Effects Ranked by Magnitude**")) %>%
  cols_label(
    Effect = "Effect",
    Estimate = "Effect Size (s)",
    SE = "Standard Error",
    t_stat = "t-statistic", 
    Abs_Effect = "Absolute Effect"
  ) %>%
  tab_footnote(
    footnote = "Positive effects indicate higher flight times at high factor levels",
    locations = cells_column_labels("Estimate")
  ) %>%
  fmt_number(columns = everything() & where(is.numeric), decimals = 3) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  # Highlight the largest effects
  tab_style(
    style = cell_fill(color = "lightblue", alpha = 0.3),
    locations = cells_body(rows = 1:3)
  )
Factorial Effects Ranked by Magnitude
Effect Effect Size (s)1 Standard Error t-statistic Absolute Effect
A (Length) 0.681 0.045 15.236 0.681
B (Width) −0.527 0.045 −11.805 0.527
C (Clip) −0.403 0.045 −9.007 0.403
AC −0.237 0.045 −5.315 0.237
BC 0.188 0.045 4.196 0.188
ABC 0.131 0.045 2.928 0.131
AB 0.131 0.045 2.928 0.131
1 Positive effects indicate higher flight times at high factor levels

Significant Effects Analysis

Statistical Significance Findings

Our ANOVA analysis identified 5 statistically significant effects at the α = 0.05 level:

Show Code
if(nrow(significant_effects) > 0) {
  significant_effects %>%
    dplyr::select(Source, `SS%`, `F-value`, `p-value`) %>%
    gt() %>%
    tab_header(title = md("**Statistically Significant Effects**")) %>%
    cols_label(
      Source = "Effect",
      `SS%` = "% Total Variation",
      `F-value` = "F-statistic",
      `p-value` = "p-value"
    ) %>%
    fmt_number(columns = c(`F-value`), decimals = 2) %>%
    fmt_number(columns = c(`p-value`), decimals = 4) %>%
    fmt_number(columns = c(`SS%`), decimals = 1) %>%
    tab_style(
      style = cell_text(weight = "bold"),
      locations = cells_column_labels()
    )
}
Statistically Significant Effects
Effect % Total Variation F-statistic p-value
A: Rotor Length 43.0 116.06 0.0000
B: Rotor Width 25.8 69.67 0.0000
C: Paper Clip 15.0 40.56 0.0000
AC: Length × Clip 5.2 14.12 0.0017
BC: Width × Clip 3.3 8.80 0.0091

Engineering Interpretation of Main Effects

Factor A - Rotor Length: +0.681 seconds

This positive effect indicates that longer rotors (8.5 cm) increase flight time by approximately 0.681 seconds compared to shorter rotors on average.

Factor B - Rotor Width: -0.527 seconds

This negative effect shows that narrower rotors (3.5 cm) increase flight time by approximately 0.527 seconds compared to wider rotors on average.

Factor C - Paper Clips: -0.402 seconds

This negative effect demonstrates that removing paper clips increases flight time by approximately 0.402 seconds compared to the two-clip condition.

Physical Mechanisms and Design Insights

Aerodynamic Considerations:

The observed effects can be understood through fundamental aerodynamic principles:

  1. Lift Generation: Rotor dimensions directly affect the lift-generating surface area, with larger rotors potentially creating more lift but also more drag.

  2. Stability and Control: The paper clip effect primarily influences helicopter stability and descent rate. The no-clip configuration appears more favorable, possibly due to reduced overall weight.

  3. Interaction Effects: Significant interactions indicate that optimal design requires considering factor combinations rather than individual factors in isolation.

Optimal Configuration Analysis

Maximum Flight Time Configuration

Optimal Treatment: a

Design Specifications: - Rotor Length: 8.5 cm (Long) - Rotor Width: 3.5 cm (Narrow)
- Paper Clips: No clips - Average Flight Time: 4.147 seconds

Minimum Flight Time Configuration

Treatment: b

Design Specifications: - Rotor Length: 7.5 cm (Short) - Rotor Width: 5.0 cm (Wide) - Paper Clips: No clips - Average Flight Time: 2.513 seconds

Performance Range Analysis

Total Performance Range: 1.633 seconds

This represents a 65% difference between the best and worst configurations, demonstrating that design choices significantly impact helicopter performance.

Show Code
# Display all treatment combinations ranked by performance
treatment_means %>%
  mutate(
    Length_Level = ifelse(A_Length == -1, "Short (7.5cm)", "Long (8.5cm)"),
    Width_Level = ifelse(B_Width == -1, "Narrow (3.5cm)", "Wide (5.0cm)"), 
    Clip_Level = ifelse(C_Clip == -1, "No Clips", "2 Clips"),
    Rank = rank(-mean_response)
  ) %>%
  arrange(Rank) %>%
  dplyr::select(Rank, Treatment, Length_Level, Width_Level, Clip_Level, mean_response) %>%
  gt() %>%
  tab_header(title = md("**Treatment Performance Ranking**")) %>%
  cols_label(
    Rank = "Rank",
    Treatment = "Treatment Code",
    Length_Level = "Rotor Length",
    Width_Level = "Rotor Width",
    Clip_Level = "Paper Clips",
    mean_response = "Mean Flight Time (s)"
  ) %>%
  fmt_number(columns = c("mean_response"), decimals = 3) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  # Highlight best and worst configurations
  tab_style(
    style = cell_fill(color = "lightgreen", alpha = 0.4),
    locations = cells_body(rows = 1)
  ) %>%
  tab_style(
    style = cell_fill(color = "lightcoral", alpha = 0.4),
    locations = cells_body(rows = 8)
  )
Treatment Performance Ranking
Rank Treatment Code Rotor Length Rotor Width Paper Clips Mean Flight Time (s)
1 a Long (8.5cm) Narrow (3.5cm) No Clips 4.147
2 ab Long (8.5cm) Wide (5.0cm) No Clips 3.610
3 (1) Short (7.5cm) Narrow (3.5cm) No Clips 3.407
4 ac Long (8.5cm) Narrow (3.5cm) 2 Clips 3.367
5 abc Long (8.5cm) Wide (5.0cm) 2 Clips 3.110
6 c Short (7.5cm) Narrow (3.5cm) 2 Clips 3.007
7 bc Short (7.5cm) Wide (5.0cm) 2 Clips 2.583
8 b Short (7.5cm) Wide (5.0cm) No Clips 2.513

Design Recommendations

For Maximum Flight Time Applications

Primary Recommendation: Use treatment a configuration - This design maximizes flight duration for applications requiring extended airtime - The combination provides optimal balance of lift generation and flight stability

For Minimum Flight Time Applications

Alternative Configuration: Treatment b may be preferred when: - Rapid descent is desired - Minimal flight time is the objective - Compact flight patterns are required

Practical Implementation Guidelines

  1. Manufacturing Tolerances: Maintain rotor dimensions within ±0.5mm for consistent performance
  2. Paper Clip Consistency: Use standard paper clips with consistent mass (typically ~0.5g each)
  3. Release Technique: Ensure consistent release height and method for reproducible results
  4. Environmental Factors: Consider air currents and temperature effects in practical applications

The systematic DOE approach has successfully identified the key factors controlling paper helicopter flight performance and provided clear optimization guidance for different application requirements.