COVID-19 Global
Spread Prediction

Trajectory-aware pipeline forecasting cumulative COVID-19 deaths for ~180 countries across a multi-week horizon. The pipeline does not apply one strategy to all — it first classifies each country by its death trajectory, then routes it to a matched forecasting approach.

🏆 Rank 4 / 884 competitors Only 47 succeeded to submit Zindi · Apr 2020 R · ETS · ARIMA · TBATS

The Problem

Forecast cumulative COVID-19 deaths for approximately 180 countries across a multi-week horizon. Data came from Zindi and Johns Hopkins CSSE time series. The challenge was that no single forecasting strategy works for all countries simultaneously — a decelerating country in Europe behaves completely differently from a surging country with 7 days of data.

An additional complication: irregular reporting. Many countries missed reporting for several consecutive days, then submitted catch-up corrections that created artificial spikes. Standard time series models treat those spikes as real signal, producing unstable forecasts.

🏆
Rank 4 out of 884 registered competitors Only 47 teams succeeded in submitting a valid forecast — the data complexity and short timeline filtered out most participants.

Core Design: Trajectory Classification Before Forecasting

Instead of fitting one model globally, the pipeline first assigns each country a trajectory class, then routes it to the forecasting strategy most appropriate for that trajectory.

STEP 1

Elasticity Scoring

For each country, compute the rate-of-change in daily deaths across two time windows: 6-day and 10-day. This produces a per-country slope signal.

# Vectorized elasticity: rate of change in daily deaths
jh[, elas := {
  prev  <- shift(death_dif, 1L, fill = 0)
  denom <- fifelse(prev == 0, prev + 1, prev)
  (death_dif - prev) / denom
}]
STEP 2

Country Classification into 4 Trajectory Classes

Each country is assigned one of four classes based on its elasticity signal and death volume.

  • DOWN: recent slope ≤ 0 and 6-day avg ≤ 10-day avg. Use damped ETS, select most conservative model.
  • UP: recent slope > 0 and daily deaths ≥ 10. Use undamped ETS/ARIMA, select median prediction.
  • NEAR_ZERO: ≤ 7 days of data. Validate against held-out days before selecting model.
  • REST: all other countries. Validation-based model selection across ETS, ARIMA, TBATS variants.
STEP 3

Per-Class Forecasting with Data Quality Preprocessing

Each forecast runs on multiple data versions — original, spike-smoothed (threshold 0.5), and more aggressively smoothed (threshold 0.33). The best version is selected per country based on validation MAE.

Key Engineering Decisions

Spike Smoothing Before Modeling

Irregular reporting (missed days followed by catch-up corrections) creates artificial spikes that distort time series models. Two thresholds (0.5 and 0.33) were applied to redistribute those spikes before fitting. The best-smoothed version was selected per country via validation — not applied uniformly.

Validation-Based Model Selection for Ambiguous Countries

For REST and NEAR_ZERO countries, models were evaluated on a held-out 5–7 day window within the training data. The model with the lowest RMSE on that validation window was used — not the best CV score across the full series. This prevents overfitting to a globally optimal but locally incorrect model.

Conservative Prior for Decelerating Countries

For DOWN countries, the pipeline selects the model with the minimum cumulative prediction rather than best fit. This encodes an epidemiological prior: a decelerating trend should not suddenly reverse in the forecast. A forecast that is slightly too low is far less damaging than one that artificially inflates projections for a country already past its peak.

Multiple Time Series Transformations

Three transformation strategies were tested per country: raw values, log transformation (to stabilize variance), and diff(log(x)) combined (to handle both variance and trend). The right transformation depends on the trajectory class and smoothing version — so the selection is done in the inner loop, not as a global choice.

Pipeline Structure

├── 00_config.R              # Libraries, dates, helper functions
├── 01_data_loading.R        # Load Zindi + Johns Hopkins data
├── 02_elasticity_analysis.R # Compute elasticity, classify countries
├── 03_data_preprocessing.R  # Negative value cleaning, spike smoothing
├── 04_time_series_models.R  # ETS, ARIMA, TBATS model functions
├── 05_forecast_down.R       # DOWN countries (damped, conservative)
├── 06_forecast_up.R         # UP countries (undamped, median)
├── 07_forecast_near_zero.R  # NEAR_ZERO countries (validated)
├── 08_forecast_rest.R       # REST countries (validation-based)
├── 09_merge_and_submit.R    # Merge all forecasts, generate CSV
└── MAIN.R                   # Full pipeline orchestration

Key Takeaway

The main insight of this project is that heterogeneous forecasting — routing different series to different strategies — consistently outperforms applying a single best model globally. The time spent on trajectory classification and routing logic produced more improvement than any hyperparameter tuning would have.

Design insight: When forecasting many series at once, the first modeling decision is not "which algorithm?" — it is "are all these series actually the same kind of problem?" Classification before forecasting is often the highest-leverage step.