Functional responses in R

ecology tutorial
R
foraging
species interactions
predation
Holling’s functional responses in R: the type I, II and III curves, fitting the disc equation with nls, and why handling time sets a hard ceiling on intake.
Author

Tidy Ecology

Published

2026-07-29

How fast a predator eats depends on how much prey there is, but not in a straight line. Double the prey and a predator does not eat twice as much, because catching prey takes time and eating them takes more. The curve relating prey density to intake rate is the functional response, and Holling 1959 The Canadian Entomologist 91(7):385-398 sorted it into three shapes that still organise the subject.

Three shapes

Type I is the straight line: intake rises in proportion to prey density, then stops dead at a ceiling. It fits filter feeders and little else, because it assumes handling prey costs no time until, abruptly, it does.

Type II is the workhorse. Intake rises steeply at low density and bends over to a plateau, because every prey item caught must be handled, and handling time eats into searching time. Holling’s disc equation captures it with two parameters, an attack rate a and a handling time h:

\[N_e = \frac{a\,N\,T}{1 + a\,h\,N}.\]

Type III is S-shaped: intake accelerates at low prey density before bending over. The acceleration can come from predators learning to find a prey type only once it is common, or switching to whatever is abundant. That low-density dip matters out of proportion to its size, as the last section explains.

holl2 <- function(N, a, h, T = 1) a*N*T / (1 + a*h*N)
holl3 <- function(N, a, h, T = 1) a*N^2*T / (1 + a*h*N^2)
Ngrid <- seq(0, 130, 1)
c(type2_at_40 = holl2(40, 0.9, 0.045), type3_at_40 = holl3(40, 0.06, 0.045))
type2_at_40 type3_at_40 
   13.74046    18.04511 
capk <- 1/0.045
d <- rbind(
  data.frame(N = Ngrid, y = pmin(0.9 * Ngrid, capk), type = "I linear"),
  data.frame(N = Ngrid, y = holl2(Ngrid, 0.9, 0.045),  type = "II hyperbolic"),
  data.frame(N = Ngrid, y = holl3(Ngrid, 0.06, 0.045), type = "III sigmoid"))
ggplot(d, aes(N, y, colour = type)) +
  geom_hline(yintercept = capk, colour = col_ref, linetype = "dotted") +
  geom_line(linewidth = 1) +
  annotate("text", x = 5, y = capk + 0.7, label = "ceiling = 1/h", hjust = 0, colour = col_ref, size = 3.4) +
  scale_colour_manual(values = c("I linear" = col1, "II hyperbolic" = col2, "III sigmoid" = col3)) +
  labs(x = "Prey density", y = "Prey eaten per predator", colour = NULL,
       title = "Holling's three functional responses")
Three curves of intake against prey density: a straight grey line to a plateau, a green hyperbola bending to a plateau, and an ochre S-shaped curve reaching the same plateau.
Figure 1: The three functional responses. Type I (grey) is linear to a ceiling; type II (green) saturates smoothly; type III (ochre) is S-shaped, with a low-density region where intake accelerates.

Fitting a type II curve

Given feeding-trial counts, the disc equation fits straight away with nls. Over a total time T of one, the number eaten per predator is a N / (1 + a h N).

set.seed(395)
N  <- rep(c(2, 5, 10, 20, 40, 60, 80, 120), each = 5)
Ne <- rpois(length(N), 0.9 * N / (1 + 0.9 * 0.045 * N))     # type II truth
fit2 <- nls(Ne ~ aa * N / (1 + aa * hh * N), start = list(aa = 0.5, hh = 0.05))
round(coef(fit2), 4)
    aa     hh 
1.1873 0.0489 

The fit returns an attack rate of 1.19 and a handling time of 0.0489. That handling time is the more useful number, because it sets a hard ceiling. As prey density climbs, intake cannot exceed 20.4 prey per unit time, the reciprocal of the handling time, no matter how much prey there is. At a density of 120 the predator is already taking 17.9, which is 87 per cent of that ceiling.

pf <- data.frame(N = Ngrid, y = holl2(Ngrid, a_hat, h_hat))
ggplot(data.frame(N, Ne), aes(N, Ne)) +
  geom_hline(yintercept = ceiling_rate, colour = col_ref, linetype = "dotted") +
  geom_point(colour = col_ref, size = 2, alpha = 0.6) +
  geom_line(data = pf, aes(N, y), colour = col2, linewidth = 1) +
  annotate("text", x = 3, y = ceiling_rate + 0.7, label = "1/h ceiling", hjust = 0, colour = col_ref, size = 3.4) +
  labs(x = "Prey density", y = "Prey eaten per predator", title = "Handling time caps the intake rate")
Scatter of prey eaten against prey density with a saturating fitted curve levelling off at a dotted ceiling line.
Figure 2: A type II fit to feeding-trial counts. Intake saturates at one over the handling time (dotted), the maximum a predator can process however much prey is on offer.

Why the shape matters for stability

The difference between type II and type III is not cosmetic. Divide intake by prey density to get the per-capita predation risk a prey individual faces. Under a type II response that risk is highest when prey are rare and falls as prey become common: the predator is easily satiated, so being scarce is dangerous. That is destabilising; it can drive prey to local extinction. Under a type III response the risk is low when prey are rare, because the predator ignores or cannot find a scarce prey type, giving prey a low-density refuge that can stabilise the interaction (see stability and complexity in food webs).

So the interesting part of a functional response is exactly the part that is hardest to measure: the behaviour at low prey density. Two curves that look identical at the densities you sampled can imply opposite dynamics. The checking post returns to this, along with the trap that prey deplete while you are measuring. The next two posts leave the predator’s plate and ask how a forager should spend its time: how long to stay in a patch, and which prey to bother with at all.

References

  • Holling CS 1959. The Canadian Entomologist 91(7):385-398 (10.4039/Ent91385-7).
  • Pyke GH 1984. Annual Review of Ecology and Systematics 15(1):523-575 (10.1146/annurev.es.15.110184.002515).
  • Rogers D 1972. The Journal of Animal Ecology 41(2):369-383 (10.2307/3474).

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.