available · data science and machine learning roles · UKManchester, UK
bright@uzosike:~$

StrataForge concrete strength

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
Show as table
Featurer
Cement0.498
Slag0.135
Fly ash-0.106
Water-0.290
Superplasticizer0.366
Coarse aggregate-0.165
Fine aggregate-0.167
Age0.329
Superplasticizer per cubic metre
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 metreValue
none379
0 to 432
4 to 8192
8 to 12314
12 to 1659
16 to 2030
20 to 2414
24 to 3210
Curing age at test
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
daysValue
1 to 6136
7 to 13126
14 to 2762
28425
29 to 90145
91 to 180103
181 to 36533

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
017345168851371428569018036528, 8028, 61.9270, 40.3365, 41.1360, 44.390, 47365, 43.728, 36.428, 45.928, 39.390, 38.128, 28270, 4390, 42.328, 47.890, 52.990, 39.4365, 56.190, 40.6180, 42.6180, 41.828, 28.23, 8.1180, 44.2365, 52.5270, 53.3270, 41.2180, 52.128, 37.47, 38.6365, 55.3365, 52.9180, 41.7270, 42.1365, 53.7270, 38.428, 30.190, 37.790, 42.2180, 36.390, 50.5365, 43.7365, 39180, 53.190, 41.57, 35.13, 15180, 40.87, 26.37, 32.8180, 39.8180, 46.990, 33.190, 49.27, 14.67, 14.6365, 41.93, 9.1180, 50.928, 33270, 54.4270, 51.73, 9.9270, 50.7180, 48.7270, 55.1360, 44.77, 30.328, 40.928, 723, 34.43, 28.83, 33.43, 36.33, 293, 37.83, 40.23, 33.43, 28.13, 41.33, 33.43, 25.23, 41.13, 35.33, 28.33, 28.63, 35.33, 24.43, 35.33, 39.33, 40.63, 35.33, 24.17, 46.27, 42.87, 49.27, 46.87, 45.77, 55.67, 54.97, 49.27, 34.97, 46.97, 49.27, 33.47, 54.17, 55.97, 49.87, 47.17, 55.97, 387, 55.97, 56.17, 59.17, 22.97, 35.128, 61.128, 59.828, 60.328, 61.828, 56.728, 68.328, 66.928, 60.328, 50.728, 56.428, 60.328, 55.528, 68.528, 71.328, 74.728, 52.228, 71.328, 67.728, 71.328, 6628, 74.528, 71.328, 49.956, 63.456, 64.956, 64.356, 64.956, 60.256, 72.356, 69.356, 64.356, 55.256, 58.856, 64.356, 66.156, 73.756, 77.356, 80.256, 54.956, 77.356, 7356, 77.356, 71.756, 79.456, 77.356, 59.991, 64.991, 66.691, 65.291, 66.791, 62.591, 74.291, 70.791, 65.291, 57.691, 59.291, 65.291, 68.191, 75.591, 79.391, 56.591, 79.391, 76.891, 79.391, 73.391, 82.691, 79.391, 67.83, 11.614, 24.428, 24.956, 29.4100, 40.73, 10.414, 22.128, 22.856, 27.7100, 34.63, 12.514, 2528, 25.756, 34100, 37.33, 1514, 21.128, 26.456, 35.3100, 40.63, 12.514, 20.928, 24.956, 34.2100, 39.63, 1014, 20.128, 24.556, 31.5100, 35.33, 9.414, 22.728, 28.556, 38.6100, 40.43, 10.814, 25.528, 21.556, 28.6100, 33.53, 7.814, 17.828, 24.256, 32.9100, 39.23, 1814, 30.428, 45.756, 50.8100, 53.93, 13.214, 17.828, 40.256, 47.1100, 503, 13.414, 22.328, 24.556, 31.4100, 40.93, 19.914, 25.728, 30.256, 39.6100, 44.33, 13.814, 24.928, 29.256, 38.3100, 42.33, 13.514, 26.328, 31.656, 42.5100, 42.93, 13.314, 25.428, 37.456, 44.4100, 47.73, 19.514, 31.428, 38.556, 45.1100, 47.83, 15.414, 26.828, 33.756, 42.7100, 45.83, 17.214, 29.928, 29.756, 37100, 43.63, 13.114, 24.428, 32.756, 36.6100, 44.23, 13.614, 21.628, 27.856, 35.6100, 45.43, 7.314, 21.528, 31.356, 43.5100, 48.73, 7.414, 23.528, 31.156, 39.1100, 48.23, 22.514, 34.728, 34.756, 45.1100, 493, 23.114, 41.928, 48.356, 51100, 55.63, 22.914, 35.228, 39.956, 48.7100, 523, 2114, 33.428, 33.956, 44.1100, 45.43, 15.414, 28.728, 30.856, 42100, 51.13, 21.814, 42.328, 50.656, 55.8100, 613, 23.514, 42.228, 52.556, 60.3100, 66.43, 23.814, 38.828, 51.356, 56.8100, 58.63, 21.914, 3728, 47.456, 52100, 56.73, 17.614, 33.728, 40.156, 46.6100, 50.13, 17.414, 33.728, 45.956, 51.4100, 59.33, 30.414, 47.728, 63.156, 66.8100, 66.93, 27.414, 3628, 55.556, 62100, 63.53, 1814, 38.628, 52.256, 54100, 56.63, 15.314, 2628, 30.256, 37.3100, 46.23, 16.314, 25.628, 3256, 36.3100, 43.128, 67.628, 57.228, 81.828, 6428, 78.828, 41.428, 60.328, 56.828, 5128, 55.628, 44.128, 39.428, 55.628, 47.328, 44.328, 52.328, 49.228, 41.428, 29.228, 39.428, 39.328, 67.928, 58.528, 53.628, 5928, 76.228, 69.83, 14.43, 19.43, 20.73, 14.93, 21.33, 23.13, 15.53, 15.83, 12.53, 8.53, 15.63, 12.23, 1214, 16.914, 33.114, 34.214, 31.814, 29.814, 3314, 32.914, 29.614, 19.414, 24.714, 29.614, 24.314, 20.728, 26.228, 46.428, 39.228, 41.228, 33.728, 38.228, 41.428, 37.828, 24.828, 27.228, 44.628, 37.328, 33.356, 36.656, 53.756, 48.656, 51.756, 35.956, 53.856, 53.556, 4956, 31.756, 39.656, 51.356, 43.456, 39.3100, 38100, 55100, 50100, 53.7100, 37.7100, 56.1100, 56.8100, 50.9100, 33.6100, 41.2100, 53100, 44.3100, 40.128, 5728, 44.428, 5128, 53.43, 35.43, 253, 23.37, 527, 387, 39.356, 61.156, 56.156, 55.356, 54.828, 50.228, 46.728, 46.73, 22.83, 25.53, 34.87, 36.87, 45.97, 41.756, 56.356, 4856, 61.528, 4428, 55.528, 55.628, 57.93, 25.67, 33.556, 59.63, 29.57, 37.956, 61.928, 62.13, 3228, 72.17, 3956, 65.73, 32.17, 40.356, 74.428, 223, 9.97, 15.156, 23.228, 43.73, 13.47, 24.156, 44.528, 62.928, 59.53, 25.13, 23.67, 35.87, 38.656, 68.856, 66.828, 23.890, 32.13, 11.73, 19.290, 48.828, 39.628, 43.97, 34.690, 54.33, 24.43, 15.690, 21.97, 10.27, 14.628, 18.728, 327, 23.428, 25.690, 41.77, 27.73, 8.27, 9.67, 25.47, 15.728, 27.928, 32.67, 17.27, 19.828, 39.428, 25.728, 33.17, 24.17, 21.828, 21.17, 14.828, 32.17, 127, 25.428, 22.528, 25.228, 39.77, 13.128, 38.77, 7.528, 17.67, 21.228, 18.27, 17.228, 22.67, 21.97, 12.428, 25.728, 37.87, 21.928, 337, 14.528, 26.97, 828, 31.97, 10.328, 19.828, 37.47, 11.57, 24.428, 17.67, 10.728, 31.43, 13.27, 2114, 2728, 3290, 35.2180, 36.4365, 38.93, 6.514, 12.828, 18.490, 21.9180, 24.1365, 25.114, 21.328, 263, 11.490, 31.3180, 32.3360, 33.73, 9.390, 26.9180, 27.6365, 29.8180, 34.5365, 36.13, 12.528, 27.590, 32.97, 107, 7.828, 12.27, 11.228, 17.37, 17.528, 30.67, 14.228, 24.57, 15.628, 26.87, 26.128, 38.228, 43.77, 30.17, 12.728, 20.97, 20.328, 34.37, 19.590, 47.790, 43.428, 29.93, 6.990, 33.23, 4.93, 4.690, 25.528, 24.328, 33.93, 11.428, 20.67, 25.990, 29.290, 317, 10.428, 33.728, 27.97, 19.47, 11.43, 12.828, 39.33, 4.83, 16.128, 43.47, 20.43, 6.97, 153, 13.690, 32.57, 15.77, 7.728, 38.828, 3328, 17.328, 24.328, 2490, 36.690, 50.77, 13.73, 14.190, 47.83, 2.37, 16.97, 23.53, 6.890, 39.728, 1828, 32.928, 22.37, 10.87, 7.728, 41.73, 9.63, 6.990, 50.57, 17.228, 30.43, 9.73, 3.390, 26.390, 43.33, 6.390, 32.128, 3790, 54.67, 21.53, 9.77, 8.490, 39.77, 10.13, 4.87, 10.490, 43.690, 51.93, 11.97, 17.228, 27.890, 35.8120, 38.73, 14.37, 17.428, 31.790, 37.9120, 39.43, 15.97, 928, 33.690, 40.7120, 40.97, 12.128, 17.57, 18.928, 25.27, 3128, 43.990, 54.328, 36.97, 14.528, 22.41, 12.63, 26.17, 33.214, 36.928, 44.17, 52.614, 59.828, 67.390, 69.7180, 71.6270, 74.27, 18.114, 22.528, 27.356, 3090, 31.4180, 32.71, 6.33, 14.77, 23.214, 27.928, 31.4180, 39360, 41.23, 153, 13.57, 2428, 37.47, 11.528, 22.47, 21.228, 31.87, 14.828, 25.228, 17.57, 14.228, 21.790, 29.43, 13.57, 16.328, 31.490, 37.27, 18.128, 32.790, 39.5180, 41.1360, 42.114, 18.1180, 26.7180, 61.990, 47.2180, 51270, 55.23, 41.67, 13.728, 19.728, 31.63, 19.128, 39.690, 48.87, 2428, 37.47, 11.528, 19.77, 1528, 27.990, 34.7180, 37.3360, 38.13, 33.87, 42.414, 48.428, 55.990, 58.8270, 67.114, 20.828, 25.2180, 29.628, 21.828, 39.13, 24.47, 50.528, 7528, 37.228, 33.828, 16.528, 2028, 36.328, 33.728, 15.428, 33.428, 39.128, 27.728, 26.928, 45.328, 30.128, 15.628, 44.628, 53.528, 57.228, 65.928, 52.828, 33.428, 1828, 37.428, 32.828, 42.628, 40.128, 41.928, 61.228, 40.928, 33.328, 52.428, 15.128, 38.528, 37.328, 35.228, 42.128, 31.928, 41.528, 39.528, 37.928, 44.328, 31.228, 23.728, 32.828, 32.428, 28.628, 36.828, 18.328, 33.128, 31.428, 3128, 44.428, 12.228, 25.628, 36.428, 3328, 23.828, 26.228, 1828, 40.728, 1928, 33.728, 8.528, 13.528, 32.228, 23.528, 29.728, 49.828, 52.428, 40.928, 44.928, 13.228, 37.428, 29.928, 56.628, 12.528, 23.828, 13.328, 39.428, 46.228, 44.528, 23.728, 26.128, 15.528, 43.628, 35.928, 41.128, 2928, 46.228, 26.928, 10.528, 25.128, 29.128, 9.728, 33.828, 39.828, 2728, 27.228, 30.628, 33.128, 24.628, 21.928, 30.928, 15.328, 24.328, 23.928, 22.928, 29.428, 28.628, 36.828, 18.328, 32.728, 31.428, 28.928, 40.928, 12.228, 25.628, 36.428, 3328, 23.828, 26.228, 1828, 38.628, 1928, 33.728, 8.528, 13.528, 32.228, 23.528, 29.728, 49.828, 52.428, 40.928, 44.928, 13.228, 37.428, 29.928, 56.628, 12.528, 23.828, 13.328, 39.428, 46.228, 44.528, 23.728, 26.128, 15.528, 43.628, 35.928, 41.128, 2928, 46.228, 26.928, 10.528, 25.128, 29.128, 9.728, 33.828, 37.228, 33.828, 16.528, 2028, 36.428, 38.228, 15.428, 33.428, 39.128, 27.728, 26.928, 45.328, 30.128, 15.628, 44.628, 53.528, 57.228, 65.928, 52.828, 33.428, 1828, 37.428, 35.328, 42.628, 40.128, 43.828, 61.228, 40.928, 33.328, 52.428, 15.128, 38.528, 37.328, 35.228, 42.128, 31.928, 41.528, 39.528, 37.928, 44.328, 31.228, 23.728, 32.828, 32.4age in days (log scale)strength (MPa)
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)
2880
2861.9
27040.3
36541.1
36044.3
9047
36543.7
2836.4
2845.9
2839.3
9038.1
2828
27043
9042.3
2847.8
9052.9
9039.4
36556.1
9040.6
18042.6
18041.8
2828.2
38.1
18044.2
36552.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
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
ItemValue
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%
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.

strength = 23.914 + 0.0974 × cement − 2.545 × ln(superplasticizer)
           − 0.2374 × water + 9.759 × ln(age) + 0.0683 × slag

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.

Stack

  • R
  • dplyr
  • ggplot2
  • car
  • caret
  • corrplot
  • readxl
  • RVAideMemoire
  • React
  • Vite

All work

shell · brightuzosike.devEsc to close · every command is also a link