# Are gifted players less athletic? Collider bias and the academy

Source: https://www.footballdatascience.co.uk/learn/collider-bias
Published: 2026-10-14

> Pick the best 200 of 5,000 young players and, among those picked, the more technical turn out to be the less athletic, even when the two have nothing to do with each other. How the way you choose your data can create a link that was never there, with a simulator and a real check on league tables.

**On the terraces:** You hear it about every academy: the skilful ones can't run and the quick ones can't play. Look only at the players who got in and the numbers seem to agree. This piece shows the academy's own choices can put that link there.

## The football question

**Are technically gifted footballers less athletic, or have we fooled ourselves with the data?**

Picture every young footballer a club's scouts have rated across their region over several seasons: 5,000 of them, all hoping to get into its academy. Each has been scored on two things: technical ability (passing, dribbling, ball control) and physical ability (pace, strength, endurance).

Across all 5,000 there's virtually no link between the two. Knowing a player is technically gifted tells you next to nothing about how athletic they are.

Over those seasons the academy takes the best 200, on a mix of both. A player who's a bit weaker in one area can still get in by being exceptional in the other.

Run the same analysis on just those 200 and something odd turns up: a strong negative link. The more technical the player, the less athletic they tend to be, and the other way round. It's tempting to think we've found something fundamental about elite footballers. We haven't. We've walked straight into **collider bias**.

You can try it yourself in the [Academy Selection Simulator](/models/academy-selection): change how the academy picks and watch the link come and go.

## The concept

The measure of a link here is the **correlation** (Pearson's *r*): 0 means no link, −1 a perfect "more of one, less of the other", +1 a perfect "more of both".

A **collider** is something caused by two other things. Getting into the academy is caused by technique and by athleticism, so it's a collider. Look only at the players who got in, or "control for" getting in, and the two causes become linked, even if they had nothing to do with each other. Statisticians call this collider bias. The version where you select on the collider, as the academy does, is also known as **Berkson's paradox**, after the doctor who spotted it in hospital patients in 1946.

<figure class="rank-chart">
<div role="img" aria-label="Technique and athleticism each point to picked by the academy. A dashed line between technique and athleticism is the link we see when we look only at the players who were picked.">

</div>
<figcaption>Both abilities decide who gets picked. Look only at the picked players and the two abilities seem linked.</figcaption>
</figure>

It's a cousin of two traps covered elsewhere on the site, and worth keeping apart from them. **Survivorship bias** looks only at the ones that lasted, like the [midfield partnerships](/learn/midfield-partnerships-cohort-analysis) that seemed to gel. **Selection bias** compares groups that were chosen differently, like the [substitutions](/research/do-substitutions-work) managers make after a bad spell. Collider bias is more specific: the filter depends on **both** of the things you're comparing.

## One academy, 200 places in 5,000

I ran a quick Python simulation with 5,000 made-up footballers. Technique and athleticism were drawn independently, so there was no underlying link between them at all. Then I picked the top 200 on their combined score.

| Group | Correlation |
|---|---|
| All 5,000 players | +0.03 |
| The 200 picked | −0.81 |

That's near enough zero to strongly negative, without changing a single player's ability. The only thing that changed was who we were looking at.

<figure class="rank-chart">
<div role="img" aria-label="A scatter of 1,000 made-up players, technique across and athleticism up, in a round cloud with no trend. The 40 picked sit in a band at the top right, and the trend through them slopes steeply down.">

</div>
<figcaption>A smaller made-up intake drawn the same way: 1,000 players, with the best 40 on the combined score picked (the same 4 in 100). The cloud has no trend; the picked players' trend slopes down.</figcaption>
</figure>

### So what actually happened?

We selected the players on the two things we were trying to study. A brilliant technician could get in without being much of an athlete, and a real athlete could get in with fairly average technique. Nobody gets in by being ordinary at both. Inside the academy, one strength is covering for the other, and that's what produces the negative link. In the chart, the picked players fill a band across the top right, and a band like that can only slope down.

## What makes it worse, and what makes it go away

One academy is one draw of the dice, so the snippet repeats each version over 60 different intakes of 5,000 players and averages them.

<figure class="rank-chart">
<div role="img" aria-label="Average correlation among those picked, from 60 intakes. Top 2,500 minus 0.47, top 1,000 minus 0.65, top 200 minus 0.78, top 50 minus 0.82, technique only minus 0.01, a minimum in both minus 0.02, a real link of plus 0.3 then the top 200 minus 0.62.">

</div>
<figcaption>The correlation among the players picked, averaged over 60 intakes. All but the last start from no link at all.</figcaption>
</figure>

- **The pickier the academy, the stronger the false link.** Take half the players and it's −0.47. Take the top 200 and it's −0.78; the top 50, −0.82.
- **The totals don't matter, the share picked does.** A single age group at a smaller club, 12 picked from 300 rated, gives the same link on average (−0.76). One small intake just wobbles more from year to year.
- **Picking on technique alone creates nothing** (−0.01). Nobody can trade one ability for the other, so no link appears.
- **Neither does a minimum in both** (−0.02): everyone above the same bar for technique and for athleticism. You still pick about 200 players, but a brilliant technician who can't run doesn't get in.
- **A different mix changes the size, not the story.** Weight it 70% technique, 30% athleticism, and the top 200 come out at −0.67.
- **It can turn a real link round.** Suppose technique and athleticism really do go together, at +0.3 across all 5,000. The top 200 still come out at **−0.62**, and at −0.45 when the real link is +0.5. The selection hasn't just invented a link. It has hidden a real one and shown the opposite.

So it's not selection as such that does it. It's selecting on a score where one thing can make up for the other.

## Does it happen in real football?

League tables are a selection of exactly this kind. Points come from scoring goals and from keeping them out, and a side can finish high with a great attack and a so-so defence, or the other way round. So split the table in two and the link between attack and defence should weaken in each half, even if it's strong across the whole league.

The test uses every Scottish Championship season from 2000/01 to 2025/26: ten clubs a season, 260 team-seasons. **Attack** is goals scored a game and **defence** is goals kept out a game, each measured against that season's average, so a free-scoring season doesn't flatter everyone in it.

Across all 260, attack and defence go together strongly: **+0.59**. Good sides tend to be good at both. Within each half of the table the link is much weaker: **+0.39** in the top five and **+0.16** in the bottom five.

That might mean struggling sides really do choose between attacking and defending. But the snippet checks what selection alone would do: 1,000 made-up leagues with the same +0.59 link and nothing else going on, each split by points the same way. Each half comes out at about **+0.27**, and anywhere from about +0.10 to +0.44 in 95 leagues out of 100.

<figure class="rank-chart">
<div role="img" aria-label="Championship, correlation between attack and defence. All 260 team-seasons plus 0.59. Top five plus 0.39 against a selection-alone range of 0.10 to 0.43. Bottom five plus 0.16 against a range of 0.09 to 0.44.">

</div>
<figcaption>How closely attack and defence go together in the Championship, 2000/01 to 2025/26. The pale bands are the range that selection alone gives each half of the table.</figcaption>
</figure>

Both real halves sit inside those ranges. Splitting the table by points is enough on its own to explain why the link weakens, so it says nothing either way about whether bottom-half sides play differently. The Premiership shows the same thing: outside Celtic and Rangers, attack and defence go together at **+0.41**, but only **+0.08** within the bottom six. (The Old Firm are left out of that comparison because they're so much stronger than everyone else that they'd make any link look stronger.)

## Why it matters

Think about the data we use all the time. Players in a top league have already come through years of selection. Studies of successful transfers tend to leave out the ones who never settled. Academy graduates get compared without the players who didn't make it. Managers still in a job get analysed, and the ones who were sacked quietly drop out.

None of that automatically means collider bias; most of it is survivorship. It becomes collider bias when the filter depends on the very things you're comparing. If a club signs players who either score a lot or create a lot, its squad will show scorers who don't create and creators who don't score, whatever the link in the wider game.

This is also why adding more variables to a model doesn't automatically make it better. "Controlling for" a collider, comparing players only with others who got into the academy, or teams only with others who finished in the same half, does exactly what looking only at the picked players does. Control for the wrong thing and you can introduce the bias you were trying to avoid. [Do football stats mislead?](/research/correlation-causation) covers the opposite trap, the hidden cause you **should** control for. Telling the two apart means asking which way the arrows run: does the third thing cause both of yours, or do both of yours cause it?

One analyst looks at the 200 academy players and concludes technique and athleticism are negatively related. Another looks at all 5,000 and finds nothing. Both correlations are calculated correctly. The difference is that one analyst knows what the selection did to the data.

So before you decide what makes a great player, ask how the players you're studying ended up in your dataset in the first place. How your data was collected and filtered matters every bit as much as which model you run on it.

## Limitations

- **The players are made up.** Real academies weigh far more than two things, and real technique and athleticism may well be linked. The simulation shows what selection does to whatever link there is, not what the real link is.
- **Selection alone explains the tables, but that doesn't rule out real differences.** Bottom-half sides might trade attack for defence a little; the data can't tell that apart from the selection's effect, because both push the same way.
- **Goals, not chances.** Goals scored and conceded include plenty of luck. Expected goals would measure attack and defence more cleanly, but the Scottish results files don't have them.
- **The selection-alone check is rough.** It treats a season as ten teams with a fixed link between attack and defence, and splits the table on attack plus defence exactly. Real tables are decided by points, with draws and luck in who finishes where.

## Reproduce the analysis

The first two parts need nothing but Python 3. For the third, save the Championship and Premiership results files (SC1 and SC0) from football-data.co.uk, which the [download script](/data#get-the-files) fetches under the names the snippet expects, in the same folder. It takes about 20 seconds:

```python
# From Football Data Science by Bryan McGuire. Free to use with credit.
# https://www.footballdatascience.co.uk/learn/collider-bias
import csv
import random
import statistics as st

def r(pairs):                                   # Pearson's correlation between the two numbers in each pair
    return st.correlation([a for a, b in pairs], [b for a, b in pairs])

def intake(seed, link=0.0, n=5000):             # (technique, athleticism) for n players, linked by `link`
    rng = random.Random(seed)
    players = []
    for _ in range(n):
        t = rng.gauss(0, 1)
        players.append((t, link * t + (1 - link ** 2) ** 0.5 * rng.gauss(0, 1)))
    return players

def top(players, places, weight=0.5):           # the academy's pick: best on a mix of the two
    return sorted(players, key=lambda p: weight * p[0] + (1 - weight) * p[1], reverse=True)[:places]

# 1. One academy
players = intake(seed=16)
picked = top(players, 200)
print(f"All 5,000 players:   r = {r(players):+.3f}")
print(f"The 200 picked:      r = {r(picked):+.3f}")

# 2. Other academies, averaged over 60 intakes so one lucky draw doesn't decide it
def average(choose, link=0.0):
    return st.mean(r(choose(intake(seed, link))) for seed in range(1, 61))

def both(players, places=200):                  # the same minimum in each, set so about `places` pass
    share = (places / len(players)) ** 0.5
    cut_t = sorted(t for t, a in players)[int(len(players) * (1 - share))]
    cut_a = sorted(a for t, a in players)[int(len(players) * (1 - share))]
    return [(t, a) for t, a in players if t >= cut_t and a >= cut_a]

print("\nAverage r among those picked, 60 intakes")
for places in (2500, 1000, 200, 50):
    print(f"  top {places:>4} on the combined score     {average(lambda p: top(p, places)):+.2f}")
print(f"  top  200 on technique only           {average(lambda p: top(p, 200, weight=1)):+.2f}")
print(f"  a minimum in both (about 200)        {average(both):+.2f}")
print(f"  top  200, weighted 70/30 to technique {average(lambda p: top(p, 200, weight=0.7)):+.2f}")
print(f"  top   12 of only 300 rated           {average(lambda p: top(p[:300], 12)):+.2f}")
for link in (0.3, 0.5):
    print(f"  top  200, true link {link:+.1f}             {average(lambda p: top(p, 200), link):+.2f}")

# 3. Real tables: attack and defence, each measured against its own season
def team_seasons(div):
    rows = []
    for y in range(2000, 2026):
        season = {}
        with open(f"{div}_{y % 100:02d}{(y + 1) % 100:02d}.csv", encoding="latin-1") as f:
            for m in csv.DictReader(f):
                if not m.get("FTHG"):
                    continue
                hg, ag = int(m["FTHG"]), int(m["FTAG"])
                for team, scored, let_in in ((m["HomeTeam"], hg, ag), (m["AwayTeam"], ag, hg)):
                    team = {"Raith Rvs": "Raith"}.get(team, team)        # one club, two spellings in 2004/05
                    s = season.setdefault(team, [0, 0, 0, 0])
                    s[0] += 1; s[1] += scored; s[2] += let_in
                    s[3] += 3 if scored > let_in else 1 if scored == let_in else 0
        teams = [(name, gf / n, ga / n, pts / n) for name, (n, gf, ga, pts) in season.items()]
        mf, sf = st.mean(t[1] for t in teams), st.pstdev(t[1] for t in teams)
        ma, sa = st.mean(t[2] for t in teams), st.pstdev(t[2] for t in teams)
        teams.sort(key=lambda t: -t[3])
        for place, (name, gf, ga, ppg) in enumerate(teams, 1):
            rows.append({"team": name, "place": place, "size": len(teams),
                         "attack": (gf - mf) / sf, "defence": (ma - ga) / sa})   # defence: goals kept out
    return rows

def pairs(rows):
    return [(x["attack"], x["defence"]) for x in rows]

def selection_alone(link, teams=10, seasons=26, runs=1000, seed=1):
    """r in each half of the table if attack and defence were linked by `link` and nothing else happened."""
    rng, out = random.Random(seed), {"top": [], "bottom": []}
    for _ in range(runs):
        halves = {"top": [], "bottom": []}
        for _ in range(seasons):
            table = sorted(intake(rng.random(), link, teams), key=lambda p: p[0] + p[1], reverse=True)
            halves["top"] += table[:teams // 2]
            halves["bottom"] += table[teams // 2:]
        for h in out:
            out[h].append(r(halves[h]))
    return {h: (st.mean(v), sorted(v)[25], sorted(v)[974]) for h, v in out.items()}

print("\nChampionship 2000/01-2025/26: attack v defence")
ch = team_seasons("SC1")
link = r(pairs(ch))
print(f"  all {len(ch)} team-seasons            r = {link:+.2f}")
alone = selection_alone(link)
for half, rows in (("top", [x for x in ch if x["place"] <= 5]), ("bottom", [x for x in ch if x["place"] > 5])):
    m, lo, hi = alone[half]
    print(f"  {half:>6} five ({len(rows)})           r = {r(pairs(rows)):+.2f}   selection alone: {m:+.2f} ({lo:+.2f} to {hi:+.2f})")

print("\nPremiership 2000/01-2025/26")
pr = team_seasons("SC0")
rest = [x for x in pr if x["team"] not in ("Celtic", "Rangers")]
print(f"  all {len(pr)} team-seasons            r = {r(pairs(pr)):+.2f}")
print(f"  without Celtic and Rangers ({len(rest)})  r = {r(pairs(rest)):+.2f}")
print(f"  bottom six ({sum(x['place'] > 6 for x in pr)})              r = {r(pairs([x for x in pr if x['place'] > 6])):+.2f}")
```

## Further reading

- [Berkson's paradox](https://en.wikipedia.org/wiki/Berkson%27s_paradox), Wikipedia. The selection version of collider bias, with Berkson's original hospital example and others.
- [Collider (statistics)](https://en.wikipedia.org/wiki/Collider_(statistics)), Wikipedia. What a collider is in a cause-and-effect diagram, and why conditioning on one creates a link between its causes.
- Julia Rohrer, "Thinking Clearly About Correlations and Causation: Graphical Causal Models for Observational Data", *Advances in Methods and Practices in Psychological Science* (2018). A readable guide to confounders and colliders, and to which variables to control for.
