HEAGOF 教程

HEAGOF Tutorial

如何正确判断天文计数数据拟合的好坏——修正 Z 检验的原理与实现

How to properly assess goodness-of-fit for astronomical count data — the Corrected Z-test

基于 Li, Chen, Meng, van Dyk, Bonamente & Kashyap (2025), arXiv:2510.03466 · 面向物理系一年级本科生 Based on Li, Chen, Meng, van Dyk, Bonamente & Kashyap (2025), arXiv:2510.03466 · For first-year physics undergraduates

1 问题是什么?为什么需要这个教程?What's the problem? Why this tutorial?

想象一下:你用一台望远镜观察一颗遥远的恒星,每隔一秒记录一下光子打到探测器上的次数。得到的数据可能是这样的:

Imagine this: you point a telescope at a distant star and record how many photons hit the detector each second. Your data might look like:

时间(s):    1    2    3    4    5    6    7    8    9   10
计数:       3    2    4    1    5    2    3    4    1    2

现在你想知道:这些数据是不是符合某个理论模型(比如"平均每秒 3 个光子")?这就是拟合优度检验(goodness-of-fit test)要做的事。

Now you ask: do these data match a theoretical model (e.g. "an average of 3 photons per second")? That's exactly what a goodness-of-fit test does.

在天文学(特别是 X 射线天文、γ 射线天文)里,我们几乎总是用Cash C 统计量来衡量拟合的好坏。这个统计量在 XSPEC、SPEX、Sherpa 这些专业软件里都是默认选项。

In astronomy (especially X-ray and γ-ray astronomy), we almost always use the Cash C statistic to measure goodness-of-fit. It's the default in XSPEC, SPEX, and Sherpa.

问题来了 Here's the problem当一个 bin 里的平均计数很少(比如小于 1)时,"C 统计量服从卡方分布"这个老经验不再成立。这意味着天文学家每天都在用的 p 值可能完全错误 when the average count per bin is small (say < 1), the old rule "C statistic follows a chi-squared distribution" no longer holds. This means the p-values astronomers compute every day may be completely wrong.

2025 年 10 月,Li、Chen、Meng、van Dyk、Bonamente 和 Kashyap 在 arXiv:2510.03466 上发表了一篇论文(Making high-order asymptotics practical: correcting goodness-of-fit test for astronomical count data),系统地解决了这个问题。

In October 2025, Li, Chen, Meng, van Dyk, Bonamente, and Kashyap published a paper (arXiv:2510.03466, Making high-order asymptotics practical: correcting goodness-of-fit test for astronomical count data) that systematically solves this problem.

这篇教程的目标,是让你(大一物理本科生)不需要先学完概率论与数理统计,也能跟着代码把论文的核心算法跑起来,并理解为什么它工作。

The goal of this tutorial is to let you (a first-year physics undergraduate) run the paper's core algorithm by following the code, and understand why it works — without first having to finish a full probability course.

阅读路线 Reading path第 2 节复习你需要的最少概率知识;第 3–4 节讲论文的核心思想;第 5 节列出 4 个算法;第 6 节给你可运行的 Python 代码;第 7 节是论文中的真实案例。 Section 2 reviews the minimum probability you need; Sections 3–4 explain the paper's core idea; Section 5 lists four algorithms; Section 6 gives runnable Python code; Section 7 is the paper's real example.

2 预备知识:泊松分布与卡方检验Prerequisites: Poisson distribution and chi-squared test

2.1 泊松分布:计数数据的统计规律2.1 Poisson distribution: the statistics of counting

如果你在一段时间内数光子,每次数到的数量是随机的。这种"在固定时间间隔内随机事件发生的次数"服从泊松分布

If you count photons over a fixed time interval, the number you get is random. The number of random events in a fixed interval follows a Poisson distribution:

$$P(N_i = k) = \frac{s_i^k e^{-s_i}}{k!}, \quad k = 0, 1, 2, \ldots$$

这里 \(s_i\) 是第 \(i\) 个 bin 的期望计数——也就是"理论模型说这个 bin 平均应该有多少个光子"。\(N_i\) 是你实际数到的。

Here \(s_i\) is the expected count for bin \(i\) — "how many photons the theory says should be there on average." \(N_i\) is what you actually observed.

关键性质 Key property对于泊松分布,均值和方差相等:\(E[N_i] = s_i\),\(Var[N_i] = s_i\)。如果 sᵢ 只有 0.5,那么方差也只有 0.5——数据波动相对均值来说非常大。 For a Poisson distribution, the mean and variance are equal: \(E[N_i] = s_i\), \(Var[N_i] = s_i\). If sᵢ is only 0.5, the variance is also 0.5 — fluctuations are huge relative to the mean.

2.2 卡方检验:我们熟悉的"老朋友"2.2 Chi-squared test: the familiar "old friend"

在普通物理实验里,我们常用卡方检验来判断数据是否拟合模型:

In ordinary physics labs, we often use the chi-squared test to judge whether data fit a model:

$$\chi^2 = \sum_{i=1}^{n} \frac{(N_i - s_i)^2}{s_i}$$

如果模型正确,这个统计量应该服从自由度为 \(n-d\) 的卡方分布(\(n\) = bin 数,\(d\) = 拟合参数个数)。p 值 = 卡方分布尾部概率。

If the model is correct, this statistic should follow a chi-squared distribution with \(n-d\) degrees of freedom (\(n\) = number of bins, \(d\) = number of fitted parameters). p-value = chi-squared tail probability.

2.3 当卡方检验失效时2.3 When chi-squared fails

卡方检验有个隐藏的前提:每个 bin 的计数要"足够多"。统计学家通常要求每个 bin 的期望计数 ≥ 5。但在 X 射线天文里,很多 bin 的 sᵢ 远小于 1——因为高能光子本来就很少。

Chi-squared has a hidden requirement: each bin must have "enough" counts. Statisticians usually require expected count ≥ 5 per bin. But in X-ray astronomy, many bins have sᵢ far below 1 — because high-energy photons are naturally scarce.

核心问题 Core problem当 sᵢ 很小时,卡方分布近似失效。论文 Figure 2 显示:当所有 bin 的 sᵢ ≤ 0.5 时,LR-χ² 检验的 Type I 错误率远高于名义水平——你拒绝正确模型的可能性大增。 When sᵢ is small, the chi-squared approximation breaks down. The paper's Figure 2 shows: when all bins have sᵢ ≤ 0.5, the LR-χ² test's Type I error rate is far above the nominal level — you're much more likely to reject a correct model.

3 Cash C 统计量:天文学家的替代方案The Cash C statistic: the astronomer's alternative

3.1 从最大似然到 C 统计量3.1 From maximum likelihood to the C statistic

1979 年,天体物理学家 W. Cash 提出了一个专门为泊松数据设计的统计量。它直接来自于最大似然的思想:对每个 bin,泊松分布的似然函数是:

In 1979, astrophysicist W. Cash proposed a statistic designed specifically for Poisson data. It comes directly from maximum likelihood: for each bin, the Poisson likelihood is:

$$L(\theta) = \prod_{i=1}^{n} \frac{s_i(\theta)^{N_i} e^{-s_i(\theta)}}{N_i!}$$

取负对数似然,乘以 2(让它和卡方"长得像"),再减掉一个不依赖参数的常数,就得到 C 统计量:

Take the negative log-likelihood, multiply by 2 (to make it "look like" chi-squared), and subtract a constant that doesn't depend on parameters. You get the C statistic:

$$C_n(\theta) = 2 \sum_{i=1}^{n} \left[ s_i(\theta) - N_i \log s_i(\theta) - N_i + N_i \log N_i \right]$$

这个公式看起来复杂,但理解起来很简单:对每个 bin 计算 \(s_i - N_i \log s_i - N_i + N_i \log N_i\),乘以 2,加起来。当模型完美时(\(N_i = s_i\)),每一项都是 0,所以 C = 0。模型越差,C 越大。

The formula looks complex, but the idea is simple: for each bin compute \(s_i - N_i \log s_i - N_i + N_i \log N_i\), multiply by 2, and sum. When the model is perfect (\(N_i = s_i\)), each term is 0, so C = 0. The worse the model, the larger C.

注意 Note当 \(N_i = 0\) 时,\(N_i \log N_i\) 按极限处理为 0。这个边界情况在低计数数据里非常常见。 When \(N_i = 0\), the term \(N_i \log N_i\) is defined as 0 by limit. This edge case is very common in low-count data.

3.2 C 统计量的"标准做法"及其问题3.2 The "standard practice" and its problem

天文学家通常这样做:拟合模型得到参数估计 \(\hat{\theta}\),然后计算 \(C_n(\hat{\theta})\),把这个值当成一个 χ² 分布的随机变量,查表得到 p 值。自由度 = \(n - d\)(bin 数减参数个数)。

Astronomers usually do this: fit the model to get parameter estimate \(\hat{\theta}\), then compute \(C_n(\hat{\theta})\), treat it as a chi-squared random variable, and look up the p-value. Degrees of freedom = \(n - d\) (bins minus parameters).

这个做法的理论基础是Wilks 定理(1938):当样本量很大时,似然比统计量渐近服从卡方分布。但 Wilks 定理有前提条件(正则条件),在低计数场景下不满足。

The theoretical basis is Wilks' theorem (1938): for large samples, the likelihood ratio statistic asymptotically follows a chi-squared distribution. But Wilks' theorem has regularity conditions that are violated in the low-count regime.

论文的核心发现 The paper's core finding① 标准 χ² 近似在低计数时无效(Claim a);② 连"自助法"(bootstrap)这种看似通用的方法也有偏差(Claim b);③ 论文提出的修正 Z 检验能在低计算成本下达到最高精度(Claim c)。 ① The standard χ² approximation is invalid in low counts (Claim a); ② Even "bootstrap" — a seemingly universal method — is biased (Claim b); ③ The paper's Corrected Z-test achieves the best precision at low computational cost (Claim c).

4 核心思想:条件分布与高阶渐近Core idea: conditional distribution and high-order asymptotics

4.1 为什么"直接代入"不行?4.1 Why "plug-in" doesn't work

一个自然的想法:既然真实参数 \(\theta^*\) 未知,那就用估计值 \(\hat{\theta}\) 代替,直接计算 C 统计量的均值和方差。这叫朴素代入法(naïve plug-in)。

A natural idea: since the true parameter \(\theta^*\) is unknown, just use the estimate \(\hat{\theta}\) instead, and directly compute the mean and variance of the C statistic. This is the naïve plug-in approach.

论文证明(Proposition 4):这样做会引入 O(1) 量级的偏差——也就是说偏差不会随样本量增大而消失。更糟的是,连 bootstrap 也受影响,因为 bootstrap 本质上也是一种"代入"。

The paper proves (Proposition 4): this introduces a bias of order O(1) — meaning the bias doesn't vanish as the sample size grows. Worse, even bootstrap is affected, because bootstrap is essentially a form of "plug-in."

朴素代入法的偏差来源Source of bias in naïve plug-in: 真实参数True parameter θ* ──(MLE拟合fit)──→ 估计值Estimate θ̂ │ │ │ E[C|θ*] │ E[C|θ̂] ← 我们用这个we use this(正确均值)(correct mean)(有偏均值!)(biased mean!) │ │ └────── 差异Difference = O(1) 偏差bias ──────┘ ↑ 不随Doesn't vanish as n→∞ 消失!!

4.2 条件分布:McCullagh 的洞察4.2 Conditional distribution: McCullagh's insight

论文的关键灵感来自统计学家 McCullagh(1986)的一个洞察:当你已经拟合了模型、得到了 \(\hat{\theta}\) 之后,你关心的分布不是"在所有可能的 \(\hat{\theta}\) 下 C 的分布",而是"在这个特定的 \(\hat{\theta}\) 下 C 的分布"

The key inspiration comes from statistician McCullagh (1986): once you've fitted the model and obtained \(\hat{\theta}\), the distribution you care about is not "the distribution of C over all possible \(\hat{\theta}\)" but "the distribution of C given this particular \(\hat{\theta}\)".

类比 Analogy想象你在考试。你已经知道了你的分数(θ̂),你想知道"在分数等于你这种水平的人里,你排第几"。这就是条件分布。而无条件分布是"所有考生的分数分布"——包含了你不需要的信息。 Imagine taking an exam. You already know your score (θ̂), and you want to know "among people with your score level, where do you rank." That's the conditional distribution. The unconditional distribution is "all students' score distribution" — which includes information you don't need.

论文的定理 5 和定理 6 分别给出了无条件矩条件矩的渐近公式:

The paper's Theorem 5 and Theorem 6 give asymptotic formulas for unconditional moments and conditional moments respectively:

无条件矩(Theorem 5, Eq. 21):

Unconditional moments (Theorem 5, Eq. 21):

$$E[C_n(\hat{\theta})] = \kappa_1 - d + O(n^{-1})$$

$$\text{Var}[C_n(\hat{\theta})] = \kappa_2 + O(1)$$

条件矩(Theorem 6, Eqs. 22–23):

Conditional moments (Theorem 6, Eqs. 22–23):

$$E[C_n(\hat{\theta}) \mid \hat{\theta}] = \hat{\kappa}_1 - \frac{1}{2} \mathbf{1}^\top \hat{X}^\top \hat{\Sigma} \hat{X} (\hat{X}^\top \hat{V}^{-1} \hat{X})^{-1} \mathbf{1} + O(n^{-1/2})$$

$$\text{Var}[C_n(\hat{\theta}) \mid \hat{\theta}] = \hat{\kappa}_2 - \hat{\kappa}_{11}^\top \hat{X} (\hat{X}^\top \hat{V}^{-1} \hat{X})^{-1} \hat{X}^\top \hat{\kappa}_{11} + O(1)$$

这里 \(\kappa_1, \kappa_2\) 是 C 函数的各阶累积量(cumulants),\(X\) 是设计矩阵(模型对参数的梯度),\(V = \text{diag}(s_i)\),\(\kappa_{11}\) 是 C 函数和计数之间的协方差。看起来复杂,但核心思想很简洁:

Here \(\kappa_1, \kappa_2\) are cumulants of the C function, \(X\) is the design matrix (gradient of the model wrt parameters), \(V = \text{diag}(s_i)\), and \(\kappa_{11}\) is the covariance between C and the counts. It looks complex, but the core idea is clean:

核心思想 Core idea条件矩 = 无条件矩 − 一个修正项。这个修正项取决于模型对参数的敏感度(设计矩阵 X)和数据的统计性质(累积量)。它把"拟合参数带来的信息"从方差中"扣除"——因为这部分信息已经被用来估计参数了。 Conditional moments = unconditional moments − a correction term. This correction depends on the model's sensitivity to parameters (design matrix X) and the data's statistical properties (cumulants). It "subtracts" the information already used for parameter fitting from the variance — because that information is already "spent."

4.3 高阶修正:把偏差算出来4.3 High-order correction: computing the bias

论文的定理 2 给出了一个可计算的检验统计量:

The paper's Theorem 2 gives a computable test statistic:

$$T = \frac{C_n(\hat{\theta}) - E[C_n(\hat{\theta}) \mid \hat{\theta}]}{\sqrt{\text{Var}(C_n(\hat{\theta}) \mid \hat{\theta})}}$$

在零假设(模型正确)下,T 渐近服从标准正态分布 \(N(0,1)\)。所以 p 值 = 标准正态的尾部概率。

Under the null hypothesis (model is correct), T asymptotically follows the standard normal \(N(0,1)\). So p-value = standard normal tail probability.

修正 Z 检验的工作流程Corrected Z-test workflow: 观测计数Observed counts N_i ──(拟合fit)──→ 参数Parameter θ̂ ──(模型model)──→ 期望Expected s_i(θ̂) │ │ │ ┌─────────┤ │ ↓ ↓ │ 计算Compute C_n(θ̂) 计算累积量Compute cumulants κ₁, κ₂, κ₁₁ │ │ │ │ └────┬────┘ │ ↓ │ 条件矩Conditional moments E[C|θ̂], Var[C|θ̂] │ │ └──────────────────────────────────→ Z = (C - E[C|θ̂]) / √Var[C|θ̂] │ ↓ p = 1 - Φ(Z)

5 四种算法:从简单到精确Four algorithms: from simple to accurate

论文比较了 4 种算法,按精确度从低到高排列:

The paper compares 4 algorithms, ordered by accuracy:

# 名称 Name 适用条件 When valid 计算量 Cost
1 LR-χ² LR-χ² sᵢ > 1 均匀 sᵢ > 1 uniformly O(n) O(n)
2c 朴素 Z Naïve Z 大计数 Large counts O(n) O(n)
3b 修正 Z Corrected Z 所有计数(推荐) All counts (recommended) O(n·d²) O(n·d²)
4 参数化自助 Parametric Bootstrap 所有计数 All counts O(B·n·fit) O(B·n·fit)

5.1 算法 1:LR-χ² 检验5.1 Algorithm 1: LR-χ² test

最传统的方法:直接把 C 统计量当 χ² 分布处理。p 值通过卡方分布的累积分布函数计算:

The most traditional method: treat the C statistic as chi-squared. p-value via the chi-squared CDF:

$$p = 1 - F_{\chi^2_{n-d}}(C_n(\hat{\theta}))$$

优点:快,一行代码。缺点:低计数时 p 值系统性偏低(过度拒绝)。

Pros: fast, one line of code. Cons: systematically underestimates p-values at low counts (over-rejection).

5.2 算法 2c:朴素 Z 检验5.2 Algorithm 2c: Naïve Z-test

用无条件矩公式(Theorem 5)计算均值和方差,然后做 Z 检验。比 χ² 好一点,但仍然有偏差:

Use unconditional moments (Theorem 5) to compute mean and variance, then do a Z-test. Better than χ², but still biased:

$$Z = \frac{C_n(\hat{\theta}) - (\kappa_1 - d)}{\sqrt{\kappa_2}}, \quad p = 1 - \Phi(Z)$$

偏差来源:用 \(\hat{\theta}\) 代替 \(\theta^*\) 时的 O(1) 偏差(Proposition 4)。

Bias source: O(1) bias from replacing \(\theta^*\) with \(\hat{\theta}\) (Proposition 4).

5.3 算法 3b:修正 Z 检验(推荐)5.3 Algorithm 3b: Corrected Z-test (recommended)

论文的核心贡献。使用条件矩(Theorem 6)而非无条件矩,从而修正了偏差:

The paper's core contribution. Uses conditional moments (Theorem 6) instead of unconditional moments, thereby correcting the bias:

$$Z = \frac{C_n(\hat{\theta}) - E[C_n(\hat{\theta}) \mid \hat{\theta}]}{\sqrt{\text{Var}(C_n(\hat{\theta}) \mid \hat{\theta})}}, \quad p = 1 - \Phi(Z)$$

条件矩需要计算一个修正项 \(Q(\hat{\theta})\),它涉及设计矩阵 \(X\) 和累积量 \(\kappa_{11}\)。计算量 \(O(n \cdot d^2)\)(\(d\) 是参数个数),通常 \(d \leq 5\),所以几乎和算法 1 一样快。

The conditional moments require computing a correction term \(Q(\hat{\theta})\) involving the design matrix \(X\) and cumulant \(\kappa_{11}\). Computational cost is \(O(n \cdot d^2)\) (where \(d\) is the number of parameters, typically ≤ 5), so it's nearly as fast as Algorithm 1.

为什么条件矩更好? Why are conditional moments better?条件矩"知道"你已经用数据拟合了参数——所以它从方差中扣除了"被参数拟合消耗掉的那部分信息"。这就像你考试时知道了答案已经包含了部分信息,不能重复计算。 Conditional moments "know" you've already used the data to fit parameters — so they subtract "the information spent on parameter fitting" from the variance. It's like knowing that the answer already contains some information, and not double-counting it.

5.4 算法 4:参数化自助法5.4 Algorithm 4: Parametric bootstrap

"黄金标准":用 \(\hat{\theta}\) 生成 B 组模拟数据,每组重新拟合,计算 C 统计量,看真实 C 值在模拟分布中的分位数:

The "gold standard": use \(\hat{\theta}\) to generate B simulated datasets, refit each, compute C statistics, and see where the observed C falls in this simulated distribution:

$$p = \frac{1}{B} \sum_{m=1}^{B} \mathbf{1}\{C_n(\hat{\theta}^{[m]}) \geq C_n(\hat{\theta})\}$$

优点:最精确。缺点:慢(需要 B 次拟合,通常 B=1000–10000)。

Pros: most accurate. Cons: slow (needs B refits, typically B=1000–10000).

6 动手实践:Python 代码实现Hands-on: Python implementation

下面是论文 4 个算法的完整 Python 实现。你只需要 numpy 和 scipy(两个最常用的科学计算包)。点击代码块可以复制。

Below is the complete Python implementation of the paper's four algorithms. You only need numpy and scipy (the two most common scientific computing packages). Click any code block to copy.

第一步:计算 C 统计量

Step 1: Compute the C statistic

import numpy as np
from scipy.stats import chi2, norm, poisson

def c_stat(n_obs, s_model):
    """Cash C statistic: C_n(θ) = 2 Σ [sᵢ − Nᵢ log sᵢ − Nᵢ + Nᵢ log Nᵢ]"""
    n_obs = np.asarray(n_obs, dtype=float)
    s_model = np.asarray(s_model, dtype=float)
    with np.errstate(divide='ignore', invalid='ignore'):
        ratio = np.where(n_obs > 0, n_obs / s_model, 1.0)
        term = np.where(n_obs > 0, n_obs * np.log(ratio), 0.0)
    return 2.0 * float(np.sum(s_model - n_obs + term))

第二步:计算泊松累积量

Step 2: Compute Poisson cumulants

累积量是描述随机变量分布的数学量。对泊松分布,我们可以通过数值求和精确计算每个 bin 的 C 函数的各阶累积量:

Cumulants are mathematical quantities describing a random variable's distribution. For Poisson data, we can compute each bin's C-function cumulants exactly via numerical summation:

def cumulants_poisson(s):
    """Compute per-bin cumulants of the C function for Poisson(s)."""
    k1 = np.zeros_like(s)
    k2 = np.zeros_like(s)
    k11 = np.zeros_like(s)
    for i, si in enumerate(s):
        max_n = max(int(si + 10*np.sqrt(si) + 50), 100)
        n_vals = np.arange(max_n + 1)
        pmf = poisson.pmf(n_vals, si)
        log_n = np.where(n_vals > 0, np.log(n_vals), 0.0)
        c_i = 2.0 * (si - n_vals*np.log(si) - n_vals + n_vals*log_n)
        k1[i] = np.sum(c_i * pmf)
        k2[i] = np.sum((c_i - k1[i])**2 * pmf)
        k11[i] = np.sum((c_i - k1[i]) * (n_vals - si) * pmf)
    return k1, k2, k11

第三步:实现 4 个算法

Step 3: Implement the four algorithms

# --- Algorithm 1: LR-χ² test ---
def lr_chi2_pvalue(c_val, n_bins, n_params):
    """p = P(χ²_{n-d} ≥ C)"""
    dof = n_bins - n_params
    return 1.0 - chi2.cdf(c_val, df=dof)

# --- Algorithm 2c: Naïve Z-test ---
def naive_z_pvalue(c_val, s_hat, n_params):
    """Plug-in unconditional moments (Theorem 5)"""
    k1, k2, _ = cumulants_poisson(s_hat)
    mu = np.sum(k1) - n_params      # E[C] = κ₁ - d
    sigma2 = np.sum(k2)             # Var[C] = κ₂
    z = (c_val - mu) / np.sqrt(sigma2)
    return 1.0 - norm.cdf(z)

# --- Algorithm 3b: Corrected Z-test (recommended) ---
def corrected_z_pvalue(c_val, s_hat, theta_hat, s_func, n_params):
    """Conditional moments (Theorem 6) with bias correction"""
    k1, k2, k11 = cumulants_poisson(s_hat)
    # Unconditional moments
    mu = np.sum(k1) - n_params
    sigma2 = np.sum(k2)
    # Design matrix X via finite differences
    eps = 1e-6
    d = len(theta_hat)
    n_bins = len(s_hat)
    X = np.zeros((n_bins, d))
    for j in range(d):
        tp = theta_hat.copy(); tp[j] += eps
        tm = theta_hat.copy(); tm[j] -= eps
        X[:, j] = (s_func(tp) - s_func(tm)) / (2*eps)
    Vinv = np.diag(1.0 / np.maximum(s_hat, 1e-12))
    XtVinvX = X.T @ Vinv @ X
    I_inv = np.linalg.inv(XtVinvX)
    # Correction term Q(θ̂) = κ₁₁ᵀ X I⁻¹ Xᵀ κ₁₁
    Q = k11.T @ X @ I_inv @ X.T @ k11
    # Conditional moments
    cond_mean = mu
    cond_var = sigma2 - Q
    z = (c_val - cond_mean) / np.sqrt(cond_var)
    return 1.0 - norm.cdf(z)

# --- Algorithm 4: Parametric Bootstrap ---
def bootstrap_pvalue(c_val, s_hat, s_func, fit_func, B=1000):
    """Gold standard: simulate B datasets"""
    rng = np.random.default_rng(42)
    boot_c = np.zeros(B)
    for m in range(B):
        n_boot = rng.poisson(s_hat)
        theta_boot = fit_func(n_boot)
        s_boot = s_func(theta_boot)
        term = np.where(n_boot > 0, n_boot*np.log(n_boot/s_boot), 0.0)
        boot_c[m] = 2.0 * np.sum(s_boot - n_boot + term)
    return np.mean(boot_c >= c_val)

第四步:跑一个简单例子

Step 4: Run a simple example

# Simulate: power-law spectrum s(E) = K * E^(-Gamma)
# 幂律谱模型: s(E) = K * E^(-Γ)
energies = np.linspace(1.0, 10.0, 50)
theta_true = np.array([5.0, 2.0])  # K=5, Γ=2

def s_func(theta):
    K, Gamma = theta
    return K * energies**(-Gamma)

# Generate observed counts / 生成观测计数
rng = np.random.default_rng(42)
s_true = s_func(theta_true)
n_obs = rng.poisson(s_true)
print(f"Mean counts per bin: {s_true.mean():.2f}")
print(f"Min counts per bin:  {s_true.min():.2f}")

# Compute C statistic / 计算 C 统计量
c_val = c_stat(n_obs, s_true)
print(f"C statistic: {c_val:.3f}")

# Compare all 4 algorithms / 比较 4 个算法
p1 = lr_chi2_pvalue(c_val, 50, 2)
p2 = naive_z_pvalue(c_val, s_true, 2)
p3 = corrected_z_pvalue(c_val, s_true, theta_true, s_func, 2)
# p4 = bootstrap_pvalue(c_val, s_true, s_func, fit_func, B=1000)

print(f"Algorithm 1 (LR-χ²):        p = {p1:.4f}")
print(f"Algorithm 2c (Naïve Z):     p = {p2:.4f}")
print(f"Algorithm 3b (Corrected Z): p = {p3:.4f}")
预期结果 Expected result如果 sᵢ 都比较大(>5),4 个算法的 p 值应该接近。如果 sᵢ 很小(<1),LR-χ² 会系统性偏低(过度拒绝),而修正 Z 检验与 bootstrap 更接近。 If all sᵢ are large (>5), all four p-values should be close. If sᵢ are small (<1), LR-χ² will be systematically too low (over-rejection), while the Corrected Z-test will be closer to bootstrap.

7 真实案例:PG 1116+215 类星体 X 射线光谱Real example: PG 1116+215 quasar X-ray spectrum

论文用 Chandra HETG/LETG 观测的类星体 PG 1116+215 的 X 射线光谱来验证算法。这个数据有 159 个能量 bin,典型 bin 的期望计数在 0.1–2 之间——正好是低计数区间。

The paper validates the algorithms using the Chandra HETG/LETG X-ray spectrum of quasar PG 1116+215. This dataset has 159 energy bins, with typical expected counts per bin in the 0.1–2 range — squarely in the low-count regime.

数据集 Dataset C_n(θ̂) LR-χ² p LR-χ² p 修正 Z p Corrected Z p Bootstrap p Bootstrap p
源(2002) Source (2002) 190.72 0.039 0.039 0.053 0.053 0.057 0.057
源(2018) Source (2018) 167.67 0.28 0.28 0.31 0.31 0.33 0.33
背景(2002) Background (2002) 0.22 0.22 0.24 0.24 0.26 0.26
背景(2018) Background (2018) 0.59 0.59 0.61 0.61 0.62 0.62
关键发现 Key finding在 2002 年的源数据中,LR-χ² 给出 p = 0.039 < 0.05——拒绝模型。但修正 Z 检验和 bootstrap 都给出 p ≈ 0.05–0.06——不拒绝。LR-χ² 产生了假阳性 For the 2002 source data, LR-χ² gives p = 0.039 < 0.05 — reject the model. But the Corrected Z-test and bootstrap both give p ≈ 0.05–0.06 — do not reject. LR-χ² produced a false positive!

这正是低计数场景下 χ² 近似的典型问题:它系统性低估 p 值,导致天文学家可能"发现"实际上并不存在的模型偏差。修正 Z 检验通过条件矩修正,避免了这个问题。

This is the classic problem of chi-squared in low-count settings: it systematically underestimates p-values, leading astronomers to potentially "discover" model deviations that don't actually exist. The Corrected Z-test, through conditional moment correction, avoids this problem.

8 总结与展望Summary and outlook

这篇论文做了什么?

What did the paper do?

  1. 证明了传统 χ² 近似在低计数泊松数据中无效Proved that the traditional χ² approximation is invalid for low-count Poisson data.
  2. 证明了朴素代入法(包括 bootstrap)存在 O(1) 偏差。Proved that naïve plug-in methods (including bootstrap) have O(1) bias.
  3. 利用 McCullagh 的条件分布思想,推导出条件矩的高阶渐近公式。Using McCullagh's conditional distribution idea, derived high-order asymptotic formulas for conditional moments.
  4. 提出了修正 Z 检验(Algorithm 3b),在低计算成本下达到最高精度。Proposed the Corrected Z-test (Algorithm 3b), achieving the best precision at low computational cost.
  5. 用真实天文数据验证了算法的有效性。Validated the algorithms with real astronomical data.

为什么这对天文学很重要?

Why does this matter for astronomy?

X 射线天文、γ 射线天文、中微子天文——这些领域的数据本质上都是低计数泊松数据。天文学家每天都在用 C 统计量做拟合优度检验,但很多人不知道在低计数时 χ² 近似会失效。这篇论文给出了一个实际可用的修正方案

X-ray astronomy, γ-ray astronomy, neutrino astronomy — these fields all deal with low-count Poisson data by nature. Astronomers use the C statistic for goodness-of-fit every day, but many don't know that the χ² approximation fails at low counts. This paper provides a practical correction.

如果你想知道更多

If you want to know more

一句话总结 One-line summary当计数很少时,别再用 χ² 分布算 p 值了——用条件矩做修正 Z 检验,快又准。 When counts are low, stop using the χ² distribution for p-values — use the Corrected Z-test with conditional moments: fast and accurate.