Retrofit Radar: Predicting Building Energy Use with MATLAB
Team MatrixNotFound · Learn to Hack: MATLAB Edition 2026
Project pages: Home · Results dashboard (interactive charts that update while main.m runs) · 3D simulator (click a building and the model runs in your browser) · Written report (this page) This report walks through our solution using the results saved by main.m. It does not retrain anything, so it runs in seconds. Run main.m first to create results/pipelineResults.mat, results/retrofit_shortlist.csv and the figures in figures/.
1. The problem
Buildings are a major source of energy use and carbon emissions. If we can predict how much energy a building should use from its type, size, age and local climate, then buildings that use far more than that are the first candidates for an energy audit or a retrofit.
We predict site energy use intensity (site_eui, kBtu per square foot per year) for 9,705 buildings from the WiDS Datathon 2022 data, training on 75,757 building-years.
projectRoot = fileparts(which("ProjectReport"));
addpath(fullfile(projectRoot, "src"));
load(fullfile(projectRoot, "results", "pipelineResults.mat"), "finalModel", "cvReport", "yTrain");
figuresDir = fullfile(projectRoot, "figures");
2. What the data told us
Three findings shaped every later decision:
- The test set is one unseen year. Every test row is Year_Factor 7; training covers years 1 to 6. As a category, year 7 would be meaningless to a model, so we treat the year as a number.
- The test set has no State_6 buildings, although State_6 is 67% of the training data. The test distribution is shifted.
- Key fields are missing. energy_star_rating is missing for 35% of training rows, and wind and fog readings are missing for 88-94% of test rows.
Our responses:
- energy_star_rating is filled with the median of the building's facility type; year_built = 0 is treated as missing.
- Every fill value is learned from training rows only, and a <name>_missing flag records where data was absent, because missingness itself is informative.
- We skipped wind-vector features: with 88% of test values missing they would mostly encode filled-in medians.
- New features with a physical meaning: building age, floor area × heating degree days, floor area × cooling degree days (seasonal heating and cooling load) and Energy Star rating × floor area (the scale of efficiency).
3. Validating the way the test set is built
Random k-fold cross-validation mixes years, and the same building appears in several years, so a model can "recognise" buildings it has already seen. We instead use leave-one-year-out (LOYO) cross-validation: hold out one entire year, train on the other five, repeat for all six years. Imputation and feature engineering are refit inside every training fold, so held-out rows never leak into preprocessing.
The baseline tree shows why this matters: random folds report a noticeably lower error than holding out a year.
disp(cvReport.validationGap)
Scheme Tree_RMSE
____________________ _________
"Random 5-fold" 50.287
"Leave-one-year-out" 61.022
showFigure(fullfile(figuresDir, "2_validation_gap.png"));
4. Iterating on the model
We compared four models under LOYO, keeping every attempt in the table:
- Regression tree (fitrtree): a single interpretable baseline.
- LSBoost v1 (fitrensemble, 300 trees, learning rate 0.1): gradient-boosted trees handle mixed numeric and categorical predictors without scaling and capture non-linear interactions such as size × climate × building type.
- LSBoost v2 (shrinkage): learning rate 0.05, 400 trees, minimum leaf of 50 buildings and histogram binning, to reduce over-fitting to year-specific patterns.
- LSBoost v3 (feature selection): v1 without the 36 monthly temperature columns. Those values are identical for every building in a state-year, so trees can memorise them as a fingerprint that never matches an unseen year.
The model with the lowest LOYO RMSE is refit on all training data and used for the submission.
disp(cvReport.results)
Model LOYO_RMSE CV_Runtime_min
________________________________ _________ ______________
"Regression tree (baseline)" 61.022 1.2412
"LSBoost v1 (initial)" 41.056 9.5884
"LSBoost v2 (shrinkage)" 41.849 3.9857
"LSBoost v3 (feature selection)" 40.123 6.802
disp(cvReport.perYearRMSE)
Year_1 Year_2 Year_3 Year_4 Year_5 Year_6
______ ______ ______ ______ ______ ______
Regression tree (baseline) 67.748 70.057 54.83 73.431 50.274 59.374
LSBoost v1 (initial) 54.778 41.919 46.958 37.789 37.002 41.081
LSBoost v2 (shrinkage) 56.296 43.062 47.193 39.269 36.423 42.573
LSBoost v3 (feature selection) 54.48 41.873 46.249 37.08 33.384 41.314
fprintf("Selected model: %s\n", cvReport.bestModelName);
Selected model: LSBoost v3 (feature selection)
showFigure(fullfile(figuresDir, "1_rmse_by_year.png"));
The journey from baseline to final model: boosting removed a third of the error, shrinkage (v2) did not help, and feature selection (v3) gave the best result.
showFigure(fullfile(figuresDir, "11_model_journey.png"));
The full leave-one-year-out grid: every model against every held-out year. Year 1 is the hardest for every model, and it has by far the fewest buildings (2,117). v3 has the lightest cells in 5 of 6 years.
showFigure(fullfile(figuresDir, "10_iteration_heatmap.png"));
5. How accurate is it?
Out-of-fold predictions come from models that never saw the year being predicted, so this plot is an honest picture of accuracy on new data. The model tracks typical buildings well; the largest errors are the rare extreme consumers, which are exactly the buildings worth investigating.
fprintf("Out-of-fold RMSE of the selected model: %.2f\n", ...
computeRMSE(yTrain, cvReport.outOfFoldPredictions));
Out-of-fold RMSE of the selected model: 40.12
RMSE is one view of accuracy. The full set, all computed on out-of-fold predictions:
metrics = readtable(fullfile(projectRoot, "results", "extended_metrics.csv"));
disp(metrics)
MAE MSE RMSE R2 MedianAE P90AE MeanBias
______ ______ ______ _______ ________ ______ ________
21.038 1609.8 40.123 0.52563 12.565 44.608 0.13204
- MAE ≈ 21 kBtu/ft²/yr: the typical prediction misses by about 21, and half of all predictions miss by less than about 13 (median absolute error), against a median building EUI of about 75.
- R² ≈ 0.53: the model explains about half of the variation in energy intensity across buildings it has never seen, using only type, size, age, rating and climate. The rest reflects things the data cannot see, such as occupancy, equipment and operating hours.
- RMSE (40) is about twice MAE (21) because squared error is dominated by a small number of extreme consumers. Those are exactly the buildings Retrofit Radar is designed to surface.
The residual diagnostics confirm this: errors are centred close to zero for typical buildings, with a long tail of buildings that use far more than predicted.
diagnosticsDir = fullfile(projectRoot, "results", "loyo_diagnostics");
if isfile(fullfile(diagnosticsDir, "residual_diagnostics.png"))
showFigure(fullfile(diagnosticsDir, "residual_diagnostics.png"));
showFigure(fullfile(figuresDir, "3_predicted_vs_actual.png"));
Facility type and Energy Star rating dominate, and our engineered features (efficiency × floor area, building age, floor area × degree days) are among the strongest predictors.
showFigure(fullfile(figuresDir, "4_feature_importance.png"));
6. Diagnostics in matrix form
Prediction error heatmap. A regression model has no confusion matrix, so this is the closest equivalent: each cell counts buildings by actual EUI (x) and predicted EUI (y). Mass on the diagonal means accurate predictions. The bright core sits on the line for typical buildings (50-120 kBtu/ft²); the cells far below the line at high actual EUI are the extreme consumers the model does not expect. Retrofit Radar is built around exactly those cases.
showFigure(fullfile(figuresDir, "7_prediction_heatmap.png"));
Feature correlation matrix. Energy Star rating is the strongest single linear signal (−0.37 with site_eui). The floor-area interactions are strongly correlated with floor area itself (0.92-0.97), which is why tree models, unlike linear ones, are a good fit: they use whichever split is most informative and are not destabilised by correlated inputs. Linear correlation also understates the engineered features, which rank highly in the non-linear model's importance.
showFigure(fullfile(figuresDir, "6_feature_correlation.png"));
Residuals by year and state. Each cell is the mean error for a state in a year, predicted by a model that never saw that year. The bias shifts from year to year (for example, State 10 swings from +15.7 in year 3 to −14.7 in year 5). That year-to-year drift is exactly why random cross-validation, which mixes years, is optimistic, and why we validate one held-out year at a time.
showFigure(fullfile(figuresDir, "9_residual_year_state.png"));
Test-set correlation matrix (30 × 30). The same view for the 9,705 test buildings, using our predicted site_eui (the test set has no measured values) and the 29 numeric features most correlated with it. Energy Star rating has the strongest link (−0.54): better-rated buildings are predicted to use less energy, consistent with it being the model's second most important predictor. The climate variables form two tight blocks: cold-winter measures correlate positively with predicted use, and mild-winter minimum temperatures negatively. Inside each block, features correlate at 0.9 or more with each other, which is why dropping the 36 monthly temperature columns (v3) lost no information. The wind correlations rest on only the ~12% of test rows that have wind data.
showFigure(fullfile(figuresDir, "12_test_correlation_30x30.png"));
7. Retrofit Radar: from prediction to action
For each building in the most recent year we compare its actual EUI with the expected EUI from the out-of-fold model, which is what similar buildings in a similar climate use. Multiplying the excess by floor area gives the excess energy in MWh per year, and ranking by it puts the biggest savings opportunities first.
shortlist = readtable(fullfile(projectRoot, "results", "retrofit_shortlist.csv"));
disp(shortlist(1:10, ["rank", "facility_type", "State_Factor", "floor_area", ...
"actual_eui", "expected_eui", "excess_pct", "excess_MWh_per_year"]))
rank facility_type State_Factor floor_area actual_eui expected_eui excess_pct excess_MWh_per_year
____ ________________________________________ ____________ __________ __________ ____________ __________ ___________________
1 {'Mixed_Use_Commercial_and_Residential'} {'State_11'} 4.9484e+05 903.67 89.451 910.24 1.1808e+05
2 {'Public_Assembly_Other' } {'State_6' } 6.3122e+05 648.12 107.11 505.1 1.0008e+05
3 {'Education_College_or_university' } {'State_2' } 4.2065e+05 933.43 286.55 225.75 79748
4 {'Education_College_or_university' } {'State_4' } 2.9441e+06 167.94 97.361 72.488 60894
5 {'Industrial' } {'State_6' } 4.232e+05 579.91 104.6 454.42 58951
6 {'Industrial' } {'State_6' } 1.5462e+06 268.56 145.73 84.294 55666
7 {'Commercial_Other' } {'State_11'} 4.2940e+05 637.96 197.55 222.93 55424
8 {'Education_College_or_university' } {'State_2' } 1.0476e+06 379.29 220.81 71.775 48660
9 {'Service_Vehicle_service_repair_shop' } {'State_6' } 2.4767e+05 679.84 51.436 1221.7 45612
10 {'Health_Care_Inpatient' } {'State_6' } 2.9719e+06 358.92 308.68 16.273 43751
showFigure(fullfile(figuresDir, "5_retrofit_radar.png"));
Where to send auditors. The heatmap shows the median excess EUI for each state and facility type. We use the median rather than the mean so that one extreme building cannot colour a whole cell. Most cells are blue, because the typical building uses a little less than predicted: the excess is concentrated in a minority of buildings. Red cells mark combinations where even the typical building runs above expectation, for example retail in State 2, lodging and commercial buildings in State 4, and warehouses in State 1. These are the best targets for a programme-level audit campaign, while the shortlist above targets individual buildings.
showFigure(fullfile(figuresDir, "8_radar_state_facility.png"));
The radar flags buildings for inspection rather than delivering a verdict: a hospital may legitimately run hot, and the anonymised data cannot tell us why a building is high. The most extreme cases (several times the expected EUI) may also be metering or data-entry errors, which are worth catching too. Either way, it tells an energy manager where to look first.
8. Local impact: NTU campus (what-if)
What would our model say about two NTU landmarks, The Hive (completed 2015) and The Arc (2018)? Each is described as a university building and run through exactly the same preprocessing and final model as the test set.
This is an illustration, not a measurement. The training data covers anonymised US states, so Singapore's climate is not in it. We use the median weather of the hottest training state, among states with at least 100 buildings, as the closest analogue. Floor areas are rough estimates, and Energy Star ratings are unknown, so they are filled the same way as for any other building.
ntu = readtable(fullfile(projectRoot, "results", "ntu_landmarks.csv"), TextType="string");
disp(ntu(:, ["name", "year_built", "floor_area", "predicted_eui", "predicted_MWh_per_year", "climate_state"]))
name year_built floor_area predicted_eui predicted_MWh_per_year climate_state
__________ __________ __________ _____________ ______________________ _____________
"The Hive" 2015 1.4e+05 109.43 4490 "State_2"
"The Arc" 2018 1.2e+05 102.21 3594.4 "State_2"
showFigure(fullfile(figuresDir, "ntu_landmarks.png"));
Both come out above the median measured US university building. In a hot climate, cooling load pushes intensity up, and new buildings get no age discount in the model. The practical use is the same as Retrofit Radar: once a building's real metered EUI is known, comparing it with this expected value shows whether it is performing better or worse than its peers.
9. Summary
- Data: leakage-safe imputation fitted inside every fold, missingness flags and physically meaningful features.
- Validation: leave-one-year-out cross-validation that mirrors the unseen test year.
- Model: gradient-boosted trees, improved over a single tree and iterated with shrinkage and feature selection (LOYO RMSE 61.0 → 40.1; MAE 21.0; R² 0.53).
- Impact: a ranked shortlist of the buildings that use the most energy beyond what their peers use, plus a state × facility-type map for audit campaigns.
- Explore: open dashboard/index.html for the interactive results page.
function showFigure(file)
%SHOWFIGURE Display a saved PNG figure inline in the report.