Understand Prediction Results
client.predict() returns a PredictionResult, never a bare DataFrame. to_pandas() gives you the DataFrame.
Result fields
The fields that are present depend on the prediction task and the requested outputs.
Do not assume that every task returns every field or one row per input entity. Binary and multiclass classification return one row per entity, forecasting returns one row per forecast timeframe, and a RANK TOP K query returns one row per entity with prediction/rankings holding the candidates as a list — three entities ranked TOP 3 come back as three rows, not nine. Inspect the DataFrame columns or client.capabilities(model) before processing optional outputs.
A ranking can hold fewer than k candidates
A ranking asks one yes-or-no question per candidate — will this entity link to it in the window? If no entity in a candidate’s sampled context linked to it, that question has only one answer in context, and the endpoint refuses it. The client leaves that candidate out rather than failing the whole ranking: it gets no score and appears in no row’s prediction or rankings.
A candidate’s context is shared by every entity in the ranking, so a candidate left out for one entity is left out for all, and result.unscored_candidates lists them in the order you passed them. The client warns once, naming the candidates it left out. If every candidate is left out, there is nothing to rank and predict() raises UnsupportedRequestError before sending anything.
A larger num_context_samples can reach entities that did link to a candidate. On sparse data — a catalogue where most products sell rarely — it often can’t.
Recommended pattern: rank over a table of pairs
When you can provide a table with one row per (entity, candidate) pair, ask one classification over the pairs instead of a RANK TOP K ranking. Each pair is scored from the features of its entity and its candidate, and the context pools every pair, so a candidate nobody linked to recently is still scored — with one classification request, batched if needed, rather than one call per candidate.
The events table needs a pair_id column linking each event to its pair; the pairs table should hold the pairs you want scored, plus enough others — some that converted, some that didn’t — for the context to learn from.
What a regression reports
The relational model predicts a distribution for a regression; prediction is its median by default, matching what the engine returns. Pass materialize(..., prediction_statistic="mean") for the mean instead — the NIM averages the model’s predicted quantiles to compute it, which trims the tails.
Choose "mean" when predictions will be summed across entities, such as total demand across products: means add up and medians don’t, so on a right-skewed target the sum of the medians understates the total. Neither statistic is pinned to a floor of 0 on a zero-inflated target, because predicted quantiles aren’t clipped to the target’s range.
A regression on the tabular model always reports the mean and doesn’t take prediction_statistic.
Joining
Every row carries row_index. It also carries id when the model has an entity key.
Join on id when it is present, otherwise on row_index. Never on row order. The endpoint is not required to return rows in the order they were sent, and a request split across several calls will not preserve it either.
Checking whether a requested field arrived
A field the client declares for a model can still be refused by a particular deployment — see Prediction Types. sdm-client warns when a field you asked for by name is absent from the answer.
Explanations
On a compatible client and deployment, EXPLAIN or the explanation output requests per-entity gradients and labelled-context cohorts in result.explanations.
result.to_pandas() is unaffected. See Prediction Explainability.