Challenges · C06 · ★★★☆☆

Galaxies: how many clusters, and who belongs where?

Difficulty
★★★☆☆
Time
3-4 hours
Data
Postman, Huchra & Geller (1986) via Roeder (1990): recession velocities of 82 galaxies in the Corona Borealis region
Skills
Finite mixture models · diagnosing a sampler whose chains disagree · constrained parameters and initial values · marginalised vs explicit discrete assignments · membership and co-clustering probabilities · choosing K with LOO and PPCs (and knowing when you cannot) · predictive probabilities of a void

The brief

An astronomy group has measured the recession velocities of 82 galaxies in six well-separated conic sections of sky towards Corona Borealis. By Hubble's law velocity is a proxy for distance, so if galaxies were spread evenly through space the velocities would form one smooth hump. Theories of large-scale structure say otherwise: galaxies should sit in clusters separated by voids, which would show up as several humps with gaps in between.

The group asks three questions:

  1. Do the velocities show distinct clusters separated by voids, and how many?
  2. Which galaxies belong together? For the ones near a boundary, how sure are you?
  3. Is the apparent void between the main body and the fastest galaxies real, or the kind of gap that 82 draws from a smooth distribution produce by chance?

They want answers with uncertainty, in language they can put in a paper.

How this notebook works

  • Each task states what to deliver, not how. Write your code in the YOUR CODE HERE cells.
  • Stuck? h.hint("task2") reveals hints one level at a time: nudge → approach → code skeleton. Try to get by on nudges.
  • h.check("task2", mu_main=...) compares your numbers with the reference solution.
  • A full worked solution lives in notebooks/solutions/. Open it only when you are done (or truly stuck).
import time

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pymc as pm
from scipy import stats

from pymc_challenges import Hints, data

RANDOM_SEED = 1986
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-variat")

h = Hints("C06")
h.tasks()

C06 - Galaxies: how many clusters, and who belongs where?

  • task0 - Look at the data (2 hints)
  • task1 - The textbook mixture (3 hints, 1 check(s))
  • task2 - Fix the model (3 hints, 3 check(s))
  • task3 - Membership probabilities, two ways (3 hints, 2 check(s))
  • task4 - How many clusters? (3 hints, 2 check(s))
  • task5 - Unequal spreads and heavy tails (3 hints, 2 check(s))
  • task6 - Co-clustering and the void (3 hints, 3 check(s))

The data

A single column, dat: velocity in km/s. We sort it and work in thousands of km/s, so galaxy 0 is the nearest and galaxy 81 the most distant, and sensible prior scales are of order 1-10 rather than 1,000-10,000.

A historical footnote: this is the version distributed with R's MASS package, which contains a famous typo - the 78th value is 26690 where Roeder's paper has 26960. It makes no difference to anything below, but it is the reason published analyses of "the galaxy data" do not always agree to the last digit.

data.describe("galaxies")
galaxies = data.load("galaxies")
v = np.sort(galaxies["dat"].to_numpy() / 1000)
N = len(v)
print(N, "galaxies, from", v.min(), "to", v.max(), "thousand km/s")
galaxies: Velocities (km/s) of 82 galaxies in the Corona Borealis region.
  source: Postman, Huchra & Geller (1986) via Roeder (1990) and R package `MASS`.
  url:    https://vincentarelbundock.github.io/Rdatasets/csv/MASS/galaxies.csv
82 galaxies, from 9.172 to 34.279 thousand km/s

Task 0 · Look at the data

Deliver a plot that shows every single galaxy (82 points deserve better than a 10-bin histogram alone) and a list of the largest gaps between neighbouring velocities. How many groups do you see, how many galaxies in each, and how wide is each group?

Your turn Work this task in the notebook.

Task 1 · The textbook mixture

Fit a mixture of three Gaussians: three means, one standard deviation shared by all components, and three mixture weights. Write it the way a textbook would, with the same prior for every component - nothing in the model should distinguish "component 0" from "component 2". Use the default four chains.

Deliver

  1. The summary table and whatever plots you need to decide whether you can trust it.
  2. A diagnosis. Be precise: what exactly went wrong, is any individual chain wrong, and is there a quantity in the summary table that is perfectly fine - and why that one?
Your turn Work this task in the notebook.

Task 2 · Fix the model

Deliver

  1. A three-component mixture with the same likelihood and clean diagnostics. Fix the model, not the sampler settings. If your fix refuses to start sampling, find out which term of the log-probability is to blame before changing anything else.
  2. Evidence for your Task 1 diagnosis: get an acceptable r_hat for the means out of the Task 1 posterior draws, without refitting anything.
  3. One sentence each: does your fix change the fitted density? Would it still work if two clusters had nearly the same mean?
Your turn Work this task in the notebook.

Task 3 · Which galaxies belong together?

The mixture likelihood you have been using sums over the unknown cluster of each galaxy, so the cluster assignments appear nowhere in the posterior. The astronomers want them.

Deliver

  1. From the Task 2 posterior, $P(\text{galaxy } i \text{ belongs to cluster } k)$ for every galaxy and cluster. Plot it against velocity. Which galaxies are genuinely ambiguous?
  2. The same model written the other way: with an explicit discrete assignment for every galaxy. Compare what the sampler had to do, how long it took, the effective sample sizes and the membership probabilities.
  3. Which formulation would you use by default, and why?
Your turn Work this task in the notebook.

Task 4 · How many clusters?

Three was the eye's answer. Let the data have a say: fit the same shared-spread mixture for K = 2, 3, 4, 5, 6.

Deliver

  1. A table of sampler diagnostics per K (worst r_hat, smallest bulk ESS, divergences). Do not expect all of them to be pretty - say what the ugly ones are telling you.
  2. The az.compare table, and how far you trust it with N = 82.
  3. A posterior predictive check that separates at least one K from the rest: the fitted density over the data, plus a test statistic aimed at something the astronomers care about.
  4. For the largest K: what do the posterior weights of the extra components look like?
  5. Your answer to "how many clusters?", with the honest amount of hedging.
Your turn Work this task in the notebook.

Task 5 · Clusters are not all the same size

One standard deviation for a group of 7 tightly packed galaxies and for a main body that spans 10,000 km/s is an assumption of convenience. Relax it, in two steps, staying with K = 3:

  • each cluster gets its own standard deviation;
  • additionally, the components get heavy tails (Student-t with a small, fixed number of degrees of freedom), the usual recipe for making a mixture robust to stragglers.

Deliver

  1. A prior for the cluster spreads that an astronomer would sign off on. (Rich galaxy clusters have velocity dispersions of the order of 1,000 km/s.)
  2. Both fits with their diagnostics. More flexible mixtures have more ways to go wrong: watch the initial values, and if a fit misbehaves work out what it is undecided about.
  3. A LOO comparison with all the Task 4 models. What was K = 6 really doing?
  4. What changed for the boundary galaxies of Task 3?
Your turn Work this task in the notebook.

Task 6 · What the astronomers can say

Two deliverables for the paper. Use the model you would defend, and show how much the answers move under a credible alternative.

1. Co-clustering. Cluster labels are a modelling convenience; "are galaxies i and j in the same cluster?" is not. Deliver the posterior co-clustering probability for all pairs (a picture) and for the pairs listed below (numbers).

2. The void. No galaxy was observed between 27,500 and 31,500 km/s. Deliver the posterior predictive mass of that interval, the number of galaxies a new survey of the same size should expect to find in it, and the probability that such a survey finds it empty again. Compare with what a single smooth (one-component) population would predict. Then write the two or three sentences that go in the paper.

VOID = (27.5, 31.5)
PAIRS = [(16.084, 16.170), (16.084, 10.406), (16.084, 18.419), (26.995, 25.633), (26.995, 32.065)]
Your turn Work this task in the notebook.

Going further

  • The famous typo: set the 78th velocity to Roeder's 26960 and rerun. Which of your numbers move, and which conclusions?
  • Fit an overfitted mixture, K = 10 with a sparse Dirichlet(0.1) prior on the weights, and report the posterior of the number of components holding at least one galaxy's worth of weight. Expect the sampler to struggle, and decide how much of the output you believe.
  • Verify the invariance claim: compute the co-clustering matrix from the naive Task 1 fit, broken r_hat and all, and compare it with the ordered model's.
  • Repeat the void analysis for the other gap (10,400-16,100 km/s), and do the look-elsewhere correction properly: simulate surveys from the single-Normal model and record the emptiest 4,000 km/s window in each.
h.progress()

C06: 0/20 hints used, 13/13 checks passed.

Reference solution

A complete, executed solution with commentary on every task — including the ways the obvious model fails. Try the hint ladder first.

Open the worked solution →