Skip to main content

A scikit-learn compatible toolkit for predictive donor analytics in the nonprofit sector.

Project description

PhilanthroPy logo

PhilanthroPy: Code for a causeโ€”predictive analytics for advancement teams.

version python sklearn documentation [![Tests](https://github.com/PhilanthroPy-Project/PhilanthroPy/actions/workflows/ci.yml/badge.svg)](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

Affinity score distribution separating major from non-major donors
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; support set_output(transform="pandas"), clone(), get_params() / set_params()
  • NaN-transparent โ€” wealth and clinical transformers declare allow_nan = True; no silent data loss
  • PII-aware โ€” EncounterTransformer auto-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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

philanthropy-0.3.0.tar.gz (64.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

philanthropy-0.3.0-py3-none-any.whl (75.8 kB view details)

Uploaded Python 3

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

Hashes for philanthropy-0.3.0.tar.gz
Algorithm Hash digest
SHA256 791f5a7e3362b92d1abc24772e8113d08f1c66291c9df4a1d72970ac48db8287
MD5 c7f75a51bb16918937c5d6f6310e3826
BLAKE2b-256 abd5036b73e2e053122a4feb6c970ea37567b42d33464424eb7e32d73572c76e

See more details on using hashes here.

Provenance

The following attestation bundles were made for philanthropy-0.3.0.tar.gz:

Publisher: publish.yml on PhilanthroPy-Project/PhilanthroPy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for philanthropy-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5469ecd718ae44c534b7d226dc9c4f7ba2e99fb03eac43294354cd9de3545b15
MD5 0e34d5bc700a95fdc4035f73004b8396
BLAKE2b-256 1321d2b224fe215c61877d3f410904a723a9abad88ec60781595025bfeedc18c

See more details on using hashes here.

Provenance

The following attestation bundles were made for philanthropy-0.3.0-py3-none-any.whl:

Publisher: publish.yml on PhilanthroPy-Project/PhilanthroPy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page