# 4th Place Winner Solution - WiDS Global Datathon 2026

The link to our detailed notebook:
[click here](https://www.kaggle.com/code/mariagantoi/wids-datathon-2026-4th-place-solution)
Context
Business Context:
[https://www.kaggle.com/competitions/WiDSWorldWide_GlobalDathon26/overview](https://www.kaggle.com/competitions/WiDSWorldWide_GlobalDathon26/overview)
Data Context:
[https://www.kaggle.com/competitions/WiDSWorldWide_GlobalDathon26/data](https://www.kaggle.com/competitions/WiDSWorldWide_GlobalDathon26/data)
Overview of the Approach
Summary of our solution
The final model used a combination of Gradient Boosting Survival Analysis and LightGBM classification models to estimate the probability that a wildfire would reach a location within 12, 24, 48, and 72 hours. The survival model was the core method, while the LightGBM models were trained separately for specific time horizons and then blended with the survival predictions to improve performance. The most important features were related to distance, fire movement speed, fire growth, estimated time of arrival, alignment between the fire and the target, and temporal variables such as the event start hour. Several engineered features were used, including log_dist, inv_dist, eta, eta_effective, threat, growth, hour_sin, and hour_cos. The model was developed in Python, using libraries such as pandas, NumPy, LightGBM, scikit-learn, and scikit-survival. Training was done using 5-fold cross-validation and multiple random seeds, and the full training process takes between 1.2 and 2.1  minutes, depending on the computer or runtime used.
Exploratory Data Analysis
Feature Selection / Engineering
Feature selection was based on a combination of exploratory analysis, correlation analysis, target utility, and domain logic. We first analyzed the numerical variables using histograms, which showed that many wildfire-related features were highly skewed, especially variables related to fire area, growth, radial growth, projected advance, and distance change.
First matrix of histograms:
![](assets/689f698a077a1d405ef9ed6b.png)
Second matrix of histograms:
![](assets/3bc3821d54184efd883a07fe.png)
Third matrix of histograms:
![](assets/abc08b4871df0269b0008627.png)
We chose some logarithmic transformations such as log1p_area_first, log1p_growth, and log_area_ratio_0_5h. They were useful for reducing the influence of extreme values.
Correlations between the features
We also used correlation heatmaps before and after pruning to identify redundant variables.
Before:
![](assets/47f025b0a46a7df8c24c48b8.png)
The heatmap before pruning showed several groups of highly correlated features, especially among growth, movement, and distance-change variables. When two variables were strongly correlated, we kept the one with higher target utility and removed the less informative one.
Some of the features dropped:
closing_speed_m_per_h
area_growth_abs_0_5h
centroid_speed_m_per_h
The
after pruning heatmap
is more representative for the number of features dropped:
![](assets/dc1d984889de55d4a6c8e248.png)
The most relevant retained feature groups were related to fire size, fire growth, distance, movement, alignment, and time.
Training Method(s)
No external data was used; the model was trained only on the data provided in the competition files.
We provide the global configuration used throughout the model training in sample (1).
#sample (1)
SEEDS = [42, 2024, 777]   #reduced for stability
FOLDS = 5

HORIZONS = [12, 24, 48, 72]

W = {12: (0.97, 0.03),
    24: (0.95, 0.05),
    48: (0.45, 0.55)}

POWER_24 = 1.1   #mild calibration (safe)
We used Gradient Boosting Survival Analysis as the main model.
As you can see in the sample (2), multiple GradientBoostingSurvivalAnalysis configurations are trained using stratified cross-validation and several random seeds to improve robustness. Each model learns the time-to-event distribution and generates survival probabilities for the predefined horizons. Predictions from all configurations and seeds are averaged to reduce variance and improve generalization.
#sample (2)
#%% GBSA (SURVIVAL CORE)

gbsa_cfgs = [
    dict(n_estimators=1200, learning_rate=0.01, max_depth=3),
    dict(n_estimators=1500, learning_rate=0.008, max_depth=2),
    dict(n_estimators=1000, learning_rate=0.015, max_depth=3),
]

kf = StratifiedKFold(n_splits=FOLDS, shuffle=True, random_state=42)

oof_gbsa = np.zeros((len(X),4))
test_gbsa = np.zeros((len(X_test),4))

for cfg in gbsa_cfgs:
    for seed in SEEDS:

        fold_oof = np.zeros((len(X),4))
        fold_test = np.zeros((len(X_test),4))

        for tr,va in kf.split(X, train['event']):

            model = GradientBoostingSurvivalAnalysis(**cfg, random_state=seed)

            model.fit(X.iloc[tr], y_surv[tr])

            fold_oof[va] = get_surv_preds(model, X.iloc[va])
            fold_test   += get_surv_preds(model, X_test) / FOLDS

        oof_gbsa += fold_oof / (len(SEEDS)*len(gbsa_cfgs))
        test_gbsa += fold_test / (len(SEEDS)*len(gbsa_cfgs))
…but
to improve the predictions
for specific horizons, we also trained
LightGBM binary classification models
for 12h, 24h, and 48h, as shown in sample (3). For each horizon, the task is converted into a binary classification problem that predicts whether the event occurs before the specified time limit. Cross-validation and multiple random seeds are again used to produce stable out-of-fold and test predictions.
#sample (3)
#%% LGB (IPCW-lite but stable)

oof_lgb = {}
test_lgb = {}

for h in [12,24,48]:

    y = make_target(train['time_to_hit_hours'], train['event'], h)

    oof = np.zeros(len(X))
    preds = np.zeros(len(X_test))

    for seed in SEEDS:

        for tr,va in kf.split(X, train['event']):

            model = lgb.LGBMClassifier(
                n_estimators=300,
                learning_rate=0.03,
                max_depth=3,
                subsample=0.7,
                colsample_bytree=0.7,
                random_state=seed
            )

            model.fit(X.iloc[tr], y.iloc[tr])

            oof[va] += model.predict_proba(X.iloc[va])[:,1] / len(SEEDS)
            preds   += model.predict_proba(X_test)[:,1] / (len(SEEDS)*FOLDS)

    oof_lgb[h] = oof
    test_lgb[h] = preds
Final predictions
The final predictions
are obtained by combining
the strengths of the survival model and the LightGBM classifiers
. Horizon-specific weights control the contribution of each model type. A mild calibration step is applied to the 24-hour survival predictions to improve probability calibration. The 72-hour probability is fixed at a high constant value, reflecting the assumption that most events occur within this extended time window. Monotonicity is enforced after blending to ensure logically consistent probabilities across horizons.
The exact weights for:
…12 hours:
97% Survival model
3% LightGBM
…24 hours:
95% Survival model
5% LightGBM
…48 hours:
45% Survival model
55% LightGBM
…72 hours:
we used a fixed probability of 0.9 and then applied a monotonicity correction so that probabilities increased consistently across time horizons.
These techniques are also shown in sample (4), with the weights defined in sample (1), in the global configuration.
#sample (4)
# calibration only for 24h (proven)
oof_gbsa[:,1] = np.clip(oof_gbsa[:,1] ** POWER_24, 0, 1)
test_gbsa[:,1] = np.clip(test_gbsa[:,1] ** POWER_24, 0, 1)

oof_final = oof_gbsa.copy()
test_final = test_gbsa.copy()

for i,h in enumerate([12,24,48]):
    w_gbsa, w_lgb = W[h]
    oof_final[:,i]  = w_gbsa*oof_gbsa[:,i] + w_lgb*oof_lgb[h]
    test_final[:,i] = w_gbsa*test_gbsa[:,i] + w_lgb*test_lgb[h]

# 72h trick (VERY IMPORTANT)
oof_final[:,3]  = 0.9
test_final[:,3] = 0.9

oof_final  = enforce_monotonicity(oof_final)
test_final = enforce_monotonicity(test_final)
Details of the submission
Anti-Overfit Check
As presented in sample (5), before generating the final submission,
summary statistics of the out-of-fold predictions
are printed. The mean and standard deviation of predicted probabilities provide a quick validation that the model outputs are reasonable and do not exhibit obvious signs of overfitting or instability.
#sample (5)
#%% SANITY CHECK (ANTI-OVERFIT)

print("OOF stats:")
print("mean:", oof_final.mean(axis=0))
print("std :", oof_final.std(axis=0))
Final predicted probabilities
The final predicted probabilities for all horizons are combined with the corresponding event_id values from the test set. The resulting submission file is saved in CSV format and is ready for evaluation or competition submission, sample (6).
#sample (6)
submission = pd.DataFrame({
    'event_id': test['event_id'],
    'prob_12h': test_final[:,0],
    'prob_24h': test_final[:,1],
    'prob_48h': test_final[:,2],
    'prob_72h': test_final[:,3],
})

submission.to_csv("submission.csv", index=False)
print("Saved as <<submission.csv>>")
Interesting Findings
The goal was not only to predict if a fire would become dangerous, but also when it could threaten an evacuation zone.
The most useful idea was to treat the task as a survival problem and combine it with LightGBM models for specific time horizons.
The censored observations are important, because some fires did not reach the evacuation zone within the observed 72-hour window, but they still contained useful information for the model.
Another useful trick was making sure that the probabilities were monotonic, so the risk at 48h or 72h could not be lower than the risk at 12h or 24h.
What helped our solution was focusing on variables that describe the first hours of the fire, especially distance, speed, growth, and direction, because these early signals are the only information available in the real scenario.
Sources
Pölsterl, S. (2020). scikit-survival: A Library for Time-to-Event Analysis Built on Top of scikit-learn. Journal of Machine Learning Research, 21(212), 1–6.
[http://jmlr.org/papers/v21/20-729.html](http://jmlr.org/papers/v21/20-729.html)
(PDF) LightGBM: A Highly Efficient Gradient Boosting Decision Tree. (n.d.). Retrieved June 1, 2026, from
[https://www.researchgate.net/publication/378480234_LightGBM_A_Highly_Efficient_Gradient_Boosting_Decision_Tree](https://www.researchgate.net/publication/378480234_LightGBM_A_Highly_Efficient_Gradient_Boosting_Decision_Tree)
Tibshirani, R. (2022). What is Cox’s Proportional Hazards Model? Significance, 19(2), 38–39.
[https://doi.org/10.1111/1740-9713.01633](https://doi.org/10.1111/1740-9713.01633)
Hothorn, T., Bühlmann, P., Dudoit, S., Molinaro, A., & van der Laan, M. J. (2006). Survival ensembles. Biostatistics, 7(3), 355–373.
[https://doi.org/10.1093/BIOSTATISTICS/KXJ011](https://doi.org/10.1093/BIOSTATISTICS/KXJ011)
[https://medium.com/@indradeep.chatterjee/cyclic-encoding-in-machine-learning-a-practical-guide-with-code-and-intuition-049846419ccb](https://medium.com/@indradeep.chatterjee/cyclic-encoding-in-machine-learning-a-practical-guide-with-code-and-intuition-049846419ccb)
[https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html)
[https://lightgbm.readthedocs.io/](https://lightgbm.readthedocs.io/)
[https://scikit-survival.readthedocs.io/en/stable/user_guide/00-introduction.html](https://scikit-survival.readthedocs.io/en/stable/user_guide/00-introduction.html)