Water Turbidity Prediction Using Sentinel-2
A machine learning project that uses Sentinel-2 spectral reflectance features and a Random Forest regression model to estimate water turbidity in Formazin Nephelometric Units (FNU).
Overview
This project explores satellite-based water turbidity estimation using Sentinel-2 spectral data and water-quality measurements. It includes dataset cleaning, feature selection, model training, and site-grouped cross-validation.
Highlights
- Model: Random Forest Regressor
- Input: 10 Sentinel-2 spectral reflectance features
- Target: Water turbidity (FNU)
- Cleaned dataset: 233,720 observations
- Sampling sites: 44
- Validation: Five-fold GroupKFold cross-validation by sampling site
- Cross-validation R²: 0.632 on the log1p-transformed target scale
- Model artifact:
turbidity_rf_model.pkl
Model Information
| Property | Details |
|---|---|
| Algorithm | Random Forest Regression |
| Framework | scikit-learn |
| Number of trees | 300 |
| Input features | 10 Sentinel-2 spectral features |
| Prediction target | Turbidity in FNU |
| Target transformation | log1p(y) |
| Validation strategy | Five-fold GroupKFold by sampling site |
The target transformation reduces the influence of the highly skewed turbidity distribution during model training and evaluation.
Input Features
The model uses the following Sentinel-2 spectral reflectance features:
| Feature | Sentinel-2 band |
|---|---|
B02_buf250_mean |
B02 - Blue |
B03_buf250_mean |
B03 - Green |
B04_buf250_mean |
B04 - Red |
B05_buf250_mean |
B05 - Vegetation red edge |
B06_buf250_mean |
B06 - Vegetation red edge |
B07_buf250_mean |
B07 - Vegetation red edge |
B08_buf250_mean |
B08 - Near-infrared |
B8A_buf250_mean |
B8A - Narrow near-infrared |
B11_buf250_mean |
B11 - Short-wave infrared |
B12_buf250_mean |
B12 - Short-wave infrared |
The buf250_mean features represent buffer-based mean reflectance values in the source dataset.
Dataset
The cleaned dataset is derived from water-quality data attributed to Grady P. Ball and Scott D. Ducar (2025), available through the U.S. Geological Survey.
Source: USGS — DOI: 10.5066/P1A7T4FV
Dataset Summary
| Metric | Raw file 1 | Raw file 2 | Cleaned dataset |
|---|---|---|---|
| Rows | 874,234 | 874,233 | 233,720 |
| Columns | 85 | 85 | 86 |
| Unique sampling sites | 34 | 26 | 44 |
| Rows missing at least one selected spectral feature | 227,464 | 167,521 | 0 |
| Rows missing target values | 0 | 0 | 0 |
| Exact duplicate rows | 0 | 1 | 1 |
Cleaning Results
- No missing values in the ten selected spectral features or the target.
- No negative turbidity values.
- 321 observations with zero turbidity.
- One exact duplicate row in the cleaned dataset.
- 44 unique sampling sites in the cleaned dataset.
Turbidity Distribution
| Statistic | Value |
|---|---|
| Observations | 233,720 |
| Mean | 20.48 FNU |
| Median | 8.5 FNU |
| Standard deviation | 46.25 FNU |
| 25th percentile | 2.5 FNU |
| 75th percentile | 22.5 FNU |
| 90th percentile | 53.2 FNU |
| 95th percentile | 75.8 FNU |
| 99th percentile | 159 FNU |
| Maximum | 4,000 FNU |
Model Evaluation
The model was evaluated using five-fold GroupKFold cross-validation. Observations were grouped by sampling site, ensuring that validation sites were excluded from the corresponding training folds.
Pooled Out-of-Fold Results
| Metric | Result |
|---|---|
| R² on the log1p-transformed target | 0.6321 |
| MAE on the log1p-transformed target | 0.5015 |
| Median absolute error on the log1p-transformed target | 0.3409 |
| RMSE on the log1p-transformed target | 0.7293 |
| Explained variance on the log1p-transformed target | 0.6351 |
| MAE in FNU | 9.33 FNU |
| Median absolute error in FNU | 2.96 FNU |
| RMSE in FNU | 39.83 FNU |
The reported R² is calculated on the log1p-transformed target scale. It is not a percentage-accuracy measurement.
Cross-Validation Results by Fold
| Fold | R² on log1p target | MAE (FNU) | RMSE (FNU) |
|---|---|---|---|
| 1 | 0.4322 | 7.13 | 32.18 |
| 2 | 0.5714 | 7.88 | 16.03 |
| 3 | 0.7545 | 8.35 | 17.60 |
| 4 | 0.6946 | 12.29 | 26.25 |
| 5 | 0.0908 | 10.99 | 75.09 |
Performance varies across held-out sampling sites.
Baseline Comparison
| Model | MAE |
|---|---|
| Random Forest | 9.33 FNU |
| Training-fold median baseline | 17.99 FNU |
The Random Forest achieved a lower pooled MAE than the training-fold median baseline.
Performance by Turbidity Range
| Actual turbidity range | Observations | MAE (FNU) | RMSE (FNU) |
|---|---|---|---|
| ≤ 5 FNU | 87,847 | 3.33 | 8.37 |
| > 5 to 20 FNU | 82,242 | 5.05 | 7.72 |
| > 20 to 100 FNU | 57,178 | 13.96 | 20.73 |
| > 100 FNU | 6,453 | 104.43 | 227.92 |
Prediction errors are larger for high-turbidity observations.
Example Usage
The repository includes the trained model artifact turbidity_rf_model.pkl.
The following Google Colab example loads the model and generates one prediction using placeholder feature values.
Install Dependencies
!pip install numpy pandas scikit-learn
Load the Model and Predict
import pickle
import numpy as np
import pandas as pd
from google.colab import files
# Upload turbidity_rf_model.pkl.
uploaded = files.upload()
MODEL_PATH = next(iter(uploaded))
FEATURES = [
"B02_buf250_mean",
"B03_buf250_mean",
"B04_buf250_mean",
"B05_buf250_mean",
"B06_buf250_mean",
"B07_buf250_mean",
"B08_buf250_mean",
"B8A_buf250_mean",
"B11_buf250_mean",
"B12_buf250_mean",
]
with open(MODEL_PATH, "rb") as f:
model = pickle.load(f)
# Example input only
example_input = pd.DataFrame(
[[0.05] * len(FEATURES)],
columns=FEATURES,
)
example_prediction = model.predict(example_input)
predicted_turbidity_fnu = np.expm1(example_prediction[0])
print(f"Example predicted turbidity: {predicted_turbidity_fnu:.2f} FNU")
The example assumes the saved model predicts log1p-transformed turbidity and expects features prepared as in the training workflow. The placeholder values are illustrative and do not represent measured Sentinel-2 observations.
Limitations
- Performance varies across held-out sampling sites.
- Prediction errors increase at high turbidity levels.
- Geographic, seasonal, and environmental differences may affect generalization.
Intended Use
- Satellite-based water-quality research.
- Machine learning experimentation.
- Environmental data analysis.
- Educational demonstrations of Random Forest regression.
The model is not a substitute for laboratory testing or independently validated water-quality measurements.
Acknowledgements
The underlying water-quality dataset is attributed to Grady P. Ball and Scott D. Ducar (2025) and is available through the U.S. Geological Survey.
Dataset DOI: 10.5066/P1A7T4FV
The source dataset is identified as CC0 1.0. Original measurements are attributed to their source.
License
This repository uses the MIT License for the project artifacts covered by that license. The original dataset remains subject to its own licensing and attribution terms.