A regression model in R that predicts concrete strength from the mix, tested against every classical assumption and checked again later with cross-validation.
ANALYSISConstruction materials20263 min readLive console ↗
Adjusted R² · final model
0.81
five predictors, all p < 0.001
10-fold cross-validated R²
0.80
average error 7.2 MPa on unseen mixes
Mix trials
1,030
8 inputs, no missing values
Problem
Concrete strength decides whether a slab or foundation can carry its load, so engineers tend to over-specify cement to be safe. Cement is the most expensive part of the mix and the biggest source of its carbon.
StrataForge is a stand-in name for a real construction materials client that I can’t name under contract. The firm had two years of mix trials but still designed mixes by rule of thumb. The task was to show which ingredients drive strength, test whether cheaper fly ash weakens concrete, and give the team a formula they could use.
Data
Because the client’s own records are confidential, I worked on the public UCI Concrete Compressive Strength dataset. It holds 1,030 mix trials. Each one has the amounts of seven ingredients in kg per cubic metre, the curing age in days and the measured strength in megapascals. Strength runs from 2.3 to 82.6 MPa and nothing is missing.
Cement, age and superplasticizer correlate most with strength, and water works against it. Age and superplasticizer are heavily skewed, which matters for a linear model.
How each ingredient correlates with strength, all 1,030 mixesPearson r, from −1 to 1
−101
Cement0.50
Slag0.14
Fly ash-0.11
Water-0.29
Superplasticizer0.37
Coarse aggregate-0.17
Fine aggregate-0.17
Age0.33
Show as table
Feature
r
Cement
0.498
Slag
0.135
Fly ash
-0.106
Water
-0.290
Superplasticizer
0.366
Coarse aggregate
-0.165
Fine aggregate
-0.167
Age
0.329
Superplasticizer per cubic metre
379none
320 to 4
1924 to 8
3148 to 12
5912 to 16
3016 to 20
1420 to 24
1024 to 32
kg per cubic metre
379 mixes contain no superplasticizer at all. That spike is why taking its log later forced those rows out.
Show as table
kg per cubic metre
Value
none
379
0 to 4
32
4 to 8
192
8 to 12
314
12 to 16
59
16 to 20
30
20 to 24
14
24 to 32
10
Curing age at test
1361 to 6
1267 to 13
6214 to 27
42528
14529 to 90
10391 to 180
33181 to 365
days
749 of 1,030 mixes were tested at 28 days or sooner, 425 of them at exactly 28 days, the industry standard. A thin tail runs out to a year, a strong right skew.
Show as table
days
Value
1 to 6
136
7 to 13
126
14 to 27
62
28
425
29 to 90
145
91 to 180
103
181 to 365
33
Approach
I built the model in R by forward stepwise selection. I fitted twelve candidate models, adding predictors in order of correlation and checking that every term stayed significant.
The skew in age and superplasticizer limited the plain models to about 61%. Taking logs of both lifted the fit sharply.
Strength against curing age, on a log scale
On a log scale of age the trend is close to a straight line, which is what a linear model needs. Strength rises fast in the first month and slowly after that.
Show as table (first 25 points)
age in days (log scale)
strength (MPa)
28
80
28
61.9
270
40.3
365
41.1
360
44.3
90
47
365
43.7
28
36.4
28
45.9
28
39.3
90
38.1
28
28
270
43
90
42.3
28
47.8
90
52.9
90
39.4
365
56.1
90
40.6
180
42.6
180
41.8
28
28.2
3
8.1
180
44.2
365
52.5
Superplasticizer is zero in 379 mixes, and the log of zero is undefined, so those rows dropped out and the final model uses 651 mixes.
Adjusted R² as the stepwise model grewhigher is better
Cement only24.7%
+ superplasticizer35.0%
+ age48.0%
+ water, fine and coarse aggregate56.8%
+ slag59.4%
+ fly ash61.3%
Logged age and superplasticizer, five predictors81.3%
Every bar uses adjusted R². The last bar is fitted on 651 mixes and the others on all 1,030, so part of that final jump comes from the smaller sample.
Show as table
Item
Value
Cement only
24.7%
+ superplasticizer
35.0%
+ age
48.0%
+ water, fine and coarse aggregate
56.8%
+ slag
59.4%
+ fly ash
61.3%
Logged age and superplasticizer, five predictors
81.3%
model_9 <- lm(concrete_strength ~ cement + superPlasticizer + water + age + slag,
data = concrete_clean) # superPlasticizer and age are logged
summary(model_9) # adjusted R-squared 0.8135, every term p < 0.001
vif(model_9) # 1.01 to 1.49, no multicollinearity
The final formula reads directly as engineering advice.
More cement, more slag and longer curing raise strength. More water lowers it, which is why the water to cement ratio matters so much.
Hypothesis tests
Shapiro-Wilk showed strength is not normally distributed within the groups, so I used Kruskal-Wallis tests, plus a chi-square test for the two categories.
Question
Test
p-value
Result
Does coarse or fine concrete differ in strength?
Kruskal-Wallis
0.336
No significant difference
Do mixes with fly ash differ in strength?
Kruskal-Wallis
0.232
No significant difference
Is fly ash use linked to concrete category?
Chi-square
0.981
No association
A logistic regression also predicts whether a mix contains fly ash from its other ingredients, with an AIC of 145.12 and every predictor significant.
Evaluation
All five classical assumptions for linear regression were checked with residual plots, a Q-Q plot, a scale-location plot and variance inflation factors.
The original project only measured fit on the same rows it was trained on. In October 2026 I rebuilt the model from the raw spreadsheet, reproduced every coefficient, and ran 10-fold cross-validation repeated 20 times. The cross-validated R² is 0.80 ± 0.04 with an average error of 7.2 MPa, so the model holds up on mixes it has not seen.
What didn’t work
The report’s worked example predicted 100.4 MPa for a high-cement mix. The strongest mix ever tested was 82.6 MPa, so that prediction sits outside the data and a linear model should not be trusted there.
The fly ash tests found no significant difference, which is not the same as proving fly ash has no effect. Mixes with and without fly ash also differ in other ingredients, so a fair comparison would hold those fixed.
Limitations
The final model only applies to mixes that contain superplasticizer.
The cost example cuts cement by 90 kg per cubic metre on a 2,000 cubic metre job. At £120 per tonne that saves £21,600, but it does not count the 100 kg per cubic metre of slag added in its place.
The model was built on the public UCI dataset rather than the client’s own records, so its numbers describe that data.