How to use Full and Fractional Factorial Experimental Designs
Theory and Practice in R
Forward
Context for This Post
I taught myself about Design of Experiments and how to make the designs in R for a project, and thought I might as well document the learning process! The category is Discovery Learning, named after an inquiry-based learning technique that starts by posing questions to answer or problems to overcome.
Read this post if
- you know what Design of Experiments (DoE) is,
- perhaps you have used experimental designs generated by your stats group or have generated one yourself using software such as JMP,
- you would appreciate a deep-dive into
- why certain design types are used,
- the linear algebra and geometry underpinning the designs,
- how to make the designs yourself.
- While I’m using R here, you could easily take the concepts and make an Excel sheet to generate experimental designs.
Design of Experiments is something of a buzzword; any experiment that you put thought into planing is, in some sense, designed.
The term Design of Experiments (DOE) as used in industry usually refers to a set of defined statistical methods used to design sets of experiments and analyze the results. The available designs can be broadly classified into three groups, comparative designs, screening designs, and response surface designs; which you should use depends on the objective of your experiment.
- Use a Comparative Design to choose between alternatives.
- Use a Screening Design to identify which factors or effects are important.
- Use a Response Surface Design to
- hit a target,
- maximize or minimize a response,
- reduce variation by locating a region where a process is easier to manage,
- make a process robust.
Here, I’m delving into factorial designs, which are a subset of screening designs.
Notes
Information here based heavily on the Choosing an experimental design section of the NIST/SEMATECH e-Handbook of Statistical Methods
General Screening Designs
The goal of a screening design is to determine what factors have an effect on the measured result (the response).
Response
The response is the result of the experimental measurements.
Factor
The factors are the variables of the system that, when changed, may have an effect on the response.
This is accomplished by setting the factors to various levels, performing experimental runs at those levels, and recording the response.
Levels
The levels are the values each factor are set to in the design.
Run
A run is a single measurement or in the design.
Two-Level Full Factorial Designs
A common experimental design is one with all input factors set at two levels each. These levels are called high and low or +1 and -1, respectively. A design with all possible high/low combinations of all the input factors is called a full factorial design in two levels. In general, a design with \(n\) levels and \(k\) factors is noted as a \(n^k\) design.
For example, consider a two-level design with three factors; a \(2^3\) design. Because this will be a full-factorial design, we want every possible combination of factor and level; \(2^3=8\), so we will have 8 runs.
Here is how you would make this design in R:
# Creates 3 Factors with levels -1 and 1
Factor.1 <- c(-1,1)
Factor.2 <- c(-1,1)
Factor.3 <- c(-1,1)
# expand.grid() creates a data frame with all posible combinations of Factors and Levels
design.base <- expand.grid(Factor.1,Factor.2,Factor.3)
# Renames the columns to be the factors we made above
colnames(design.base) <- c("Factor.1", "Factor.2", "Factor.3")
# mutate() adds a new column, here we're just labeling the runs explicitly
design.base <- design.base %>%
mutate(Run = 1:nrow(design.base))
# Rearranges the columns so that 'Run' is first
design.base <- design.base[,c(4,1,2,3)]
# Displays the table
design.base %>% knitr::kable()
Run | Factor.1 | Factor.2 | Factor.3 |
---|---|---|---|
1 | -1 | -1 | -1 |
2 | 1 | -1 | -1 |
3 | -1 | 1 | -1 |
4 | 1 | 1 | -1 |
5 | -1 | -1 | 1 |
6 | 1 | -1 | 1 |
7 | -1 | 1 | 1 |
8 | 1 | 1 | 1 |
The \(2^3\) design is easily represented graphically; the design space is a cube in three-dimensional space, with each factor represented by one of the dimensions. Because we only have two levels, each run is on a vertex of the design space.
# These function use plotly to graph the design space
fig <- plot_ly(showlegend = F) %>%
add_markers(data = design.base, x=~Factor.1, y=~Factor.2, z=~Factor.3,
marker = list(
color = ~Run,
showscale = TRUE
)
) %>%
add_paths(data = t, x=~Factor.1, y=~Factor.2, z=~Factor.3)
fig %>% layout(annotations = list(
x = 1.08,
y = 1.05,
text = "Run",
showarrow = FALSE))
The design could be improved by adding replicates,
# sets the number of replicates to 2
replicates <- 2
# loops over the number of replicates ...
for(i in 1:replicates) {
# bind_rows() combines rows in the dataframe
design <- bind_rows(design.base,design.base)
}
# Displays the table
design %>% knitr::kable()
Run | Factor.1 | Factor.2 | Factor.3 |
---|---|---|---|
1 | -1 | -1 | -1 |
2 | 1 | -1 | -1 |
3 | -1 | 1 | -1 |
4 | 1 | 1 | -1 |
5 | -1 | -1 | 1 |
6 | 1 | -1 | 1 |
7 | -1 | 1 | 1 |
8 | 1 | 1 | 1 |
1 | -1 | -1 | -1 |
2 | 1 | -1 | -1 |
3 | -1 | 1 | -1 |
4 | 1 | 1 | -1 |
5 | -1 | -1 | 1 |
6 | 1 | -1 | 1 |
7 | -1 | 1 | 1 |
8 | 1 | 1 | 1 |
by adding center points,
# Creates a dataframe with the center points
center <- data.frame(
Factor.1 = 0,
Factor.2 = 0,
Factor.3 = 0
)
# Sets the number of center points to 3
n.center <- 3
# Loops over the number of center points ...
for (i in 1:n.center) {
# ... adding the center points to the design
design <- bind_rows(design, center)
}
# Displays the table
design %>% knitr::kable()
Run | Factor.1 | Factor.2 | Factor.3 |
---|---|---|---|
1 | -1 | -1 | -1 |
2 | 1 | -1 | -1 |
3 | -1 | 1 | -1 |
4 | 1 | 1 | -1 |
5 | -1 | -1 | 1 |
6 | 1 | -1 | 1 |
7 | -1 | 1 | 1 |
8 | 1 | 1 | 1 |
1 | -1 | -1 | -1 |
2 | 1 | -1 | -1 |
3 | -1 | 1 | -1 |
4 | 1 | 1 | -1 |
5 | -1 | -1 | 1 |
6 | 1 | -1 | 1 |
7 | -1 | 1 | 1 |
8 | 1 | 1 | 1 |
NA | 0 | 0 | 0 |
NA | 0 | 0 | 0 |
NA | 0 | 0 | 0 |
and by randomizing the order of runs.
# Randomizes the row order
design <- design[sample(1:nrow(design)), ]
# Displays the table
design %>% knitr::kable()
Run | Factor.1 | Factor.2 | Factor.3 | |
---|---|---|---|---|
6 | 6 | 1 | -1 | 1 |
19 | NA | 0 | 0 | 0 |
1 | 1 | -1 | -1 | -1 |
18 | NA | 0 | 0 | 0 |
3 | 3 | -1 | 1 | -1 |
5 | 5 | -1 | -1 | 1 |
13 | 5 | -1 | -1 | 1 |
10 | 2 | 1 | -1 | -1 |
15 | 7 | -1 | 1 | 1 |
2 | 2 | 1 | -1 | -1 |
4 | 4 | 1 | 1 | -1 |
11 | 3 | -1 | 1 | -1 |
17 | NA | 0 | 0 | 0 |
16 | 8 | 1 | 1 | 1 |
12 | 4 | 1 | 1 | -1 |
8 | 8 | 1 | 1 | 1 |
14 | 6 | 1 | -1 | 1 |
9 | 1 | -1 | -1 | -1 |
7 | 7 | -1 | 1 | 1 |
Interactions
Recall that the purpose of screening designs such as factorial designs is to determine what factors are important. Interactions are also important to consider; it is possible that some factors do not have an effect on the response when varied on their own, but do have an effect when varied in conjunction.
Interaction
If a level change of a combination of factors has an effect on the response, and those factors have no effect when changed individually, those factors are said to have an interaction.
An interaction is possible between any combination of factors; the \(2^3\) design has three possible two-factor interactions and one possible three-factor interaction. The levels are found by multiplying the constituent columns.
# Adds the interaction columns for the base design
design <- design.base %>%
mutate(
"F1xF2" = Factor.1 * Factor.2,
"F2xF3" = Factor.2 * Factor.3,
"F1xF3" = Factor.1 * Factor.3,
"F1xF2xF3" = Factor.1 * Factor.2 * Factor.3
)
# Displays the table
design %>% knitr::kable()
Run | Factor.1 | Factor.2 | Factor.3 | F1xF2 | F2xF3 | F1xF3 | F1xF2xF3 |
---|---|---|---|---|---|---|---|
1 | -1 | -1 | -1 | 1 | 1 | 1 | -1 |
2 | 1 | -1 | -1 | -1 | 1 | -1 | 1 |
3 | -1 | 1 | -1 | -1 | -1 | 1 | 1 |
4 | 1 | 1 | -1 | 1 | -1 | -1 | -1 |
5 | -1 | -1 | 1 | 1 | -1 | -1 | 1 |
6 | 1 | -1 | 1 | -1 | -1 | 1 | -1 |
7 | -1 | 1 | 1 | -1 | 1 | -1 | -1 |
8 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Blocking
Blocking is used to eliminate the influence of external variables. One of the most common reasons for blocking is that the number of experiments is too large to complete in one day and we are concerned about day-to-day drift in an instrument. The price you pay for blocking is that you can no longer distinguish the high-order interactions from the blocking effect; they have been confounded.
Confounding
If the effect of either a factor or an interaction of factors cannot be measured independently of all other factors and interactions, the effects that cannot be measured independently are confounded.
For example, take the \(2^3\) design; If 8 runs are too many to complete in one day, we need to block the design. Given that we need to confound the blocking effect with an interaction, we should choose the interaction we care the least about; in general we choose the highest order interaction(s).
Here is how this looks in R assigning the blocks according the three-factor interaction.
design <- design %>% mutate(Block = "")
# Loops over the number of rows ...
for(i in 1:nrow(design)) {
# ... and assigns blocks acording the the value of `F1-F2-F3`
if(design$`F1xF2xF3`[[i]] == -1) {
design$Block[[i]] <- 1
} else {
design$Block[[i]] <- 2
}
}
# Displays the table
design %>% knitr::kable()
Run | Factor.1 | Factor.2 | Factor.3 | F1xF2 | F2xF3 | F1xF3 | F1xF2xF3 | Block |
---|---|---|---|---|---|---|---|---|
1 | -1 | -1 | -1 | 1 | 1 | 1 | -1 | 1 |
2 | 1 | -1 | -1 | -1 | 1 | -1 | 1 | 2 |
3 | -1 | 1 | -1 | -1 | -1 | 1 | 1 | 2 |
4 | 1 | 1 | -1 | 1 | -1 | -1 | -1 | 1 |
5 | -1 | -1 | 1 | 1 | -1 | -1 | 1 | 2 |
6 | 1 | -1 | 1 | -1 | -1 | 1 | -1 | 1 |
7 | -1 | 1 | 1 | -1 | 1 | -1 | -1 | 1 |
8 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 |
# These function use plotly to graph the design space
fig <- plot_ly() %>%
add_markers(data = design, x=~Factor.1, y=~Factor.2, z=~Factor.3, color = ~Block, showscale = TRUE) %>%
add_paths(data = t, x=~Factor.1, y=~Factor.2, z=~Factor.3, showlegend = F)
fig %>% layout(annotations = list(
x = 1.08,
y = 1.05,
text = "Block",
showarrow = FALSE))
You can see this results in the assignment of opposing corners of each face of the cube to the same block, meaning no run in the design space is connected to a run in the same block. This is because the \(2^3\) design has the geometric properties of being balanced and orthogonal.
Orthogonal
Vectors of the same length are orthogonal if the sum of their products is 0.
An experimental design is orthogonal if the effects of any factor sum to zero across the effects of the other factors.
Balanced
An experimental design is balanced if all combinations of factor levels have the same number of runs.
Blocks are treated as separate experiments, addition of center points and randomization are performed within each block.
# Creates a separate dataframe for each block
# filter() creates a subset of the data based on the chriteria supplied
design.block.1 <- design %>% filter(Block == 1)
design.block.2 <- design %>% filter(Block == 2)
# Sets the number of center points
n.center <- 2
for (i in 1:n.center) {
design.block.1 <- bind_rows(design.block.1, center)
design.block.2 <- bind_rows(design.block.2, center)
}
# Randomizes the runs within beach block
design.block.1 <- design.block.1[sample(1:nrow(design.block.1)), ]
design.block.2 <- design.block.2[sample(1:nrow(design.block.2)), ]
# Displays the tables
design.block.1 %>% knitr::kable(caption = "Block 1")
Run | Factor.1 | Factor.2 | Factor.3 | F1xF2 | F2xF3 | F1xF3 | F1xF2xF3 | Block | |
---|---|---|---|---|---|---|---|---|---|
5 | NA | 0 | 0 | 0 | NA | NA | NA | NA | NA |
6 | NA | 0 | 0 | 0 | NA | NA | NA | NA | NA |
2 | 4 | 1 | 1 | -1 | 1 | -1 | -1 | -1 | 1 |
3 | 6 | 1 | -1 | 1 | -1 | -1 | 1 | -1 | 1 |
4 | 7 | -1 | 1 | 1 | -1 | 1 | -1 | -1 | 1 |
1 | 1 | -1 | -1 | -1 | 1 | 1 | 1 | -1 | 1 |
design.block.2 %>% knitr::kable(caption = "Block 2")
Run | Factor.1 | Factor.2 | Factor.3 | F1xF2 | F2xF3 | F1xF3 | F1xF2xF3 | Block | |
---|---|---|---|---|---|---|---|---|---|
1 | 2 | 1 | -1 | -1 | -1 | 1 | -1 | 1 | 2 |
4 | 8 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 |
6 | NA | 0 | 0 | 0 | NA | NA | NA | NA | NA |
5 | NA | 0 | 0 | 0 | NA | NA | NA | NA | NA |
3 | 5 | -1 | -1 | 1 | 1 | -1 | -1 | 1 | 2 |
2 | 3 | -1 | 1 | -1 | -1 | -1 | 1 | 1 | 2 |
Two-level Fractional Factorial Designs
The downside to full-factorial designs is the exponential expansion of the number of runs as the number of factors increases. For example just 6 factors yields 64 runs, not including replicates and center points.
# Creates a full-factorial design with 6 factors
design <- expand.grid(c(-1,1),c(-1,1),c(-1,1),c(-1,1),c(-1,1),c(-1,1))
# mutate() adds a new column, here we're just labeling the runs explicitly
design <- design %>%
mutate(Run = 1:nrow(design))
# Rearranges the columns so that 'Run' is first
design <- design[,c(7,1,2,3,4,5,6)]
# Displays the table
design %>% knitr::kable()
Run | Var1 | Var2 | Var3 | Var4 | Var5 | Var6 |
---|---|---|---|---|---|---|
1 | -1 | -1 | -1 | -1 | -1 | -1 |
2 | 1 | -1 | -1 | -1 | -1 | -1 |
3 | -1 | 1 | -1 | -1 | -1 | -1 |
4 | 1 | 1 | -1 | -1 | -1 | -1 |
5 | -1 | -1 | 1 | -1 | -1 | -1 |
6 | 1 | -1 | 1 | -1 | -1 | -1 |
7 | -1 | 1 | 1 | -1 | -1 | -1 |
8 | 1 | 1 | 1 | -1 | -1 | -1 |
9 | -1 | -1 | -1 | 1 | -1 | -1 |
10 | 1 | -1 | -1 | 1 | -1 | -1 |
11 | -1 | 1 | -1 | 1 | -1 | -1 |
12 | 1 | 1 | -1 | 1 | -1 | -1 |
13 | -1 | -1 | 1 | 1 | -1 | -1 |
14 | 1 | -1 | 1 | 1 | -1 | -1 |
15 | -1 | 1 | 1 | 1 | -1 | -1 |
16 | 1 | 1 | 1 | 1 | -1 | -1 |
17 | -1 | -1 | -1 | -1 | 1 | -1 |
18 | 1 | -1 | -1 | -1 | 1 | -1 |
19 | -1 | 1 | -1 | -1 | 1 | -1 |
20 | 1 | 1 | -1 | -1 | 1 | -1 |
21 | -1 | -1 | 1 | -1 | 1 | -1 |
22 | 1 | -1 | 1 | -1 | 1 | -1 |
23 | -1 | 1 | 1 | -1 | 1 | -1 |
24 | 1 | 1 | 1 | -1 | 1 | -1 |
25 | -1 | -1 | -1 | 1 | 1 | -1 |
26 | 1 | -1 | -1 | 1 | 1 | -1 |
27 | -1 | 1 | -1 | 1 | 1 | -1 |
28 | 1 | 1 | -1 | 1 | 1 | -1 |
29 | -1 | -1 | 1 | 1 | 1 | -1 |
30 | 1 | -1 | 1 | 1 | 1 | -1 |
31 | -1 | 1 | 1 | 1 | 1 | -1 |
32 | 1 | 1 | 1 | 1 | 1 | -1 |
33 | -1 | -1 | -1 | -1 | -1 | 1 |
34 | 1 | -1 | -1 | -1 | -1 | 1 |
35 | -1 | 1 | -1 | -1 | -1 | 1 |
36 | 1 | 1 | -1 | -1 | -1 | 1 |
37 | -1 | -1 | 1 | -1 | -1 | 1 |
38 | 1 | -1 | 1 | -1 | -1 | 1 |
39 | -1 | 1 | 1 | -1 | -1 | 1 |
40 | 1 | 1 | 1 | -1 | -1 | 1 |
41 | -1 | -1 | -1 | 1 | -1 | 1 |
42 | 1 | -1 | -1 | 1 | -1 | 1 |
43 | -1 | 1 | -1 | 1 | -1 | 1 |
44 | 1 | 1 | -1 | 1 | -1 | 1 |
45 | -1 | -1 | 1 | 1 | -1 | 1 |
46 | 1 | -1 | 1 | 1 | -1 | 1 |
47 | -1 | 1 | 1 | 1 | -1 | 1 |
48 | 1 | 1 | 1 | 1 | -1 | 1 |
49 | -1 | -1 | -1 | -1 | 1 | 1 |
50 | 1 | -1 | -1 | -1 | 1 | 1 |
51 | -1 | 1 | -1 | -1 | 1 | 1 |
52 | 1 | 1 | -1 | -1 | 1 | 1 |
53 | -1 | -1 | 1 | -1 | 1 | 1 |
54 | 1 | -1 | 1 | -1 | 1 | 1 |
55 | -1 | 1 | 1 | -1 | 1 | 1 |
56 | 1 | 1 | 1 | -1 | 1 | 1 |
57 | -1 | -1 | -1 | 1 | 1 | 1 |
58 | 1 | -1 | -1 | 1 | 1 | 1 |
59 | -1 | 1 | -1 | 1 | 1 | 1 |
60 | 1 | 1 | -1 | 1 | 1 | 1 |
61 | -1 | -1 | 1 | 1 | 1 | 1 |
62 | 1 | -1 | 1 | 1 | 1 | 1 |
63 | -1 | 1 | 1 | 1 | 1 | 1 |
64 | 1 | 1 | 1 | 1 | 1 | 1 |
We can reduce the number of runs by using a fractional factorial design instead.
As an example, let’s fractionate the \(2^3\) design by \(1/2\). Mathematically, \(1/2 \cdot 2^3\) is equivalent to \(2^{3-1}\), meaning there are \(2^2=4\) runs.
Therefore, let’s start with \(2^2\) as the base design.
# Creates two factors
Factor.1 <- c(-1,1)
Factor.2 <- c(-1,1)
# Creates the design with all possible combinations of factors and levels
design.base <- expand.grid(Factor.1,Factor.2)
# Renames the columns
colnames(design.base) <- c("Factor.1", "Factor.2")
# Adds the run column
design.base <- design.base %>%
mutate(Run = 1:nrow(design.base))
# Re-orders the columns
design.base <- design.base[,c(3,1,2)] %>%
# Makes a new column by multiplying Factor 1 by Factor 2
mutate("Fact.1 x Fact.2" = Factor.1*Factor.2)
# Displays the table
design.base %>% knitr::kable()
Run | Factor.1 | Factor.2 | Fact.1 x Fact.2 |
---|---|---|---|
1 | -1 | -1 | 1 |
2 | 1 | -1 | -1 |
3 | -1 | 1 | -1 |
4 | 1 | 1 | 1 |
To generate the missing column, we need one or more design generators; to do this we need to again consider confounding.
Design Generator
A confounding relationship used to generate an experimental design
The price we pay for fractionating is the same as for blocking; to add a third factor we need to confound it with an interaction. Here, we only have one to choose, the \(Factor_1 \cdot Factor_2\) interaction. Therefore, we need to use the following design generator.
\[3 = 1 \cdot 2\]
# Alters the base design by renaming the two-factor interaction column to factor 3
design <- design.base
colnames(design)[4] <- "Factor.3"
# Displays the table
design %>% knitr::kable()
Run | Factor.1 | Factor.2 | Factor.3 |
---|---|---|---|
1 | -1 | -1 | 1 |
2 | 1 | -1 | -1 |
3 | -1 | 1 | -1 |
4 | 1 | 1 | 1 |
You can see that the runs we have retained by fractionating the design are orthogonal, they are on opposite corners of each face of the cube that represents our design space.
# These function use plotly to graph the design space
fig <- plot_ly(showlegend = F) %>%
add_markers(data = design, x=~Factor.1, y=~Factor.2, z=~Factor.3,
marker = list(
color = ~Run,
showscale = TRUE
)
) %>%
add_paths(data = t, x=~Factor.1, y=~Factor.2, z=~Factor.3)
fig %>% layout(annotations = list(
x = 1.08,
y = 1.05,
text = "Run",
showarrow = FALSE))
Note we could have also generated the design by setting Factor 3 equal to \(-Factor_1 \cdot Factor_2\); this results in the opposite corners of the design space being included.
\[3 = -1 \cdot 2\]
design <- design.base %>%
mutate("Factor.3" = -Factor.1*Factor.2)
# Displays the table
design %>% knitr::kable()
Run | Factor.1 | Factor.2 | Fact.1 x Fact.2 | Factor.3 |
---|---|---|---|---|
1 | -1 | -1 | 1 | -1 |
2 | 1 | -1 | -1 | 1 |
3 | -1 | 1 | -1 | 1 |
4 | 1 | 1 | 1 | -1 |
# These function use plotly to graph the design space
fig <- plot_ly(showlegend = F) %>%
add_markers(data = design, x=~Factor.1, y=~Factor.2, z=~Factor.3,
marker = list(
color = ~Run,
showscale = TRUE
)
) %>%
add_paths(data = t, x=~Factor.1, y=~Factor.2, z=~Factor.3)
fig %>% layout(annotations = list(
x = 1.08,
y = 1.05,
text = "Run",
showarrow = FALSE))
Fractional Factorial Notation
In the previous case, we confounded the Factor 3 main effect with the Factor 1 and Factor 2 interaction effect.
\[Factor_3 = Factor_1 \cdot Factor_2 \implies 3 = 1 \cdot 2 \]
From linear algebra, we know that the identity (\(I\)) is a matrix with 1 along its diagonal and 0 elsewhere. Here, the identity is also a column of all 1’s. Also note that because the factor levels are set at -1 and 1, any factor column multiplied by itself yields \(I\).
For example, lets multiply each side of the previously used design generator by \(3\).
\[ 3 \cdot ( 3 = 1 \cdot 2 ) \implies 3 \cdot 3 = 1 \cdot 2 \cdot 3 \] \[\therefore\] \[ I = 1 \cdot 2 \cdot 3 \] When noted as equivalent to \(I\), it is also a design word; the words have their own shorthand.
\[ ( I = 1 \cdot 2 \cdot 3 ) \implies 1 \cdot 2 \cdot 3 \]
Because this is the relationship we used to generate the design it is also the design generator.
Design Words
The confounding relationships used to generate a fractional factorial design. A \(2^{k-p}\) design has \(p\) design words
Generating the \(2^{8-3}\) design
As a more complicated final example, lets consider a \(2^8\) design fractionated by \(1/8\). Because we have 2 levels the base of the exponential is 2 and this is equivalent to \(2^{8-3}\). Therefore the base design has 5 factors:
# Creates a full-factorial design with 5 factors
design.base <- expand.grid(c(-1,1),c(-1,1),c(-1,1),c(-1,1),c(-1,1))
# Names the columns
colnames(design.base) <- c("F.1", "F.2", "F.3", "F.4", "F.5")
# Makes a run column
design.base <- design.base %>%
mutate(Run = 1:nrow(design.base))
# Puts the 'Run' column first
design.base <- design.base[,c(6,1,2,3,4,5)]
# Displays the table
design.base %>% knitr::kable()
Run | F.1 | F.2 | F.3 | F.4 | F.5 |
---|---|---|---|---|---|
1 | -1 | -1 | -1 | -1 | -1 |
2 | 1 | -1 | -1 | -1 | -1 |
3 | -1 | 1 | -1 | -1 | -1 |
4 | 1 | 1 | -1 | -1 | -1 |
5 | -1 | -1 | 1 | -1 | -1 |
6 | 1 | -1 | 1 | -1 | -1 |
7 | -1 | 1 | 1 | -1 | -1 |
8 | 1 | 1 | 1 | -1 | -1 |
9 | -1 | -1 | -1 | 1 | -1 |
10 | 1 | -1 | -1 | 1 | -1 |
11 | -1 | 1 | -1 | 1 | -1 |
12 | 1 | 1 | -1 | 1 | -1 |
13 | -1 | -1 | 1 | 1 | -1 |
14 | 1 | -1 | 1 | 1 | -1 |
15 | -1 | 1 | 1 | 1 | -1 |
16 | 1 | 1 | 1 | 1 | -1 |
17 | -1 | -1 | -1 | -1 | 1 |
18 | 1 | -1 | -1 | -1 | 1 |
19 | -1 | 1 | -1 | -1 | 1 |
20 | 1 | 1 | -1 | -1 | 1 |
21 | -1 | -1 | 1 | -1 | 1 |
22 | 1 | -1 | 1 | -1 | 1 |
23 | -1 | 1 | 1 | -1 | 1 |
24 | 1 | 1 | 1 | -1 | 1 |
25 | -1 | -1 | -1 | 1 | 1 |
26 | 1 | -1 | -1 | 1 | 1 |
27 | -1 | 1 | -1 | 1 | 1 |
28 | 1 | 1 | -1 | 1 | 1 |
29 | -1 | -1 | 1 | 1 | 1 |
30 | 1 | -1 | 1 | 1 | 1 |
31 | -1 | 1 | 1 | 1 | 1 |
32 | 1 | 1 | 1 | 1 | 1 |
We need to generate three more factors to reach 8; to do this we need to pick three confounding relations to use as design generators. We should pick the interactions we’re least interested in (in general the higher order interactions). Here, we can just pick the 5-factor interaction and two of the five possible 4-factor interactions.
\[ 6 = 1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \] \[ 7 = 2 \cdot 3 \cdot 4 \cdot 5 \] \[ 8 = 1 \cdot 3 \cdot 4 \cdot 5 \]
# Adds Factors according to the equations in the Design Generators
design <- design.base %>% mutate(
F.6 = F.1*F.2*F.3*F.4*F.5,
F.7 = F.2*F.3*F.4*F.5,
F.8 = F.1*F.3*F.4*F.5
)
# Displays the table
design %>% knitr::kable()
Run | F.1 | F.2 | F.3 | F.4 | F.5 | F.6 | F.7 | F.8 |
---|---|---|---|---|---|---|---|---|
1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 |
2 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | -1 |
3 | -1 | 1 | -1 | -1 | -1 | 1 | -1 | 1 |
4 | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 |
5 | -1 | -1 | 1 | -1 | -1 | 1 | -1 | -1 |
6 | 1 | -1 | 1 | -1 | -1 | -1 | -1 | 1 |
7 | -1 | 1 | 1 | -1 | -1 | -1 | 1 | -1 |
8 | 1 | 1 | 1 | -1 | -1 | 1 | 1 | 1 |
9 | -1 | -1 | -1 | 1 | -1 | 1 | -1 | -1 |
10 | 1 | -1 | -1 | 1 | -1 | -1 | -1 | 1 |
11 | -1 | 1 | -1 | 1 | -1 | -1 | 1 | -1 |
12 | 1 | 1 | -1 | 1 | -1 | 1 | 1 | 1 |
13 | -1 | -1 | 1 | 1 | -1 | -1 | 1 | 1 |
14 | 1 | -1 | 1 | 1 | -1 | 1 | 1 | -1 |
15 | -1 | 1 | 1 | 1 | -1 | 1 | -1 | 1 |
16 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 |
17 | -1 | -1 | -1 | -1 | 1 | 1 | -1 | -1 |
18 | 1 | -1 | -1 | -1 | 1 | -1 | -1 | 1 |
19 | -1 | 1 | -1 | -1 | 1 | -1 | 1 | -1 |
20 | 1 | 1 | -1 | -1 | 1 | 1 | 1 | 1 |
21 | -1 | -1 | 1 | -1 | 1 | -1 | 1 | 1 |
22 | 1 | -1 | 1 | -1 | 1 | 1 | 1 | -1 |
23 | -1 | 1 | 1 | -1 | 1 | 1 | -1 | 1 |
24 | 1 | 1 | 1 | -1 | 1 | -1 | -1 | -1 |
25 | -1 | -1 | -1 | 1 | 1 | -1 | 1 | 1 |
26 | 1 | -1 | -1 | 1 | 1 | 1 | 1 | -1 |
27 | -1 | 1 | -1 | 1 | 1 | 1 | -1 | 1 |
28 | 1 | 1 | -1 | 1 | 1 | -1 | -1 | -1 |
29 | -1 | -1 | 1 | 1 | 1 | 1 | -1 | -1 |
30 | 1 | -1 | 1 | 1 | 1 | -1 | -1 | 1 |
31 | -1 | 1 | 1 | 1 | 1 | -1 | 1 | -1 |
32 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
In considering the confounding patterns of this design, it can be helpful to convert the design relations to design words, accomplished by multiplying each side of the relation by the factor it was assigned to. In general, a \(2^{k-p}\) design has \(p\) design words, so here we have 3.
\[ I = \] \[ 1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 = \] \[ 2 \cdot 3 \cdot 4 \cdot 5 \cdot 7 = \] \[ 1 \cdot 3 \cdot 4 \cdot 5 \cdot 8 \]
We also need to find the defining relationship, which is the product of all the design generators. In general a \(2^{k-p}\) design has \(2^p\) total words (including \(I\) itself and the design words), so here we’re looking for \(2^3 = 8\).
Defining Relationship
The total collection of design words that are held constant to define the fraction in a fractional factorial design. A \(2^{k-p}\) design has \(2^p\) design words.
We already have 4; we can find more with two-factor combinations of the design words.
\[ I = \] \[ 1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 7 = \] \[ 1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 7 \cdot 8 =\] \[ 1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 8 \]
The final word is the three-factor combination, leading to the complete defining relationship.
\[ I = 1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 7 \cdot 8 \]
Defining Relation
\[I=\] \[2 \cdot 3 \cdot 4 \cdot 5 \cdot 7=\] \[1 \cdot 3 \cdot 4 \cdot 5 \cdot 8=\] \[1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 6=\] \[1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 7=\] \[1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 7 \cdot 8=\] \[1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 8=\] \[1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 7 \cdot 8\]
The defining relation is useful in considering confounding. For example, multiplying all the words by 1 gives all the confounding relations for Factor 1
\[I \cdot 1 \implies 1=\] \[1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 \cdot 7 =\] \[3 \cdot 4 \cdot 5 \cdot 8 =\] \[2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 =\] \[2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 7 =\] \[2 \cdot 3 \cdot 4 \cdot 5 \cdot 7 \cdot 8 =\] \[2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 8 =\] \[2 \cdot 3 \cdot 4 \cdot 5 \cdot 6 \cdot 7 \cdot 8\]
The length of the shortest word in the defining relation is also equivalent to the resolution of the design.
Resolution III
Main effects are confounded with two-factor interactions.
For example, the generating relation in our \(2^{3-1}\) design was $ 3 = 1 2 $. Because the number of factors is low there is only one word; therefore the generating relation is also the defining relation, $ I = 1 2 3 $. Because the shortest word is 3 Factors the main effects are confounded with 2 factor interactions.
\[ 1 = 2 \cdot 3 \] \[ 2 = 1 \cdot 3 \] \[ 3 = 1 \cdot 2 \]
Resolution IV
No main effects are aliased with two-factor interactions, but two-factor interactions are aliased with each other.
Resolution V
No main effect or two-factor interaction is aliased with any other main effect or two-factor interaction, but two-factor interactions are aliased with three-factor interactions.
The \(2^{8-3}\) design above is a resolution V design.
Close
In writing this post, I learned
- why one would use the different DoE design types,
- how to make factorial designs,
- why one would use a fractional factorial design, and
- what compromises are needed to make a fractional design.
Next Steps
- First functionalize the above,
- Possibly make a Shiny app.
- I could add more design types,
- but response surface designs are rarely used in product development
- (I think they’d be used more in manufacturing)
- and as far as I can tell comparative designs aren’t used heavily in any industry.
- Plackett-Burman designs are very popular, but are just a less general factorial design that forces the number of rows to be multiples of 4. For example,
- a Plackett-Burman design with 3 factors is equivalent to a \(2^{3-1}\) design (4 runs, resolution III),
- a Placket-Burman design with 4 factors is equivalent to a \(2^{4}\) design (16 runs, resolution \(\infty\)),
- a Placket-Burman design with 7 factors is equivalent to a \(2^{7-4}\) design (16 runs, resolution III).
- but response surface designs are rarely used in product development