Elasticity Analysis for Songbird Population Models
Author
Brian
Published
March 6, 2026
1 Overview
This document walks through an elasticity analysis for a songbird population. Elasticity analysis is a matrix population modeling tool that tells us which vital rates have the greatest proportional influence on population growth rate. In applied contexts this information may inform management priorities, but the analysis itself describes ecological leverage within the life cycle.
This tutorial follows a straightforward workflow:
Define the vital rates — identify the survival and reproduction rates that drive population dynamics.
Build the projection matrix — place those rates into a matrix that summarizes the life cycle.
Run the eigenanalysis — calculate the quantities that describe long-term population behavior.
Interpret \(\lambda\), stable stage distribution, and reproductive value — understand population growth, long-term stage structure, and stage importance.
Calculate sensitivity and elasticity — ask how much population growth would change if vital rates changed.
Conduct a perturbation analysis — explore how population growth responds across a broader range of values.
Draw biological conclusions — identify which parts of the life cycle matter most for population growth.
The three focal vital rates in this analysis are:
Adult survival — the probability that an adult bird alive in year \(t\) is also alive in year \(t + 1\)
Nest success — the probability that a given nesting attempt produces at least one fledgling
Post-fledgling survival — the probability that a bird survives from fledging all the way to its first breeding season (i.e., from fledgling to age 1)
2 The Projection Matrix
2.1 What is a matrix population model?
A matrix population model (also called a Leslie matrix or stage-based matrix model) is a mathematical framework that projects how many individuals will be in each age or stage class in the next time step, given the current number of individuals at each stage.
In this model, we track two stage classes:
Juveniles — birds hatched in the current year that have not yet reached their first breeding season
Adults — birds that are at least one year old and capable of breeding
We use a theoretical post-breeding census, meaning we count individuals after the breeding season each year. This is standard for many songbird studies because it lets us include new fledglings in the count immediately.
The model advances in annual time steps, so every quantity below represents one full year.
2.2 Matrix structure
The projection matrix\(\mathbf{A}\) is a 2×2 table where each cell describes a demographic transition or contribution between stage classes:
Juveniles cannot yet reproduce — they contribute no new fledglings
[1, 2]
\(F\)
Fecundity — the per-capita contribution of adults to new juveniles
[2, 1]
\(\phi_j\)
Post-fledgling survival — probability a juvenile survives to become an adult
[2, 2]
\(\phi_a\)
Adult survival — probability an adult survives to breed again
Columns represent the current stage and rows represent the future stage, so each column describes what happens to individuals in that stage over one year. Here is how we can visualize this matrix:
Note: There is no [1, 1] in this model because juveniles cannot reproduce!
Take-home message
The matrix is just a compact mathematical way of describing who survives and who produces new young.
Fecundity \(F\) is the product of several biological rates that together determine how many new fledglings each adult female contributes to the population per year:
\[
F = B \times CS \times SR \times NS
\]
where:
Symbol
Parameter
Default value
\(B\)
Breeding attempts per season
1.8
\(CS\)
Mean clutch size (eggs per attempt)
3.5
\(SR\)
Sex ratio — proportion of offspring that are female
0.5
\(NS\)
Nest success — probability a nest produces ≥ 1 fledgling
This means each adult female is expected to contribute about 1.1 female fledglings per year in this simplified model.
Why multiply?
Each factor represents one component of annual reproductive output, and together they combine multiplicatively to determine expected fecundity. For example, if a female attempts 1.8 nests per season and each nest has a 35% chance of success, then the expected number of successful nests is 0.63. Expected annual production of female fledglings then depends further on clutch size and the proportion of offspring that are female.
If a female attempts more nests, lays more eggs per attempt, and a greater fraction of nests succeed, her expected annual contribution of female offspring increases through the product of those components.
3 Building and Solving the Matrix
3.1 Constructing the projection matrix
We will start by creating a custom function that we can use to construct a projection matrix:
I know. The goal here is not to master linear algebra vocabulary (try not to fixate on it!), but to understand what these outputs tell us about population behavior.
3.2.1 What is an eigenvalue?
To understand how the population changes over time, we perform an eigenanalysis — a mathematical procedure that extracts special numbers (eigenvalues) and vectors (eigenvectors) that describe the long-term behavior of the matrix.
Imagine using these vital rates to project the population forward many years. At first, the proportion of juveniles and adults may fluctuate depending on the starting population. But eventually the population settles into a stable mix of stages. This is called the stable stage distribution. Once that happens, the entire population simply grows or declines by the same factor every year. That factor is the population growth rate lambda\(\lambda\) and is defined mathematically as the dominant eigenvalue (i.e., the most biologically meaningful eigenvalue).
3.2.2 Population growth rate (\(\lambda\))
Lambda (\(\lambda\)) is the long-run annual population growth rate after the stage structure stabilizes. It tells you the multiplicative factor by which the population changes each year once it reaches a stable stage distribution.
Value
Interpretation
\(\lambda > 1\)
Population is growing
\(\lambda = 1\)
Population is stable (replacement-level reproduction)
\(\lambda < 1\)
Population is declining
For example, \(\lambda = 0.95\) means the population declines by 5% per year.
3.2.3 What is an eigenvector?
An eigenvector is a special direction in mathematical space that does not rotate when you apply the matrix — it only scales. In demographic terms:
The right eigenvector (\(\mathbf{w}\)) gives the stable stage distribution: the proportion of individuals in each stage class that the population converges to over time regardless of its starting composition.
The left eigenvector (\(\mathbf{v}\)) gives the reproductive values: the relative contribution of an individual in a given stage class to all future population growth.
Note
Reproductive value is not the same as current reproduction. It reflects how much future population growth an individual in that stage is expected to generate on average.
Higher reproductive value means that individuals in that stage contribute more strongly to future population growth on average. Adults generally have higher reproductive value than juveniles because they have already survived the risky early-life period and are immediately available to breed.
We can break this into four steps:
Conduct an eigenanalysis of the projection matrix
Determine \(\lambda\) based on the maximum eigenvalue
Determine the stable stage distribution (right eigenvector)
Determine the reproductive values (left eigenvector)
Here we go!
Conduct an eigenanalysis of the projection matrix:
# Get eigenvalues and eigenvectors of the projection matrix:ev <-eigen(projection_mat)
Determine \(\lambda\) based on the maximum eigenvalue:
# Determine the id with the maximum eigenvalue:lambda_idx <- ev$values %>%# `Re()` is used to ensure real values are returned (because `eigen()` can # sometimes return complex numbers):Re() %>%# Get the index at the maximum eigenvalue:which.max()# Calculate lambda (population growth rate):lambda <- ev$values[lambda_idx] %>%Re()
Determine the stable stage distribution (right eigenvector; \(\mathbf{w}\)).
Note: This sums to 1 (so it reads as proportions).
# Right eigenvector: stable stage distribution:w <-# Select the eigenvector associated with lambda: ev$vectors[, lambda_idx] %>%Re() %>%# Normalize the vector to sum to 1: {. /sum(.)}
Determine the reproductive values (left eigenvector; \(\mathbf{v}\)).
This uses a standard scaling convention (\(\mathbf{v} \cdot \mathbf{w} = 1\)) that ensures sensitivity and elasticity formulas work correctly. This is known as a dot product – a dot product is the sum of pairwise products across two vectors, written as \(\sum_i x_i y_i\).
# Left eigenvector: reproductive values:ev_left <- projection_mat %>%# Transpose matrix (flip the rows and columns):t() %>%# Compute eigenvalues and eigenvectors:eigen()v <- ev_left$vectors[, lambda_idx] %>%Re() %>% {. /as.numeric(. %*% w)}
Key outputs from eigenanalysis of the projection matrix.
Quantity
Value
Growth rate
$$\lambda$$
0.868
$$log(\lambda) = r$$
-0.142
Stable stage distribution (w)
Stable stage: Juvenile
0.560
Stable stage: Adult
0.440
Reproductive values (v)
Reproductive value: Juvenile
0.479
Reproductive value: Adult
1.662
Interpreting this output: A \(\lambda\) of 0.868 means the modeled population is declining. Under the stable stage distribution, roughly 56% of individuals are juveniles and 44% are adults. Adults have a higher reproductive value than juveniles, which makes biological sense: an adult has already cleared the high-mortality post-fledgling period and is positioned to breed immediately.
Take-home message
Lambda tells us whether the population grows or declines, while the eigenvectors describe the long-term stage structure and stage importance.
4 Sensitivity and Elasticity
Warning, formulas ahead!
I know that formulas can be scary. On a first read, it is fine to focus on the biological meaning and the code comments, then come back to the notation after the big picture makes sense.
4.1 Sensitivity
The formula below answers the question: if we modify a matrix element, or the vital rate that enters it, by a small absolute amount, how much would \(\lambda\) change?
This is known as Sensitivity – formally, the sensitivity of \(\lambda\) to matrix element \(a_{ij}\) is:
This is a product of reproductive values (how strongly individuals in each stage contribute to future population growth) and stable stage proportion (how common that stage is), normalized so the values are comparable. A high sensitivity means that even a small absolute nudge to that matrix element would substantially change \(\lambda\). Sensitivity is high when a stage is both common in the stable population and important for future growth.
In code, this formula is implemented by taking the outer product of reproductive values and stable stage proportions, then dividing by their dot product for normalization. In R, this is expressed as (v %o% w) / as.numeric(v %*% w) where:
v %o% w: The product of every possible combination of v and w (outer product of vectors). Note: An outer product of vectors is a matrix representing all pairwise products across two input vectors.
as.numeric(v %*% w): The dot product of v and w, returned as a single numeric value for normalization.
Together, these two steps recreate the sensitivity formula: the outer product gives the numerator, and the dot product gives the denominator used for scaling.
Vital rates are measured on very different scales — a survival probability ranges from 0 to 1, while fecundity can easily exceed 2 or 3. Comparing raw sensitivities across vital rates that operate on different scales is misleading, like comparing miles per hour to kilometers per hour without converting. That is why we use elasticity instead.
4.2 Elasticity
The formula below answers the question: if a vital rate changes by 1%, how much would \(\lambda\) change in percentage terms?
This is known as Elasticity, a proportional (scale-free) version of sensitivity (Note: Elasticities sum to 1) that is formally described as:
Because elasticities are dimensionless (proportion in, proportion out), they are directly comparable across vital rates. A key property: the elasticities of all matrix elements sum to exactly 1.0, which means they divide the total proportional influence on \(\lambda\) among the matrix elements.
Common confusion
Sensitivity and elasticity are related, but they are not the same thing.
Sensitivity uses absolute changes
Elasticity uses proportional changes
For example:
Suppose a vital rate changes from 0.50 to 0.51. That is an absolute change of 0.01, but a proportional change of 2%. Sensitivity uses the 0.01 framing and elasticity uses the 2% framing.
That is why elasticity is usually better for comparing very different kinds of vital rates.
Also note: A vital rate can have a high elasticity even if its current value is not large. Elasticity is about influence on lambda, not about which rate has the biggest numeric value.
4.2.1 Vital-rate elasticities via the chain rule
Nest success (\(NS\)) enters the matrix through fecundity (\(F = B\times CS \times SR \times NS\)). Because all four components multiply together into a single matrix element, the elasticity of \(\lambda\) to each component individually equals the elasticity of \(\lambda\) to the whole fecundity element. This follows from the chain rule of calculus, but the important idea is simpler – because fecundity is built by multiplying several components together, a proportional change in any one component creates the same proportional change in fecundity.
The chain rule
The chain rule of calculus states that the proportional sensitivity of a product to any one of its multiplicative components is the same, regardless of that component’s scale.
Code
# First, calculate the sensitivity matrix from the quantities that we already# estimate above:{(v %o% w) /as.numeric(v %*% w)} %>%list(# Then rescale the sensitivity matrix to generate the elasticity matrix: (projection_mat / lambda) * . ) %>%set_names("sensitivity_matrix", "elasticity_matrix") %>%# Add column and row names:map( \(mat) {dimnames(mat) <-dimnames(projection_mat) mat } ) %>%list2env(.GlobalEnv)# Extract values from the elasticity matrix:elasticity_fecundity <- elasticity_matrix[1, 2]elasticity_phi_j <- elasticity_matrix[2, 1]elasticity_phi_a <- elasticity_matrix[2, 2]# Put them together:v_rate_elasticities <-tibble(vital_rate =c("Nest success","Post-fledgling survival","Adult survival" ),parameter =c("nest_success","phi_j","phi_a" ),value =c( v_rate$nest_success, v_rate$phi_j, v_rate$phi_a ),elasticity =c( elasticity_fecundity, elasticity_phi_j, elasticity_phi_a ),sensitivity =c( lambda * elasticity_fecundity / v_rate$nest_success, lambda * elasticity_phi_j / v_rate$phi_j, lambda * elasticity_phi_a / v_rate$phi_a ) )
Vital-rate elasticities ranked by magnitude.
Rank
Vital rate
Parameter
Current value
Sensitivity
Elasticity
% of total
1
Adult survival
phi_a
0.55
0.7320
0.464
46.4%
2
Nest success
nest_success
0.35
0.6644
0.268
26.8%
3
Post-fledgling survival
phi_j
0.25
0.9301
0.268
26.8%
Key idea
We want to know which biological rate matters most for population growth.
If we slightly improve a vital rate:
Sensitivity tells us how much \(\lambda\) changes in absolute terms
Elasticity tells us how much \(\lambda\) changes in proportional terms
Elasticity is usually what we care about because it lets us compare very different kinds of rates (survival vs reproduction). For example:
If adult survival increases from 0.55 → 0.56 that is a +0.01 absolute change.
Sensitivity asks: “How much does \(\lambda\) change if survival increases by 0.01?”
Elasticity asks: “How much does \(\lambda\) change if survival increases by 1%?”
Because elasticity uses percentages, we can compare it fairly to fecundity or nest success.
Take-home message
Elasticity is usually more useful than sensitivity because it lets us compare very different vital rates on the same proportional scale.
5 Perturbation Analysis
Elasticity values describe the local slope of \(\lambda\) at the current vital rate estimates. But what does \(\lambda\) look like across the full plausible range of each vital rate? Perturbation analysis answers this by systematically varying one vital rate at a time while holding the others fixed at their baseline values, then recalculating \(\lambda\) at each step.
We’ll start by creating a function for calculating lambda from a population matrix:
get_lambda <-function(mat) { ev <-eigen(mat)max(Re(ev$values) ) }
Next, we’ll write a function that will get lambda across all potential values of a given vital rate:
Figure 1: Elasticities of lambda to each focal vital rate. Taller bars indicate greater proportional influence on population growth rate.
How to read this chart: Each bar represents one focal vital rate. The height of the bar is the elasticity — the proportional change in \(\lambda\) you would expect from a 1% proportional change in that vital rate. Because elasticities sum to 1, the bars can be interpreted like slices of a pie: a bar at 0.55 means that vital rate accounts for 55% of the total proportional influence represented in this model.
The vital rate with the tallest bar is the one that, proportionally speaking, exerts the strongest proportional influence on population growth within the modeled life cycle.
How to interpret this figure in practice
If the elasticity of adult survival is 0.46, this means that a 1% increase in adult survival would increase λ by approximately 0.46%.
Likewise, if the elasticity of nest success is 0.27, a 1% increase in nest success would increase \(\lambda\) by about 0.27%.
So a proportional change in adult survival would have about 1.73 times the effect on population growth as the same proportional change in nest success.
6.2 Perturbation curves
As you interpret the curves below, ask yourself three questions:
Which vital rate has the steepest curve near the baseline point?
Which curve crosses \(\lambda = 1\) at the lowest value?
Figure 2: Lambda as a function of each vital rate varied across its full plausible range. Other vital rates are held at baseline values. Open circles mark the current estimates. The dashed line marks lambda = 1 (stable population).
How to read these curves: Each panel shows a single vital rate on the x-axis and the resulting \(\lambda\) on the y-axis. The open circle marks the current (baseline) estimate. The dashed horizontal line at \(\lambda = 1\) marks the threshold between growth and decline.
Why this figure is useful: Elasticity tells us what happens for very small changes near the current estimate. The perturbation curves show what happens across the entire plausible range of the vital rate. This helps answer questions like: How much would adult survival need to increase before the population shifts from decline to growth?
Several things to look for:
Steepness: A steep curve means \(\lambda\) changes rapidly as the vital rate changes — consistent with high elasticity. A shallow curve means \(\lambda\) is relatively insensitive to that rate across its range.
Crossing the dashed line: Points above the line represent population growth and points below the line represent decline. If the curve crosses \(\lambda = 1\), you can read off the critical value of that vital rate needed to prevent decline. This is especially useful for identifying ecological thresholds where population growth shifts from declining to increasing.
Non-linearity: Perturbation curves are sometimes curved (non-linear), which means the elasticity (which is a local slope) does not perfectly describe behavior far from the baseline. If the baseline point sits in a particularly steep or flat region of the curve, interpret elasticities cautiously.
6.3 Summary table
Elasticity summary. Model lambda = 0.8677 (r = -0.1420).
Rank
Vital rate
Current value
Elasticity
Sensitivity
% of total
1
Adult survival
0.55
0.464
0.7320
46.4%
2
Nest success
0.35
0.268
0.6644
26.8%
3
Post-fledgling survival
0.25
0.268
0.9301
26.8%
How to read this table:
Current value — the baseline vital rate estimate used to build the matrix.
Elasticity — the proportional change in \(\lambda\) per proportional unit change in that vital rate. Higher = more leverage.
Sensitivity — the absolute change in \(\lambda\) per unit change in the vital rate (not scale-corrected). Compare these cautiously across vital rates measured on different scales.
% of total — each vital rate’s share of the total elasticity. These percentages sum to 100%.
How to interpret this table biologically
This table identifies which demographic process exerts the greatest proportional influence on \(\lambda\) under the assumptions of the model. The top-ranked vital rate has the greatest leverage on population growth.
In this model, we can see that Adult survival has the greatest proportional influence on \(\lambda\).
7 Extension: Required nest success for population stability
A useful extension of our perturbation analysis is to solve directly for the value of a vital rate that would place the population exactly at replacement.
For example, we can use it to address the question ‘Given fixed adult and post-fledgling survival, what value of nest success would bring the population to exactly \(\lambda = 1\)?’ This can be solved analytically from the characteristic equation of the 2×2 matrix. Setting \(\lambda = 1\) and rearranging for \(NS\):
Under the current vital rates, nest success would need to reach 0.571 — a change of 0.221 from its baseline value of 0.35 — for the population to achieve replacement-level growth. This represents an increase relative to the current estimate.
Why use the analytic solution here?
For a 2×2 matrix, setting \(\lambda = 1\) in the characteristic equation reduces to a linear equation in \(NS\), so the solution is exact. No iteration required. This approach works whenever \(NS\) enters the matrix through a single multiplicative fecundity term, as it does here.
In this tutorial, we built a simple two-stage population model, used eigenanalysis to describe long-term population behavior, and applied sensitivity and elasticity analysis to identify which demographic processes matter most for population growth. The key idea is that matrix models let us connect biological rates such as survival and nest success to overall population dynamics in a rigorous and interpretable way.
8 Glossary
Term
Definition
Adult survival (\(\phi_a\))
Probability a breeding adult survives from one breeding season to the next
Chain rule
A calculus rule that describes how to differentiate a composite function; here it explains why each multiplicative component of fecundity shares the same elasticity
Dot product
Sum of the pairwise products across two vectors, written as \(\sum_i x_i y_i\) and expressed in R as as.numeric(x %*% y)
Elasticity
The proportional change in \(\lambda\) resulting from a proportional change in a vital rate; scale-free and sums to 1 across all matrix elements
Eigenvalue
A scalar (number) associated with a matrix that describes how much a special vector is stretched or compressed by the matrix; in demography, the dominant eigenvalue is \(\lambda\)
Eigenvector
A vector whose direction is unchanged by the matrix; in demography, the right eigenvector gives the stable stage distribution and the left eigenvector gives reproductive values
Fecundity (\(F\))
Per-capita contribution of adults to the juvenile stage class; the product of breeding attempts, clutch size, sex ratio, and nest success
Lambda (\(\lambda\))
Asymptotic annual population growth rate; the factor by which population size changes per year once stage proportions stabilize
log(\(\lambda\)) = \(r\)
The continuous-time intrinsic rate of increase; equivalent to lambda on a log scale
Matrix model
A mathematical model that projects population dynamics by multiplying a matrix of vital rates by a vector of stage-specific abundances
Nest success
Probability a nest produces ≥ 1 fledgling
Outer product of vectors
The product of every possible pair of elements from two input vectors, expressed in R as x %o% y
Perturbation analysis
A systematic examination of how \(\lambda\) responds to changes in a vital rate across its full plausible range
Post-breeding census
A census taken after the breeding season, so newly fledged young are included in the count
Post-fledgling survival (\(\phi_j\))
Probability a fledgling survives from leaving the nest through its first winter to become a breeding adult
Reproductive value
The relative expected contribution of an individual in a given stage class to future population growth
Stable stage distribution
The equilibrium proportion of individuals in each stage class; the distribution the population converges to regardless of its starting composition
Sensitivity
The absolute change in \(\lambda\) resulting from a small absolute change in a vital rate or matrix element