Failure prediction with XGBoost
Effort: ~1–2 hours · You'll build: an engineered tabular feature set and a
gradient-boosted classifier with feature importance · Stack: the SDK for data in/out,
plus pandas, numpy and xgboost.
Some questions are best answered from tabular features, not raw sequences: will this asset fail in the next week? XGBoost is the workhorse here — it eats engineered features, handles missing values natively, trains fast, and tells you which features drive the prediction. We'll predict an electric submersible pump (ESP) failure seven days out, but the recipe fits any "label the history, predict the window" problem.
These steps read series and failure events that already exist. Generate a sandbox first — section E ingests the labelled failure history this build trains on.
No background needed. Skim the gentle primer for the ideas in plain language — model, feature, training, and the algorithm itself — and use Generate sample data for a sandbox to run this against.
1. Engineer one feature row per asset per day
For each pump on each day, summarise the recent week of its sensors into a feature row.
Gaps stay as NaN — XGBoost handles missing values without imputation.
- Python
- Java
- Rust
import datahub_sdk, numpy as np, pandas as pd
client = datahub_sdk.DataHubClient.from_env()
def feature_row(pump, day):
def agg(metric, *aggs):
rf = datahub_sdk.RetrieveFilter(
ts=f"{pump}_{metric}", start=day - pd.Timedelta(days=7), end=day,
aggregates=list(aggs), granularity="7d")
pts = client.timeseries.retrieve_datapoints(rf)[0].get_datapoints()
return pts[-1] if pts else None
ip, mt, vb = agg("intake_pressure_bar", "avg", "min"), \
agg("motor_temp_c", "avg", "max"), \
agg("vibration_mm_s", "avg", "max")
return {
"intake_avg": float(ip.average) if ip else np.nan,
"intake_min": float(ip.min) if ip else np.nan,
"motor_avg": float(mt.average) if mt else np.nan,
"motor_max": float(mt.max) if mt else np.nan,
"vib_avg": float(vb.average) if vb else np.nan,
"vib_max": float(vb.max) if vb else np.nan,
}
// Java pulls each pump's weekly aggregates; the model below is Python.
var filter = new RetrieveFilter();
filter.setExternalId("pump_esp_a12_motor_temp_c");
filter.setStart(ZonedDateTime.now().minusDays(7));
filter.setEnd(ZonedDateTime.now());
filter.setAggregates(List.of("avg", "max"));
filter.setGranularity("7d");
var request = new DataRetriever<RetrieveFilter>();
request.setItems(List.of(filter));
var agg = client.timeseries().retrieve(request).getItems().get(0).getDatapoints();
// Rust pulls each pump's weekly aggregates; the model below is Python.
use dataplatform_rust_sdk::generic::{DataWrapper, RetrieveFilter};
use chrono::Utc;
let filter = RetrieveFilter {
external_id: Some("pump_esp_a12_motor_temp_c".into()),
start: Some(Utc::now() - chrono::Duration::days(7)),
end: Some(Utc::now()),
aggregates: Some(vec!["avg".into(), "max".into()]),
granularity: Some("7d".into()),
..Default::default()
};
let agg = api.time_series
.retrieve_datapoints(&DataWrapper::from(vec![filter])).await?
.get_items().remove(0);
2. Label from the failure history
A row is positive if the pump failed within the next 7 days. Pull historical failure
events and label each (pump, day) accordingly.
# failures: DataFrame of (pump, failed_on), pulled from /events (type = esp_failure)
failures = pd.DataFrame([
{"pump": e.metadata.get("pump"), "failed_on": pd.to_datetime(e.event_time, utc=True)}
for e in client.events.filter(datahub_sdk.EventFilter(
datahub_sdk.BasicEventFilter(type="esp_failure"), limit=1000))])
# one feature row per pump per day across the history you have
training_grid = [(p, d) for p in ["pump_esp_a12"]
for d in pd.date_range(end=pd.Timestamp.now(tz="UTC"), periods=260, freq="1d")]
def label(pump, day, failures):
upcoming = failures[(failures["pump"] == pump) &
(failures["failed_on"] > day) &
(failures["failed_on"] <= day + pd.Timedelta(days=7))]
return int(len(upcoming) > 0)
rows = [(feature_row(p, d), label(p, d, failures)) for p, d in training_grid]
X = pd.DataFrame([r for r, _ in rows])
y = np.array([lab for _, lab in rows])
3. Train — with class imbalance and early stopping
Failures are rare, so weight the positive class and optimise for ranking (aucpr).
Early stopping on a validation split avoids over-fitting.
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import average_precision_score
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
clf = XGBClassifier(
n_estimators=600, max_depth=5, learning_rate=0.03,
subsample=0.8, colsample_bytree=0.8,
eval_metric="aucpr", early_stopping_rounds=40,
scale_pos_weight=(y == 0).sum() / max((y == 1).sum(), 1))
clf.fit(Xtr, ytr, eval_set=[(Xte, yte)], verbose=False)
print(f"avg precision: {average_precision_score(yte, clf.predict_proba(Xte)[:, 1]):.3f}")
4. Score live, with the reasons attached
Score today's row per pump, write the risk back as a series, and raise a
failure_predicted event for high-risk pumps — including the feature importances so
the planner sees why (rising vibration vs. falling intake pressure point to different
fixes).
import_ = dict(sorted(zip(X.columns, clf.feature_importances_),
key=lambda kv: kv[1], reverse=True)[:3])
today = pd.DataFrame([feature_row("pump_esp_a12", pd.Timestamp.now(tz="UTC"))])
risk = float(clf.predict_proba(today)[0, 1])
client.timeseries.create([datahub_sdk.TimeSeries(
external_id="pump_esp_a12_failure_risk", name="ESP A-12 7-day failure risk", unit="score", value_type="float")])
client.timeseries.insert_from_lists(
timestamps=[pd.Timestamp.now(tz="UTC")], values=[risk], ts="pump_esp_a12_failure_risk")
if risk > 0.6:
client.events.create([datahub_sdk.Event(
external_id=f"failure_predicted_a12_{int(pd.Timestamp.now().timestamp())}",
type="failure_predicted", status="open",
event_time=pd.Timestamp.now(tz="UTC"),
metadata={"pump": "pump_esp_a12", "risk": f"{risk:.2f}",
"top_drivers": ", ".join(import_)})])
Where to take it further
- Real explainability. Use SHAP for per-prediction attributions, not just global importance — the planner sees what drove this pump's score.
- Monotonic constraints. Tell XGBoost that higher vibration only ever raises risk
(
monotone_constraints) for a model that matches engineering intuition. - Compare. Benchmark against Random Forest and the sklearn gradient booster in fraud classification.
Further reading
- XGBoost — Wikipedia · docs
- Gradient boosting — Wikipedia
- Feature importance — scikit-learn guide
See also
- Oil & gas — production monitoring — the ESPs this protects.
- Predictive maintenance — the unsupervised counterpart.
- Turn readings into events — emitting the prediction.