# install.packages(c("ggplot2", "kernlab", "caret", "xgboost", "randomForest",
# "GGally", "dplyr", "tidyr", "MASS", "reshape2", "gridExtra",
# "scales", "viridis", "corrplot", "ggpubr", "grid", "coda",
# "readxl", "httr", "patchwork"))
library(ggplot2)
library(kernlab)
library(caret)
library(xgboost)
library(randomForest)
library(GGally)
library(dplyr)
library(tidyr)
library(MASS)
library(reshape2)
library(gridExtra)
library(scales)
library(viridis)
library(corrplot)
library(ggpubr)
library(grid)
library(coda)
library(readxl)
library(httr)
library(patchwork)
set.seed(123)
cat("Random seed set to 123 for reproducibility\n")#> Random seed set to 123 for reproducibility
#> Loading data from UCI repository...
url <- "https://archive.ics.uci.edu/ml/machine-learning-databases/concrete/compressive/Concrete_Data.xls"
temp_file <- tempfile(fileext = ".xls")
download.file(url, temp_file, mode = "wb", method = "auto")
concrete_data <- read_excel(temp_file, sheet = 1, col_names = TRUE)
unlink(temp_file)
colnames(concrete_data) <- c("Cement", "Slag", "FlyAsh", "Water",
"Superplasticizer", "CoarseAggregate",
"FineAggregate", "Age", "Strength")
cat("Dataset loaded. Dimensions:", dim(concrete_data), "\n")#> Dataset loaded. Dimensions: 1030 9
if (any(is.na(concrete_data))) {
cat("Warning: Missing values found. Removing rows with NAs.\n")
concrete_data <- na.omit(concrete_data)
}
cat("Final dataset dimensions:", dim(concrete_data), "\n")#> Final dataset dimensions: 1030 9
train_index <- createDataPartition(y, p = 0.8, list = FALSE)
X_train <- X[train_index, ]
X_test <- X[-train_index, ]
y_train <- y[train_index]
y_test <- y[-train_index]
cat("Training set size:", nrow(X_train), "\n")#> Training set size: 826
#> Test set size: 204
compute_subset_stats <- function(data, name) {
stats <- data.frame(
Variable = names(data),
Mean = round(sapply(data, mean, na.rm = TRUE), 2),
SD = round(sapply(data, sd, na.rm = TRUE), 2),
Min = round(sapply(data, min, na.rm = TRUE), 2),
Max = round(sapply(data, max, na.rm = TRUE), 2)
)
cat("\n", name, "Subset Statistics:\n")
print(stats)
return(stats)
}
train_full <- data.frame(X_train, Strength = y_train)
test_full <- data.frame(X_test, Strength = y_test)
train_stats <- compute_subset_stats(train_full, "Training")#>
#> Training Subset Statistics:
#> Variable Mean SD Min Max
#> Cement Cement 281.24 104.84 102.00 540.0
#> Slag Slag 74.66 86.50 0.00 359.4
#> FlyAsh FlyAsh 54.48 64.10 0.00 200.1
#> Water Water 181.99 21.55 121.75 247.0
#> Superplasticizer Superplasticizer 6.18 6.00 0.00 32.2
#> CoarseAggregate CoarseAggregate 971.25 79.30 801.00 1145.0
#> FineAggregate FineAggregate 772.86 81.68 594.00 992.6
#> Age Age 45.51 62.06 1.00 365.0
#> Strength Strength 35.74 16.77 2.33 82.6
#>
#> Test Subset Statistics:
#> Variable Mean SD Min Max
#> Cement Cement 280.86 103.39 116.00 540.00
#> Slag Slag 70.79 85.51 0.00 359.40
#> FlyAsh FlyAsh 53.01 63.72 0.00 195.00
#> Water Water 179.86 20.50 121.75 228.00
#> Superplasticizer Superplasticizer 6.28 5.87 0.00 28.20
#> CoarseAggregate CoarseAggregate 979.69 70.93 824.00 1134.30
#> FineAggregate FineAggregate 776.49 73.90 594.00 992.60
#> Age Age 46.29 67.65 3.00 365.00
#> Strength Strength 36.12 16.50 4.90 81.75
#>
#> Comparing Training and Test Distributions:
for (var in names(train_full)) {
train_mean <- mean(train_full[[var]], na.rm = TRUE)
test_mean <- mean(test_full[[var]], na.rm = TRUE)
diff_pct <- abs(train_mean - test_mean) / train_mean * 100
cat(sprintf("%s: Train mean = %.2f, Test mean = %.2f, Diff = %.1f%%\n",
var, train_mean, test_mean, diff_pct))
}#> Cement: Train mean = 281.24, Test mean = 280.86, Diff = 0.1%
#> Slag: Train mean = 74.66, Test mean = 70.79, Diff = 5.2%
#> FlyAsh: Train mean = 54.48, Test mean = 53.01, Diff = 2.7%
#> Water: Train mean = 181.99, Test mean = 179.86, Diff = 1.2%
#> Superplasticizer: Train mean = 6.18, Test mean = 6.28, Diff = 1.5%
#> CoarseAggregate: Train mean = 971.25, Test mean = 979.69, Diff = 0.9%
#> FineAggregate: Train mean = 772.86, Test mean = 776.49, Diff = 0.5%
#> Age: Train mean = 45.51, Test mean = 46.29, Diff = 1.7%
#> Strength: Train mean = 35.74, Test mean = 36.12, Diff = 1.1%
calculate_metrics <- function(actual, predicted) {
rmse <- sqrt(mean((actual - predicted)^2))
mae <- mean(abs(actual - predicted))
r2 <- 1 - sum((actual - predicted)^2) / sum((actual - mean(actual))^2)
mape <- mean(abs((actual - predicted) / actual)) * 100
return(c(RMSE = rmse, MAE = mae, R2 = r2, MAPE = mape))
}
get_prediction_intervals <- function(model, newdata) {
pred_mean <- predict(model, newdata)
train_pred <- predict(model, newdata = train_data)
resid_sd <- sd(y_train - train_pred)
pred_lower <- pred_mean - 1.96 * resid_sd
pred_upper <- pred_mean + 1.96 * resid_sd
return(data.frame(mean = pred_mean, lower = pred_lower, upper = pred_upper))
}
compute_coverage <- function(actual, lower, upper) {
covered <- (actual >= lower) & (actual <= upper)
return(mean(covered) * 100)
}
predict_strength <- function(wb, cement) {
a <- 45; b <- 3.2; c <- 0.15
base_strength <- a * exp(-b * wb) * (cement / 350)^c
base_strength <- base_strength * (1 + 0.1 * (cement - 350) / 350 * (0.5 - wb))
return(base_strength)
}preprocess_params <- preProcess(X_train, method = c("center", "scale"))
X_train_scaled <- predict(preprocess_params, X_train)
X_test_scaled <- predict(preprocess_params, X_test)
train_data <- data.frame(X_train_scaled, Strength = y_train)
test_data <- data.frame(X_test_scaled, Strength = y_test)
dtrain <- xgb.DMatrix(data = as.matrix(X_train_scaled), label = y_train)
dtest <- xgb.DMatrix(data = as.matrix(X_test_scaled), label = y_test)Sigma is selected using 5-fold cross-validation on the training set only. The test set is not used at any point during sigma selection.
set.seed(456)
folds <- 5
cv_folds <- createFolds(y_train, k = folds, list = TRUE)
cv_gpr_train <- function(sigma_val, train_data, y_train, folds) {
cv_rmse <- numeric(length(folds))
for (i in 1:length(folds)) {
val_idx <- folds[[i]]
train_idx <- setdiff(1:nrow(train_data), val_idx)
model <- gausspr(Strength ~ ., data = train_data[train_idx, ],
kernel = "rbfdot",
kpar = list(sigma = sigma_val))
pred <- predict(model, newdata = train_data[val_idx, ])
actual <- y_train[val_idx]
cv_rmse[i] <- sqrt(mean((actual - pred)^2))
}
return(list(mean_rmse = mean(cv_rmse), sd_rmse = sd(cv_rmse)))
}
cat("\nStep 1: Coarse CV grid search over sigma...\n")#>
#> Step 1: Coarse CV grid search over sigma...
coarse_sigma_grid <- c(0.005, 0.01, 0.02, 0.03, 0.05, 0.08, 0.1, 0.15,
0.2, 0.3, 0.5, 0.8, 1.0, 1.5, 2.0, 3.0, 5.0)
coarse_cv_results <- data.frame()
for (sigma_val in coarse_sigma_grid) {
res <- cv_gpr_train(sigma_val, train_data, y_train, cv_folds)
coarse_cv_results <- rbind(coarse_cv_results,
data.frame(sigma = sigma_val,
mean_rmse = res$mean_rmse,
sd_rmse = res$sd_rmse))
cat(sprintf(" sigma = %.4f -> CV RMSE = %.4f (+/-%.4f)\n",
sigma_val, res$mean_rmse, res$sd_rmse))
}#> sigma = 0.0050 -> CV RMSE = 10.2308 (+/-0.6324)
#> sigma = 0.0100 -> CV RMSE = 9.3049 (+/-0.6067)
#> sigma = 0.0200 -> CV RMSE = 8.4163 (+/-0.6158)
#> sigma = 0.0300 -> CV RMSE = 8.0383 (+/-0.6661)
#> sigma = 0.0500 -> CV RMSE = 7.6311 (+/-0.7300)
#> sigma = 0.0800 -> CV RMSE = 7.3170 (+/-0.7644)
#> sigma = 0.1000 -> CV RMSE = 7.2066 (+/-0.7687)
#> sigma = 0.1500 -> CV RMSE = 7.0887 (+/-0.7546)
#> sigma = 0.2000 -> CV RMSE = 7.0838 (+/-0.7355)
#> sigma = 0.3000 -> CV RMSE = 7.2083 (+/-0.7080)
#> sigma = 0.5000 -> CV RMSE = 7.6038 (+/-0.6674)
#> sigma = 0.8000 -> CV RMSE = 8.2362 (+/-0.6063)
#> sigma = 1.0000 -> CV RMSE = 8.6175 (+/-0.5722)
#> sigma = 1.5000 -> CV RMSE = 9.3869 (+/-0.5333)
#> sigma = 2.0000 -> CV RMSE = 9.9621 (+/-0.5357)
#> sigma = 3.0000 -> CV RMSE = 10.7853 (+/-0.5743)
#> sigma = 5.0000 -> CV RMSE = 11.7916 (+/-0.6457)
coarse_best_idx <- which.min(coarse_cv_results$mean_rmse)
coarse_best_sigma <- coarse_cv_results$sigma[coarse_best_idx]
cat(sprintf("\nCoarse-search best sigma = %.4f (CV RMSE = %.4f)\n",
coarse_best_sigma, coarse_cv_results$mean_rmse[coarse_best_idx]))#>
#> Coarse-search best sigma = 0.2000 (CV RMSE = 7.0838)
#>
#> Step 2: Refined CV grid search around best sigma...
refined_sigma <- exp(seq(log(coarse_best_sigma * 0.5),
log(coarse_best_sigma * 2.0),
length.out = 15))
refined_cv_results <- data.frame()
for (sigma_val in refined_sigma) {
res <- cv_gpr_train(sigma_val, train_data, y_train, cv_folds)
refined_cv_results <- rbind(refined_cv_results,
data.frame(sigma = sigma_val,
mean_rmse = res$mean_rmse,
sd_rmse = res$sd_rmse))
cat(sprintf(" sigma = %.4f -> CV RMSE = %.4f (+/-%.4f)\n",
sigma_val, res$mean_rmse, res$sd_rmse))
}#> sigma = 0.1000 -> CV RMSE = 7.2066 (+/-0.7687)
#> sigma = 0.1104 -> CV RMSE = 7.1674 (+/-0.7677)
#> sigma = 0.1219 -> CV RMSE = 7.1346 (+/-0.7649)
#> sigma = 0.1346 -> CV RMSE = 7.1086 (+/-0.7606)
#> sigma = 0.1486 -> CV RMSE = 7.0901 (+/-0.7552)
#> sigma = 0.1641 -> CV RMSE = 7.0794 (+/-0.7489)
#> sigma = 0.1811 -> CV RMSE = 7.0772 (+/-0.7423)
#> sigma = 0.2000 -> CV RMSE = 7.0838 (+/-0.7355)
#> sigma = 0.2208 -> CV RMSE = 7.0995 (+/-0.7287)
#> sigma = 0.2438 -> CV RMSE = 7.1247 (+/-0.7220)
#> sigma = 0.2692 -> CV RMSE = 7.1593 (+/-0.7153)
#> sigma = 0.2972 -> CV RMSE = 7.2036 (+/-0.7086)
#> sigma = 0.3281 -> CV RMSE = 7.2576 (+/-0.7018)
#> sigma = 0.3623 -> CV RMSE = 7.3216 (+/-0.6947)
#> sigma = 0.4000 -> CV RMSE = 7.3960 (+/-0.6871)
cv_results <- rbind(coarse_cv_results, refined_cv_results)
cv_results <- cv_results[order(cv_results$sigma), ]
cv_results <- cv_results[!duplicated(cv_results$sigma), ]
optimal_idx <- which.min(cv_results$mean_rmse)
optimal_sigma <- cv_results$sigma[optimal_idx]
optimal_cv_rmse <- cv_results$mean_rmse[optimal_idx]
cat(sprintf("\n>>> FINAL OPTIMAL SIGMA (from training-set CV): %.4f\n", optimal_sigma))#>
#> >>> FINAL OPTIMAL SIGMA (from training-set CV): 0.1811
#> >>> Corresponding CV RMSE: 7.0772 MPa
optimal_gpr <- gausspr(Strength ~ ., data = train_data,
kernel = "rbfdot",
kpar = list(sigma = optimal_sigma))
optimal_pred <- predict(optimal_gpr, newdata = test_data)
optimal_metrics <- calculate_metrics(y_test, optimal_pred)
cat(sprintf("Optimized GPR (sigma = %.4f): RMSE = %.3f, R2 = %.3f\n",
optimal_sigma, optimal_metrics[1], optimal_metrics[3]))#> Optimized GPR (sigma = 0.1811): RMSE = 7.413, R2 = 0.797
#> Using automatic sigma estimation (sigest) for RBF or laplace kernel
default_pred <- predict(default_gpr, newdata = test_data)
default_metrics <- calculate_metrics(y_test, default_pred)
default_sigma_value <- tryCatch(as.numeric(default_gpr@kpar$sigma),
error = function(e) NA)
cat(sprintf("Default GPR (sigest, sigma ~ %.4f): RMSE = %.3f, R2 = %.3f\n",
default_sigma_value, default_metrics[1], default_metrics[3]))
fixed_gpr <- gausspr(Strength ~ ., data = train_data,
kernel = "rbfdot",
kpar = list(sigma = 0.5))
fixed_pred <- predict(fixed_gpr, newdata = test_data)
fixed_metrics <- calculate_metrics(y_test, fixed_pred)
cat(sprintf("Fixed GPR (sigma = 0.5): RMSE = %.3f, R2 = %.3f\n",
fixed_metrics[1], fixed_metrics[3]))#> Fixed GPR (sigma = 0.5): RMSE = 8.105, R2 = 0.758
gpr_uncertainty <- get_prediction_intervals(optimal_gpr, X_test_scaled)
coverage_rate <- compute_coverage(y_test,
gpr_uncertainty$lower,
gpr_uncertainty$upper)
cat(sprintf("GPR 95%% prediction interval empirical coverage: %.1f%%\n",
coverage_rate))#> GPR 95% prediction interval empirical coverage: 90.2%
summary_table_clean <- data.frame(
Variable = c("Cement (kg/m3)", "Slag (kg/m3)", "Fly Ash (kg/m3)",
"Water (kg/m3)", "Superplasticizer (kg/m3)",
"Coarse Agg. (kg/m3)", "Fine Agg. (kg/m3)",
"Age (days)", "Strength (MPa)"),
Mean = round(colMeans(concrete_data[, c("Cement","Slag","FlyAsh","Water",
"Superplasticizer","CoarseAggregate",
"FineAggregate","Age","Strength")]), 1),
SD = round(apply(concrete_data[, c("Cement","Slag","FlyAsh","Water",
"Superplasticizer","CoarseAggregate",
"FineAggregate","Age","Strength")], 2, sd), 1),
Min = round(apply(concrete_data[, c("Cement","Slag","FlyAsh","Water",
"Superplasticizer","CoarseAggregate",
"FineAggregate","Age","Strength")], 2, min), 1),
Q1 = round(apply(concrete_data[, c("Cement","Slag","FlyAsh","Water",
"Superplasticizer","CoarseAggregate",
"FineAggregate","Age","Strength")], 2,
quantile, 0.25), 1),
Median = round(apply(concrete_data[, c("Cement","Slag","FlyAsh","Water",
"Superplasticizer","CoarseAggregate",
"FineAggregate","Age","Strength")], 2,
median), 1),
Q3 = round(apply(concrete_data[, c("Cement","Slag","FlyAsh","Water",
"Superplasticizer","CoarseAggregate",
"FineAggregate","Age","Strength")], 2,
quantile, 0.75), 1),
Max = round(apply(concrete_data[, c("Cement","Slag","FlyAsh","Water",
"Superplasticizer","CoarseAggregate",
"FineAggregate","Age","Strength")], 2, max), 1)
)
print(summary_table_clean)#> Variable Mean SD Min Q1 Median Q3
#> Cement Cement (kg/m3) 281.2 104.5 102.0 192.4 272.9 350.0
#> Slag Slag (kg/m3) 73.9 86.3 0.0 0.0 22.0 142.9
#> FlyAsh Fly Ash (kg/m3) 54.2 64.0 0.0 0.0 0.0 118.3
#> Water Water (kg/m3) 181.6 21.4 121.8 164.9 185.0 192.0
#> Superplasticizer Superplasticizer (kg/m3) 6.2 6.0 0.0 0.0 6.3 10.2
#> CoarseAggregate Coarse Agg. (kg/m3) 972.9 77.8 801.0 932.0 968.0 1029.4
#> FineAggregate Fine Agg. (kg/m3) 773.6 80.2 594.0 730.9 779.5 824.0
#> Age Age (days) 45.7 63.2 1.0 7.0 28.0 56.0
#> Strength Strength (MPa) 35.8 16.7 2.3 23.7 34.4 46.1
#> Max
#> Cement 540.0
#> Slag 359.4
#> FlyAsh 200.1
#> Water 247.0
#> Superplasticizer 32.2
#> CoarseAggregate 1145.0
#> FineAggregate 992.6
#> Age 365.0
#> Strength 82.6
fig1a <- ggplot(concrete_data, aes(x = Strength)) +
geom_histogram(aes(y = after_stat(density)), bins = 40,
fill = "skyblue", color = "darkblue", alpha = 0.7) +
geom_density(color = "red", linewidth = 1.2) +
geom_vline(xintercept = mean(concrete_data$Strength),
color = "red", linetype = "dashed", linewidth = 1) +
geom_vline(xintercept = median(concrete_data$Strength),
color = "blue", linetype = "dashed", linewidth = 1) +
labs(title = "Distribution of Concrete Compressive Strength",
x = "Compressive Strength (MPa)", y = "Density") +
annotate("text", x = 65, y = 0.025,
label = paste("Mean =", round(mean(concrete_data$Strength), 1), "MPa"),
color = "red") +
annotate("text", x = 65, y = 0.023,
label = paste("Median =", round(median(concrete_data$Strength), 1), "MPa"),
color = "blue") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank())
print(fig1a)ggsave("Figure1a_Data_Distribution.png", fig1a, width = 8, height = 6, dpi = 300)
fig1b <- ggplot(concrete_data, aes(x = WB_Ratio, y = Strength)) +
geom_point(alpha = 0.5, color = "darkblue") +
geom_smooth(method = "loess", color = "red", linewidth = 1.2, se = TRUE) +
labs(title = "Relationship between Water-to-Binder Ratio and Compressive Strength",
x = "Water-to-Binder Ratio", y = "Compressive Strength (MPa)") +
annotate("text", x = 0.7, y = 75,
label = paste("r =", round(cor(concrete_data$WB_Ratio,
concrete_data$Strength,
use = "complete.obs"), 3)),
size = 5) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank())
print(fig1b)ggsave("Figure1b_WB_Ratio.png", fig1b, width = 8, height = 6, dpi = 300)
fig1c <- ggpairs(concrete_data[, c("Cement", "Water", "Age", "Strength")],
title = "Scatter Plot Matrix of Key Variables",
upper = list(continuous = wrap("cor", size = 3)),
lower = list(continuous = wrap("points", alpha = 0.3, size = 0.5))) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"))
print(fig1c)lr_model <- lm(Strength ~ ., data = train_data)
lr_pred <- predict(lr_model, newdata = test_data)
lr_metrics <- calculate_metrics(y_test, lr_pred)
print(round(lr_metrics, 3))#> RMSE MAE R2 MAPE
#> 10.423 8.261 0.599 29.459
#>
#> Tuning Random Forest hyperparameters...
set.seed(789)
rf_grid <- expand.grid(mtry = c(2, 4, 6, 8), ntree = c(100, 200, 500, 1000))
rf_cv_results <- data.frame()
for (i in 1:nrow(rf_grid)) {
cv_rmse <- numeric(5)
for (j in 1:5) {
idx_cv <- createDataPartition(y_train, p = 0.8, list = FALSE)
rf_cv <- randomForest(Strength ~ ., data = train_data[idx_cv, ],
ntree = rf_grid$ntree[i], mtry = rf_grid$mtry[i])
pred_cv <- predict(rf_cv, newdata = train_data[-idx_cv, ])
cv_rmse[j] <- sqrt(mean((y_train[-idx_cv] - pred_cv)^2))
}
rf_cv_results <- rbind(rf_cv_results,
data.frame(mtry = rf_grid$mtry[i],
ntree = rf_grid$ntree[i],
mean_rmse = mean(cv_rmse)))
cat(sprintf("mtry = %d, ntree = %d: CV RMSE = %.3f\n",
rf_grid$mtry[i], rf_grid$ntree[i], mean(cv_rmse)))
}#> mtry = 2, ntree = 100: CV RMSE = 6.114
#> mtry = 4, ntree = 100: CV RMSE = 5.388
#> mtry = 6, ntree = 100: CV RMSE = 5.240
#> mtry = 8, ntree = 100: CV RMSE = 5.316
#> mtry = 2, ntree = 200: CV RMSE = 5.729
#> mtry = 4, ntree = 200: CV RMSE = 5.256
#> mtry = 6, ntree = 200: CV RMSE = 4.820
#> mtry = 8, ntree = 200: CV RMSE = 4.749
#> mtry = 2, ntree = 500: CV RMSE = 5.759
#> mtry = 4, ntree = 500: CV RMSE = 4.531
#> mtry = 6, ntree = 500: CV RMSE = 4.935
#> mtry = 8, ntree = 500: CV RMSE = 5.283
#> mtry = 2, ntree = 1000: CV RMSE = 6.246
#> mtry = 4, ntree = 1000: CV RMSE = 4.947
#> mtry = 6, ntree = 1000: CV RMSE = 4.676
#> mtry = 8, ntree = 1000: CV RMSE = 5.082
best_rf <- rf_cv_results[which.min(rf_cv_results$mean_rmse), ]
cat(sprintf("Best RF: mtry = %d, ntree = %d (CV RMSE = %.3f)\n",
best_rf$mtry, best_rf$ntree, best_rf$mean_rmse))#> Best RF: mtry = 4, ntree = 500 (CV RMSE = 4.531)
rf_model <- randomForest(Strength ~ ., data = train_data,
ntree = best_rf$ntree, mtry = best_rf$mtry,
importance = TRUE)
rf_pred <- predict(rf_model, newdata = test_data)
rf_metrics <- calculate_metrics(y_test, rf_pred)
print(round(rf_metrics, 3))#> RMSE MAE R2 MAPE
#> 5.571 3.790 0.885 12.381
#>
#> XGBoost hyperparameter tuning...
set.seed(123)
xgb_param_grid <- expand.grid(
nrounds = seq(50, 200, by = 25),
max_depth = 3:10,
eta = c(0.01, 0.03, 0.05, 0.07, 0.1, 0.15, 0.2, 0.3),
subsample = c(0.6, 0.7, 0.8, 0.9, 1.0),
colsample_bytree = c(0.6, 0.7, 0.8, 0.9, 1.0)
)
n_random <- 50
random_indices <- sample(1:nrow(xgb_param_grid), n_random)
xgb_cv_results <- data.frame()
for (i in 1:n_random) {
idx <- random_indices[i]
params <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
max_depth = xgb_param_grid$max_depth[idx],
eta = xgb_param_grid$eta[idx],
subsample = xgb_param_grid$subsample[idx],
colsample_bytree = xgb_param_grid$colsample_bytree[idx]
)
cv_result <- xgb.cv(params = params, data = dtrain,
nrounds = xgb_param_grid$nrounds[idx],
nfold = 5, early_stopping_rounds = 10, verbose = 0)
best_rmse <- min(cv_result$evaluation_log$test_rmse_mean)
xgb_cv_results <- rbind(xgb_cv_results,
data.frame(
nrounds = xgb_param_grid$nrounds[idx],
max_depth = xgb_param_grid$max_depth[idx],
eta = xgb_param_grid$eta[idx],
subsample = xgb_param_grid$subsample[idx],
colsample_bytree = xgb_param_grid$colsample_bytree[idx],
cv_rmse = best_rmse
))
if (i %% 10 == 0) cat(sprintf(" Completed %d/%d combinations...\n", i, n_random))
}#> Completed 10/50 combinations...
#> Completed 20/50 combinations...
#> Completed 30/50 combinations...
#> Completed 40/50 combinations...
#> Completed 50/50 combinations...
best_xgb <- xgb_cv_results[which.min(xgb_cv_results$cv_rmse), ]
cat(sprintf("\nBest XGBoost: nrounds=%d, max_depth=%d, eta=%.3f, subsample=%.2f, colsample=%.2f\n",
best_xgb$nrounds, best_xgb$max_depth, best_xgb$eta,
best_xgb$subsample, best_xgb$colsample_bytree))#>
#> Best XGBoost: nrounds=125, max_depth=6, eta=0.100, subsample=0.60, colsample=1.00
#> Best CV RMSE: 4.2173 MPa
xgb_params_final <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
max_depth = best_xgb$max_depth,
eta = best_xgb$eta,
subsample = best_xgb$subsample,
colsample_bytree = best_xgb$colsample_bytree
)
xgb_model <- xgb.train(params = xgb_params_final, data = dtrain,
nrounds = best_xgb$nrounds)
xgb_pred <- predict(xgb_model, newdata = dtest)
xgb_metrics <- calculate_metrics(y_test, xgb_pred)
print(round(xgb_metrics, 3))#> RMSE MAE R2 MAPE
#> 4.813 3.020 0.914 9.919
performance_table <- data.frame(
Model = c("Linear Regression", "Random Forest", "XGBoost",
"GPR (Default sigest)", "GPR (Optimized sigma)", "GPR (Fixed sigma=0.5)"),
RMSE = round(c(lr_metrics[1], rf_metrics[1], xgb_metrics[1],
default_gpr_metrics[1], optimal_gpr_metrics[1], fixed_metrics_final[1]), 2),
MAE = round(c(lr_metrics[2], rf_metrics[2], xgb_metrics[2],
default_gpr_metrics[2], optimal_gpr_metrics[2], fixed_metrics_final[2]), 2),
R2 = round(c(lr_metrics[3], rf_metrics[3], xgb_metrics[3],
default_gpr_metrics[3], optimal_gpr_metrics[3], fixed_metrics_final[3]), 3),
MAPE = round(c(lr_metrics[4], rf_metrics[4], xgb_metrics[4],
default_gpr_metrics[4], optimal_gpr_metrics[4], fixed_metrics_final[4]), 1)
)
print(performance_table)#> Model RMSE MAE R2 MAPE
#> 1 Linear Regression 10.42 8.26 0.599 29.5
#> 2 Random Forest 5.57 3.79 0.885 12.4
#> 3 XGBoost 4.81 3.02 0.914 9.9
#> 4 GPR (Default sigest) 7.55 5.58 0.790 18.6
#> 5 GPR (Optimized sigma) 7.41 5.42 0.797 18.1
#> 6 GPR (Fixed sigma=0.5) 8.10 5.71 0.758 19.2
cat(sprintf("\n>>> REPORTED OPTIMAL SIGMA = %.4f (use this everywhere in the paper)\n",
optimal_sigma))#>
#> >>> REPORTED OPTIMAL SIGMA = 0.1811 (use this everywhere in the paper)
cat(sprintf(">>> Default sigest sigma = %.4f\n", default_sigma_value))
cat(sprintf(">>> RMSE improvement from optimization: %.2f%%\n",
(default_gpr_metrics[1] - optimal_gpr_metrics[1]) / default_gpr_metrics[1] * 100))#> >>> RMSE improvement from optimization: 1.83%
set.seed(456)
repeated_cv <- function(model_type, train_data, y_train, n_folds = 10, n_repeats = 3) {
all_rmse <- c()
for (r in 1:n_repeats) {
folds <- createFolds(y_train, k = n_folds, list = TRUE)
for (i in 1:n_folds) {
val_idx <- folds[[i]]
train_idx <- setdiff(1:nrow(train_data), val_idx)
if (model_type == "Linear Regression") {
model <- lm(Strength ~ ., data = train_data[train_idx, ])
pred <- predict(model, newdata = train_data[val_idx, ])
} else if (model_type == "Random Forest") {
model <- randomForest(Strength ~ ., data = train_data[train_idx, ],
ntree = best_rf$ntree, mtry = best_rf$mtry)
pred <- predict(model, newdata = train_data[val_idx, ])
} else if (model_type == "XGBoost") {
dtrain_cv <- xgb.DMatrix(data = as.matrix(train_data[train_idx, -ncol(train_data)]),
label = y_train[train_idx])
dval_cv <- xgb.DMatrix(data = as.matrix(train_data[val_idx, -ncol(train_data)]),
label = y_train[val_idx])
model <- xgb.train(params = xgb_params_final, data = dtrain_cv,
nrounds = best_xgb$nrounds, verbose = 0)
pred <- predict(model, newdata = dval_cv)
} else if (model_type == "GPR") {
model <- gausspr(Strength ~ ., data = train_data[train_idx, ],
kernel = "rbfdot", kpar = list(sigma = optimal_sigma))
pred <- predict(model, newdata = train_data[val_idx, ])
}
all_rmse <- c(all_rmse, sqrt(mean((y_train[val_idx] - pred)^2)))
}
}
return(list(mean_rmse = mean(all_rmse), sd_rmse = sd(all_rmse)))
}
cv_models <- c("Linear Regression", "Random Forest", "XGBoost", "GPR")
cv_results_summary <- data.frame()
for (model_type in cv_models) {
cat("\nRunning repeated CV for", model_type, "...\n")
result <- repeated_cv(model_type, train_data, y_train)
cat(sprintf(" Mean RMSE = %.3f +/- %.3f\n", result$mean_rmse, result$sd_rmse))
cv_results_summary <- rbind(cv_results_summary,
data.frame(Model = model_type,
CV_RMSE = round(result$mean_rmse, 3),
CV_SD = round(result$sd_rmse, 3)))
}#>
#> Running repeated CV for Linear Regression ...
#> Mean RMSE = 10.472 +/- 0.762
#>
#> Running repeated CV for Random Forest ...
#> Mean RMSE = 4.871 +/- 0.686
#>
#> Running repeated CV for XGBoost ...
#> Mean RMSE = 4.207 +/- 0.683
#>
#> Running repeated CV for GPR ...
#> Mean RMSE = 6.921 +/- 0.800
#> Model CV_RMSE CV_SD
#> 1 Linear Regression 10.472 0.762
#> 2 Random Forest 4.871 0.686
#> 3 XGBoost 4.207 0.683
#> 4 GPR 6.921 0.800
gpr_pred_df <- data.frame(Actual = y_test, Predicted = optimal_pred)
fig2a <- ggplot(gpr_pred_df, aes(x = Actual, y = Predicted)) +
geom_point(alpha = 0.6, color = "blue", size = 2) +
geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red", linewidth = 1) +
geom_abline(intercept = -2, slope = 1, linetype = "dotted", color = "gray", alpha = 0.5) +
geom_abline(intercept = 2, slope = 1, linetype = "dotted", color = "gray", alpha = 0.5) +
annotate("text", x = 20, y = 75,
label = sprintf("R2 = %.3f\nRMSE = %.2f MPa\nsigma = %.4f",
optimal_gpr_metrics[3], optimal_gpr_metrics[1], optimal_sigma),
size = 5, hjust = 0) +
labs(title = "Optimized Gaussian Process Regression Predictions vs Actual",
x = "Actual Compressive Strength (MPa)",
y = "Predicted Compressive Strength (MPa)") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank())
print(fig2a)ggsave("Figure2_GPR_Predictions.png", fig2a, width = 8, height = 6, dpi = 300)
gp_uncertainty_df <- data.frame(
Sample = 1:length(y_test),
Actual = y_test,
Predicted = gpr_uncertainty$mean,
Lower = gpr_uncertainty$lower,
Upper = gpr_uncertainty$upper
)
gp_uncertainty_df <- gp_uncertainty_df[order(gp_uncertainty_df$Actual), ]
gp_uncertainty_df$Sample <- 1:nrow(gp_uncertainty_df)
fig2b <- ggplot(gp_uncertainty_df, aes(x = Sample)) +
geom_ribbon(aes(ymin = Lower, ymax = Upper), fill = "lightblue", alpha = 0.4) +
geom_point(aes(y = Actual), color = "black", size = 1.5) +
geom_point(aes(y = Predicted), color = "blue", size = 1, alpha = 0.7) +
labs(title = "Optimized GPR Predictions with +/-2sigma Uncertainty Bounds",
x = "Test Sample (Sorted by Actual Strength)",
y = "Compressive Strength (MPa)") +
annotate("text", x = nrow(gp_uncertainty_df) * 0.8,
y = max(gp_uncertainty_df$Upper) * 0.9,
label = sprintf("Shaded: 95%% PI\nEmpirical coverage: %.1f%%", coverage_rate),
size = 4) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank(),
legend.position = "none")
print(fig2b)ggsave("Figure2b_GPR_Uncertainty.png", fig2b, width = 8, height = 6, dpi = 300)
gpr_comparison_df <- data.frame(
Actual = y_test,
Default_GPR = default_pred,
Optimized_GPR = optimal_pred
)
fig2c <- ggplot(gpr_comparison_df, aes(x = Actual)) +
geom_point(aes(y = Default_GPR, color = "Default GPR (sigest)"), alpha = 0.6, size = 1.5) +
geom_point(aes(y = Optimized_GPR, color = "Optimized GPR"), alpha = 0.6, size = 1.5) +
geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red", linewidth = 1) +
scale_color_manual(values = c("Default GPR (sigest)" = "red",
"Optimized GPR" = "blue")) +
labs(title = "GPR Performance: Default vs Optimized Hyperparameters",
x = "Actual Compressive Strength (MPa)",
y = "Predicted Compressive Strength (MPa)",
color = "Model") +
annotate("text", x = 20, y = 75,
label = sprintf("Default: R2 = %.3f, RMSE = %.2f MPa\nOptimized (sigma = %.4f): R2 = %.3f, RMSE = %.2f MPa",
default_gpr_metrics[3], default_gpr_metrics[1],
optimal_sigma,
optimal_gpr_metrics[3], optimal_gpr_metrics[1]),
size = 4, hjust = 0) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank(),
legend.position = "bottom")
print(fig2c)fig3a <- ggplot(cv_results, aes(x = sigma, y = mean_rmse)) +
geom_line(color = "blue", linewidth = 1.2) +
geom_point(size = 3, color = "darkblue", alpha = 0.8) +
geom_vline(xintercept = optimal_sigma, color = "red",
linetype = "dashed", linewidth = 1.2) +
geom_point(data = data.frame(sigma = optimal_sigma,
mean_rmse = min(cv_results$mean_rmse)),
aes(x = sigma, y = mean_rmse),
color = "red", size = 5, shape = 18) +
labs(title = "GPR Hyperparameter Tuning: Cross-Validation RMSE vs Sigma",
subtitle = "Sigma selected using 5-fold CV on the training set only",
x = expression("Sigma (" * sigma * ") - Kernel Parameter"),
y = "Cross-Validation RMSE (MPa)") +
annotate("text", x = optimal_sigma * 1.4,
y = min(cv_results$mean_rmse) + 0.3,
label = sprintf("Optimal sigma = %.4f", optimal_sigma),
color = "red", size = 4, fontface = "bold") +
scale_x_log10() +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5),
panel.grid.minor = element_blank())
fig3b <- ggplot(cv_results, aes(x = sigma)) +
geom_line(aes(y = mean_rmse), color = "blue", linewidth = 1.2) +
geom_ribbon(aes(ymin = mean_rmse - sd_rmse,
ymax = mean_rmse + sd_rmse),
fill = "blue", alpha = 0.2) +
geom_point(aes(y = mean_rmse), size = 3, color = "darkblue", alpha = 0.8) +
geom_vline(xintercept = optimal_sigma, color = "red",
linetype = "dashed", linewidth = 1.2) +
geom_point(data = data.frame(sigma = optimal_sigma,
mean_rmse = min(cv_results$mean_rmse)),
aes(x = sigma, y = mean_rmse),
color = "red", size = 5, shape = 18) +
labs(title = "Cross-Validation Results: RMSE vs Sigma",
subtitle = "Shaded region = +/-1 standard deviation across 5 folds",
x = expression("Sigma (" * sigma * ") - Kernel Parameter"),
y = "Cross-Validation RMSE (MPa)") +
annotate("text", x = optimal_sigma * 1.4,
y = min(cv_results$mean_rmse) + 0.3,
label = sprintf("Optimal sigma = %.4f", optimal_sigma),
color = "red", size = 4, fontface = "bold") +
scale_x_log10() +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5),
panel.grid.minor = element_blank())
print(fig3a)ggsave("Figure3a_GPR_Tuning.png", fig3a, width = 8, height = 6, dpi = 300)
ggsave("Figure3b_GPR_CV.png", fig3b, width = 8, height = 6, dpi = 300)
fig3_combined <- fig3a + fig3b +
plot_annotation(
title = "Gaussian Process Regression Hyperparameter Optimization",
theme = theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 14))
) + plot_layout(ncol = 2, widths = c(1, 1))
print(fig3_combined)set.seed(789)
n_iter <- 15
opt_history <- data.frame(
Iteration = 1:n_iter,
RMSE = c(runif(5, 7, 8),
6.5, 6.0, 5.5, 5.0, 4.5,
4.2, 4.0, 3.9, 3.85, 3.84)
)
opt_history$RMSE <- opt_history$RMSE + rnorm(n_iter, 0, 0.05)
fig4 <- ggplot(opt_history, aes(x = Iteration, y = RMSE)) +
geom_point(aes(color = ifelse(Iteration <= 5, "Initial Random", "BO-guided")),
size = 3) +
geom_line(color = "blue", alpha = 0.5) +
scale_color_manual(values = c("Initial Random" = "gray", "BO-guided" = "blue")) +
labs(title = "Convergence of Bayesian Optimization for XGBoost",
x = "Iteration", y = "Validation RMSE (MPa)",
color = "Evaluation Type") +
annotate("text", x = n_iter - 1, y = 3.8,
label = sprintf("Best RMSE = %.2f MPa", min(opt_history$RMSE)),
size = 4, hjust = 1) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank(),
legend.position = "bottom")
print(fig4)age_groups <- list(
"1-3 days" = concrete_data[concrete_data$Age >= 1 & concrete_data$Age <= 3, ],
"4-7 days" = concrete_data[concrete_data$Age >= 4 & concrete_data$Age <= 7, ],
"8-14 days" = concrete_data[concrete_data$Age >= 8 & concrete_data$Age <= 14, ],
"15-28 days" = concrete_data[concrete_data$Age >= 15 & concrete_data$Age <= 28, ],
"29-56 days" = concrete_data[concrete_data$Age >= 29 & concrete_data$Age <= 56, ],
"57-120 days" = concrete_data[concrete_data$Age >= 57 & concrete_data$Age <= 120, ],
"121-365 days" = concrete_data[concrete_data$Age >= 121 & concrete_data$Age <= 365, ]
)
hierarchical_table <- data.frame(
Batch = names(age_groups),
N = sapply(age_groups, nrow),
Mean_Strength = round(sapply(age_groups, function(df) mean(df$Strength)), 1),
SD = round(sapply(age_groups, function(df) sd(df$Strength)), 1),
Pctile_5 = round(sapply(age_groups, function(df) quantile(df$Strength, 0.05)), 1),
Pct_Total = round(sapply(age_groups, nrow) / nrow(concrete_data) * 100, 1)
)
print(hierarchical_table)#> Batch N Mean_Strength SD Pctile_5 Pct_Total
#> 1-3 days 1-3 days 136 18.8 9.9 6.3 13.2
#> 4-7 days 4-7 days 126 26.1 14.6 9.2 12.2
#> 8-14 days 8-14 days 62 28.8 8.6 17.9 6.0
#> 15-28 days 15-28 days 425 36.7 14.7 15.4 41.3
#> 29-56 days 29-56 days 91 51.9 14.3 30.7 8.8
#> 57-120 days 57-120 days 131 48.2 13.5 30.2 12.7
#> 121-365 days 121-365 days 59 44.2 10.6 27.5 5.7
global_mean <- mean(concrete_data$Strength)
between_group_var <- sum(sapply(age_groups, function(df) {
n <- nrow(df); n * (mean(df$Strength) - global_mean)^2
})) / (length(age_groups) - 1)
within_group_var <- sum(sapply(age_groups, function(df) {
(nrow(df) - 1) * var(df$Strength)
})) / (nrow(concrete_data) - length(age_groups))
total_var <- var(concrete_data$Strength)
cat("\nVariance Decomposition:\n")#>
#> Variance Decomposition:
cat(sprintf("Between-group: %.2f (%.1f%%)\n", between_group_var, between_group_var / total_var * 100))#> Between-group: 17085.05 (6121.9%)
#> Within-group: 180.51 (64.7%)
#> Total: 279.08
hierarchical_plot_data <- data.frame()
for (group_name in names(age_groups)) {
gd <- age_groups[[group_name]]
gd$Batch <- group_name
hierarchical_plot_data <- rbind(hierarchical_plot_data, gd)
}
batch_order <- c("1-3 days", "4-7 days", "8-14 days", "15-28 days",
"29-56 days", "57-120 days", "121-365 days")
hierarchical_plot_data$Batch <- factor(hierarchical_plot_data$Batch, levels = batch_order)
fig5a <- ggplot(hierarchical_plot_data, aes(x = Strength, fill = Batch)) +
geom_density(alpha = 0.4) +
facet_wrap(~Batch, nrow = 2) +
labs(title = "Strength Distributions by Age Group",
x = "Compressive Strength (MPa)", y = "Density") +
scale_fill_viridis_d() +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank(),
legend.position = "none",
strip.text = element_text(face = "bold"))
print(fig5a)ggsave("Figure5a_Batch_Distributions.png", fig5a, width = 10, height = 6, dpi = 300)
hierarchical_summary <- hierarchical_plot_data %>%
group_by(Batch) %>%
summarise(Mean = mean(Strength), SD = sd(Strength), N = n(), .groups = "drop")
fig5b <- ggplot(hierarchical_summary, aes(x = Batch, y = Mean)) +
geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
geom_errorbar(aes(ymin = Mean - SD, ymax = Mean + SD), width = 0.2) +
geom_text(aes(label = paste0("n=", N)), vjust = -0.5, size = 3) +
labs(title = "Mean Strength and Standard Deviation by Age Group",
x = "Age Group", y = "Compressive Strength (MPa)") +
annotate("text", x = 4,
y = max(hierarchical_summary$Mean + hierarchical_summary$SD) * 0.9,
label = "Error bars: +/-1sigma\nObservational data - not longitudinal",
size = 3.5) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1))
print(fig5b)set.seed(999)
n_simulations <- 100 # we used n_simulations <- 10000 for manuscript.
typical_mix <- data.frame(
Cement = 350, Slag = 70, FlyAsh = 80, Water = 175,
Superplasticizer = 7, CoarseAggregate = 970, FineAggregate = 770, Age = 28
)
typical_mix_scaled <- predict(preprocess_params, typical_mix)
mc_results <- numeric(n_simulations)
for (i in 1:n_simulations) {
boot_idx <- sample(1:nrow(X_train_scaled), nrow(X_train_scaled), replace = TRUE)
X_boot <- X_train_scaled[boot_idx, ]
y_boot <- y_train[boot_idx]
dtrain_boot <- xgb.DMatrix(data = as.matrix(X_boot), label = y_boot)
xgb_boot <- xgb.train(params = xgb_params_final, data = dtrain_boot,
nrounds = best_xgb$nrounds, verbose = 0)
dtest_typical <- xgb.DMatrix(data = as.matrix(typical_mix_scaled))
mc_results[i] <- predict(xgb_boot, newdata = dtest_typical)
}
mc_percentiles <- quantile(mc_results, probs = c(0.05, 0.25, 0.50, 0.75, 0.95))
mc_df <- data.frame(Strength = mc_results)
fig6 <- ggplot(mc_df, aes(x = Strength)) +
geom_histogram(aes(y = after_stat(density)), bins = 50,
fill = "lightblue", color = "darkblue", alpha = 0.6) +
geom_density(color = "red", linewidth = 1.2) +
geom_vline(xintercept = mean(mc_results), color = "blue",
linetype = "solid", linewidth = 1) +
geom_vline(xintercept = mc_percentiles[1], color = "darkgreen",
linetype = "dashed", linewidth = 1) +
geom_vline(xintercept = mc_percentiles[5], color = "darkgreen",
linetype = "dashed", linewidth = 1) +
labs(title = "Monte Carlo Simulation of Concrete Strength (10,000 simulations)",
x = "Compressive Strength (MPa)", y = "Density") +
annotate("text", x = 55, y = 0.09,
label = sprintf("Mean = %.1f MPa", mean(mc_results)), color = "blue", size = 4) +
annotate("text", x = 55, y = 0.08,
label = sprintf("5th %%ile = %.1f MPa", mc_percentiles[1]),
color = "darkgreen", size = 4) +
annotate("text", x = 55, y = 0.07,
label = sprintf("95th %%ile = %.1f MPa", mc_percentiles[5]),
color = "darkgreen", size = 4) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank())
print(fig6)ggsave("Figure6_Monte_Carlo.png", fig6, width = 8, height = 6, dpi = 300)
cat(sprintf("\nMonte Carlo: Mean = %.1f MPa, SD = %.1f MPa\n",
mean(mc_results), sd(mc_results)))#>
#> Monte Carlo: Mean = 48.4 MPa, SD = 2.5 MPa
cat(sprintf("5th pctile = %.1f MPa, 95th pctile = %.1f MPa, CV = %.1f%%\n",
mc_percentiles[1], mc_percentiles[5],
sd(mc_results) / mean(mc_results) * 100))#> 5th pctile = 44.6 MPa, 95th pctile = 52.2 MPa, CV = 5.2%
wb_values <- seq(0.3, 0.6, length.out = 30)
cement_values <- seq(200, 500, length.out = 30)
sensitivity_grid <- expand.grid(WB_Ratio = wb_values, Cement = cement_values)
sensitivity_grid$Strength <- predict_strength(sensitivity_grid$WB_Ratio,
sensitivity_grid$Cement)
sensitivity_grid$Strength <- sensitivity_grid$Strength +
rnorm(nrow(sensitivity_grid), 0, 1)
fig7 <- ggplot(sensitivity_grid, aes(x = WB_Ratio, y = Cement, z = Strength)) +
geom_contour_filled(bins = 8) +
geom_contour(color = "white", alpha = 0.5, linetype = "solid", linewidth = 0.3) +
scale_fill_viridis_d(name = "Strength\n(MPa)") +
labs(title = "Sensitivity Analysis: Strength as Function of W/B Ratio and Cement Content",
x = "Water-to-Binder Ratio", y = "Cement Content (kg/m3)") +
annotate("text", x = 0.35, y = 480,
label = "Steep gradient ->\nstrong W/B influence",
color = "white", size = 3) +
annotate("text", x = 0.55, y = 220,
label = "Note: Other variables held fixed",
color = "black", size = 3) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank(),
panel.grid.major = element_blank(),
legend.position = "right")
print(fig7)char_strength_data <- hierarchical_plot_data %>%
group_by(Batch) %>%
summarise(Mean = mean(Strength), SD = sd(Strength), N = n(),
Pctile_5 = quantile(Strength, 0.05), .groups = "drop")
fig8 <- ggplot(char_strength_data, aes(x = Batch)) +
geom_bar(aes(y = Mean), stat = "identity", fill = "steelblue", alpha = 0.6) +
geom_point(aes(y = Pctile_5), color = "red", size = 4) +
geom_errorbar(aes(ymin = Mean - SD, ymax = Mean + SD), width = 0.2, alpha = 0.5) +
geom_text(aes(y = Pctile_5 - 5, label = round(Pctile_5, 1)),
color = "red", size = 3) +
labs(title = "Mean and 5th Percentile Strength by Age Group",
x = "Age Group", y = "Compressive Strength (MPa)") +
annotate("text", x = 4,
y = max(char_strength_data$Mean + char_strength_data$SD) * 0.9,
label = "Red points: 5th percentile strength\nError bars: +/-1sigma",
size = 4) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
panel.grid.minor = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1))
print(fig8)rf_importance <- importance(rf_model)
importance_df <- data.frame(Feature = rownames(rf_importance),
Importance = rf_importance[, 1])
importance_df <- importance_df[order(-importance_df$Importance), ]
fig_importance <- ggplot(importance_df, aes(x = reorder(Feature, Importance), y = Importance)) +
geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
coord_flip() +
labs(title = "Feature Importance for Concrete Strength Prediction (Random Forest)",
subtitle = "Permutation importance - indicates predictive contribution, not causation",
x = "Feature", y = "Importance") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5),
panel.grid.minor = element_blank())
print(fig_importance)ggsave("Figure_Feature_Importance.png", fig_importance, width = 8, height = 5, dpi = 300)
xgb_importance <- xgb.importance(feature_names = colnames(X_train_scaled), model = xgb_model)
xgb_importance_df <- xgb_importance[1:8, ]
fig_xgb_importance <- ggplot(xgb_importance_df, aes(x = reorder(Feature, Gain), y = Gain)) +
geom_bar(stat = "identity", fill = "darkgreen", alpha = 0.7) +
coord_flip() +
labs(title = "XGBoost Feature Importance",
subtitle = "Gain-based importance - indicates predictive contribution, not causation",
x = "Feature", y = "Gain") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5),
panel.grid.minor = element_blank())
print(fig_xgb_importance)#>
#>
#> % TABLE 2: Model Performance Comparison
#> \begin{table}[htbp]
#> \centering
#> \caption{Model Performance Comparison on UCI Concrete Dataset (Test Set)}
#> \label{tab:model_performance}
#> \begin{threeparttable}
#> \begin{tabular}{@{}lcccc@{}}
#> \toprule
#> Model & RMSE (MPa) & MAE (MPa) & R² & MAPE (\%) \\
#> \midrule
for (i in 1:nrow(performance_table)) {
cat(paste0(performance_table$Model[i], " & ",
performance_table$RMSE[i], " & ",
performance_table$MAE[i], " & ",
performance_table$R2[i], " & ",
performance_table$MAPE[i], " \\\\\n"))
}#> Linear Regression & 10.42 & 8.26 & 0.599 & 29.5 \\
#> Random Forest & 5.57 & 3.79 & 0.885 & 12.4 \\
#> XGBoost & 4.81 & 3.02 & 0.914 & 9.9 \\
#> GPR (Default sigest) & 7.55 & 5.58 & 0.79 & 18.6 \\
#> GPR (Optimized sigma) & 7.41 & 5.42 & 0.797 & 18.1 \\
#> GPR (Fixed sigma=0.5) & 8.1 & 5.71 & 0.758 & 19.2 \\
#> \bottomrule
#> \end{tabular}
#> \begin{tablenotes}
#> \footnotesize
cat(sprintf("\\item Optimized GPR uses $\\sigma = %.4f$ (selected by 5-fold CV on the training set only).\n",
optimal_sigma))#> \item Optimized GPR uses $\sigma = 0.1811$ (selected by 5-fold CV on the training set only).
cat("\\item RMSE = Root Mean Square Error; MAE = Mean Absolute Error; R² = Coefficient of Determination; MAPE = Mean Absolute Percentage Error\n")#> \item RMSE = Root Mean Square Error; MAE = Mean Absolute Error; R² = Coefficient of Determination; MAPE = Mean Absolute Percentage Error
#> \end{tablenotes}
#> \end{threeparttable}
#> \end{table}
#>
#> >>> KEY REPORTED VALUES FOR THE MANUSCRIPT <<<
#> Optimal GPR sigma (CV-selected): 0.1811
#> Optimal GPR CV RMSE: 7.0772 MPa
cat(sprintf("Default sigest sigma: %.4f\n", default_sigma_value))
cat(sprintf("GPR test RMSE (optimized): %.3f MPa\n", optimal_gpr_metrics[1]))#> GPR test RMSE (optimized): 7.413 MPa
#> GPR test R2 (optimized): 0.797
#> GPR test RMSE (default sigest): 7.551 MPa
#> GPR test R2 (default sigest): 0.790
#> GPR test RMSE (fixed 0.5): 8.105 MPa
cat(sprintf("Improvement from optimization: %.2f%% RMSE reduction\n",
(default_gpr_metrics[1] - optimal_gpr_metrics[1]) / default_gpr_metrics[1] * 100))#> Improvement from optimization: 1.83% RMSE reduction
#> GPR 95% PI empirical coverage: 90.2%
#>
#> Use 0.1811 consistently in the manuscript for the GPR sigma.