Temporary Emigration

Author

Deon Roos

Published

July 24, 2026

Another ambiguous zero

By now you’re hopefully sick to death of ambiguous zeros. A non-detection might mean the animal’s dead, or it might mean it’s alive and you just missed it. CJS and the robust design both wrestle with that by modelling survival and detection as two separate things.

But there’s one last flavour of ambiguous zero I’ve been quietly dodging, and it’s time to stop dodging it.

What if an animal is alive, hasn’t permanently upped and left, but just isn’t in your study area during one particular primary period? It’s popped out for a bit. Maybe a migratory bird has flown south for the winter and will be back in spring. Maybe a marine mammal has drifted offshore to feed and will show up again next season. Maybe a male deer has shoved its range sideways during the rut.

None of these animals are dead. None of them have gone for good. But for the primary period when they’re absent, you can’t detect them, and it doesn’t matter how good your sampling is. Their \(\tilde{p}_t\) is effectively zero, not because your detection is rubbish, but because the animal simply isn’t there to be caught.

Ignore this, assume every animal is always around to be captured in every primary period, and your detection probability estimate gets dragged downward. You end up blaming poor detection for an absence that was really just the animal being somewhere else. And once detection is biased, it drags \(N_t\) and \(\phi\) off with it.

This is temporary emigration, and the robust design handles it with two parameters, \(\gamma'\) and \(\gamma''\) (gamma prime and gamma double prime).

Lily, this is not a hypothetical for you: it is the central feature of your study system. Willow warblers are long-distance migrants. Every autumn they leave the UK for sub-Saharan Africa and every spring they return. During winter, your study area contains zero willow warblers not because you have failed to detect them, but because they are genuinely absent. The model needs to distinguish between “the bird is here but I didn’t detect it” and “the bird is simply not here right now.” That is exactly what \(\gamma'\) and \(\gamma''\) do.

In practice, this means you should expect very high \(\gamma'\) during winter (birds that have left are overwhelmingly likely to stay away) and near-zero \(\gamma''\) during the breeding season (birds on territory are very unlikely to vanish mid-season). Those biological expectations can, and should, inform how you structure your model.

The availability problem

Before we get stuck into the parameters, it’s worth being clear about what “availability” even means here.

In the basic robust design we’ve built so far, we quietly assumed every animal in the population is available for capture in every primary period. Available just means physically in the study area, and so catchable in principle. Under that assumption, the one and only reason an animal goes unseen across a primary period is imperfect detection, \(1 - \tilde{p}_t\).

Temporary emigration smashes that assumption. Suddenly there are two reasons an animal might go unseen across a whole primary period:

  • It was there but you didn’t catch it: probability \((1 - \tilde{p}_t)\)

  • It wasn’t there at all: it had temporarily cleared off out of the study area

Both of those give you the exact same thing in your data. A row of zeros across every secondary occasion in that period. Sound familiar?

Yep. It’s another bloody ambiguous zero, this time living at the between-period level instead of within a period. And same as always, we need a model that keeps the two explanations apart instead of quietly lumping them into one.

Introducing \(\gamma'\) and \(\gamma''\)

The robust design deals with temporary emigration by handing each animal an extra state to be in. At any primary period an animal is either available (in the study area) or unavailable (temporarily off somewhere else). The two gamma parameters are the probabilities of moving between those two states.

Following RMark’s convention:

\(\gamma''_t\) is the probability an animal is unavailable at primary period \(t\), given it was available at period \(t-1\). In plain terms, the probability of leaving the study area.

\(\gamma'_t\) is the probability an animal is unavailable at primary period \(t\), given it was also unavailable at period \(t-1\). In plain terms, the probability of staying away once it’s already gone.

So \(\gamma''\) is about leaving, and \(\gamma'\) is about not coming back yet.

Code
library(ggplot2)
library(dplyr)

states <- data.frame(
  x = c(1, 3),
  y = c(1, 1),
  label = c("Available", "Unavailable")
)

ggplot() +
  geom_point(data = states, aes(x = x, y = y),
             size = 30, colour = c("#d19527", "#FF5733"), alpha = 0.3) +
  geom_text(data = states, aes(x = x, y = y, label = label),
            fontface = "bold", size = 4.5) +
  geom_curve(aes(x = 1.35, xend = 2.65, y = 1.08, yend = 1.08),
             arrow = arrow(length = unit(0.3, "cm")),
             curvature = -0.3, colour = "grey30", linewidth = 0.8) +
  geom_curve(aes(x = 2.65, xend = 1.35, y = 0.92, yend = 0.92),
             arrow = arrow(length = unit(0.3, "cm")),
             curvature = -0.3, colour = "grey30", linewidth = 0.8) +
  geom_curve(aes(x = 2.6, xend = 3.4, y = 1.15, yend = 1.15),
             arrow = arrow(length = unit(0.3, "cm")),
             curvature = -0.8, colour = "#FF5733", linewidth = 0.8) +
  geom_curve(aes(x = 0.6, xend = 1.4, y = 1.15, yend = 1.15),
             arrow = arrow(length = unit(0.3, "cm")),
             curvature = -0.8, colour = "#d19527", linewidth = 0.8) +
  annotate("text", x = 2, y = 1.28, label = "γ''", size = 5, colour = "grey30") +
  annotate("text", x = 2, y = 0.72, label = "1 - γ'", size = 5, colour = "grey30") +
  annotate("text", x = 3.5, y = 1.25, label = "γ'", size = 5, colour = "#FF5733") +
  annotate("text", x = 0.5, y = 1.25, label = "1 - γ''", size = 5, colour = "#d19527") +
  scale_x_continuous(limits = c(0, 4)) +
  scale_y_continuous(limits = c(0.5, 1.5)) +
  labs(title = "Temporary emigration state transitions",
       subtitle = "Between consecutive primary periods") +
  theme_void_dark_site() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5),
        plot.subtitle = element_text(hjust = 0.5, colour = "#9a927b"))

The thing worth clocking here is that two gamma parameters buy the model a bit of memory. Whether an animal is around at time \(t\) is allowed to lean on whether it was around at time \(t-1\). That’s what makes it a Markov process, which is just a posh way of saying the next state depends only on where you are right now, not on the whole history of how you got there.

Random vs Markovian temporary emigration

There are two versions of temporary emigration in the robust design, Markovian and random. It all hinges on memory: does an animal being absent last period make it any more likely to be absent this period, or not?

Markovian temporary emigration

This is the general case, where \(\gamma' \neq \gamma''\). Whether an animal is absent this period leans on whether it was absent last period. One that was away last time is more likely to still be away now (high \(\gamma'\)), while one that was around last time has only a small chance of having wandered off (low \(\gamma''\)).

For a lot of species that’s exactly what you’d expect. A bird that’s currently sat on its wintering grounds is far more likely to still be there next month than a bird that’s currently on its breeding territory. Where the animal is has genuine stickiness to it.

Random temporary emigration

This is the special case where \(\gamma' = \gamma''\). The chance of being absent at time \(t\) is the same whether or not the animal was absent at \(t-1\). Availability has no memory whatsoever. Every primary period, each animal effectively flips the same weighted coin from scratch to decide whether it’s around.

Under random temporary emigration the fraction of animals available at any moment is just \(1 - \gamma'\), and it stays put across every primary period.

Random emigration is the simpler model, one parameter lighter, and it’s often a perfectly sensible place to start. If you’ve got no strong biological reason to think absences are sticky, it’s a fine default to reach for.

Have a play with the sliders below to see how the two gammas shape who’s around and when. Start with \(\gamma' \approx \gamma''\) for random emigration, then drag them apart and watch the Markovian behaviour show up.

Try setting \(\gamma' = \gamma''\). The mean absence run drops close to 1 and the grid looks scattered about with no real clusters. Now crank \(\gamma'\) well above \(\gamma''\) and watch long runs of back-to-back absence appear. That mean absence run length in the stats above is basically a direct read-out of how much memory the availability process has. For Lily’s willow warblers, that number is going to be enormous across the months the birds are away on migration.

What temporary emigration does to your estimates

Let’s make this concrete and walk through what actually goes wrong if you’ve got real temporary emigration but you ignore it and fit a model that assumes everyone’s always available.

If some animals are temporarily absent, then the detection rate you observe across all the secondary occasions in a period comes out lower than the true detection probability for the animals that are genuinely there. The model has no idea some animals were off elsewhere, so it pins the whole shortfall on bad detection. Result: \(\hat{p}\) gets biased downward.

And a wonky \(\hat{p}\) feeds straight into \(\hat{\tilde{p}}_t\), which feeds straight into \(\hat{N}_t\). Push \(\hat{\tilde{p}}_t\) too low and \(\hat{N}_t = n_t / \hat{\tilde{p}}_t\) comes out too high. You’ll think there are more animals out there than there really are.

The damage to \(\hat{\phi}\) is sneakier. Survival gets estimated from the between-period history, which now smushes together real mortality and temporary absence. To the model, an animal that’s temporarily gone looks exactly like a dead one. So \(\hat{\phi}\) gets biased downward too: you’ll underestimate survival, because some of those apparent disappearances were never deaths in the first place.

This is the same apparent-survival problem we met all the way back on the detection problem page, just arriving through a different door. Temporary emigration is one more thing that prises true survival apart from apparent survival.

The full model with temporary emigration

Adding temporary emigration to the robust design just means tracking one more hidden state per animal. At each primary period, every animal now carries a survival state \(z_{i,t}\) (alive or dead, with \(z_{i,t} = 1\) meaning alive) and, if it’s alive, an availability state \(a_{i,t}\) (around, or off elsewhere).

The availability process, following RMark’s convention, is:

\[a_{i,t} \mid z_{i,t} = 1 \sim \begin{cases} Bernoulli(1 - \gamma''_t) & \text{if } a_{i,t-1} = 1 \text{ (was available: leaves with prob } \gamma'') \\ Bernoulli(1 - \gamma'_t) & \text{if } a_{i,t-1} = 0 \text{ (was unavailable: stays away with prob } \gamma') \end{cases}\]

And a detection now needs three things to line up, survival, availability, and actually catching it:

\[\omega_{i,t} \sim Bernoulli(\tilde{p}_t \times a_{i,t} \times z_{i,t})\]

where \(\omega_{i,t} = 1\) if animal \(i\) turned up on at least one secondary occasion in primary period \(t\), and 0 if it didn’t.

Look at what that product does. A dead animal can’t be caught (\(z_{i,t} = 0 \Rightarrow \omega_{i,t} = 0\)). An animal that’s alive but off elsewhere can’t be caught either (\(a_{i,t} = 0 \Rightarrow \omega_{i,t} = 0\)). Only an animal that’s both alive and present is in with a chance, and even then you still only get it with probability \(\tilde{p}_t\). So a zero can be three completely different things hiding behind the same digit, and every layer we’ve bolted onto this model has really just been us refusing to let those three get muddled together.

Fitting the model with temporary emigration

Now let’s fit the full thing, and this time we set the gamma parameters free to be estimated from the data instead of nailing them to zero.

Code
library(RMark)

set.seed(1988)

N_true               <- 300
phi_true             <- 0.80
p_true               <- 0.40
gamma_prime_true     <- 0.1  # Prob of staying away if currently unavailable 
gamma_dbl_prime_true <- 0.5  # Prob of leaving if currently available
n_primary            <- 6
n_secondary          <- 3
Code
sim_robust_te <- function(N, phi, p, gp, gdp, n_prim, n_sec, seed = 1988) {
  set.seed(seed)

  # Survival states
  alive <- matrix(0, nrow = N, ncol = n_prim)
  alive[, 1] <- 1
  for (t in 2:n_prim) {
    alive[, t] <- rbinom(N, 1, alive[, t - 1] * phi)
  }

  # Availability states
  avail <- matrix(0, nrow = N, ncol = n_prim)
  avail[, 1] <- 1
  for (t in 2:n_prim) {
    for (i in 1:N) {
      if (alive[i, t] == 0) {
        avail[i, t] <- 0
      } else if (avail[i, t - 1] == 1) {
        # Was available: becomes unavailable with prob gdp
        avail[i, t] <- rbinom(1, 1, 1 - gdp)
      } else {
        # Was unavailable: stays unavailable with prob gp
        avail[i, t] <- rbinom(1, 1, 1 - gp)
      }
    }
  }

  # Detection history
  det <- matrix(0, nrow = N, ncol = n_prim * n_sec)
  for (t in 1:n_prim) {
    for (s in 1:n_sec) {
      col <- (t - 1) * n_sec + s
      det[, col] <- rbinom(N, 1, avail[, t] * p)
    }
  }

  det[rowSums(det) > 0, ]
}

ch_data <- sim_robust_te(N_true, phi_true, p_true,
                          gamma_prime_true, gamma_dbl_prime_true,
                          n_primary, n_secondary)

ch_strings <- apply(ch_data, 1, paste, collapse = "")
rd_df <- data.frame(ch = ch_strings, stringsAsFactors = FALSE)

cat("Individuals detected at least once:", nrow(rd_df))
Individuals detected at least once: 280
Code
time_intervals <- c(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)

rd_processed <- process.data(rd_df,
                              model = "Robust",
                              time.intervals = time_intervals)

rd_ddl <- make.design.data(rd_processed)

rd_fit <- mark(rd_processed, rd_ddl,
               model.parameters = list(
                 S                = list(formula = ~ 1),
                 p                = list(formula = ~ 1),
                 GammaPrime       = list(formula = ~ 1),
                 GammaDoublePrime = list(formula = ~ 1)
               ),
               output = FALSE,
               silent = TRUE)
Code
beta <- rd_fit$results$beta

extract_est <- function(beta, param, true_val) {
  est <- plogis(beta[param, "estimate"])
  lcl <- plogis(beta[param, "estimate"] - 1.96 * beta[param, "se"])
  ucl <- plogis(beta[param, "estimate"] + 1.96 * beta[param, "se"])
  cat(param, "(true =", true_val, "):",
      round(est, 3), "  95% CI:", round(lcl, 3), "to", round(ucl, 3), "\n")
}

extract_est(beta, "S:(Intercept)",                phi_true)
S:(Intercept) (true = 0.8 ): 0.802   95% CI: 0.768 to 0.831 
Code
extract_est(beta, "p:(Intercept)",                p_true)
p:(Intercept) (true = 0.4 ): 0.378   95% CI: 0.321 to 0.439 
Code
extract_est(beta, "GammaPrime:(Intercept)",       gamma_prime_true)
GammaPrime:(Intercept) (true = 0.1 ): 0.017   95% CI: 0 to 0.995 
Code
extract_est(beta, "GammaDoublePrime:(Intercept)", gamma_dbl_prime_true)
GammaDoublePrime:(Intercept) (true = 0.5 ): 0.476   95% CI: 0.399 to 0.553 

The true values should land inside, or at least near, the confidence interval for each parameter. The gammas usually come out with wider intervals than \(\phi\) and \(p\), simply because they’re harder to estimate: the model has to tease apart different patterns of consecutive absence stretched across the primary periods. Throw more primary periods and a bigger population at it and those intervals will tighten up.

What the gamma parameters tell you biologically

It’s worth stopping to ask what \(\gamma'\) and \(\gamma''\) are actually telling you about your species, rather than treating them as nuisance numbers to estimate and then forget about.

\(\gamma''\) is the probability an animal clears off out of your study area between one primary period and the next, given it was there last time. A high \(\gamma''\) means animals are forever slipping out of the study area. A low one means they tend to stay put once they’ve arrived.

\(\gamma'\) tells you how sticky being absent is. A high \(\gamma'\) means that once an animal has gone, it tends to stay gone for several primary periods. A low one means animals nip back not long after they leave.

If \(\gamma' \approx \gamma''\), the movement is basically random: where the animal is now says nothing about where it was before. If \(\gamma' \gg \gamma''\), absences are long and stubborn compared to how often animals leave in the first place.

For a migratory species sampled across seasons, you’d expect \(\gamma'\) to be sky-high during migration: once a bird has cleared out for its wintering grounds, it’s staying gone the whole winter. For a species that mooches in and out more freely, both gammas might come out moderate and pretty similar to each other.

A note on identifiability at the first and last primary periods

One practical thing worth flagging. The gammas can’t be identified at every primary period. Specifically, \(\gamma''\) can’t be pinned down at the very first period (there’s no earlier availability state to condition on), and \(\gamma'\) can’t be pinned down at the very last one. RMark sorts this out for you behind the scenes, but you might spot that the design data for these parameters has fewer rows than you were expecting.

You don’t need to do anything about this. Just know that the gamma estimates in your output are speaking about the middle primary periods, not the two ends.

What next?

With temporary emigration bolted on, you’ve now got the complete robust design model. Three hidden processes all running at once:

Survival between primary periods, run by \(\phi\).

Availability among the animals that are alive, run by \(\gamma'\) and \(\gamma''\).

Detection of the available animals across secondary occasions, run by \(p\).

And every one of these can be modelled as a function of covariates, exactly like in a bog-standard GLM. Survival that shifts over time. Detection that depends on habitat. Availability that swings with the season. The robust design isn’t really one model, it’s a framework you bend to fit the biology of your species and the shape of your data.

The next page is where we roll our sleeves up and actually do this in RMark: how to wrangle your real data into shape, and how to set up model structures with covariates.