Espartaco

“Is that to say we are against Free Trade? No, we are for Free Trade, because by Free Trade all economical laws, with their most astounding contradictions, will act upon a larger scale, upon the territory of the whole earth; and because from the uniting of all these contradictions in a single group, where they will stand face to face, will result the struggle which will itself eventuate in the emancipation of the proletariat.”

Karl Heinrich Marx · Marx-Engels Collected Works, Vol. VI, p. 290

28,098 views since December 2020

28,098 visitas desde diciembre de 2020

EnglishEspañol

QUALITATIVE PREDICTORS AND INTERACTIONS IN R: APPLICATIONS FOR THE INSURANCE MARKET AND STRENGTH OF MATERIALS

Marxist Philosophy of Science
Mathematical statistics · Regression · Experimental design

Qualitative Predictors and Interactions in R

Insurance, textile strength, and the geometry of comparisons
JOSÉ MAURICIO GÓMEZ JULIÁN
Originally published: December 2, 2021
Comprehensively revised: November 4, 2026

01 · What changes when a predictor is qualitative

An insurance company may be mutual or stock-owned; a fiber may be manufactured with different cotton percentages. These differences enter statistical analysis through decisions about what to compare and which relationships to allow. Coding categories represents those decisions: which group supplies the reference, whether slopes must coincide, and whether the relationship between a quantity and the response depends on group membership.

The discussion connects two applications. Insurance data link time to innovation adoption with firm assets and ownership form. Materials data compare tensile strength across five textile compositions. Both cases connect regression, analysis of covariance, and analysis of variance within the general linear model. A model is linear in its parameters; it may contain products of predictors or powers of a variable.

02 · Insurance: question, units, and complete data

Let Y be the elapsed number of months until innovation adoption and X the insurer’s size, measured by assets in millions of dollars. Ownership distinguishes mutual and stock companies. Fewer months indicate faster adoption; because the analyzed response is time, a negative coefficient means a shorter expected waiting time. These teaching data come from the example in Kutner, Nachtsheim, Neter, and Li (2005, pp. 313–318), reproduced in the author’s paper [1–2].

The observational unit is the firm: there are 20 companies, 10 of each type. Recording three attributes does not multiply sample size. Innovation, competition, and profitability provide the economic motivation, while these data support the study of conditional associations. A diffusion mechanism between firms would require temporal or network information, and a causal effect would require additional design and identification assumptions.

Insurance data dictionary
Symbol / codeDefinitionUnit / levels
Y / monthsTime to adoptionMonths
X / assetsFirm assetsMillion dollars
typeOwnership formMutual / Stock
DIndicator for stock company0 = mutual; 1 = stock
The 20 observations used in every insurance fit
FirmMonthsAssetsTypeD
117151Mutual0
22692Mutual0
321175Mutual0
43031Mutual0
522104Mutual0
60277Mutual0
712210Mutual0
819120Mutual0
94290Mutual0
1016238Mutual0
1128164Stock1
1215272Stock1
1311295Stock1
143868Stock1
153185Stock1
1621224Stock1
1720166Stock1
1813305Stock1
1930124Stock1
2014246Stock1

The zero-month observation is retained. Its operational meaning—immediate adoption, rounding, or another measurement convention—matters in an actual application. The response is not log-transformed, and no arbitrary constant is added to accommodate zero.

insurance <- data.frame(
  id = 1:20,
  months = c(17,26,21,30,22,0,12,19,4,16,
             28,15,11,38,31,21,20,13,30,14),
  assets = c(151,92,175,31,104,277,210,120,290,238,
             164,272,295,68,85,224,166,305,124,246),
  type = factor(rep(c("Mutual", "Stock"), each = 10),
                levels = c("Mutual", "Stock"))
)
contrasts(insurance$type) <- contr.treatment(levels(insurance$type), base = 1)
stopifnot(nrow(insurance) == 20L, !anyNA(insurance),
          all(table(insurance$type) == 10L))
insurance$D <- as.integer(insurance$type == "Stock")

03 · Indicator functions, factors, and the design matrix

Di = 𝟙{typei = Stock}
D(Stock) = 1;  D(Mutual) = 0

An indicator assigns 1 when a condition holds and 0 otherwise. Zero represents non-membership in the indicated group and remains a valid datum. NA denotes a missing value and NULL a null R object: they are not interchangeable. Factor labels are categories; internal integer codes implement storage. The design matrix contains the columns actually used to fit the model [3–4].

Yi = β0 + β1Xi + β2Di + εi;  xiT = [1, Xi, Di]

With an intercept and treatment coding, a factor with c observed levels contributes c − 1 columns. Including both an intercept and all c indicators produces an exact dependency because the indicators sum to the column of ones. With two groups, 1 = DMutual + DStock. This concerns independent columns, rather than a general prohibition on including multiple binary variables.

With a full-rank matrix, least-squares coefficients are uniquely identifiable. Under rank deficiency, multiple coefficient vectors may generate the same fitted values: projection onto the column space remains unique. Identification must be assessed in the complete matrix; absent categories or redundant combinations may also cause problems. lm() uses QR decomposition and allows inspection of rank and non-estimable coefficients [5].

contrasts(insurance$type)
X <- model.matrix(~ assets + type, data = insurance)
head(X)
stopifnot(qr(X)$rank == ncol(X))
X_bad <- with(insurance, cbind(Intercept = 1,
  Mutual = as.integer(type == "Mutual"), Stock = D))
c(columns = ncol(X_bad), rank = qr(X_bad)$rank)

04 · Assumptions: estimation, inference, and design

The model specifies a conditional mean, E(Y | X, D), with E(ε | X, D) = 0. Classical Gauss–Markov results require full rank and conditional covariance matrix σ²I: constant variance and uncorrelated errors. Conditional normality of errors provides exact finite-sample distributions for classical t and F tests and intervals. Neither the predictors nor the marginal response must be normally distributed.

Companies may share markets, shocks, or ownership links. Such possibilities require substantive information about data collection; a residual plot cannot establish independence. In the textile case, run order, randomization, batches, and machines affect interpretation of the error term. A good numerical fit does not replace a description of the design.

05 · Additive model: differences between parallel lines

E(Y | X, D) = β0 + β1X + β2D

For mutual companies, D = 0 and the mean is β0 + β1X. For stock companies, D = 1 and it is (β0 + β2) + β1X. The common slope is a testable restriction; the shift β2 compares ownership groups at the same size. This is also a form of analysis of covariance: comparing groups while adjusting for a continuous covariate.

m_size <- lm(months ~ assets, data = insurance)
m_add <- lm(months ~ assets + type, data = insurance)
summary(m_add)
confint(m_add)
anova(m_size, m_add)
c(n = nobs(m_add), rank = m_add$rank, df = df.residual(m_add),
  SSE = deviance(m_add), MSE = sigma(m_add)^2,
  residual_SD = sigma(m_add), R_squared = summary(m_add)$r.squared)
Additive-model estimates; classical inference
TermEstimateStandard error95% CIp
Intercept / β₀33.8740691.813858[30.047163, 37.700976]9.145e-13
Assets / β₁-0.1017420.008891[-0.120501, -0.082983]2.075e-09
Stock − Mutual / β₂8.0554691.459106[4.977025, 11.133913]3.742e-05
ŶMutual = 33.874069 − 0.101742X
ŶStock = 41.929538 − 0.101742X

An additional million dollars in assets is associated with 0.1017 fewer months to adoption, holding ownership type fixed. An increase of 100 million corresponds to 10.1742 fewer months within the linear approximation. At the same size, stock companies have an estimated mean adoption time 8.0555 months longer than mutual companies; the 95% interval for that difference is 4.9770 to 11.1339 months. The intercept refers to X = 0, outside the observed asset range, so it primarily serves as a geometric parameter.

The fit gives SSE = 176.386665, MSE = 10.375686 months², residual standard deviation s = 3.221131 months, and R² = 0.895058. R² summarizes explained variation within this sample; it measures neither causality nor out-of-sample predictive performance. Intervals are two-sided 95% intervals, and p-values use the classical assumptions stated above.

06 · Interaction: allowing slopes to differ

E(Y | X, D) = β0 + β1X + β2D + β3XD

The product XD allows the size–time association to depend on ownership type. The mutual-company slope is β1; the stock-company slope is β1 + β3. Thus β3 estimates the difference in slopes. Retaining X and D alongside XD respects model hierarchy and interprets the interaction in a complete parameterization. The expression assets * type expands the two main terms and their interaction [5].

m_int <- lm(months ~ assets * type, data = insurance)
summary(m_int)
confint(m_int)
anova(m_add, m_int)
linear_contrast <- function(model, L, level = 0.95) {
  stopifnot(length(L) == length(coef(model)), !anyNA(coef(model)))
  estimate <- drop(L %*% coef(model))
  se <- sqrt(drop(L %*% vcov(model) %*% L))
  q <- qt(1 - (1 - level)/2, df.residual(model))
  c(estimate = estimate, SE = se,
    lower = estimate - q * se, upper = estimate + q * se)
}
linear_contrast(m_int, c(0, 0, 1, 150))
ŶMutual = 33.838369 − 0.101531X
ŶStock = 41.969620 − 0.101948X

The estimated slope difference is −0.000417 months per million dollars; its 95% CI is [−0.039278, 0.038443]. Testing H₀: β3 = 0 gives F(1,16) = 0.000518 and p = 0.982126. These data provide very little evidence against a common slope. The interval also conveys uncertainty: a large p-value does not establish exact equivalence of slopes.

image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ 50 100 150 200 250 300 Assets (million dollars) 0 5 10 15 20 25 30 35 Months to adoption Common slope Mutual Stock 50 100 150 200 250 300 Assets (million dollars) Months to adoption Group-specific slopes Mutual Stock
Figure 1. Points are the 20 original records. Left: additive model; right: interaction model. Each line is drawn within the asset range observed for its group. Separately estimated slopes are nearly identical.

07 · Changing the reference and centering assets

Changing the reference group changes the coordinates of the representation. In the additive model with stock companies as the reference, the intercept becomes 41.929538 and the mutual-company coefficient becomes −8.055469; the slope remains −0.101742. Fitted values, residuals, SSE, and R² stay the same because the column space is unchanged. relevel() reorders levels; under treatment coding the first is the reference.

With interaction, the group coefficient compares companies at X = 0. It is more useful to center assets at 150 million, a value within both groups’ observed ranges. Defining Xc = X − 150 makes the group coefficient compare firms of that size and the intercept estimate the corresponding mutual-company mean time. Centering does not change the fitted curve.

Δ(X) = E(Y | X, D = 1) − E(Y | X, D = 0) = β2 + β3X

In the original-response interaction model, Δ(150) = 8.068679 months, with 95% CI [4.651119, 11.486239]. Its variance combines the variances of β̂2 and β̂3 and their covariance. Adding their standard errors is insufficient. In general, for contrast vector L, the standard error is √(Lᵀ V̂ L), where V̂ is the coefficient covariance matrix.

insurance$stock_ref <- relevel(insurance$type, ref = "Stock")
contrasts(insurance$stock_ref) <- contr.treatment(
  levels(insurance$stock_ref), base = 1)
m_ref <- lm(months ~ assets + stock_ref, data = insurance)
insurance$assets_c <- insurance$assets - 150
m_center <- lm(months ~ assets_c * type, data = insurance)
coef(m_ref)
coef(m_center)
stopifnot(isTRUE(all.equal(unname(fitted(m_add)),
                          unname(fitted(m_ref)), tolerance = 1e-10)),
          isTRUE(all.equal(unname(fitted(m_int)),
                          unname(fitted(m_center)), tolerance = 1e-10)))

08 · A teaching modification that makes interaction visible

To see what happens when slopes change, define a response constructed from the original. The modification adds 0.05 months per million dollars of assets only to mutual companies. This is a deliberate teaching operation on the data; it adds no firms and does not represent another empirical study.

Y* = Y + 0.05X(1 − D)
β*0 = β0;  β*1 = β1 + 0.05;  β*2 = β2;  β*3 = β3 − 0.05

Because D identifies stock companies, 1 − D identifies mutual companies. The value 0.05 has units of months per million dollars. It is neither a probability, a sampling weight, nor the proportion of mutual firms. In R, == compares values and returns a logical result; as.integer() explicitly converts it to 0 or 1.

# Deliberately modified response: a teaching exercise, not new observations.
insurance$months_demo <- with(insurance,
  months + 0.05 * assets * as.integer(type == "Mutual"))
m_demo_add <- lm(months_demo ~ assets + type, data = insurance)
m_demo_int <- lm(months_demo ~ assets * type, data = insurance)
coef(m_demo_int)
anova(m_demo_add, m_demo_int)
linear_contrast(m_demo_int, c(0, 0, 1, 150))
stopifnot(isTRUE(all.equal(unname(residuals(m_int)),
                          unname(residuals(m_demo_int)), tolerance = 1e-10)))
Two different responses for the same 20 firms
ScenarioMutual slopeStock slopeSlope differenceInteraction p
Original response−0.101531−0.101948−0.0004170.982126
Teaching response−0.051531−0.101948−0.0504170.014224

Comparing the additive and interaction models for Y* gives F(1,16) = 7.564391 and p = 0.014224. The slope difference is detected in the scenario we just constructed. The fitted lines are Ŷ*Mutual = 33.838369 − 0.051531X and Ŷ*Stock = 41.969620 − 0.101948X. Their estimated crossing is around 161.28 million dollars: a property of this exercise, not a causally identified economic threshold.

An instructive algebraic consequence follows. The added term lies in the full model’s column space, so some coefficients change while the full-model residuals remain identical to those from the original response. R² may change because the new response’s total variation also changes. Comparing R² between Y and Y* does not evaluate two models for the same response.

image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ 50 100 150 200 250 300 Assets (million dollars) 10 15 20 25 30 35 Months to adoption Deliberately modified response Mutual Stock
Figure 2. The mutual-company response increases by 0.05X. Slopes now visibly differ and the lines cross. Symbols distinguish groups without relying on color.
image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ 100 150 200 250 300 Assets −10 −5 0 5 10 Difference: stock − mutual (months) Original data 100 150 200 250 300 Assets Teaching exercise
Figure 3. Stock minus mutual by assets, within the overlapping range of 68–290 million. Shaded areas show pointwise 95% CIs, not a simultaneous band. At 150 million the contrast changes from 8.0687 to 0.5687 months; in the second scenario, the interval [−2.8489, 3.9862] includes zero.

09 · Separate fits, degrees of freedom, and model comparison

Fitting one line per group gives the same group-line coefficients and fitted values as the joint model with full interaction, provided the same records and unweighted least squares are used. Inference may differ: the classical joint fit estimates a common residual variance, while each separate regression estimates its own variance.

group_models <- lapply(levels(insurance$type), function(g)
  lm(months ~ assets, data = insurance, subset = type == g))
names(group_models) <- levels(insurance$type)
sapply(group_models, coef)
sapply(group_models, sigma)
group_fit <- numeric(nrow(insurance))
for (g in names(group_models)) {
  k <- insurance$type == g
  group_fit[k] <- predict(group_models[[g]], newdata = insurance[k, ])
}
stopifnot(max(abs(group_fit - fitted(m_int))) < 1e-8)
SSE = Σ ei²;  glres / dfres = n − rank(X)
MSE = SSE / dfres;  s = √MSE
Dispersion and restrictions for the original response
FitnParameters / rankResidual dfSSEs
Original additive20317176.3866653.221131
Original interaction20416176.3809573.320212
Separate mutual1028111.0844073.726332
Separate stock102865.2965502.856933

Adding an independent column cannot increase SSE for the same sample and response. However, MSE and s may increase because residual degrees of freedom decrease. Here, adding interaction reduces SSE only minimally and increases s. The full model’s pooled variance is an average of group variances weighted by their residual degrees of freedom; it need not be smaller than both.

F = [(SSER − SSEF) / (pF − pR)] / [SSEF / (n − pF)]

R denotes the restricted model and F the full model; p is each design matrix’s rank. The numerator measures the average cost of imposing restrictions, and the denominator estimates the full model’s residual variance. With one restriction, F equals the corresponding coefficient’s squared t statistic. Comparison requires nested models, the same response, and the same records. anova(m_add, m_int) tests slopes; anova(m_int) produces a sequential decomposition that may depend on term order [6].

10 · Diagnostics: residuals, leverage, and influence

A residual ei = yi − ŷi measures vertical discrepancy from the fit. Leverage hii depends on predictors and the design matrix. Influence evaluates how much the fit changes when an observation is removed. A firm with extreme assets may have high leverage and a small residual; another may have a large residual with moderate leverage. Neither characteristic alone determines overall influence.

H = X(XᵀX)−1Xᵀ;  ŷ = Hy;  ∂ŷi/∂yi = hii
ri = ei / [s√(1 − hii)]
Cooki = [ei² / (p s²)] · [hii / (1 − hii)²]

These expressions assume full-rank least squares, p parameters, and estimable residual variance. Under homoscedasticity, Var(e | X) = σ²(I − H): raw residuals do not all have equal variance and are not independent, even when the errors satisfy the model. Standardization accounts for this geometric effect. rstudent() uses a dispersion estimate that excludes the observation being evaluated [7–8].

Read each diagnostic according to its purpose
PlotQuestion it addresses
Residuals / fittedIs systematic mean structure left over?
Normal Q–QIs residual shape compatible with normality?
Scale–LocationDoes spread change with the fitted level?
Residuals / leverageDo discrepancy and leverage combine?
image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ 5 10 15 20 25 30 35 Fitted −6 −4 −2 0 2 4 6 Residual Residuals / fitted −2 −1 0 1 2 Theoretical quantiles −2.0 −1.5 −1.0 −0.5 0.0 0.5 1.0 1.5 2.0 Standardized residual Q–Q normal 5 10 15 20 25 30 35 Fitted 0.4 0.6 0.8 1.0 1.2 1.4 √|r| Scale / location 0.05 0.10 0.15 0.20 0.25 0.30 hᵢᵢ −3 −2 −1 0 1 2 3 Standardized residual 6 10 17 0.2 0.5 Residuals / leverage
Figure 4. Diagnostics computed for the original additive model. The lower-right panel labels firms 6, 10, and 17; contours illustrate Cook distances of 0.2 and 0.5. Plots explore compatibility and sensitivity without certifying assumptions.
Five largest Cook distances in the additive fit
FirmResidualStandardized residualhᵢᵢCook
6-5.6915-1.96230.18920.2995
106.34062.11830.13650.2364
17-5.0403-1.65530.10640.1087
142.98891.05250.22270.1058
34.93081.61380.10030.0968

Firms 6 and 10 have Cook distances of about 0.2995 and 0.2364. The heuristic 4/n = 0.20 flags them for investigation; the threshold 2p/n = 0.30 flags no extreme leverage in this fit. These are exploratory rules, not tests or instructions to delete data. The largest Cook distance is below 0.5, which does not remove the need to examine measurement, provenance, and sensitivity of conclusions.

diagnostics <- data.frame(id = insurance$id,
  residual = residuals(m_add), standardized = rstandard(m_add),
  studentized = rstudent(m_add), leverage = hatvalues(m_add),
  Cook = cooks.distance(m_add))
diagnostics[order(diagnostics$Cook, decreasing = TRUE), ]
influence.measures(m_add)
hc3_vcov <- function(model) {
  Z <- model.matrix(model)
  stopifnot(qr(Z)$rank == ncol(Z))
  bread <- solve(crossprod(Z))
  u <- residuals(model)/(1 - hatvalues(model))
  bread %*% crossprod(Z, Z * as.vector(u^2)) %*% bread
}
cbind(estimate = coef(m_add), classical_SE = sqrt(diag(vcov(m_add))),
      HC3_SE = sqrt(diag(hc3_vcov(m_add))))
# Sensitivity analysis: retain the original fit as the main analysis.
sensitivity <- lapply(c(6L, 10L), function(k)
  coef(lm(months ~ assets + type, data = insurance,
          subset = id != k)))
names(sensitivity) <- c("omit_6", "omit_10")
sensitivity

The laboratory adds HC3 standard errors as a heteroscedasticity sensitivity analysis. It computes (XᵀX)⁻¹ Xᵀ diag[eᵢ²/(1 − hᵢᵢ)²] X (XᵀX)⁻¹. OLS coefficients remain unchanged; their estimated covariance changes. HC3 does not repair a misspecified mean, causal confounding, or dependence between firms. With 20 observations, robust inference still requires care about small-sample approximation.

Sensitivity of additive-model standard errors
TermClassical SEHC3 SE
Intercept1.8138581.311688
Assets0.0088910.009051
Stock − Mutual1.4591061.699138

11 · A minimal sensitivity experiment

Consider six points on y = x, with x = (1, 2, 3, 4, 5, 20). Increase y by four units first at x = 3 and then at x = 20, leaving the other observations unchanged. Perturbation size is identical; the point’s position in the design differs. The x = 20 point has leverage 0.9668, whereas x = 3 has leverage 0.1987.

toy <- data.frame(x = c(1,2,3,4,5,20), y = c(1,2,3,4,5,20))
toy$y_near <- toy$y
toy$y_far <- toy$y
toy$y_near[3] <- toy$y_near[3] + 4
toy$y_far[6] <- toy$y_far[6] + 4
toy_base <- lm(y ~ x, data = toy)
toy_near <- lm(y_near ~ x, data = toy)
toy_far <- lm(y_far ~ x, data = toy)
data.frame(x = toy$x, leverage = hatvalues(toy_base))
rbind(original = coef(toy_base), near = coef(toy_near), far = coef(toy_far))
# The exact baseline has SSE=0: do not interpret Cook's D for that fit.
image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ 0 5 10 15 20 x 0 5 10 15 20 25 y Base Change at x = 3 Change at x = 20
Figure 5. Equal vertical perturbations at different positions. Changing the distant point alters the slope more. The baseline fit is exact, with SSE = 0: its Cook distance should not be interpreted using zero residual variance.

The example demonstrates a geometric property of least squares. The distant point is not automatically invalid: it may be a valuable observation extending the study range. Applied assessment combines geometry, discrepancy, record quality, and knowledge of the process.

12 · Prediction and uncertainty for a firm

A conditional mean and a future observation have different uncertainties. For a design row x₀, a mean confidence interval uses s√[x₀ᵀ(XᵀX)⁻¹x₀]. An individual prediction interval uses s√[1 + x₀ᵀ(XᵀX)⁻¹x₀]: it adds variation of a new response around its mean. Both use the appropriate t quantile under the classical assumptions [9].

new_firms <- data.frame(assets = rep(150, 2),
  type = factor(c("Mutual", "Stock"), levels = levels(insurance$type)))
predict(m_add, newdata = new_firms, interval = "confidence")
predict(m_add, newdata = new_firms, interval = "prediction")
Firms with 150 million dollars in assets; times in months; additive model
TypeEstimated mean95% mean CI95% individual PI
Mutual18.6128[16.4349, 20.7906][11.4763, 25.7492]
Stock26.6682[24.3600, 28.9765][19.4909, 33.8455]

Observed assets range from 31 to 290 million for mutual companies and from 68 to 305 million for stock companies. Between-group comparisons have common support from 68 to 290 million. Far extrapolations may yield negative times, revealing a limitation of this approximation to a nonnegative duration. Censoring, unobserved adoption events, or strong asymmetry motivate duration models; a Gamma distribution with log link requires strictly positive responses and does not directly accommodate the zero observed here.

13 · Materials: fiber composition and tensile strength

The second case studies fibers in a blend containing cotton. The predictor is cotton percentage by weight: 15, 20, 25, 30, and 35%, with five strength measurements per level. The physical variable is quantitative; it can be represented as a factor when the aim is to estimate an unrestricted mean for each tested level. The data and their interpretation are verified against Penn State’s teaching example on textile strength [10].

Complete data: five levels and 25 strength measurements
Cotton (%)Strength observations (lb/in²)nMean
157, 7, 15, 11, 959.8
2012, 17, 12, 18, 18515.4
2514, 18, 18, 19, 19517.6
3019, 25, 22, 19, 23521.6
357, 10, 11, 15, 11510.8

The experimental unit is the fiber or material unit assigned a composition; a causal interpretation of treatment differences requires randomized execution, independent replicates, and control of other conditions. Five readings on one specimen would not equal five independent replicates. The identifier treatment denotes composition levels, rather than presupposing five different mechanical test types.

cotton <- data.frame(
  percent = rep(c(15,20,25,30,35), each = 5),
  strength = c(7,7,15,11,9, 12,17,12,18,18,
               14,18,18,19,19, 19,25,22,19,23, 7,10,11,15,11)
)
cotton$treatment <- factor(cotton$percent, levels = c(15,20,25,30,35))
contrasts(cotton$treatment) <- contr.treatment(
  levels(cotton$treatment), base = 1)
stopifnot(nrow(cotton) == 25L, !anyNA(cotton))
aggregate(strength ~ treatment, data = cotton, FUN = mean)
# Safe conversion of numeric-looking labels, not the internal factor codes:
stopifnot(identical(as.numeric(as.character(cotton$treatment)),
                    cotton$percent))

To recover numerical values from labels such as “15” and “20”, use as.numeric(as.character(factor)). Applying as.numeric() directly to the factor returns internal codes 1, 2, 3, 4, and 5. A factor represents one categorical variable with multiple levels; it needs no second variable in order to be created. Explicit coercion can convert numeric text to numbers, while unparseable strings produce missing values with a warning [4].

14 · Five means, four indicators, and one fit

Yij = μj + εij,  j = 1, …, 5;  i = 1, …, 5
Y = α + δ20D20 + δ25D25 + δ30D30 + δ35D35 + ε

The first form assigns a separate mean to each level. In the second, 15% is the reference: α = μ15 and δj = μj − μ15. Estimated coefficients are α̂ = 9.8, δ̂20 = 5.6, δ̂25 = 7.8, δ̂30 = 11.8, and δ̂35 = 1.0. Reconstruct a mean by adding the intercept and its corresponding difference; for example, μ̂30 = 9.8 + 11.8 = 21.6.

Design rows: each pattern occurs five times
LevelInterceptD₂₀D₂₅D₃₀D₃₅
1510000
2011000
2510100
3010010
3510001

The factor, four manual indicators, and five indicators without an intercept generate the same fitting space. They therefore yield the same fitted values, residuals, SSE, and R²; the last parameterization estimates the five means directly. The word class can denote a group here without imposing countability or a universal hierarchy. An indicator vector represents group membership; it does not create a new ontological class of observations.

m_factor <- lm(strength ~ treatment, data = cotton)
m_cell <- lm(strength ~ 0 + treatment, data = cotton)
cotton$D20 <- as.integer(cotton$treatment == "20")
cotton$D25 <- as.integer(cotton$treatment == "25")
cotton$D30 <- as.integer(cotton$treatment == "30")
cotton$D35 <- as.integer(cotton$treatment == "35")
m_manual <- lm(strength ~ D20 + D25 + D30 + D35, data = cotton)
m_linear <- lm(strength ~ percent, data = cotton)
m_quadratic <- lm(strength ~ percent + I(percent^2), data = cotton)
summary(m_factor)
anova(m_factor)
confint(m_factor)
coef(m_cell)
anova(m_linear, m_factor)       # linear lack of fit: 3 restrictions
anova(m_quadratic, m_factor)    # quadratic lack of fit: 2 restrictions
stopifnot(max(abs(fitted(m_factor) - fitted(m_manual))) < 1e-10,
          max(abs(fitted(m_factor) - fitted(m_cell))) < 1e-10)

15 · ANOVA: partitioning variation to compare means

SST = ΣjΣi(yij − ȳ)²
SSB = Σj nj(ȳj − ȳ)²;  SSE = ΣjΣi(yij − ȳj)²
SST = SSB + SSE;  F = [SSB / (c − 1)] / [SSE / (n − c)]

SST measures variation around the overall mean; SSB captures differences between composition means; SSE captures variation within each composition. Under H₀: μ15 = μ20 = μ25 = μ30 = μ35, both mean squares estimate the same variance. Their ratio is compared with F(4,20) under the classical assumptions. Although the method analyzes variances, its central question concerns equality of population means.

ANOVA of strength by cotton percentage
SourceSum of squaresdfMean squareFp
Composition475.764118.9414.7568249.128e−6
Error161.20208.06——
Total636.9624———

The test rejects global equality of means at the 0.05 level. Residual standard deviation is √8.06 = 2.839014 lb/in² and R² = 0.746923. In this one-factor design, η² = SSB/SST = 0.746923 equals R² as a description of the sample. It is neither a percentage of a demonstrated causal mechanism nor a removal of estimation uncertainty.

image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ 15 20 25 30 35 Cotton content (%) 7.5 10.0 12.5 15.0 17.5 20.0 22.5 25.0 Tensile strength (lb/in²) Mean and pointwise 95% CI
Figure 6. All data, group means, and pointwise 95% intervals using the pooled ANOVA variance. Each mean has margin 2.648434 lb/in². Replicates are slightly separated horizontally to reveal overlapping observations.

The largest observed mean occurs at 30% cotton. Claiming that it exceeds another level requires a comparison with uncertainty and attention to the number of comparisons performed. A significant global test does not establish a difference for every pair.

16 · Percentage as a number or factor: different restrictions

A straight line treats percentage increases as changes governed by a common slope. A quadratic admits curvature while remaining linear in its coefficients. The factor model permits five means without specifying a continuous shape between levels. The choice depends on the scientific question and whether the restrictions describe the data.

E(Y | x) = a + bx
E(Y | x) = a + bx + cx²
E(Y | level = j) = μj
Same response and same 25 observations
ModelRankSSER²Lack-of-fit F vs. factorp
Linear2603.3400000.05278218.285360 (3,20)5.967e−6
Quadratic3260.1257140.5916146.136831 (2,20)0.008353
Factor5161.2000000.746923——

Five replicates per level allow pure error estimation through the factor model. A lack-of-fit test compares that error with the extra variation left by a restricted shape. Both the line and the parabola show detectable lack of fit at the 0.05 level. An unconvincing linear slope does not imply absence of a relationship: it may indicate that the proposed shape misses a substantial decline in strength.

image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ 15.0 17.5 20.0 22.5 25.0 27.5 30.0 32.5 35.0 Cotton (%) 10 12 14 16 18 20 22 Mean strength Linear Quadratic Level means
Figure 7. The straight line misses the rise and fall; the parabola approximates it but also leaves lack of fit. Connecting factor means is a visual guide: the model does not identify responses between untested levels.

17 · Multiple comparisons and interval interpretation

Five means generate ten pairwise comparisons. If the question concerns that entire family, Tukey adjusts inference to control the family-wise error rate under classical ANOVA. Simultaneous intervals have 95% joint coverage, unlike ten pointwise intervals treated as a single claim [11].

cotton_aov <- aov(strength ~ treatment, data = cotton)
TukeyHSD(cotton_aov, "treatment", conf.level = 0.95)
mean_grid <- data.frame(treatment = factor(c(15,20,25,30,35),
                         levels = levels(cotton$treatment)))
predict(m_factor, newdata = mean_grid, interval = "confidence")
Complete family of Tukey comparisons
Pair (% cotton)Difference (lb/in²)Simultaneous 95% CIAdjusted p
20 − 155.60[0.2270, 10.9730]0.0385
25 − 157.80[2.4270, 13.1730]0.002595
30 − 1511.80[6.4270, 17.1730]1.901e-05
35 − 151.00[-4.3730, 6.3730]0.9798
25 − 202.20[-3.1730, 7.5730]0.7372
30 − 206.20[0.8270, 11.5730]0.01889
35 − 20-4.60[-9.9730, 0.7730]0.1163
30 − 254.00[-1.3730, 9.3730]0.2101
35 − 25-6.80[-12.1730, -1.4270]0.009065
35 − 30-10.80[-16.1730, -5.4270]6.241e-05
image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/ −15 −10 −5 0 5 10 15 Mean difference; simultaneous 95% CI 20 − 15 25 − 15 30 − 15 35 − 15 25 − 20 30 − 20 35 − 20 30 − 25 35 − 25 35 − 30 Cotton (%)
Figure 8. Mean differences and simultaneous Tukey intervals. Intervals crossing zero do not reject equality for that pair at the 5% family-wise level. All use the same strength units.

The 30% − 25% contrast estimates 4.0 lb/in², with interval [−1.3730, 9.3730] and adjusted p = 0.2101. Thus the largest mean at 30% does not establish superiority over 25% under this adjustment. By contrast, 30% − 35% estimates 10.8 lb/in², with interval [5.4270, 16.1730] and adjusted p ≈ 0.0000624. Failing to reject a difference does not establish equivalence: equivalence requires margins and a procedure designed for that question.

With heterogeneous variances, the validity of pooled ANOVA and Tukey comparisons should be reconsidered. Depending on design, Welch ANOVA with compatible comparisons, a variance model, or robust inference may be appropriate. Batches or repeated measurements require a dependence structure. No automatic correction replaces that substantive decision.

18 · What the two cases support

In insurance, assets are negatively associated with adoption time, and ownership form is associated with a size-adjusted level difference. The original data provide little evidence of different slopes. The teaching modification examines an explicitly introduced interaction and shows why interpretation must depend on the response actually analyzed.

In materials, composition is associated with strength differences that a straight line does not adequately describe. The factor preserves five means and exposes the connection between regression and ANOVA. Multiple comparison distinguishes the largest observed average from a statistically supported difference for a specific pair.

Both cases show that coding selects a testable representation. Changing the reference preserves predictions; imposing a common slope changes restrictions; transforming the response changes the analytical object. Scientific interpretation requires distinguishing those operations, examining uncertainty, and reconstructing the material conditions under which data were produced.

19 · Reproducing the analysis and figures

R blocks appear in execution order and use only base R and its standard packages. All data are included. Copy them sequentially into one script without additional input files. Group codes stay in English (Mutual, Stock) so both language versions execute exactly the same analysis. Reported numbers were recomputed using least squares and t, F, and studentized-range distributions; minor rounding differences do not change the conclusions.

The following block saves data, diagnostics, PDF plots, and session information in a folder relative to the working directory. This article’s figures are drawn from the same data and models. Documenting a new study would additionally require provenance, a measurement protocol, software versions, and exclusion criteria before interpreting results.

outdir <- "predictores_resultados"
dir.create(outdir, showWarnings = FALSE)
write.csv(insurance, file.path(outdir, "insurance.csv"), row.names = FALSE)
write.csv(cotton, file.path(outdir, "cotton.csv"), row.names = FALSE)
write.csv(diagnostics, file.path(outdir, "diagnostics.csv"), row.names = FALSE)
pdf(file.path(outdir, "diagnostics.pdf"), width = 8, height = 8)
par(mfrow = c(2,2))
plot(m_add, which = c(1,2,3,5), ask = FALSE)
plot(m_factor, which = c(1,2,3,5), ask = FALSE)
dev.off()
pdf(file.path(outdir, "cases.pdf"), width = 8, height = 6)
plot(months ~ assets, data = insurance, pch = 16 + insurance$D,
     col = c("#8b4513", "#554637")[1 + insurance$D])
b <- coef(m_add)
abline(b[1], b[2], col = "#8b4513")
abline(b[1] + b[3], b[2], col = "#554637", lty = 2)
legend("topright", levels(insurance$type), pch = c(16,17),
       col = c("#8b4513", "#554637"), lty = c(1,2), bty = "n")
plot(months_demo ~ assets, data = insurance, pch = 16 + insurance$D)
b <- coef(m_demo_int)
abline(b[1], b[2], col = "#8b4513")
abline(b[1] + b[3], b[2] + b[4], col = "#554637", lty = 2)
boxplot(strength ~ treatment, data = cotton, col = "#fffdf7",
        xlab = "Cotton percentage", ylab = "Tensile strength")
plot(TukeyHSD(cotton_aov, "treatment"), las = 1)
dev.off()
capture.output(sessionInfo(), file = file.path(outdir, "sessionInfo.txt"))

20 · References and documentation

  1. José Mauricio Gómez Julián (2021). Variables cualitativas e interacciones. 24-page paper, published under the pseudonym Isadore Nabi. Read.
  2. Kutner, M. H., Nachtsheim, C. J., Neter, J., & Li, W. (2005). Applied Linear Statistical Models, 5th ed. McGraw-Hill/Irwin. Chapter 8, pp. 313–318.
  3. R Core Team. model.matrix: constructing design matrices. Read.
  4. R Core Team. factor: levels, labels, and coercion. Read.
  5. R Core Team. lm: linear models and formulas. Read.
  6. R Core Team. anova.lm: sequential tables and nested comparisons. Read.
  7. R Core Team. plot.lm: diagnostic plots. Read.
  8. R Core Team. influence.measures: residuals, leverage, and case deletion. Read.
  9. R Core Team. predict.lm: confidence and prediction intervals. Read.
  10. Penn State, Department of Statistics. STAT 503, Lesson 03: Experiments with a Single Factor. Fiber-strength example with different cotton percentages. Read.
  11. R Core Team. TukeyHSD: simultaneous mean comparisons. Read.

R references point to official documentation consulted on November 4, 2026. The insurance example preserves the observations in the source paper; the complete textile table is cross-checked against the university source listed above. The modified response is consistently identified as a teaching construction.


Discover more from Marxist Philosophy of Science

Subscribe to get the latest posts sent to your email.

Follow the blogSeguí al blog

Comments

Leave a Comment/Deja un Comentario