library(ggplot2)
library(dplyr)
library(FrF2)
The experiment is a single replicate of a \(2^3\) factorial design (8 runs) with factors \(A, B, C\).
When a \(2^k\) design is run in \(2^p\) blocks, we must confound \(p\) independent effects (the “block generators”) with the blocks. The total number of effects confounded is \(2^p - 1\), consisting of the \(p\) generators and all their generalized interactions.
To run a \(2^3\) design in two blocks (\(2^p = 2 \Rightarrow p = 1\)), we need to confound exactly 1 independent effect to define the blocks.
Standard practice is to confound the highest-order interaction, which is the 3-factor interaction \(ABC\).
(Note: Any single main effect or 2-way interaction could theoretically be chosen, but confounding a main effect or a 2-way interaction ruins your ability to estimate it cleanly. Confounding \(ABC\) is standard because 3-way interactions are usually negligible.)
Effect confounded: \(ABC\)
We assign the 8 treatment combinations to Block 1 or Block 2 based on the sign of the \(ABC\) contrast (using the standard \((-1, +1)\) coding).
| Run | Treatment | A | B | C | ABC sign | Block |
|---|---|---|---|---|---|---|
| 1 | \((1)\) | - | - | - | \(-1\) | 1 |
| 2 | \(a\) | + | - | - | \(+1\) | 2 |
| 3 | \(b\) | - | + | - | \(+1\) | 2 |
| 4 | \(ab\) | + | + | - | \(-1\) | 1 |
| 5 | \(c\) | - | - | + | \(+1\) | 2 |
| 6 | \(ac\) | + | - | + | \(-1\) | 1 |
| 7 | \(bc\) | - | + | + | \(-1\) | 1 |
| 8 | \(abc\) | + | + | + | \(+1\) | 2 |
Proposed Design: - Block 1: \((1), ab, ac, bc\) (4 runs) - Block 2: \(a, b, c, abc\) (4 runs)
Now we want to run the \(2^3\) design in four blocks (\(2^p = 4 \Rightarrow p = 2\)).
We must confound 2 independent effects (generators). Together with their generalized interaction, a total of 3 effects will be confounded.
Because we only have 3 factors, we are forced to confound two 2-way interactions. The standard approach is to pick two 2-way interactions (usually \(AB\) and \(AC\)).
Generators: \(AB\) and \(AC\) Generalized Interaction: \(AB \times AC = A^2BC = BC\) (since \(A^2 = I\))
Effects confounded: \(AB\), \(AC\), and \(BC\). (Notice that all three 2-way interactions are completely confounded with blocks. We can still cleanly estimate all main effects and the \(ABC\) interaction).
We assign blocks based on the signs of \(AB\) and \(AC\) (coded as \(+\) or \(-\)). Each block will contain \(8 / 4 = 2\) runs.
| Run | Treatment | A | B | C | AB | AC | Block (AB, AC) |
|---|---|---|---|---|---|---|---|
| 1 | \((1)\) | - | - | - | + | + | ++ (Block 1) |
| 2 | \(a\) | + | - | - | - | - | – (Block 4) |
| 3 | \(b\) | - | + | - | - | + | -+ (Block 3) |
| 4 | \(ab\) | + | + | - | + | - | +- (Block 2) |
| 5 | \(c\) | - | - | + | + | + | ++ (Block 1) |
| 6 | \(ac\) | + | - | + | - | - | – (Block 4) |
| 7 | \(bc\) | - | + | + | - | + | -+ (Block 3) |
| 8 | \(abc\) | + | + | + | + | - | +- (Block 2) |
Proposed Design: - Block 1 (++): \((1), c\) - Block 2 (+-): \(ab, abc\) - Block 3 (-+): \(b, bc\) - Block 4 (–): \(a, ac\)
The data below corresponds to the two-block design proposed in 1(b).
# Data for the 2-block design
# Block 1: (1), ab, ac, bc
# Block 2: a, b, c, abc
df <- data.frame(
A = c(-1, -1, 1, 1, -1, -1, 1, 1),
B = c(-1, 1, -1, 1, -1, 1, -1, 1),
C = c(-1, -1, -1, -1, 1, 1, 1, 1),
Treatment = c("(1)", "b", "a", "ab", "c", "bc", "ac", "abc"),
Life = c(22, 35, 32, 55, 44, 60, 40, 39),
Block = c(1, 2, 2, 1, 2, 1, 1, 2)
)
# Make sure factors are formatted correctly
df$Block <- factor(df$Block)
df
## A B C Treatment Life Block
## 1 -1 -1 -1 (1) 22 1
## 2 -1 1 -1 b 35 2
## 3 1 -1 -1 a 32 2
## 4 1 1 -1 ab 55 1
## 5 -1 -1 1 c 44 2
## 6 -1 1 1 bc 60 1
## 7 1 -1 1 ac 40 1
## 8 1 1 1 abc 39 2
To generate the half-normal plot, we fit the full model and extract the absolute values of the effect estimates.
# Fit the full factorial model (without blocks first to get unadjusted effects)
model <- lm(Life ~ A * B * C, data = df)
# Extract coefficients
coefs <- coef(model)[-1] # Remove intercept
effects <- 2 * coefs # Effect estimates are twice the regression coefficients
abs_effects <- abs(effects)
# Create a data frame for plotting
plot_data <- data.frame(
Effect_Name = names(effects),
Abs_Effect = abs_effects
)
# Calculate half-normal quantiles
n <- length(abs_effects)
plot_data <- plot_data %>%
arrange(Abs_Effect) %>%
mutate(
Half_Normal_Quantile = qnorm(0.5 + 0.5 * (1:n - 0.5) / n)
)
# Plot using base R for a clean half-normal plot
plot(plot_data$Half_Normal_Quantile, plot_data$Abs_Effect,
main = "Half-Normal Plot of Effects",
xlab = "Half-Normal Quantiles",
ylab = "Absolute Effect",
pch = 19, col = "steelblue", cex = 1.5)
# Add labels to the points
text(plot_data$Half_Normal_Quantile, plot_data$Abs_Effect,
labels = plot_data$Effect_Name, pos = 4, cex = 0.8, col = "darkred")
# Add a line through the origin and the central mass
abline(0, coef(lm(Abs_Effect ~ Half_Normal_Quantile, data = plot_data[1:5,])),
col = "gray50", lty = 2)
Interpretation: - Look at the plot. Points that fall significantly off the straight reference line are the significant effects. - From the raw data, the largest effects are A, B, and C. The interactions (\(AB, AC, BC, ABC\)) are all relatively small and cluster near the origin along the line. - Significant Effects: A (Cutting Speed), B (Tool Geometry), and C (Cutting Angle) main effects. (Depending on the exact scale, A and B are the largest, C is moderate but distinct).
To check if the block was significant, we fit the model including the Block effect. Because \(ABC\) was confounded with the block, we cannot estimate the \(ABC\) interaction separately from the block effect.
# Fit model accounting for blocks (we remove ABC because it's confounded)
block_model <- lm(Life ~ A * B * C + Block, data = df) # This won't work cleanly in lm if it's perfectly confounded
# Better way: just fit main effects and 2-way interactions, then Block
block_model2 <- lm(Life ~ A + B + C + A:B + A:C + B:C + Block, data = df)
summary(block_model2)
##
## Call:
## lm.default(formula = Life ~ A + B + C + A:B + A:C + B:C + Block,
## data = df)
##
## Residuals:
## ALL 8 residuals are 0: no residual degrees of freedom!
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 44.250 NaN NaN NaN
## A 0.625 NaN NaN NaN
## B 6.375 NaN NaN NaN
## C 4.875 NaN NaN NaN
## Block2 -6.750 NaN NaN NaN
## A:B -0.875 NaN NaN NaN
## A:C -6.875 NaN NaN NaN
## B:C -2.625 NaN NaN NaN
##
## Residual standard error: NaN on 0 degrees of freedom
## Multiple R-squared: 1, Adjusted R-squared: NaN
## F-statistic: NaN on 7 and 0 DF, p-value: NA
Conclusion on Block Significance: Looking at the
summary(block_model2) output, we check the p-value for
Block2. If it is greater than \(\alpha = 0.05\), the block is not
significant.
Given the data, the sum of squares for the block is identical to what the \(ABC\) interaction would have been. The \(ABC\) effect is small (based on the half-normal plot), so the block effect is not significant. This means the blocking did not introduce a large amount of nuisance variability, but it was still a good idea to protect the experiment.
# ============================================================
# Assignment 18 — Blocking in 2^k Factorial Designs
# ============================================================
library(ggplot2)
library(dplyr)
# ---- Question 1 & 2 Block Assignments ----
# (See markdown text for tables)
# ---- Question 3: Data Entry & Analysis ----
df <- data.frame(
A = c(-1, -1, 1, 1, -1, -1, 1, 1),
B = c(-1, 1, -1, 1, -1, 1, -1, 1),
C = c(-1, -1, -1, -1, 1, 1, 1, 1),
Treatment = c("(1)", "b", "a", "ab", "c", "bc", "ac", "abc"),
Life = c(22, 35, 32, 55, 44, 60, 40, 39),
Block = c(1, 2, 2, 1, 2, 1, 1, 2)
)
df$Block <- factor(df$Block)
# Half-Normal Plot
model <- lm(Life ~ A * B * C, data = df)
effects <- 2 * coef(model)[-1]
abs_effects <- abs(effects)
plot_data <- data.frame(
Effect_Name = names(effects),
Abs_Effect = abs_effects
)
n <- length(abs_effects)
plot_data <- plot_data %>%
arrange(Abs_Effect) %>%
mutate(Half_Normal_Quantile = qnorm(0.5 + 0.5 * (1:n - 0.5) / n))
plot(plot_data$Half_Normal_Quantile, plot_data$Abs_Effect,
main = "Half-Normal Plot of Effects",
xlab = "Half-Normal Quantiles",
ylab = "Absolute Effect",
pch = 19, col = "steelblue", cex = 1.5)
text(plot_data$Half_Normal_Quantile, plot_data$Abs_Effect,
labels = plot_data$Effect_Name, pos = 4, cex = 0.8, col = "darkred")
# Block Significance
# Since ABC is confounded with Blocks, we omit it from the model to test Block
block_model <- lm(Life ~ A + B + C + A:B + A:C + B:C + Block, data = df)
summary(block_model)