A scikit-learn compatible toolkit for predictive donor analytics in the nonprofit sector.
Project description
PhilanthroPy: Code for a causeโpredictive analytics for advancement teams.
[](https://github.com/PhilanthroPy-Project/PhilanthroPy/actions/workflows/ci.yml)
๐ View the Full Documentation Site
What is PhilanthroPy?
PhilanthroPy is a production-ready Python library that slots directly into sklearn.pipeline.Pipeline. It covers the full predictive workflow for nonprofit and academic medical center (AMC) fundraising โ from raw CRM cleaning and wealth imputation to major-gift propensity scoring, lapse prediction, and planned-giving intent.
Installation
git clone https://github.com/PhilanthroPy-Project/PhilanthroPy.git
cd PhilanthroPy
pip install -e ".[dev]"
Or with Conda:
conda env create -f environment.yml && conda activate Philanthropy
pip install -e ".[dev]"
Quick Start
from philanthropy.datasets import generate_synthetic_donor_data
from philanthropy.models import DonorPropensityModel
df = generate_synthetic_donor_data(n_samples=500, random_state=42)
X = df[["total_gift_amount", "years_active", "event_attendance_count"]].to_numpy()
y = df["is_major_donor"].to_numpy()
model = DonorPropensityModel(n_estimators=200, random_state=0)
model.fit(X, y)
scores = model.predict_affinity_score(X) # 0โ100 affinity scale
Output of plot_affinity_distribution(): the 0โ100 affinity scores cleanly separate major from non-major donors.
Feature Overview โ v0.3.0
๐งน Preprocessing
| Transformer | Description |
|---|---|
CRMCleaner |
Standardise raw CRM exports โ coerce types, strip whitespace, drop PII columns |
WealthScreeningImputer |
Leakage-safe wealth imputation (median / mean / zero) with optional missingness indicator columns; fill stats frozen at fit() time |
WealthPercentileTransformer |
Per-column wealth percentile rank (0โ100); NaN-in โ NaN-out |
FiscalYearTransformer |
Fiscal year & quarter features from gift dates; configurable fiscal year start month |
RFMTransformer |
RecencyโFrequencyโMonetary value feature engineering for donor segmentation |
EncounterTransformer |
Bridge hospital EHR encounter records with philanthropy CRM; computes days_since_last_discharge and encounter_frequency_score; snapshot at fit() prevents leakage |
GratefulPatientFeaturizer โญ NEW |
Clinical gravity score + AMC service-line capacity weights (cardiac 3.2ร, oncology 2.9ร, neuroscience 2.7ร); outputs clinical_gravity_score, distinct_service_lines, distinct_physicians, total_drg_weight |
DischargeToSolicitationWindowTransformer โญ NEW |
Post-discharge solicitation window flags tracking recency; outputs in_solicitation_window (0/1), window_position_score (1.0 at midpoint, 0.0 at edges), and discharge_recency_tier (int 0-4); NaN โ 0 |
PlannedGivingSignalTransformer โญ NEW |
Bequest / legacy-gift intent vector: is_legacy_age, is_loyal_donor, inclination_score (โ1 sentinel for absent data), composite_score [0โ3] |
๐ค Models
| Model | Description |
|---|---|
DonorPropensityModel |
Random Forest classifier with predict_affinity_score() returning a 0โ100 scale |
MajorGiftClassifier |
Calibrated HistGradientBoostingClassifier โ NaN-native, with predict_affinity_score() |
ShareOfWalletRegressor |
Estimates total giving capacity and untapped-potential ratio |
LapsePredictor |
Random Forest classifier for donor lapse; see full docs below |
MovesManagementClassifier |
Multi-class portfolio stage predictor |
PropensityScorer |
Lightweight logistic propensity baseline |
PlannedGivingIntentScorer โญ NEW |
Wraps GradientBoostingClassifier to predict bequest intent scores (0-100 scale) |
FinancialForecastModel โญ NEW |
Hybrid LSTM-ARIMA revenue/giving forecaster with predict_revenue_forecast(X, horizon); leakage-safe, dependency-free, sklearn-native |
๐ Model Selection
| Splitter | Description |
|---|---|
FiscalYearGroupedSplitter โญ NEW |
Walk-forward cross-validator preventing fiscal year data leakage |
๐ Metrics
| Function | Description |
|---|---|
donor_lifetime_value |
LTV with configurable discount rate |
retention_rate |
Period-over-period donor retention |
donor_acquisition_cost |
CAC from campaign spend data |
๐ Datasets
| Function | Description |
|---|---|
generate_synthetic_donor_data |
Reproducible synthetic prospect pool โ n_samples, random_state |
DischargeToSolicitationWindowTransformer
Post-discharge solicitation window featurization. Flags donors within the optimal window (days post-discharge) and emits proximity scores.
| Parameter | Type | Default | Description |
|---|---|---|---|
| min_days_post_discharge | int | 90 | Start of solicitation window |
| max_days_post_discharge | int | 365 | End of solicitation window |
| days_since_discharge_col | str | "days_since_last_discharge" | Column name for days since discharge |
Output columns: in_solicitation_window (0/1), window_position_score [0,1], discharge_recency_tier [0โ4].
LapsePredictor
Production Random Forest classifier for donor lapse risk. Predicts whether a donor will lapse within a configurable window.
| Parameter | Type | Default | Description |
|---|---|---|---|
| n_estimators | int | 100 | Number of trees |
| max_depth | int or None | None | Maximum tree depth |
| min_samples_leaf | int | 1 | Min samples per leaf |
| class_weight | dict/"balanced"/None | None | Class weighting |
| lapse_window_years | int | 2 | Prediction window in years |
| random_state | int or None | None | Reproducibility seed |
Fitted attributes
| Attribute | Description |
|---|---|
| estimator_ | Trained RandomForestClassifier |
| classes_ | Array of class labels |
| n_features_in_ | Feature count from fit |
Methods
| Method | Returns | Description |
|---|---|---|
| .fit(X, y) | self | Train on features and binary lapse label |
| .predict(X) | ndarray (n,) | Binary predictions (1 = at-risk) |
| .predict_proba(X) | ndarray (n,2) | Class probabilities |
| .predict_lapse_score(X) | ndarray (n,) | P(lapse) ร 100, range [0.0, 100.0] |
from philanthropy.models import LapsePredictor
predictor = LapsePredictor(
n_estimators=200,
lapse_window_years=3,
class_weight="balanced",
random_state=42,
)
predictor.fit(X, y)
at_risk = predictor.predict(X) # 1 = at risk of lapsing
scores = predictor.predict_lapse_score(X) # 0โ100 lapse risk score
MajorGiftClassifier
Calibrated gradient-boosted classifier for major-gift propensity. Uses HistGradientBoostingClassifier wrapped in CalibratedClassifierCV for well-calibrated probabilities on skewed datasets. Handles NaN values natively โ no upstream imputation needed.
| Parameter | Type | Default | Description |
|---|---|---|---|
| learning_rate | float | 0.1 | Boosting step size |
| max_iter | int | 100 | Number of boosting trees |
| max_depth | int or None | None | Maximum tree depth |
| random_state | int or None | None | Reproducibility seed |
Fitted attributes
| Attribute | Description |
|---|---|
| estimator_ | CalibratedClassifierCV wrapping HistGradientBoostingClassifier |
| classes_ | Array of class labels |
| n_features_in_ | Feature count from fit |
Methods
| Method | Returns | Description |
|---|---|---|
| .fit(X, y) | self | Train model |
| .predict(X) | ndarray (n,) | Binary predictions |
| .predict_proba(X) | ndarray (n,2) | Calibrated probabilities (rows sum to 1.0) |
| .predict_affinity_score(X) | ndarray (n,) | P(major gift) ร 100, integer, range [0, 100] |
Key distinction from DonorPropensityModel: DonorPropensityModel uses Random Forest with raw probabilities and float affinity scores. MajorGiftClassifier uses HistGradientBoosting + calibration, handles NaN natively, produces integer affinity scores, and provides better-calibrated probabilities on skewed datasets.
from philanthropy.models import MajorGiftClassifier
import numpy as np
model = MajorGiftClassifier(max_iter=200, random_state=42)
model.fit(X, y)
# Works with NaN values โ no upstream imputation needed
X_with_nan = X.copy().astype(float)
X_with_nan[0, 1] = np.nan
scores = model.predict_affinity_score(X_with_nan)
PlannedGivingIntentScorer
Bequest/planned giving intent classifier. GradientBoostingClassifier + CalibratedClassifierCV for calibrated probabilities.
| Parameter | Type | Default | Description |
|---|---|---|---|
| n_estimators | int | 100 | Boosting stages |
| random_state | int or None | None | Reproducibility seed |
Methods: .fit(X, y), .predict(X), .predict_proba(X), .predict_intent_score(X) โ P(intent) ร 100, [0.0, 100.0].
FinancialForecastModel
Hybrid LSTM-ARIMA revenue/giving forecaster. Decomposes a giving series into a linear (ARIMA-surrogate) component fitted with LinearRegression and a nonlinear (LSTM-surrogate) residual component fitted with MLPRegressor โ reproducing the additive hybrid y = linear(X) + nonlinear_residual(X) with no heavy dependencies (no TensorFlow / statsmodels). Multi-step forecasts roll a frozen autoregressive model forward. All fitted statistics (fill values, sub-models, AR coefficients) are frozen at fit() for leakage safety.
| Parameter | Type | Default | Description |
|---|---|---|---|
| ar_order | int | 3 | Order of the autoregressive roll-forward |
| hidden_layer_sizes | tuple | (64,) | Residual network architecture (LSTM stand-in) |
| max_iter | int | 300 | Max optimisation iterations for the network |
| alpha | float | 1e-4 | L2 regularisation of the residual network |
| random_state | int or None | None | Reproducibility seed |
Methods: .fit(X, y), .predict(X) โ ndarray (n,), .predict_revenue_forecast(X, horizon) โ ndarray (horizon,).
from philanthropy.models import FinancialForecastModel
model = FinancialForecastModel(ar_order=3, random_state=42)
model.fit(X, y) # y = period giving revenue
forecast = model.predict_revenue_forecast(X, horizon=4) # next 4 periods
API quick reference
| Component | Module | Type |
|---|---|---|
| DonorPropensityModel | philanthropy.models | Classifier |
| MajorGiftClassifier | philanthropy.models | Classifier |
| LapsePredictor | philanthropy.models | Classifier |
| PlannedGivingIntentScorer | philanthropy.models | Classifier |
| ShareOfWalletRegressor | philanthropy.models | Regressor |
| FinancialForecastModel | philanthropy.models | Regressor |
| DischargeToSolicitationWindowTransformer | philanthropy.preprocessing | Transformer |
| RFMTransformer | philanthropy.preprocessing | Transformer |
sklearn Pipeline Example
from sklearn.pipeline import Pipeline
from philanthropy.preprocessing import (
FiscalYearTransformer, WealthScreeningImputer, DischargeToSolicitationWindowTransformer
)
from philanthropy.models import DonorPropensityModel
pipe = Pipeline([
("fy", FiscalYearTransformer(date_col="gift_date")),
("wealth", WealthScreeningImputer(wealth_cols=["estimated_net_worth"])),
("window", DischargeToSolicitationWindowTransformer()),
("model", DonorPropensityModel(n_estimators=200, random_state=0)),
])
pipe.fit(X_train, y_train)
scores = pipe.predict_proba(X_test)[:, 1]
Package Overview
philanthropy/
โโโ datasets/
โ โโโ _generator.py
โโโ preprocessing/
โ โโโ transformers.py
โ โโโ _wealth.py
โ โโโ _wealth_percentile.py
โ โโโ _encounters.py
โ โโโ _encounter_recency.py
โ โโโ _rfm.py
โ โโโ _discharge_window.py
โ โโโ _solicitation_window.py # alias โ _discharge_window
โ โโโ _grateful_patient.py
โ โโโ _planned_giving.py
โ โโโ _share_of_wallet.py
โโโ models/
โ โโโ propensity.py
โ โโโ _propensity.py
โ โโโ _wallet.py
โ โโโ _lapse.py
โ โโโ _planned_giving.py
โ โโโ _moves.py
โโโ metrics/
โ โโโ scoring.py
โ โโโ _financial.py
โโโ model_selection/
โ โโโ __init__.py
โโโ experimental/
โ โโโ _lapse.py
โโโ visualisation/
โ โโโ _plots.py
โโโ utils/
โโโ testing.py
โโโ _validation.py
Medical Philanthropy Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from philanthropy.preprocessing import EncounterTransformer, CRMCleaner
# 1. EHR data extraction
encounter_features = EncounterTransformer(
encounter_df=encounter_df,
encounter_date_col="discharge_date",
donor_id_col="mrn"
)
# 2. Use ColumnTransformer to merge EHR features with numeric CRM data
# This prevents the silent dropping of original CRM columns like estimated_net_worth
preprocessor = ColumnTransformer(
transformers=[
("encounters", encounter_features, ["mrn", "gift_date"]),
("crm_nums", CRMCleaner(), ["estimated_net_worth", "real_estate_value"]),
],
remainder="passthrough"
)
# 3. Fit pipeline on original dataset (gift_df)
gift_features = preprocessor.fit_transform(gift_df)
Testing
| Test file | Tests | What's covered |
|---|---|---|
| test_sklearn_compliance.py | 417 | check_estimator for all public estimators |
| test_sklearn_compat.py | 230 | sklearn API compliance |
| test_donor_propensity_model.py | 84 | DonorPropensityModel โ affinity, pipeline, edge cases |
| test_propensity.py | 75 | PropensityScorer, LapsePredictor โ full production coverage |
| test_preprocessing.py | 35 | FiscalYearTransformer, CRMCleaner, WealthScreeningImputer |
| test_share_of_wallet.py | 25 | ShareOfWalletRegressor โ capacity_floor, NaN inputs, predict_capacity_ratio |
| test_rfm_transformer.py | 20 | RFMTransformer โ recency/frequency/monetary, reference_date, leakage freeze |
| test_major_gift_classifier.py | 20 | MajorGiftClassifier โ calibrated proba, affinity score, check_estimator |
| test_datasets.py | 19 | generate_synthetic_donor_data |
| test_metrics.py | 18 | donor_retention_rate, donor_acquisition_cost, donor_lifetime_value |
| test_planned_giving.py | 15 | PlannedGivingIntentScorer, PlannedGivingSignalTransformer |
| test_grateful_patient_featurizer.py | 15 | GratefulPatientFeaturizer |
| test_properties.py | 14 | Hypothesis property-based tests |
| test_leakage.py | 14 | WealthScreeningImputer API, fill-value freeze |
| test_visualisation.py | 12 | plot_affinity_distribution headless, all public plot functions |
| test_solicitation_window.py | 12 | DischargeToSolicitationWindowTransformer |
| test_coverage_boost.py | 6 | Coverage expansion tests |
| test_financial.py | 5 | donor_lifetime_value |
| test_utils.py | 4 | make_donor_dataset, validation |
| test_rfm.py | 4 | RFMTransformer |
| test_preprocessing_properties.py | 3 | Hypothesis for preprocessing |
| test_estimators.py | 3 | Estimator checks |
| test_transformers_property.py | 2 | Transformer property tests |
1052 tests across 23 test files.
# Full suite
pytest tests/ -q
# sklearn check_estimator compliance
pytest tests/test_sklearn_compliance.py -q
# Property-based (Hypothesis)
pytest tests/test_properties.py tests/test_preprocessing_properties.py -q
# Leakage prevention
pytest tests/test_leakage.py -v
Roadmap
โ Completed
- CRMCleaner โ leakage-safe CRM standardisation
- WealthScreeningImputer โ median/mean/zero imputation
- EncounterTransformer โ clinical discharge โ feature engineering
- RFMTransformer โ Recency, Frequency, Monetary features
- DischargeToSolicitationWindowTransformer โ solicitation window features
- ShareOfWalletRegressor โ capacity regression + predict_capacity_ratio()
- MajorGiftClassifier โ gradient-boosted with calibrated probabilities
- LapsePredictor โ production RF with predict_lapse_score()
- PlannedGivingIntentScorer โ planned giving intent classifier
- donor_lifetime_value() โ NPV LTV with discount rate
- philanthropy.visualisation โ affinity score plots
- Property-based Hypothesis testing for FiscalYearTransformer
- Temporal leakage prevention test suite
- GitHub Actions CI (Python 3.10 + 3.11 matrix)
- Coverage gate (โฅ 85%)
- Makefile (make ci)
- Branch protection + PR workflow
๐ Next
- Full Sphinx documentation site (readthedocs.io)
- PyPI release (pip install philanthropy)
- philanthropy.visualisation.plot_retention_waterfall()
- philanthropy.visualisation.plot_capacity_heatmap()
- EnsemblePropensityModel (stacked LapsePredictor + DonorPropensityModel)
Design Principles
- Leakage-safe by design โ fill statistics, encounter summaries, and encounter snapshots are all frozen at
fit()time;transform()is fully idempotent - sklearn-native โ all estimators pass
check_estimator; supportset_output(transform="pandas"),clone(),get_params()/set_params() - NaN-transparent โ wealth and clinical transformers declare
allow_nan = True; no silent data loss - PII-aware โ
EncounterTransformerauto-drops PII-like columns before returning features
Contributing
See CONTRIBUTING.md for the full local test gate, the
new-test-file workflow, and pre-push hook setup. In short: run make ci before
every push, and never use git push --no-verify.
License
MIT License โ see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file philanthropy-0.3.0.tar.gz.
File metadata
- Download URL: philanthropy-0.3.0.tar.gz
- Upload date:
- Size: 64.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
791f5a7e3362b92d1abc24772e8113d08f1c66291c9df4a1d72970ac48db8287
|
|
| MD5 |
c7f75a51bb16918937c5d6f6310e3826
|
|
| BLAKE2b-256 |
abd5036b73e2e053122a4feb6c970ea37567b42d33464424eb7e32d73572c76e
|
Provenance
The following attestation bundles were made for philanthropy-0.3.0.tar.gz:
Publisher:
publish.yml on PhilanthroPy-Project/PhilanthroPy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
philanthropy-0.3.0.tar.gz -
Subject digest:
791f5a7e3362b92d1abc24772e8113d08f1c66291c9df4a1d72970ac48db8287 - Sigstore transparency entry: 2193307197
- Sigstore integration time:
-
Permalink:
PhilanthroPy-Project/PhilanthroPy@919389e61dd8d29dee840ac6d46c835b08ae429f -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/PhilanthroPy-Project
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@919389e61dd8d29dee840ac6d46c835b08ae429f -
Trigger Event:
release
-
Statement type:
File details
Details for the file philanthropy-0.3.0-py3-none-any.whl.
File metadata
- Download URL: philanthropy-0.3.0-py3-none-any.whl
- Upload date:
- Size: 75.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5469ecd718ae44c534b7d226dc9c4f7ba2e99fb03eac43294354cd9de3545b15
|
|
| MD5 |
0e34d5bc700a95fdc4035f73004b8396
|
|
| BLAKE2b-256 |
1321d2b224fe215c61877d3f410904a723a9abad88ec60781595025bfeedc18c
|
Provenance
The following attestation bundles were made for philanthropy-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on PhilanthroPy-Project/PhilanthroPy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
philanthropy-0.3.0-py3-none-any.whl -
Subject digest:
5469ecd718ae44c534b7d226dc9c4f7ba2e99fb03eac43294354cd9de3545b15 - Sigstore transparency entry: 2193307200
- Sigstore integration time:
-
Permalink:
PhilanthroPy-Project/PhilanthroPy@919389e61dd8d29dee840ac6d46c835b08ae429f -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/PhilanthroPy-Project
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@919389e61dd8d29dee840ac6d46c835b08ae429f -
Trigger Event:
release
-
Statement type: