Sitemap
Data And Beyond

Selected stories around Data Science, Machine Learning, Artificial Intelligence, Programming, and Technology topics. Writing guide: https://medium.com/data-and-beyond/how-to-write-for-data-and-beyond-b83ff0f3813e

Your Retention Rate Is Lying to You

What cohort analysis reveals about the hidden risks in your charity’s donor base

18 min readApr 16, 2026

--

Press enter or click to view image in full size
Photo by Jelena Ardila Vetrovec on Unsplash

Imagine you are reviewing your charity’s fundraising results. Things are moving in the right direction. Donor retention is up. Revenue is up. The loyal supporters who have been with you for years are still giving. It is tempting — understandably — to take a breath and turn attention to the next crisis.

Donor retention, the proportion of last year’s donors who gave again this year, is one of the most widely used measures of fundraising performance, and rightly so. A strong retention rate suggests that your existing supporters feel connected enough to your cause to keep giving. That matters enormously.

The problem is that retention is a lagging indicator. It tells you where you have been, not where you are going. By the time a retention problem becomes visible in your annual figures, it has typically been building quietly for years. When reading retention figures, you are, in effect, reading last year’s news.

This is more than a technical limitation. It means that a charity can record a strong retention rate and still be in serious trouble — not because the number is wrong but because it is hiding something about who is coming back. A high aggregate retention rate can be sustained entirely by long-time supporters who have given for a decade or more, even as the flow of newer donors converting into repeat givers quietly thins. The Benchmarking Project calls this the “leaky bathtub” problem: donors are draining away at the bottom faster than new ones are being retained at the top, even when the water level — your headline retention rate — still looks fine.

One early warning sign is the second-gift rate, the proportion of first-time donors who make a second gift within 12 months. That first repeat gift is not just another transaction. It is the moment a one-off donor becomes a supporter. Donors who give a second time are significantly more likely to give again — and again. The second gift is the entry point to a long-term relationship. Without it, a donor is just a name on a list.

The second-gift rate is already a form of cohort thinking. It takes each year’s first-time donors as a group and asks how many of them crossed that critical threshold within the following 12 months. That is more diagnostic than an aggregate retention rate — yet it only captures one moment in a cohort’s life. It cannot tell you what happens to those donors beyond that window or how each acquisition year’s cohort is contributing to the organization’s donor base and revenue over time. A charity could have a stable second-gift rate and still be quietly accumulating a structural problem in its cohort composition.

To see that deeper picture, you need a full cohort analysis. By grouping donors according to the year of their first gift and tracking each group’s behavior over time, cohort analysis extends the logic of the second-gift rate across a longer horizon. It lets you ask not just “are donors converting after their first gift?” but “how are successive groups of donors performing over time, and is that changing?”

In my previous articles, How I Built a Synthetic Charity Dataset That Behaves Like the Real Thing and The Day My Synthetic Donors Didn’t Pass for Human, I built a synthetic charity dataset modelled on the Australian sector. In this article, I will apply exactly this approach — beginning with the headline metrics before using cohort analysis to ask whether the picture they paint holds up under closer scrutiny.

Good News First

To derive my fictional charity’s annual retention rate, I first filter out regular giving from the df_opps dataframe. Recurring or monthly donors are, by definition, repeat givers. Their return is scheduled rather than a behavioral signal of renewed commitment. Including them would inflate the retention rate in a way that obscures rather than illuminates donor loyalty. From the filtered dataset, I extract unique donor-year pairs, giving one row per donor per year of giving.

import pandas as pd

# Remove regular giving in preparation
df_opps_filtered = df_opps[df_opps['campaign'] != 'Regular Giving'].copy()

# Get unique year-donor pairs
df_opps_filtered['year'] = df_opps_filtered['close_date'].dt.year
df_years = df_opps_filtered[['contact_id', 'year']].copy()
df_years = df_years.drop_duplicates()

The next step is determining, for each donor-year combination, whether that donor also gave in the immediately preceding year. I sort by donor and year, then use pandas’ shift() method within each donor group to bring the previous year’s value into the current row. Where the current year equals the previous year plus one, the donor gave in consecutive years and is marked as retained.

# Identify if the donor gave in the previous year
# Sort by donor and year, then check if the previous row is the same donor
# AND exactly one year prior.
df_years = df_years.sort_values(['contact_id', 'year'])
df_years['prev_year'] = df_years.groupby('contact_id')['year'].shift(1)
df_years['is_retained'] = (df_years['year'] == df_years['prev_year'] + 1)

I then aggregate by year to obtain total donor counts and retained donor counts, the latter being a simple sum of the boolean is_retained flag. To calculate the retention rate, I shift total_donors forward by one period to produce donors_last_year, the denominator: the number of donors who gave in Year Y-1, against which retained_donors in Year Y is measured. The first year is dropped from the final dataset, since it has no prior year to serve as its denominator.

# Aggregate results
results = df_years.groupby('year').agg(
total_donors=('contact_id', 'count'),
retained_donors=('is_retained', 'sum')
).reset_index()

# Calculate rate - Shift total_donors to get "last year" count for the denominator
results['donors_last_year'] = results['total_donors'].shift(1)
results['retention_rate'] = results['retained_donors'] / results['donors_last_year']

# Clean up - Remove the first year since it has no "previous year" to compare to
df_ret = results.dropna(subset=['donors_last_year']).copy()

print(df_ret)

Here is my fictional charity’s retention rate over nine years to 2025. It has grown steadily and strongly, climbing from 26.7% in 2017 to 42.2% in 2025. By this measure, the charity looks healthy — and it is. Something is clearly working in how they maintain relationships with existing supporters.

|    |   year |   total_donors |   retained_donors |   donors_last_year |   retention_rate |
|---:|-------:|---------------:|------------------:|-------------------:|-----------------:|
| 1 | 2017 | 646 | 132 | 494 | 0.267206 |
| 2 | 2018 | 671 | 204 | 646 | 0.315789 |
| 3 | 2019 | 766 | 227 | 671 | 0.338301 |
| 4 | 2020 | 798 | 236 | 766 | 0.308094 |
| 5 | 2021 | 885 | 288 | 798 | 0.360902 |
| 6 | 2022 | 929 | 331 | 885 | 0.374011 |
| 7 | 2023 | 1030 | 357 | 929 | 0.384284 |
| 8 | 2024 | 1158 | 432 | 1030 | 0.419417 |
| 9 | 2025 | 1261 | 489 | 1158 | 0.42228 |

The total donor count is also growing, from 646 in 2017 to 1,261 in 2025, nearly doubling over the period. This suggests that new donors are being acquired consistently, not just that the same loyal cohort is returning year after year. On the face of it, this charity does not look like a typical leaky bathtub case.

But the retention rate cannot tell us everything. In particular, it cannot tell us whether newly acquired donors are converting into repeat givers at a healthy rate — or whether the charity is quietly losing them after their first gift. For that, we need to look at the second-gift rate.

The First Test of Loyalty

The second-gift rate is calculated using the same approach developed in my previous article, where I worked through the methodology in full. I will not re-examine the script here but reproduce it below for reference.

# 1. Sort
df_sorted = df_opps_filtered.sort_values(['contact_id', 'close_date'])

# 2. Get the first and second gift dates per contact
first_and_second_gifts = df_sorted.groupby('contact_id')['close_date'].agg([
('first_gift_date', 'first'),
('second_gift_date', lambda x: x.iloc[1] if len(x) > 1 else pd.NaT)
])

# 3. Identify first gift year
first_and_second_gifts['first_gift_year'] = first_and_second_gifts['first_gift_date'].dt.year

# 4. Calculate months elapsed
delta = first_and_second_gifts['second_gift_date'] - first_and_second_gifts['first_gift_date']
first_and_second_gifts['months_lapsed'] = delta.dt.days / 30.4375 # Divide by 30.4375 to get the number of months

# 5. Flag "Converted" (Second gift within 12 months)
first_and_second_gifts['is_converted'] = first_and_second_gifts['months_lapsed'] <= 12

# 6. Group by year to see the trend
# I exclude 2025 because those donors haven't all had 12 months to recur yet
second_gift_by_year = first_and_second_gifts[first_and_second_gifts['first_gift_year'] < 2025].groupby('first_gift_year').agg(
total_new_donors=('is_converted', 'count'),
second_gift_conversions=('is_converted', 'sum')
)

# 7. Calculate the conversion rate
second_gift_by_year['conversion_rate'] = (second_gift_by_year['second_gift_conversions'] / second_gift_by_year['total_new_donors']) * 100

print(second_gift_by_year)
|   first_gift_year |   total_new_donors |   second_gift_conversions |   conversion_rate |
|------------------:|-------------------:|--------------------------:|------------------:|
| 2016 | 494 | 154 | 31.1741 |
| 2017 | 514 | 149 | 28.9883 |
| 2018 | 452 | 131 | 28.9823 |
| 2019 | 497 | 149 | 29.9799 |
| 2020 | 491 | 173 | 35.2342 |
| 2021 | 501 | 161 | 32.1357 |
| 2022 | 470 | 151 | 32.1277 |
| 2023 | 527 | 187 | 35.4839 |
| 2024 | 531 | 175 | 32.9567 |

The output shows, for example, that of the donors acquired in 2016, 31.17% made a second gift within 12 months. The most recent acquisition year, 2025, is excluded, as those donors have not yet had a full 12 months in which to return. While the second-gift rates are slightly below the industry benchmark, they are not alarming. New donor acquisition also appears stable across the period.

As noted earlier, however, the second-gift rate only captures one moment in a cohort’s life — that first critical transition. To understand how each acquisition cohort has contributed to the charity’s donor base and revenue over the longer term, I turn to a full cohort analysis.

Under the Hood

Building the cohort table begins with identifying each donor’s cohort year, the year of their first gift. This is already captured in the first_gift_year column constructed earlier. From this, I create cohort_map: a dictionary mapping each donor to their cohort year, which is then joined onto df_opps_filtered_summary, a dataframe of each donor’s total giving per year. Subtracting cohort_year from year produces a year_number column representing how many years have elapsed since each donor’s first gift — the backbone of any cohort analysis.

# Prepare the cohort definitions
cohort_map = first_and_second_gifts['first_gift_year'].to_dict()

# Group by year and contact_id
df_opps_filtered_summary = df_opps_filtered.groupby(['year', 'contact_id']).agg(
total_amount = ('amount', 'sum')
).reset_index()

# Map the cohort year to the main transactions dataframe
df_opps_filtered_summary['cohort_year'] = df_opps_filtered_summary['contact_id'].map(cohort_map)

# Calculate 'year_number' (Years since first gift)
df_opps_filtered_summary['year_number'] = df_opps_filtered_summary['year'] - df_opps_filtered_summary['cohort_year']

I then group df_opps_filtered_summary by cohort_year and year_number to produce cohort_counts, which records the number of donors and total donation amount for each cohort at each stage of their giving history. To take a concrete example: where cohort_year is 2016 and year_number is 0, the row captures the donors who made their very first gift in 2016 and what they contributed in that year; where year_number is 1, it shows how many of that same cohort were still giving in 2017 and their total contribution. Each subsequent year_number tells the next chapter of that cohort’s story.

# Group by cohort and year_number to get counts
cohort_counts = df_opps_filtered_summary.groupby(['cohort_year', 'year_number']).agg(
retained_donors = ('contact_id', 'count'),
total_amount = ('total_amount', 'sum')
).reset_index()

From cohort_counts, I extract each cohort’s size at year_number 0 — the starting headcount before any lapsing has occurred. This becomes original_cohort_size, which serves as the denominator for all subsequent retention rate calculations within that cohort.

# Get initial cohort sizes for the denominator
cohort_sizes = cohort_counts[cohort_counts['year_number'] == 0][['cohort_year', 'retained_donors']]
cohort_sizes = cohort_sizes.rename(columns={'retained_donors': 'original_cohort_size'})

Finally, original_cohort_size is merged back into cohort_counts, and the cohort-level retention rate — retained_donors divided by original_cohort_size — is calculated for each cohort-year combination. The result is df_cohort: a dataframe that makes it possible to track every acquisition cohort’s trajectory across time and to ask how each group’s behavior and contribution has evolved since the year they first gave.

# Merge and calculate rate
df_cohorts = cohort_counts.merge(cohort_sizes, on='cohort_year')
df_cohorts['retention_rate'] = df_cohorts['retained_donors'] / df_cohorts['original_cohort_size']

print(df_cohorts.head(20))
|    |   cohort_year |   year_number |   retained_donors |   total_amount |   original_cohort_size |   retention_rate |
|---:|--------------:|--------------:|------------------:|---------------:|-----------------------:|-----------------:|
| 0 | 2016 | 0 | 494 | 94240 | 494 | 1 |
| 1 | 2016 | 1 | 132 | 38145 | 494 | 0.267206 |
| 2 | 2016 | 2 | 74 | 9800 | 494 | 0.149798 |
| 3 | 2016 | 3 | 53 | 3625 | 494 | 0.107287 |
| 4 | 2016 | 4 | 52 | 4090 | 494 | 0.105263 |
| 5 | 2016 | 5 | 44 | 4175 | 494 | 0.0890688 |
| 6 | 2016 | 6 | 52 | 5715 | 494 | 0.105263 |
| 7 | 2016 | 7 | 51 | 5680 | 494 | 0.103239 |
| 8 | 2016 | 8 | 52 | 4620 | 494 | 0.105263 |
| 9 | 2016 | 9 | 41 | 4295 | 494 | 0.082996 |
| 10 | 2017 | 0 | 514 | 114375 | 514 | 1 |
| 11 | 2017 | 1 | 145 | 60990 | 514 | 0.282101 |
| 12 | 2017 | 2 | 96 | 26725 | 514 | 0.18677 |
| 13 | 2017 | 3 | 55 | 5640 | 514 | 0.107004 |
| 14 | 2017 | 4 | 59 | 8515 | 514 | 0.114786 |
| 15 | 2017 | 5 | 58 | 13395 | 514 | 0.11284 |
| 16 | 2017 | 6 | 43 | 12180 | 514 | 0.0836576 |
| 17 | 2017 | 7 | 43 | 5555 | 514 | 0.0836576 |
| 18 | 2017 | 8 | 56 | 8130 | 514 | 0.108949 |
| 19 | 2018 | 0 | 452 | 148720 | 452 | 1 |

How Did Each Cohort Age?

A heatmap is a natural way to visualize cohort retention, allowing each cohort’s trajectory to be tracked and compared across years in a single view. To build it, I first pivot df_cohort so that cohort_year forms the rows and year_number the columns, with retention_rate as the values.

from matplotlib import pyplot as plt
import seaborn as sns

# Pivot the data
cohort_pivot = df_cohorts.pivot(index='cohort_year', columns='year_number', values='retention_rate')

plt.figure(figsize=(12, 8))
sns.heatmap(cohort_pivot, annot=True, fmt=".0%", cmap="YlGnBu", cbar=False)
plt.title('Donor Retention Cohort Analysis', pad=20)
plt.xlabel('Year Number')
plt.ylabel('Cohort Year')
plt.show()
Press enter or click to view image in full size
Donor retention cohort analysis

Each cell shows the proportion of a cohort’s original donors still giving in that year. Of the 2016 cohort, for instance, 27% were still giving in Year 1 (2017), 15% in Year 2 (2018), and so on.

Get Kay E.’s stories in your inbox

Join Medium for free to get updates from this writer.

Two features of the heatmap are worth noting before drawing conclusions. First, the Year 1 retention rates here do not match the second-gift rates shown earlier. This is expected. Year 1 retention measures the proportion of first-time donors from Year Y-1 who gave at any point during Year Y, while the second-gift rate requires that second gift to occur within 12 months. The definitions are related but not identical. Second, retention rates occasionally tick upward rather than continuing to decline. The 2016 cohort, for example, moves from 9% in Year 5 back up to 11% in Year 6. This reflects donors who lapsed for a year or more before returning, a normal feature of donor behavior rather than an anomaly in the data.

With those caveats in place, the heatmap tells an encouraging story. Year 1 retention has trended upward across cohorts, from 27% for the 2016 group to 34% for 2023 — the 2024 dip notwithstanding, which may simply reflect that cohort’s relative immaturity in the dataset. The steep drop between Year 1 and Year 2 is consistent across all cohorts, and this too is expected. Donor retention typically falls sharply in the early years before stabilising. The pattern here is normal. The direction of travel is positive.

But retention headcounts only tell part of the story. The more consequential question is what these cohorts are contributing financially — and whether the charity’s current revenue is being carried by its earliest and most loyal donors, or whether more recently acquired cohorts are pulling their weight.

Who Is Actually Paying the Bills?

To examine the 2025 revenue mix, I filter df_cohort for rows where cohort_year and year_number sum to 2025 — the gift year for that combination — and calculate each cohort’s contribution as a percentage of total 2025 donations.

# Visualizing the "revenue mix" for 2025
# Filter for the most recent year
df_2025 = df_cohorts[df_cohorts['cohort_year'] + df_cohorts['year_number'] == 2025].copy()

# Calculate % contribution
total_2025_amt = df_2025['total_amount'].sum()
df_2025['pct_of_total'] = (df_2025['total_amount'] / total_2025_amt) * 100

# Plotting the reliance
plt.figure(figsize=(12, 6))
sns.barplot(data=df_2025, x='cohort_year', y='pct_of_total', palette='mako')
plt.title('Reliance Check: Which Cohorts Funded 2025?', pad=20)
plt.ylabel('% of Total 2025 Income')
plt.xlabel('Donor Recruitment Year')
plt.show()
Press enter or click to view image in full size
Which cohorts funded 2025?

The result is, coincidentally and hilariously, a charity quite unlike its typical Australian and New Zealand peers: an acquisition engine with a dramatic recency bias. Roughly 75% of 2025 income came from donors recruited in just the last two years, 2024 and 2025. The 2016–2019 cohorts, meanwhile, contribute almost nothing — each under 2% of total revenue. These long-time supporters may still be loyal, but so few remain that they barely register on the bottom line.

This profile carries real risk. The charity has no depth of long-term donors to fall back on. A poor recruitment year, an economic downturn, or any disruption to new donor acquisition — the kind of shock that COVID delivered across the sector — could cause the budget to collapse almost immediately. Earlier cohorts simply are not being retained at a scale that builds a stable revenue base. The charity is, in effect, running to stay in the same place.

Before drawing firm conclusions, however, one alternative explanation deserves scrutiny. The outsized revenue share of the 2024 and 2025 cohorts might not reflect acquisition volume alone. It could be driven by a small number of major gifts skewing the figures upward. To investigate, I turn to the average and median gift by cohort.

Major Gifts or Quality Acquisition?

The average gift is simply each cohort’s total contribution divided by its donor count — a straightforward calculation, yet one that reveals something unexpected.

# Check if 2024/2025 just have 'bigger' donors or just 'more' donors
df_2025['avg_gift'] = df_2025['total_amount'] / df_2025['retained_donors']
print(df_2025[['cohort_year', 'retained_donors', 'avg_gift']])
|    |   cohort_year |   retained_donors |   avg_gift |
|---:|--------------:|------------------:|-----------:|
| 9 | 2016 | 41 | 104.756 |
| 18 | 2017 | 56 | 145.179 |
| 26 | 2018 | 48 | 179.688 |
| 33 | 2019 | 58 | 106.466 |
| 39 | 2020 | 79 | 354.81 |
| 44 | 2021 | 84 | 238.036 |
| 48 | 2022 | 89 | 250 |
| 51 | 2023 | 129 | 306.86 |
| 53 | 2024 | 154 | 1218.54 |
| 54 | 2025 | 523 | 419.006 |

The averages for 2024 and 2025 are immediately striking. Even more surprising is that the earliest cohorts — 2016 through 2019 — are the charity’s lowest-value donors on a per-person basis.

The starkest comparison is between the 2024 cohort’s average gift of $1,219 and the 2016 cohort’s $105. Despite only 154 donors remaining from 2024, they are giving 11.6 times more per person than the 41 survivors from 2016. Volume aside, the 2024 recruitment appears to have captured meaningfully wealthier or more committed donors.

There is also a clear step change around 2020. Donors recruited before that point average roughly $100-$180, while those recruited from 2020 onwards average $230-$420, setting aside the 2024 figure. Something shifted in this charity’s acquisition approach — whether a greater focus on mid- to high-value giving, better digital targeting, or a different ask strategy, the donors recruited in the last five years are fundamentally more valuable per person than those from earlier years. Since this is a synthetic dataset, I can only speculate about the cause. But the pattern is unambiguous.

Before accepting this as a straightforward “high-quality acquisition” story, however, it is worth asking whether the 2024 average is being propped up by a handful of major gifts rather than reflecting the typical donor’s behavior. If the median gift is much lower than the average, the headline figure is misleading. If the median is also high, the quality-acquisition interpretation holds.

# Check median to see if a handful of donors are skewing the average
df_2025_medians = df_opps_filtered_summary[df_opps_filtered_summary['year'] == 2025].groupby('cohort_year')['total_amount'].median()
print(df_2025_medians)
|   cohort_year |   total_amount |
|--------------:|---------------:|
| 2016 | 45 |
| 2017 | 50 |
| 2018 | 50 |
| 2019 | 82.5 |
| 2020 | 60 |
| 2021 | 75 |
| 2022 | 75 |
| 2023 | 130 |
| 2024 | 195 |
| 2025 | 85 |

The 2024 median gift is $195, against an average of $1,218. The average is more than six times higher. This is primarily a major gift story. A small number of exceptionally generous donors recruited in 2024 are skewing the cohort’s average substantially upward.

That said, the high-quality acquisition story is not entirely wrong. It simply needs to be stated more carefully. A typical 2016 donor gave $45 in 2025, compared with $195 from a typical 2024 donor in the same year. Even setting aside the major gift effect, newer donors are four times more valuable per person than the charity’s longest-standing supporters. The acquisition strategy does appear to have shifted from high-volume, low-dollar recruitment toward fewer but higher-value donors.

This adds a new dimension to the structural risk identified earlier. The charity’s fragility is not just about cohort volume. It is also that its highest-value donors are concentrated among its newest and least retained cohorts. If acquisition falters, the charity loses not just numbers but disproportionate revenue. That tension is worth examining directly.

An Acquisition Engine with No Safety Net

Most Australian charities struggle with the leaky bathtub: long-term donor bases that are ageing and thinning, while new donor acquisition fails to keep pace. My fictional charity has the opposite problem — and it is, in its own way, just as precarious.

This charity is an acquisition engine. It is not sustained by a deep reserve of loyal long-term supporters. Its earliest cohorts, acquired between 2016 and 2018, are small in number and low in individual value, typically giving under $50 a year. What is sustaining the charity is its ability to recruit high-value donors right now. The 2024 intake in particular was transformational: a median gift of $195 and several major donors whose contributions are currently carrying a disproportionate share of revenue. The charity is not living off the past. It is living off its ability to find high-value new supporters in the present.

This profile is consistent with broader shifts in Australian giving behavior. Sector research suggests that younger donors — particularly Gen Z and Millennials — tend to be need-responders rather than committed long-term givers. They give larger amounts to specific, urgent causes rather than signing up for sustained support. The higher gift values observed in the 2024 and 2025 cohorts fit this pattern, and the charity’s median gifts for the 2024 cohort sit well above the national average of around $150, suggesting it is attracting a more motivated or more affluent donor base than is typical.

Therein lies the strategic vulnerability. A charity built on need-responders has no natural loyalty engine. The traditional model — committed donors establishing long-term giving habits and providing a stable revenue floor — is largely absent here. If acquisition slows, or if a disruption like COVID reduces the pool of new high-value donors, there is no deep cohort of long-term supporters to absorb the shock. The charity is, in effect, a high-growth startup with no safety net: extraordinary at finding new supporters but yet to build the infrastructure that turns them into long-term ones.

By inadvertently calibrating gift amounts to increase over time in my synthetic dataset, I appear to have simulated a very specific — and very contemporary — strategic crisis: a charity that has cracked acquisition but not retention. The question of what it would take to change that is worth exploring.

Know Which Problem You Have

My fictional charity and the typical Australian charity are mirror images of each other. One has many newcomers and not enough loyalists; the other has many loyalists and not enough newcomers. Both are strategic challenges in their own right — but that is not the central point of this analysis. The point is that aggregate metrics like retention rates hide cohort composition, and cohort composition is where strategic risk actually lives. A charity director looking at healthy retention figures would have little idea which of these two problems they are facing without doing exactly the kind of analysis walked through here.

The real takeaway for Australian charities isn’t “fix your leaky bathtub” or “build a loyalty engine.” It is “know which problem you have.” Most charities assume they have the leaky bathtub problem because that is what sector benchmarks describe. But this analysis shows that the opposite condition exists and is equally dangerous — and that the prescription differs completely depending on which situation a charity is in. You cannot determine that without cohort analysis.

The natural next analytical question is: what would it take to change the trajectory? One possibility is a stewardship simulation — modelling a 2026 campaign that specifically targets mid-cohort donors from 2020 to 2023 and asking what the revenue composition would look like in five years if second-year retention improved by 10 percentage points. Another is a donor lifetime value (LTV) analysis, asking not just what each cohort is contributing now but what they are worth over a projected giving horizon. A 2024 major donor with a two-year giving history has a very different projected LTV than a 2016 donor who has given for nine years, even if their current gift is much smaller.

Whichever direction the analysis goes next, the broader point stands: Data tells you which problem you have, but only if you ask the right questions of it. Retention rates, second-gift rates, and revenue figures are all useful — but they are summaries, and summaries conceal as much as they reveal. The charities that will navigate the next decade most successfully are unlikely to be the ones with the best acquisition strategies or the most loyal donor bases. They will be the ones that understand, at a cohort level, exactly where their strength lies and where it doesn’t — and act on that knowledge before it shows up in the headline numbers.

You can find the full source code in the Nonprofit Analytics Toolkit Repository on GitHub.

--

--

Kay E.
Kay E.

Written by Kay E.

Solving data challenges via Python & Power BI. Writing on ML & data quality to help nonprofits serve their communities with greater clarity and social impact.

Data And Beyond
Data And Beyond

Published in Data And Beyond

Selected stories around Data Science, Machine Learning, Artificial Intelligence, Programming, and Technology topics. Writing guide: https://medium.com/data-and-beyond/how-to-write-for-data-and-beyond-b83ff0f3813e