Checklist IOAI Indonesia 2026 National Training Camp 1 – Simulation 2 · 2.1 task
Hybrid Riverflow
Predict the mean river discharge over the next 12 days from a historical daily series of 50–60 observations.
The task
Each sample is one synthetic historical series of daily river discharge from a monitoring site, with an actual length T between 50 and 60 days. The target is y = (1/12) Σ_{t=T+1}^{T+12} x_t, the mean discharge over the following 12 days; the daily values themselves are not predicted.
Series may show trends, seasonality, mean reversion or local changes near their end, so a single global pattern is not assumed. Observations are stored in columns x_1 … x_60, and NaN after obs_len marks a shorter series rather than missing data. The starter notebook (by Mushthofa) provides a local Exponential Smoothing / Holt baseline per series.
Abridged and translated by SOTA from the official Indonesian materials. The official statement has the exact rules, and it wins wherever this summary differs.
In English
This task was published in Indonesian. SOTA translated its 3 files into English on 17 September 2026. Only the words changed in the notebooks: markdown, code comments, messages and printed output. The code, file names and paths are the original's, so a translated notebook runs with the original data.
- Task notebook Indonesian original of Task notebook
- Competition overview Indonesian original of Competition overview
- Data description Indonesian original of Data description
Read the task notebook in English
Simulation 2, Pelatnas 1 IOAI 2026 — Participant Starter Notebook
Predicting the Future Mean River Discharge from a Historical Time Series (by: Mushthofa)
This notebook is a starter pack for participants. It covers:
- the official problem description,
- the specification of the data files and the technical parameters,
- how to download the data from Google Drive to Google Colab,
- a simple baseline based on local forecasting per time series,
- how to create the submission file,
- an explanation of the evaluation metric, MAE.
1. Problem Description
A water-resources monitoring agency collects daily river discharge data from many observation sites.
Each sample in this task represents one historical time series of river discharge from one particular site/time window.
For each sample, historical observations covering 50 to 60 days are given. If the observation length for a sample is , then the target to be predicted is:
where:
- = the historical observations,
- = the mean river discharge over the next 12 days.
Participants are not asked to predict each of the 12 future daily values one by one, but only their mean value.
Challenge
Not all samples come from the same temporal pattern. Some time series may have:
- a trend,
- a seasonal pattern,
- mean-reverting dynamics,
- or a distinctive local change at the end of the series.
Therefore, an approach that treats all samples as coming from a single pattern is not necessarily optimal.
2. Official Technical Parameters
This task uses the following parameters:
- Length of the historical observations per sample: between 50 and 60 days
- Future horizon: days
- Target:
where is the actual observation length for that sample
- Official evaluation metric: Mean Absolute Error (MAE)
Notes
- The data in this competition can be regarded as synthetic data representing daily river discharge patterns.
- Not all samples have the same observation length.
- To make the data easier to distribute, the observations are stored in the column format
x_1tox_60, and the unused part is filled with NaN.
3. Data Files
This notebook assumes that the organisers share one ZIP file, for example data_bundle.zip, which contains:
train.csvtest.csvsample_submission.csv
Format of train.csv
Columns:
idobs_lenx_1,x_2, ...,x_60target
Description:
obs_len= the actual observation length for that samplex_1tox_60store the historical observations- if
obs_len < 60, the columns after the last observation are filled with NaN targetis the mean discharge over the next 12 days
Format of test.csv
Columns:
idobs_lenx_1,x_2, ...,x_60
Format of sample_submission.csv
Columns:
idtarget
Important note
In this data, NaN in the observation columns does not mean randomly missing data; it means that the sample genuinely has a shorter historical observation.
# If running in Google Colab, run this cell once.
!pip -q install gdown scikit-learn pandas numpy matplotlib statsmodels
4. Download the Data from Google Drive to Colab
The easiest way is to share one ZIP file containing all the dataset files, and then download it with gdown.
Replace DATA_BUNDLE_FILE_ID below with the Google Drive file ID shared by the organisers.
import subprocess
from pathlib import Path
# The file is at https://drive.google.com/file/d/180yS7AFlg8YXv0edEoDOg_EZxxes05EK/view?usp=sharing
DATA_BUNDLE_FILE_ID = "180yS7AFlg8YXv0edEoDOg_EZxxes05EK"
DATA_DIR = Path("data")
ZIP_PATH = Path("data_bundle.zip")
if DATA_BUNDLE_FILE_ID.startswith("PASTE_"):
raise ValueError(
"Please replace DATA_BUNDLE_FILE_ID with your Google Drive file ID first."
)
DATA_DIR.mkdir(exist_ok=True)
subprocess.run(
["gdown", "--id", DATA_BUNDLE_FILE_ID, "-O", str(ZIP_PATH)],
check=True
)
subprocess.run(
["unzip", "-o", "-q", str(ZIP_PATH), "-d", str(DATA_DIR)],
check=True
)
print("Contents of the data folder:")
for p in sorted(DATA_DIR.iterdir()):
print("-", p.name)
5. Loading the Data
import pandas as pd
import numpy as np
train = pd.read_csv(DATA_DIR / "train.csv")
test = pd.read_csv(DATA_DIR / "test.csv")
sample_submission = pd.read_csv(DATA_DIR / "sample_submission.csv")
print("Train shape:", train.shape)
print("Test shape :", test.shape)
print("Sample submission shape:", sample_submission.shape)
display(train.head())
6. Initial Checks
def sort_feature_cols(cols):
ts_cols = [c for c in cols if c.startswith("x_")]
return sorted(ts_cols, key=lambda c: int(c.split("_")[1]))
FEATURE_COLS = sort_feature_cols(train.columns)
assert "id" in train.columns, "Column 'id' not found in train.csv"
assert "obs_len" in train.columns, "Column 'obs_len' not found in train.csv"
assert "target" in train.columns, "Column 'target' not found in train.csv"
assert "id" in test.columns, "Column 'id' not found in test.csv"
assert "obs_len" in test.columns, "Column 'obs_len' not found in test.csv"
assert list(sample_submission.columns) == ["id", "target"], \
"The sample_submission columns must be ['id', 'target']"
print("Number of observation columns:", len(FEATURE_COLS))
print("Example observation columns:", FEATURE_COLS[:5], "...", FEATURE_COLS[-5:])
print()
print("Range of obs_len in train:", int(train["obs_len"].min()), "to", int(train["obs_len"].max()))
print("Range of obs_len in test :", int(test["obs_len"].min()), "to", int(test["obs_len"].max()))
print()
print("Total NaN in train (padding only):", int(train[FEATURE_COLS].isna().sum().sum()))
print("Total NaN in test (padding only):", int(test[FEATURE_COLS].isna().sum().sum()))
7. Visualising a Few Samples
This cell is only meant to help you see the general shape of a few time series.
Because the observation length can differ between samples, we plot each sample only up to its own obs_len.
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 5))
for i in range(min(5, len(train))):
obs_len = int(train.loc[i, "obs_len"])
values = train.loc[i, FEATURE_COLS].values[:obs_len]
plt.plot(range(1, obs_len + 1), values, alpha=0.8, label=f"sample_{i} (len={obs_len})")
plt.xlabel("Day")
plt.ylabel("Discharge")
plt.title("A few example training time series")
plt.legend()
plt.show()
8. Simple Local Validation
Because the labels for the official test data are not available, we still create a validation split from the training data to get a rough picture of a method's performance.
Note that the baseline we will use below is local forecasting per time series, so conceptually it does not learn from the other training samples. The validation split here is mainly useful so that participants have a consistent local evaluation framework when they later try more complex methods.
from sklearn.model_selection import train_test_split
train_split, valid_split = train_test_split(
train, test_size=0.2, random_state=42
)
print("Train split:", train_split.shape)
print("Valid split:", valid_split.shape)
9. Simple Baseline: Local ETS/Holt per Time Series
As an initial baseline, we use a local forecasting approach:
- for each time series, take only the valid historical observations,
- fit an Exponential Smoothing / Holt-Winters model to that series,
- forecast 12 steps ahead,
- take their mean as the target prediction.
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_absolute_error
import warnings
warnings.filterwarnings("ignore")
def get_valid_series_from_row(row, feature_cols):
obs_len = int(row["obs_len"])
values = row[feature_cols].values[:obs_len].astype(float)
return values
def safe_recent_mean(series, horizon=12):
k = min(horizon, len(series))
return float(np.mean(series[-k:]))
def local_ets_holt_forecast_mean(series, horizon=12, seasonal_periods=12):
"""
Simple baseline:
- try ETS with additive trend + additive seasonality
- if that fails, fall back to Holt with additive trend
- if that still fails, fall back to the recent mean
"""
s = np.asarray(series, dtype=float)
try:
model = ExponentialSmoothing(
s,
trend="add",
damped_trend=True,
seasonal="add",
seasonal_periods=seasonal_periods,
initialization_method="estimated",
)
fitted = model.fit(optimized=True, use_brute=True)
fcst = fitted.forecast(horizon)
return float(np.mean(fcst))
except Exception:
pass
try:
model = ExponentialSmoothing(
s,
trend="add",
damped_trend=True,
seasonal=None,
initialization_method="estimated",
)
fitted = model.fit(optimized=True, use_brute=True)
fcst = fitted.forecast(horizon)
return float(np.mean(fcst))
except Exception:
return safe_recent_mean(s, horizon=horizon)
valid_pred = []
for _, row in valid_split.iterrows():
series = get_valid_series_from_row(row, FEATURE_COLS)
pred = local_ets_holt_forecast_mean(series, horizon=12, seasonal_periods=12)
valid_pred.append(pred)
valid_pred = np.array(valid_pred, dtype=float)
valid_mae = mean_absolute_error(valid_split["target"].values, valid_pred)
print(f"Validation MAE (local ETS/Holt baseline): {valid_mae:.6f}")
10. Creating a Submission with the Local ETS/Holt Baseline
test_pred = []
for _, row in test.iterrows():
series = get_valid_series_from_row(row, FEATURE_COLS)
pred = local_ets_holt_forecast_mean(series, horizon=12, seasonal_periods=12)
test_pred.append(pred)
test_pred = np.array(test_pred, dtype=float)
submission_baseline = sample_submission.copy()
submission_baseline["target"] = test_pred
display(submission_baseline.head())
print(submission_baseline.shape)
11. Save the Submission File
SUBMISSION_PATH = "submission_baseline.csv"
submission_baseline.to_csv(SUBMISSION_PATH, index=False)
print(f"Submission file saved to: {SUBMISSION_PATH}")
12. Checking the Submission Format
The following cell checks whether the submission file has the correct format.
submission_check = pd.read_csv(SUBMISSION_PATH)
assert list(submission_check.columns) == ["id", "target"], "The submission columns must be exactly: ['id', 'target']"
assert len(submission_check) == len(test), "The number of submission rows must equal the number of rows in test.csv"
assert submission_check["id"].equals(test["id"]), "The id column in the submission must be in the same order as the id column in test.csv"
assert np.isfinite(submission_check["target"]).all(), "The target column in the submission must not contain NaN or inf"
print("Submission format is valid.")
13. Official Evaluation Method: Mean Absolute Error (MAE)
The official competition score uses the Mean Absolute Error (MAE):
where:
- = the number of samples in the test data,
- = the participant's prediction for the -th sample,
- = the true target.
Translated by SOTA. The Indonesian original is the official version and wins wherever the two differ. The starter notebook by Mushthofa holds the full statement and a local ETS/Holt baseline; the Kaggle pages add the evaluation, submission format and an alternative data link. If you organise this olympiad and would like the translation removed, email [email protected] and we will take it down.
At a glance
- You get
train.csv(id,obs_len,x_1…x_60, target),test.csv(without target) andsample_submission.csv, distributed as a ZIP on Google Drive.- You submit
submission.csvwith columns id and target.- Scoring
- Mean Absolute Error (lower is better).
- Rules
- Individual participation (maximum team size 1).
- Format
- Pelatnas 1 IOAI 2026 (first national training-and-selection camp), Simulation 2 on Kaggle; competition window 17 April 2026 15:35 UTC – 18 April 2026 07:30 UTC; individual; up to 50 submissions per day.