Explain a donor's score
donor_feature_importance tells you which signals the model leans on across the whole file. A gift officer asking "why is this donor on my list?" needs the answer for that one donor. explain_donor_scores gives it, with no dependency beyond scikit-learn and pandas.
How it works
For each donor and each feature, the feature is replaced with a typical value (the training median, or the most frequent value for a categorical column) and the donor is rescored:
A positive contribution means the donor's real value pushed the score up compared with a typical donor; a negative one pulled it down. The top top_k features per donor come back, largest absolute contribution first.
Example
from philanthropy.datasets import generate_synthetic_donor_data
from philanthropy.inspection import explain_donor_scores
from philanthropy.models import MajorGiftClassifier
cols = ["total_gift_amount", "years_active", "event_attendance_count"]
df = generate_synthetic_donor_data(n_samples=1000, random_state=0)
train, today = df.iloc[:800], df.iloc[800:]
model = MajorGiftClassifier(random_state=0).fit(train[cols], train["is_major_donor"])
reasons = explain_donor_scores(
model, today[cols], "predict_affinity_score", baseline=train[cols], top_k=2
)
print(reasons.head())
# columns: row, rank, feature, value, baseline, contribution
row is the donor's index label in today, so the reasons join straight back to your prospect list.
Choosing the method and the baseline
methodis the name of any per-row scoring method:predict_proba(the default),predict_affinity_score,predict_lapse_score,predict_retention_score,predict_intent_score,decision_function, orpredicton the regressors (AskAmountRecommender,ShareOfWalletRegressor,FinancialForecastModel). For per-class output it explains the positive class of a binary model and each donor's predicted stage forMovesManagementClassifier; passclass_indexto pick a class yourself.baselineshould be the training features (medians are taken from it) or a 1-D sequence of reference values in column order. When it is omitted, an estimator that stores its own training medians (FinancialForecastModel.fill_values_) uses those; anything else raises and asks for one.NaNin the donor's row is explained like any value: the contribution is the effect of the field being missing rather than typical.- Fitted pipelines work too, so a DataFrame with categorical columns encoded inside the pipeline is explained by its raw column names.
Reading the result honestly
- It changes one feature at a time. Contributions are exact for additive models but do not sum to the score in general, and correlated features (largest gift and total giving, say) can share or mask credit.
- A donor whose value is already close to typical gets a contribution near zero, even on an important feature. For a low-scoring donor that sits on the same side of the decision as the typical donor, every contribution can be zero: the explanation answers "what makes this donor different from typical", which is most useful for the high scorers you are about to call.
- This is an explanation of the model, not of the donor. See Compliance considerations before showing reasons to anyone outside the prospect research team.