There are three factors:
The full factorial model, including all main effects and interactions, is:
\[ \begin{aligned} Y_{ijkl} =\ &\mu + A_i + B_j + C_k \\ &+ (AB)_{ij} + (AC)_{ik} + (BC)_{jk}\\ &+ (ABC)_{ijk} + \epsilon_{ijkl} \end{aligned} \]
where \(Y\) is powder density, \(\mu\) is the overall mean, the factor and interaction terms represent their effects, and \(\epsilon\) is random error.
data_url <- "https://raw.githubusercontent.com/tmatis12/datafiles/main/PowderProduction.csv"
powder <- read.csv(data_url, check.names = FALSE)
# Keep the four columns required for this analysis
powder <- powder[, 1:4]
# Assign consistent column names
names(powder) <- c("Ammonium", "StirRate", "Temperature", "Density")
# Convert the three experimental factors into categorical variables
powder$A <- factor(powder$Ammonium,
levels = c(2, 30),
labels = c("Low", "High"))
powder$B <- factor(powder$StirRate,
levels = c(100, 150),
labels = c("Low", "High"))
powder$C <- factor(powder$Temperature,
levels = c(8, 40),
labels = c("Low", "High"))
# Display the data
knitr::kable(powder, caption = "Silver Powder Production Data")
| Ammonium | StirRate | Temperature | Density | A | B | C |
|---|---|---|---|---|---|---|
| 2 | 100 | 8 | 14.68 | Low | Low | Low |
| 2 | 100 | 8 | 15.18 | Low | Low | Low |
| 30 | 100 | 8 | 15.12 | High | Low | Low |
| 30 | 100 | 8 | 17.48 | High | Low | Low |
| 2 | 150 | 8 | 7.54 | Low | High | Low |
| 2 | 150 | 8 | 6.66 | Low | High | Low |
| 30 | 150 | 8 | 12.46 | High | High | Low |
| 30 | 150 | 8 | 12.62 | High | High | Low |
| 2 | 100 | 40 | 10.95 | Low | Low | High |
| 2 | 100 | 40 | 17.68 | Low | Low | High |
| 30 | 100 | 40 | 12.65 | High | Low | High |
| 30 | 100 | 40 | 15.96 | High | Low | High |
| 2 | 150 | 40 | 8.03 | Low | High | High |
| 2 | 150 | 40 | 8.84 | Low | High | High |
| 30 | 150 | 40 | 14.96 | High | High | High |
| 30 | 150 | 40 | 14.96 | High | High | High |
full_model <- lm(Density ~ A * B * C, data = powder)
full_anova <- anova(full_model)
knitr::kable(
full_anova,
digits = 5,
caption = "ANOVA Table for the Full 2^3 Factorial Model"
)
| Df | Sum Sq | Mean Sq | F value | Pr(>F) | |
|---|---|---|---|---|---|
| A | 1 | 44.38891 | 44.38891 | 11.18026 | 0.01018 |
| B | 1 | 70.68606 | 70.68606 | 17.80373 | 0.00292 |
| C | 1 | 0.32776 | 0.32776 | 0.08255 | 0.78117 |
| A:B | 1 | 28.11651 | 28.11651 | 7.08172 | 0.02875 |
| A:C | 1 | 0.02176 | 0.02176 | 0.00548 | 0.94281 |
| B:C | 1 | 10.12831 | 10.12831 | 2.55102 | 0.14889 |
| A:B:C | 1 | 1.51906 | 1.51906 | 0.38261 | 0.55341 |
| Residuals | 8 | 31.76235 | 3.97029 | NA | NA |
# This function extracts effect names and p-values directly
# from an ANOVA table. It excludes the residual row.
extract_pvalues <- function(anova_table) {
p_column <- grep("^Pr\\(", names(anova_table), value = TRUE)
if (length(p_column) == 0) {
stop("The ANOVA table does not contain a p-value column.")
}
effect_names <- rownames(anova_table)
p_values <- anova_table[[p_column[1]]]
keep <- !is.na(p_values) & effect_names != "Residuals"
result <- data.frame(
Effect = effect_names[keep],
P_Value = as.numeric(p_values[keep]),
row.names = NULL
)
return(result)
}
full_pvalues <- extract_pvalues(full_anova)
knitr::kable(
full_pvalues,
digits = 6,
caption = "P-values for All Main Effects and Interactions"
)
| Effect | P_Value |
|---|---|
| A | 0.010175 |
| B | 0.002918 |
| C | 0.781170 |
| A:B | 0.028754 |
| A:C | 0.942808 |
| B:C | 0.148890 |
| A:B:C | 0.553412 |
alpha <- 0.05
significant_effects <- full_pvalues[
full_pvalues$P_Value < alpha,
]
knitr::kable(
significant_effects,
digits = 6,
caption = "Significant Effects at Alpha = 0.05"
)
| Effect | P_Value | |
|---|---|---|
| 1 | A | 0.010175 |
| 2 | B | 0.002918 |
| 4 | A:B | 0.028754 |
At the 0.05 significance level, the full model identifies the following significant effects:
Temperature (C), the AC interaction, the BC interaction, and the ABC interaction are not statistically significant.
# Interaction between Ammonium and Stir Rate
interaction.plot(
x.factor = powder$A,
trace.factor = powder$B,
response = powder$Density,
fun = mean,
type = "b",
pch = c(1, 19),
col = c("blue", "red"),
xlab = "Ammonium",
ylab = "Mean Powder Density",
trace.label = "Stir Rate",
main = "Interaction Plot: Ammonium and Stir Rate"
)
# Interaction between Ammonium and Temperature
interaction.plot(
x.factor = powder$A,
trace.factor = powder$C,
response = powder$Density,
fun = mean,
type = "b",
pch = c(1, 19),
col = c("blue", "red"),
xlab = "Ammonium",
ylab = "Mean Powder Density",
trace.label = "Temperature",
main = "Interaction Plot: Ammonium and Temperature"
)
# Interaction between Stir Rate and Temperature
interaction.plot(
x.factor = powder$B,
trace.factor = powder$C,
response = powder$Density,
fun = mean,
type = "b",
pch = c(1, 19),
col = c("blue", "red"),
xlab = "Stir Rate",
ylab = "Mean Powder Density",
trace.label = "Temperature",
main = "Interaction Plot: Stir Rate and Temperature"
)
The Ammonium × Stir Rate interaction is statistically significant. The interaction plot should therefore be considered when interpreting the effects of these two factors together.
The AC and BC interactions are not statistically significant based on their p-values.
Since Temperature and all interactions involving Temperature are not statistically significant, the reduced model retains Ammonium, Stir Rate, and their interaction.
reduced_model <- lm(Density ~ A * B, data = powder)
reduced_anova <- anova(reduced_model)
knitr::kable(
reduced_anova,
digits = 5,
caption = "ANOVA Table for the Reduced Model"
)
| Df | Sum Sq | Mean Sq | F value | Pr(>F) | |
|---|---|---|---|---|---|
| A | 1 | 44.38891 | 44.38891 | 12.17268 | 0.00447 |
| B | 1 | 70.68606 | 70.68606 | 19.38409 | 0.00086 |
| A:B | 1 | 28.11651 | 28.11651 | 7.71033 | 0.01675 |
| Residuals | 12 | 43.75923 | 3.64660 | NA | NA |
reduced_pvalues <- extract_pvalues(reduced_anova)
knitr::kable(
reduced_pvalues,
digits = 6,
caption = "P-values for the Reduced Model"
)
| Effect | P_Value |
|---|---|
| A | 0.004472 |
| B | 0.000861 |
| A:B | 0.016751 |
The reduced model retains all three terms because they are statistically significant:
The interaction is retained, so both associated main effects are also retained.
par(mfrow = c(2, 2))
plot(reduced_model)
par(mfrow = c(1, 1))
These diagnostic plots are used to examine the residual assumptions, including normality, constant variance, and unusual observations.
The full \(2^3\) factorial analysis shows that Ammonium, Stir Rate, and their interaction are significant at the 0.05 level. Temperature and interactions involving Temperature are not significant.
The reduced model therefore includes Ammonium, Stir Rate, and the Ammonium × Stir Rate interaction. The interaction indicates that the effect of Ammonium on powder density depends on the Stir Rate.
A horticultural researcher wants to investigate three factors that may affect tomato yield.
| Factor | Level 1 | Level 2 |
|---|---|---|
| A: Fertilizer Type | Organic compost | Chemical fertilizer |
| B: Irrigation Frequency | Once per day | Twice per day |
| C: Temperature Setting | 22°C | 28°C |
This is a \(2^3\) factorial experiment with eight treatment combinations.
The experiment uses three replications in a randomized complete block design (RCBD), with greenhouse bench differences controlled through blocking.
There are eight treatment combinations and three blocks, resulting in 24 experimental units.
# Install agricolae if it is not already installed
if (!requireNamespace("agricolae", quietly = TRUE)) {
install.packages("agricolae")
}
library(agricolae)
# Three factors, each with two levels
treatments <- c(2, 2, 2)
# Three replications / blocks
replications <- 3
# Set a reproducible random seed
set.seed(12345)
# Generate the randomized complete block design
design_result <- agricolae::design.ab(
trt = treatments,
r = replications,
serie = 2,
design = "rcbd",
seed = 12345,
kinds = "Super-Duper",
randomization = TRUE
)
# Extract the experimental field book
field_book <- design_result$book
# Display the randomized experimental layout
knitr::kable(
field_book,
caption = "Randomized Complete Block 2^3 Factorial Experimental Layout"
)
| plots | block | A | B | C |
|---|---|---|---|---|
| 101 | 1 | 2 | 2 | 1 |
| 102 | 1 | 1 | 2 | 2 |
| 103 | 1 | 2 | 1 | 2 |
| 104 | 1 | 1 | 1 | 2 |
| 105 | 1 | 2 | 1 | 1 |
| 106 | 1 | 1 | 2 | 1 |
| 107 | 1 | 2 | 2 | 2 |
| 108 | 1 | 1 | 1 | 1 |
| 109 | 2 | 1 | 1 | 2 |
| 110 | 2 | 2 | 2 | 2 |
| 111 | 2 | 1 | 2 | 1 |
| 112 | 2 | 1 | 2 | 2 |
| 113 | 2 | 1 | 1 | 1 |
| 114 | 2 | 2 | 1 | 2 |
| 115 | 2 | 2 | 2 | 1 |
| 116 | 2 | 2 | 1 | 1 |
| 117 | 3 | 2 | 1 | 2 |
| 118 | 3 | 2 | 2 | 2 |
| 119 | 3 | 2 | 2 | 1 |
| 120 | 3 | 1 | 2 | 1 |
| 121 | 3 | 2 | 1 | 1 |
| 122 | 3 | 1 | 2 | 2 |
| 123 | 3 | 1 | 1 | 1 |
| 124 | 3 | 1 | 1 | 2 |
knitr::kable(
design_result$parameters,
caption = "Experimental Design Parameters"
)
|
|
|
|
|
|
|
|
The eight treatment combinations are:
| Treatment | Fertilizer (A) | Irrigation (B) | Temperature (C) |
|---|---|---|---|
| 1 | Organic compost | Once per day | 22°C |
| 2 | Chemical fertilizer | Once per day | 22°C |
| 3 | Organic compost | Twice per day | 22°C |
| 4 | Chemical fertilizer | Twice per day | 22°C |
| 5 | Organic compost | Once per day | 28°C |
| 6 | Chemical fertilizer | Once per day | 28°C |
| 7 | Organic compost | Twice per day | 28°C |
| 8 | Chemical fertilizer | Twice per day | 28°C |
cat("Number of treatment combinations:",
length(unique(field_book$plots)), "\n")
## Number of treatment combinations: 24
cat("Number of blocks:",
length(unique(field_book$block)), "\n")
## Number of blocks: 3
cat("Total number of experimental units:",
nrow(field_book), "\n")
## Total number of experimental units: 24
The design.ab() function from the agricolae
package was used to generate a randomized complete block design for the
\(2^3\) factorial experiment.
The experiment contains eight treatment combinations, three replications, and 24 total experimental units. Blocking helps control variation associated with differences between greenhouse benches, while randomization helps reduce potential bias in treatment assignment.