lam <- function(PA, PJ, b) PA + PJ * b # adult survival + juvenile survival * fecundity
c(source = lam(0.6, 0.3, 3.0), sink = lam(0.6, 0.3, 0.8))source sink
1.50 0.84
Tidy Ecology
2026-07-31
A basic idea in population ecology is that habitat quality varies, but the consequences of that idea are strange enough that they still catch people out. In a good patch, births outnumber deaths and the population would grow on its own. In a poor patch, deaths outnumber births and the population would dwindle to nothing if left alone. The good patch, a source, exports its surplus; the poor patch, a sink, survives only on those exports. Pulliam 1988 The American Naturalist 132(5):652-661 laid this out, and it changes what a population count is worth.
A local population changes through four flows: Births, Immigration, Deaths, Emigration. Collapse births and deaths into a local finite growth rate, the factor a population multiplies by each year without any movement. For a simple annual life it is adult survival plus juvenile survival times fecundity.
source sink
1.50 0.84
The source has a local growth rate above one, 1.50: without any immigration it would grow, so its births more than replace its deaths and the extra individuals must go somewhere. The sink has a local growth rate below one, 0.84: without immigration it shrinks every year. A sink is not a small population or a rare one; it is a population that cannot replace itself.
Take a sink and follow it two ways: isolated, and receiving the surplus that a source of 100 breeding adults sends out each year.
alone_final subsidised_final
0.074862 312.282432
Isolated, the sink falls from 80 toward zero, reaching 0.1 after forty years: local extinction, exactly as a growth rate below one demands. Fed the source’s surplus, the same sink climbs and settles at 312. The inflow of 50 immigrants a year exactly balances the annual shortfall between births and deaths. Cut the source and the sink is gone; the persistence you see is borrowed.
d <- rbind(data.frame(t = 0:years, N = Na, kind = "isolated sink"),
data.frame(t = 0:years, N = Ns, kind = "sink with source subsidy"))
ggplot(d, aes(t, N, colour = kind)) +
geom_hline(yintercept = n2_eq, colour = col_ref, linetype = "dotted") +
geom_line(linewidth = 1) +
scale_colour_manual(values = c("isolated sink" = col_alone, "sink with source subsidy" = col_sub)) +
labs(x = "Year", y = "Sink population size", colour = NULL,
title = "The sink persists only on the source's surplus")
How large that subsidised population settles depends on how deep a sink it is. A shallow sink, one whose local growth rate is only just below one, loses very little each year, so the same immigration piles up into a large standing population; a severe sink burns through immigrants fast and stays small.
l2g <- seq(0.3, 0.97, 0.01)
de <- data.frame(l2 = l2g, n2 = surplus / (1 - l2g))
ggplot(de, aes(l2, n2)) +
geom_line(colour = col_sub, linewidth = 1) +
geom_hline(yintercept = n1, colour = col_ref, linetype = "dotted") +
annotate("text", x = 0.32, y = n1 + 18, label = "source size", hjust = 0, colour = col_ref, size = 3.4) +
labs(x = "Sink's own local growth rate", y = "Equilibrium sink size",
title = "A shallow sink holds a large subsidised population")
The local growth rate is a statement about self-sufficiency: above one, the habitat can stand alone; below one, it cannot. It is not a statement about size, and this is where intuition fails. A sink can be crowded, holding far more animals than the source that feeds it, because the source keeps pushing its surplus downhill into the sink. It is not a statement about importance either: a sink can be a population trap that drains the source, or a valuable stepping stone, depending on the flows. The next post shows how a sink comes to hold more individuals than its source, which is why counting animals tells you almost nothing about habitat quality, and the checking post shows what you have to measure instead.
Newsletter
Get new tutorials by email
New R and QGIS tutorials for ecologists, straight to your inbox. No spam; unsubscribe anytime.
By subscribing you agree to receive these emails and confirm your address once. See the privacy policy.
---
title: "Source-sink population dynamics"
description: "Source-sink dynamics in R: the BIDE budget, why a sink population persists only on immigration from a source, and what the local growth rate really measures."
date: "2026-07-31 11:00"
categories: [ecology tutorial, R, population ecology, conservation, metapopulations]
image: thumbnail.png
image-alt: "A sink population collapsing to zero when isolated but rising to a stable level when subsidised by immigration from a source."
---
```{r setup}
#| include: false
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE,
dev = "png", dpi = 120, fig.path = "figures/",
fig.width = 7, fig.height = 4.4)
library(ggplot2)
theme_te <- theme_minimal(base_size = 12) +
theme(panel.background = element_rect(fill = "white", colour = NA),
plot.background = element_rect(fill = "white", colour = NA),
panel.grid.minor = element_blank(),
plot.title = element_text(face = "bold", size = 12))
theme_set(theme_te)
col_alone <- "#b5651d"; col_sub <- "#3a6b52"; col_ref <- "#555555"
lam <- function(PA, PJ, b) PA + PJ * b
l1 <- lam(0.6, 0.3, 3.0) # source
l2 <- lam(0.6, 0.3, 0.8) # sink
n1 <- 100
n2_eq <- n1 * (l1 - 1) / (1 - l2)
years <- 40; surplus <- n1 * (l1 - 1)
Na <- numeric(years+1); Ns <- numeric(years+1); Na[1] <- Ns[1] <- 80
for (t in 2:(years+1)) { Na[t] <- Na[t-1]*l2; Ns[t] <- Ns[t-1]*l2 + surplus }
```
A basic idea in population ecology is that habitat quality varies, but the consequences of that idea are strange enough that they still catch people out. In a good patch, births outnumber deaths and the population would grow on its own. In a poor patch, deaths outnumber births and the population would dwindle to nothing if left alone. The good patch, a source, exports its surplus; the poor patch, a sink, survives only on those exports. Pulliam 1988 The American Naturalist 132(5):652-661 laid this out, and it changes what a population count is worth.
## The BIDE budget
A local population changes through four flows: Births, Immigration, Deaths, Emigration. Collapse births and deaths into a local finite growth rate, the factor a population multiplies by each year without any movement. For a simple annual life it is adult survival plus juvenile survival times fecundity.
```{r lambda}
lam <- function(PA, PJ, b) PA + PJ * b # adult survival + juvenile survival * fecundity
c(source = lam(0.6, 0.3, 3.0), sink = lam(0.6, 0.3, 0.8))
```
The source has a local growth rate above one, `r sprintf("%.2f", l1)`: without any immigration it would grow, so its births more than replace its deaths and the extra individuals must go somewhere. The sink has a local growth rate below one, `r sprintf("%.2f", l2)`: without immigration it shrinks every year. A sink is not a small population or a rare one; it is a population that cannot replace itself.
## A sink lives on subsidy
Take a sink and follow it two ways: isolated, and receiving the surplus that a source of `r sprintf("%.0f", n1)` breeding adults sends out each year.
```{r dynamics}
l2 <- lam(0.6, 0.3, 0.8); surplus <- 100 * (lam(0.6, 0.3, 3.0) - 1)
sink_alone <- Reduce(function(N, .) N * l2, 1:40, accumulate = TRUE, 80)
sink_subs <- Reduce(function(N, .) N * l2 + surplus, 1:40, accumulate = TRUE, 80)
c(alone_final = tail(sink_alone, 1), subsidised_final = tail(sink_subs, 1))
```
Isolated, the sink falls from `r sprintf("%.0f", 80)` toward zero, reaching `r sprintf("%.1f", Na[years+1])` after forty years: local extinction, exactly as a growth rate below one demands. Fed the source's surplus, the same sink climbs and settles at `r sprintf("%.0f", n2_eq)`. The inflow of `r sprintf("%.0f", surplus)` immigrants a year exactly balances the annual shortfall between births and deaths. Cut the source and the sink is gone; the persistence you see is borrowed.
```{r fig-dynamics}
#| fig-cap: "A sink population over forty years. Isolated (ochre) it collapses to local extinction; subsidised by a source's surplus (green) it rises to a stable level held up entirely by immigration."
#| fig-alt: "Two trajectories of a sink population; the isolated one declines toward zero, the subsidised one rises and levels off at a stable positive size."
d <- rbind(data.frame(t = 0:years, N = Na, kind = "isolated sink"),
data.frame(t = 0:years, N = Ns, kind = "sink with source subsidy"))
ggplot(d, aes(t, N, colour = kind)) +
geom_hline(yintercept = n2_eq, colour = col_ref, linetype = "dotted") +
geom_line(linewidth = 1) +
scale_colour_manual(values = c("isolated sink" = col_alone, "sink with source subsidy" = col_sub)) +
labs(x = "Year", y = "Sink population size", colour = NULL,
title = "The sink persists only on the source's surplus")
```
How large that subsidised population settles depends on how deep a sink it is. A shallow sink, one whose local growth rate is only just below one, loses very little each year, so the same immigration piles up into a large standing population; a severe sink burns through immigrants fast and stays small.
```{r fig-equilibrium}
#| fig-cap: "Equilibrium sink size against the sink's own local growth rate, for a fixed source surplus. As the sink's growth rate approaches one, the same immigration supports an ever larger population."
#| fig-alt: "A rising curve of equilibrium sink size against the sink's local growth rate, climbing steeply as the growth rate approaches one."
l2g <- seq(0.3, 0.97, 0.01)
de <- data.frame(l2 = l2g, n2 = surplus / (1 - l2g))
ggplot(de, aes(l2, n2)) +
geom_line(colour = col_sub, linewidth = 1) +
geom_hline(yintercept = n1, colour = col_ref, linetype = "dotted") +
annotate("text", x = 0.32, y = n1 + 18, label = "source size", hjust = 0, colour = col_ref, size = 3.4) +
labs(x = "Sink's own local growth rate", y = "Equilibrium sink size",
title = "A shallow sink holds a large subsidised population")
```
## What the local growth rate does and does not tell you
The local growth rate is a statement about self-sufficiency: above one, the habitat can stand alone; below one, it cannot. It is not a statement about size, and this is where intuition fails. A sink can be crowded, holding far more animals than the source that feeds it, because the source keeps pushing its surplus downhill into the sink. It is not a statement about importance either: a sink can be a population trap that drains the source, or a valuable stepping stone, depending on the flows. The next post shows how a sink comes to hold more individuals than its source, which is why counting animals tells you almost nothing about habitat quality, and the [checking post](../checking-a-source-sink-analysis/) shows what you have to measure instead.
## Related tutorials
- [Sinks can hold high density](../sinks-can-hold-high-density/)
- [Ecological traps and pseudo-sinks](../ecological-traps-and-pseudo-sinks/)
- [Checking a source-sink analysis](../checking-a-source-sink-analysis/)
- [The Levins metapopulation model](../the-levins-metapopulation-model/)
## References
- Pulliam HR 1988. The American Naturalist 132(5):652-661 (10.1086/284880).