Assessment rules for reputation

evolutionary ecology
evolutionary game theory
social evolution
R
ecology tutorial
Four assessment rules compared in R: image scoring, standing, stern judging and shunning, and why only rules excusing justified refusal keep cooperation going.
Author

Tidy Ecology

Published

2026-08-08

The previous post found a clean threshold for indirect reciprocity, q > c/b, but it assumed something convenient: that a helper is seen as good and a defector as bad. That assumption hides the hardest problem in the whole theory. A discriminator who correctly refuses to help a known cheat has, viewed narrowly, just withheld help. Whether the population holds that against it is a matter of bookkeeping, and the bookkeeping decides whether cooperation survives.

The bookkeeping rule is called an assessment rule. This post codes four of them in R and lets each run to its equilibrium.

Four ways to keep score

A first-order rule looks only at what the donor did. A second-order rule looks at what the donor did and at the reputation of the person it did it to. That extra bit of context is the whole argument. Four rules cover the classic cases:

  • Image scoring: helping makes you good, refusing makes you bad. Context ignored (Nowak and Sigmund 1998).
  • Standing: helping makes you good, and refusing makes you bad only if the recipient was good. Refusing a cheat is excused (Sugden 1986).
  • Stern judging: helping a good recipient and refusing a bad one both make you good; the other two combinations make you bad.
  • Shunning: only helping a good recipient makes you good. Refusing anyone, or helping a cheat, makes you bad.

Each rule is a two by two table indexed by the recipient’s reputation and the donor’s action, so it goes straight into a matrix.

b <- 3; cost <- 1
# rows: recipient good, recipient bad ; columns: donor helped, donor refused
# entry 1 means the donor is recorded as good
rules <- list(
  `image scoring` = matrix(c(1, 1,  0, 0), 2, 2),
  standing        = matrix(c(1, 1,  0, 1), 2, 2),
  `stern judging` = matrix(c(1, 0,  0, 1), 2, 2),
  shunning        = matrix(c(1, 0,  0, 0), 2, 2))

# probability of intending to help, given the recipient is good or bad
strategies <- list(DISC = c(1, 0), ALLC = c(1, 1), ALLD = c(0, 0))
rules$standing
     [,1] [,2]
[1,]    1    0
[2,]    1    1

Reading the standing matrix by column: helping always records you as good (first column all ones), while refusing records you as good only when the recipient was bad (second column, one in the bad row).

Letting the reputations settle

Reputations are not fixed inputs; they are the output of the rule applied over and over. Give every strategy a starting reputation of one and iterate the update until nothing moves. One extra ingredient makes the comparison honest: an implementation error e, the chance that a donor meaning to help fails to. Errors are what separate the rules, because without them nobody ever needs excusing.

equilibrium <- function(rule, mix, e = 0.05, iters = 2000) {
  A <- rules[[rule]]; nm <- names(mix)
  g <- setNames(rep(1, length(mix)), nm)          # probability each type is seen as good
  for (it in seq_len(iters)) {
    gbar <- sum(mix * g); prior <- c(gbar, 1 - gbar)
    g_new <- sapply(nm, function(i) {
      p_help <- strategies[[i]] * (1 - e)         # intends to help, minus slips
      sum(prior * (p_help * A[, 1] + (1 - p_help) * A[, 2]))
    })
    if (max(abs(g_new - g)) < 1e-12) { g <- g_new; break }
    g <- g_new
  }
  gbar <- sum(mix * g)
  helping <- sapply(nm, function(i) sum(c(gbar, 1 - gbar) * strategies[[i]] * (1 - e)))
  received <- sapply(nm, function(i) sum(sapply(nm, function(j)
    mix[j] * (1 - e) * (g[i] * strategies[[j]][1] + (1 - g[i]) * strategies[[j]][2]))))
  list(good = g, helping = helping, payoff = -cost * helping + b * received)
}

for (r in names(rules)) {
  z <- equilibrium(r, c(DISC = 1))
  cat(sprintf("%-14s  good %.3f  helping %.3f  payoff %.3f\n", r, z$good, z$helping, z$payoff))
}
image scoring   good 0.000  helping 0.000  payoff 0.000
standing        good 0.952  helping 0.905  payoff 1.810
stern judging   good 0.952  helping 0.905  payoff 1.810
shunning        good 0.000  helping 0.000  payoff 0.000

The split is total. Standing and stern judging hold a population of discriminators at a good reputation of 0.952, helping in 90.5 per cent of encounters, for a payoff of 1.810. Image scoring and shunning both sit at zero: no reputation, no helping, no payoff.

Why image scoring eats itself

The 0.952 figure is not arbitrary. Under standing, the only way to be recorded as bad is to intend help and slip, which happens with probability e against a good recipient. Solving the fixed point gives 1/(1+e), and with e = 0.05 that is 0.952 exactly.

Image scoring has no such fixed point above zero. A discriminator whose reputation slips becomes a bad recipient, and every discriminator who then correctly refuses it is itself marked bad. Bad reputations breed bad reputations, and the whole population’s score decays geometrically.

g <- 1
for (t in 1:60) {
  g <- g * (1 - 0.05)
  if (t %in% c(1, 10, 20, 40, 60)) cat(sprintf("round %2d   good %.3f\n", t, g))
}
round  1   good 0.950
round 10   good 0.599
round 20   good 0.358
round 40   good 0.129
round 60   good 0.046

Sixty rounds take a spotless population from 0.950 to 0.046. The decay factor is 1 - e, so the collapse is not a knife edge: it happens for any error rate above zero. An error sweep makes that plain.

for (e in c(0, 0.01, 0.05, 0.10)) {
  s  <- equilibrium("image scoring", c(DISC = 1), e = e)$helping
  st <- equilibrium("standing", c(DISC = 1), e = e)$helping
  cat(sprintf("e = %.2f   image scoring %.3f   standing %.3f\n", e, s, st))
}
e = 0.00   image scoring 1.000   standing 1.000
e = 0.01   image scoring 0.000   standing 0.980
e = 0.05   image scoring 0.000   standing 0.905
e = 0.10   image scoring 0.000   standing 0.818

At zero error both rules give perfect cooperation. Add one per cent noise and image scoring is already at zero while standing barely moves, to 0.980. This is the sense in which first-order rules fail: they are fine in a world without mistakes, and that world does not exist.

library(ggplot2)
e <- 0.05
trace <- data.frame(round = 0:60,
                    `image scoring` = (1 - e)^(0:60),
                    standing = c(1, rep(1 / (1 + e), 60)),
                    check.names = FALSE)
long <- data.frame(round = rep(trace$round, 2),
                   good = c(trace$`image scoring`, trace$standing),
                   rule = rep(c("image scoring", "standing"), each = nrow(trace)))
ggplot(long, aes(round, good, colour = rule)) +
  geom_line(linewidth = 0.9) +
  scale_colour_manual(values = c("image scoring" = "#b5534e", standing = "#275139")) +
  labs(x = "Round", y = "Average good reputation", colour = NULL) +
  theme_minimal(base_size = 12) + theme(legend.position = "top")
A decaying curve for image scoring falling towards zero and a flat line for standing near 0.95.
Figure 1: Average good reputation of a discriminating population over 60 rounds at a 5 per cent error rate. Under image scoring it decays towards zero; under standing it holds at 1/(1+e) = 0.952.

Resisting defectors, and the free rider behind them

A rule has to do two jobs. It must keep out defectors, and it must keep out unconditional helpers, who cooperate without ever paying the cost of assessment and so erode discrimination from within. That second threat is the second-order free rider problem, and it is where standing and stern judging part company.

for (r in names(rules)) {
  z <- equilibrium(r, c(DISC = 0.99, ALLD = 0.01))
  cat(sprintf("%-14s  DISC %.3f  ALLD %.3f\n", r, z$payoff["DISC"], z$payoff["ALLD"]))
}
image scoring   DISC 0.000  ALLD 0.000
standing        DISC 1.792  ALLD 0.158
stern judging   DISC 1.792  ALLD 0.158
shunning        DISC 0.000  ALLD 0.000

Under standing and stern judging a rare defector earns 0.158 against residents on 1.792, so it is repelled hard. Under image scoring and shunning both payoffs are zero, which counts as resistance only in the sense that there is nothing left to invade.

for (r in names(rules)) {
  z <- equilibrium(r, c(DISC = 0.90, ALLC = 0.10))
  cat(sprintf("%-14s  ALLC good %.3f  DISC %.3f  ALLC %.3f  gap %+.4f\n",
              r, z$good["ALLC"], z$payoff["DISC"], z$payoff["ALLC"],
              z$payoff["DISC"] - z$payoff["ALLC"]))
}
image scoring   ALLC good 0.950  DISC 1.259  ALLC 1.772  gap -0.5127
standing        ALLC good 0.952  DISC 1.823  ALLC 1.778  gap +0.0452
stern judging   ALLC good 0.903  DISC 1.828  ALLC 1.651  gap +0.1773
shunning        ALLC good 0.000  DISC 0.285  ALLC -0.665  gap +0.9500

Stern judging punishes the free rider ten times harder than standing does: a gap of +0.1773 against +0.0452. The reason is in the reputations. Stern judging demands that a donor refuse a bad recipient, so an unconditional helper is recorded as bad every time it helps one, dropping to 0.903 while discriminators sit at 0.952. Standing merely excuses refusal, so an unconditional helper keeps a clean record and the selection against it is weak.

Under image scoring the sign flips: the gap is negative, and the reason is worth staring at.

for (r in c("image scoring", "standing", "stern judging")) {
  z  <- equilibrium(r, c(DISC = 0.90, ALLC = 0.10))
  z2 <- equilibrium(r, c(DISC = 0.90, ALLD = 0.10))
  cat(sprintf("%-14s  DISC good %.3f   ALLC good %.3f   ALLD good %.3f\n",
              r, z$good["DISC"], z$good["ALLC"], z2$good["ALLD"]))
}
image scoring   DISC good 0.622   ALLC good 0.950   ALLD good 0.000
standing        DISC good 0.952   ALLC good 0.952   ALLD good 0.127
stern judging   DISC good 0.953   ALLC good 0.903   ALLD good 0.127

Under image scoring the discriminator’s own reputation, 0.622, sits below the unconditional helper’s 0.950. Being discriminating is what damages it, because every justified refusal is recorded as a defection. A rule that penalises the behaviour it is supposed to reward cannot support cooperation, whatever the value of q.

Ohtsuki and Iwasa (2004, 2006) searched the space of second-order rules systematically and found that only eight combinations of assessment rule and action rule are evolutionarily stable, the set now called the leading eight. Standing and stern judging are both in it; image scoring and shunning are not.

An honest limit

Standing and stern judging tie on cooperation here, and stern judging wins on the free rider. That is not the end of the comparison. Both rules leave defectors with a good reputation 12.7 per cent of the time, because a defector who refuses another defector is recorded as behaving correctly. Refusing everyone launders your record whenever you happen to draw a bad partner, and no second-order rule can close that gap without also punishing honest discrimination.

The larger limit is that every number above assumes one shared public record: everyone observes the same events and reaches the same verdict. Drop that and the ranking changes, sharply and in a direction these equilibria do not hint at. The next post works through what happens when opinions are private.

References

Nowak MA, Sigmund K 1998. Nature 393(6685):573-577 (10.1038/31225)

Ohtsuki H, Iwasa Y 2004. Journal of Theoretical Biology 231(1):107-120 (10.1016/j.jtbi.2004.06.005)

Ohtsuki H, Iwasa Y 2006. Journal of Theoretical Biology 239(4):435-444 (10.1016/j.jtbi.2005.08.008)

Panchanathan K, Boyd R 2003. Journal of Theoretical Biology 224(1):115-126 (10.1016/S0022-5193(03)00154-1)

Leimar O, Hammerstein P 2001. Proceedings of the Royal Society B 268(1468):745-753 (10.1098/rspb.2000.1573)

Sugden R 1986. The Economics of Rights, Co-operation and Welfare. Blackwell. ISBN 978-0631144496

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.