편향보정 가속 붓스트랩 (BCa)¶
동기¶
백분위수법은 붓스트랩 분포에서 신뢰구간의 끝점을 직접 읽는다. 단순하지만 붓스트랩 분포에 편향이 있거나 \(\hat{\theta}\)의 표준오차가 \(\theta\)에 의존할 때(즉 표본분포가 치우쳐 있을 때) 포함확률이 나빠질 수 있다. Efron(1987)이 도입한 편향보정 가속(BCa) 방법은 이 두 문제를 모두 보정하도록 백분위수 구간을 조정한다.
BCa 구간은 범용 붓스트랩 신뢰구간 중 최선으로 널리 평가된다. 백분위수법의 변환 불변성을 유지하면서 2차 정확도(포함확률 오차가 \(O(n^{-1/2})\) 대신 \(O(n^{-1})\))를 달성한다.
BCa 구간¶
BCa 구간은 백분위수 구간과 같은 형태이지만 조정된 분위수를 쓴다.
여기서 \(\alpha_1\)과 \(\alpha_2\)가 백분위수법의 단순한 \(\alpha/2\)와 \(1 - \alpha/2\)를 대신한다. 조정된 수준은
이다. \(\Phi\)는 표준정규 누적분포함수, \(z_q = \Phi^{-1}(q)\)는 \(q\)번째 표준정규 분위수이며, 두 보정계수 \(\hat{z}_0\)과 \(\hat{a}\)는 아래에서 정의한다.
편향보정 계수¶
편향보정 \(\hat{z}_0\)은 붓스트랩 분포의 중심이 관측 추정값 \(\hat{\theta}\)에서 얼마나 떨어져 있는지를 잰다.
이는 관측 통계량보다 아래에 있는 붓스트랩 복제값의 비율을 \(z\) 점수로 변환한 것이다. 붓스트랩 분포가 정확히 \(\hat{\theta}\)에 중심을 두면 복제값의 절반이 아래에 있어 \(\hat{z}_0 = \Phi^{-1}(0.5) = 0\)이 되고, 편향보정이 아무 효과를 내지 않는다.
\(\hat{z}_0 \neq 0\)이면 붓스트랩 분포가 \(\hat{\theta}\)에 대해 편향되어 있다는 뜻이고, BCa 구간은 이를 보상하도록 분위수 절단점을 옮긴다.
편향보정의 해석
\(\hat{z}_0\)이 양수라는 것은 붓스트랩 복제값의 절반 이상이 \(\hat{\theta}\)를 넘는다는 뜻이 아니라 절반 미만이 넘는다는 뜻이다. 정의상 \(\hat z_0 > 0\)은 \(\hat\theta\)보다 작은 복제값의 비율이 \(0.5\)를 넘는 경우, 즉 붓스트랩 분포가 \(\hat{\theta}\)에 대해 아래로 치우친 경우이다. 이때 BCa는 분위수를 위로 옮긴다.
가속 계수¶
가속 \(\hat{a}\)는 \(\theta\)가 변할 때 \(\hat{\theta}\)의 표준오차가 어떻게 변하는지를 잰다. 표준오차가 일정하면(\(\theta\)와 무관하면) 가속이 \(0\)이고 BCa 구간이 편향보정(BC) 구간으로 환원된다. 표준오차가 \(\theta\)에 의존하면 가속이 치우침을 반영하여 분위수 절단점을 조정한다.
가속은 보통 잭나이프로 추정한다.
여기서 \(\hat{\theta}_{(-i)} = g(x_1, \ldots, x_{i-1}, x_{i+1}, \ldots, x_n)\)은 \(i\)번째 관측값을 뺀 통계량이고 \(\bar{\hat{\theta}}_{(\cdot)} = \frac{1}{n}\sum_{i=1}^{n}\hat{\theta}_{(-i)}\)는 잭나이프 값들의 평균이다.
분자는 잭나이프 분포의 치우침을, 분모는 그것을 정규화한다. 이 공식은 밑에 깔린 변환모형에서 가속 상수의 일치추정량이다.
알고리즘¶
- 원표본에서 관측 통계량 \(\hat{\theta}\)를 계산한다.
- \(B\)개의 붓스트랩 복제값 \(\hat{\theta}^{*(1)}, \ldots, \hat{\theta}^{*(B)}\)을 생성한다.
- 편향보정 \(\hat{z}_0\)을 계산한다.
- \(\hat{\theta}\)보다 작은 복제값의 비율을 센다.
- \(\hat{z}_0 = \Phi^{-1}(\text{비율})\)로 \(z\) 점수로 변환한다.
- 가속 \(\hat{a}\)를 계산한다.
- 각 \(i = 1, \ldots, n\)에 대해 \(\hat{\theta}_{(-i)}\)를 계산한다(하나씩 제거).
- 위 치우침 공식을 적용한다.
- 조정된 분위수 수준 \(\alpha_1\)과 \(\alpha_2\)를 계산한다.
- BCa 구간은 \([\hat{\theta}^*_{(\alpha_1)}, \hat{\theta}^*_{(\alpha_2)}]\)이다.
계산비용
잭나이프 단계는 \(\hat{\theta}\)를 추가로 \(n\)번 계산해야 한다(하나씩 제거한 표본마다 한 번). 계산이 무거운 통계량이나 큰 \(n\)에서는 \(B\)개의 붓스트랩 복제값에 더해 상당한 부담이 된다.
특수한 경우¶
\(\hat{z}_0 = 0\)이고 \(\hat{a} = 0\)이면 조정된 분위수가 \(\alpha_1 = \alpha/2\), \(\alpha_2 = 1 - \alpha/2\)로 환원되어 보통의 백분위수 구간이 된다.
\(\hat{a} = 0\)이지만 \(\hat{z}_0 \neq 0\)이면 편향보정(BC) 구간이라 부른다. 편향은 보정하지만 치우침은 보정하지 않는다.
두 보정이 모두 작동하면 BCa 구간이 백분위수 구간과 상당히 다른 끝점을 낼 수 있다. 표본분포가 치우친 통계량(분산, 오즈비, \(\pm 1\)에 가까운 상관계수)에서 특히 그렇다.
이론적 성질¶
BCa 구간은 2차 정확도를 달성한다. 포함확률이
를 만족하며, 이는 백분위수 구간의 \(1 - \alpha + O(n^{-1/2})\)와 대비된다. 표본크기가 커질수록 포함확률 오차가 더 빨리 줄어든다는 뜻이다.
BCa 구간은 변환 불변이기도 하다. 임의의 단조증가 함수 \(m\)에 대해, \([L, U]\)가 \(\theta\)의 BCa 구간이면 \(\phi = m(\theta)\)의 BCa 구간은 \([m(L), m(U)]\)이다.
(엄밀히 말하면 이 성질은 참 가속 상수 $a$를 쓸 때 정확하다. 잭나이프 추정값 $\hat a$는 변환 아래에서 1차 근사로만 보존되므로 실제 구현에서는 근사적 불변성이 된다. 연습문제 4에서 그 크기를 확인한다.)
2차 정확도가 왜 중요한가
\(n = 20\)인 95% 구간에서 1차 정확도는 실제 포함확률 90%를, 2차 정확도는 보통 93--95%를 준다. 개선 효과는 중간 정도의 표본크기와 치우친 통계량에서 가장 두드러진다.
오른쪽으로 치우친 분포에서 \(n = 15\)인 표본을 얻었고 표본분산이 \(s^2 = 8.4\)라 하자. 붓스트랩 복제값을 \(B = 10{,}000\)개 만들었더니 그중 \(62\%\)가 \(8.4\) 아래에 있었고, 잭나이프로 구한 가속이 \(\hat a = 0.042\)였다고 하자.
보기 1. 분산의 BCa 구간. 위 설정에서 \(\hat z_0 = \Phi^{-1}(0.62)\)이다.
(1) \(95\%\) 구간의 조정된 분위수 \(\alpha_1\), \(\alpha_2\)를 구하시오. 또 이 공식이 쓸 수 없게 되는 조건을 적고, 이 \(\hat z_0\)에서 \(\hat a\)가 얼마를 넘으면 상한이 깨지는지 구하시오.
(2) 확인하시오. \(B = 10{,}000\)에서 \(99.75\) 백분위수를 읽는다는 것은 복제값 몇 개에 기대는 일인가. 그 값이 \(97.5\) 백분위수보다 얼마나 더 흔들리는지 재어 보시오.
풀이
(1) 해석적으로. \(\hat z_0 = \Phi^{-1}(0.62) = 0.305481\)이고 \(z_{0.025} = -1.959964\), \(z_{0.975} = +1.959964\)다. 조정식에 그대로 넣는다.
두 절단점이 모두 위로 옮겨졌다. \(2.5\%\)가 \(10.7\%\)로, \(97.5\%\)가 \(99.75\%\)로 간다.
공식이 깨지는 조건. 분모 \(1 - \hat a(\hat z_0 + z)\)가 \(0\)이 되면 지수가 발산하고, 음수가 되면 부호가 뒤집혀 상한이 하한 아래로 내려간다. 곧
이 위험 구역이다. 여기서는 \(\hat a \ge 1/(0.305481 + 1.959964) = 0.441414\)다. 실제 자료에서 \(\hat a\)는 \(\hat\gamma_1/(6\sqrt n)\) 수준이라 이만큼 커지는 일은 드물지만, \(n\)이 아주 작고 자료가 극단적으로 치우치면 일어날 수 있다. \(\hat a\)를 계산하면 이 문턱과 견주어 보는 것이 안전하다. \(\hat a > 0\)일 때 하한 쪽은 \(\hat z_0 + z_{\alpha/2}\)가 음수라 분모가 \(1\)보다 커지므로 깨지지 않는다.
(2) 수치적으로.
from scipy.stats import norm
z0, a = norm.ppf(0.62), 0.042
for z in (-1.959964, 1.959964):
v = z0 + (z0 + z) / (1 - a * (z0 + z))
print(round(v, 4), round(norm.cdf(v), 4))
# -1.2415 0.1072
# 2.8091 0.9975
출력:
-1.2415 0.1072
2.8091 0.9975
조정된 자리를 실제로 읽는 일이 얼마나 안정한지 잰다. \(s^2 = 8.4\)가 되도록 맞춘 치우친 표본에서 \(B = 10{,}000\)짜리 붓스트랩을 씨앗만 바꾸어 \(20\)번 돌린다.
import numpy as np
print(f"z0 = {z0:.6f}, 공식이 깨지는 a = 1/(z0+1.96) = {1 / (z0 + 1.959964):.6f}")
rng = np.random.default_rng(2)
x = rng.exponential(1.0, 15)
x = x / x.std(ddof=1) * np.sqrt(8.4) # 표본분산을 8.4 로 맞춘다
print(f"s^2 = {x.var(ddof=1):.4f}")
B = 10_000
qs = {p: [] for p in (2.5, 10.72, 97.5, 99.75)}
for s in range(20):
r = np.random.default_rng(100 + s)
bs = np.var(x[r.integers(0, 15, (B, 15))], axis=1, ddof=1)
for p in qs:
qs[p].append(np.percentile(bs, p))
print(f"\n{'백분위점':>9}{'평균':>10}{'SD':>9}{'꼬리에 남는 복제값':>20}")
for p in (2.5, 10.72, 97.5, 99.75):
v = np.array(qs[p])
tail = min(p, 100 - p) / 100 * B
print(f"{p:>9}{v.mean():>10.4f}{v.std(ddof=1):>9.4f}{tail:>20.0f}")
출력:
z0 = 0.305481, 공식이 깨지는 a = 1/(z0+1.96) = 0.441414
s^2 = 8.4000
백분위점 평균 SD 꼬리에 남는 복제값
2.5 2.7828 0.0376 250
10.72 4.1762 0.0390 1072
97.5 13.9051 0.0886 250
99.75 16.5086 0.2320 25
BCa 구간은 \(2.5\)와 \(97.5\) 대신 붓스트랩 분포의 \(10.7\)과 \(99.75\) 백분위수를 쓴다. 두 절단점이 모두 위로 이동했으며, 이는 양의 편향보정과 양의 가속을 함께 반영한 것이다. 오른쪽으로 치우친 \(s^2\)의 분포에 맞게 구간이 위쪽으로 늘어난다.
대가는 상한의 불안정이다. \(99.75\) 백분위수는 \(10{,}000\)개 가운데 위쪽 \(25\)개에만 기대는 값이고, 씨앗을 바꾸면 \(16.51 \pm 0.23\)으로 흔들린다. 같은 실험에서 \(97.5\) 백분위수는 \(250\)개에 기대어 \(\pm 0.09\)다. 보정이 상한을 꼬리로 밀어낼수록 그 자리를 읽는 일이 어려워진다. 하한 쪽은 반대로 \(2.5\%\)에서 \(10.72\%\)로 안쪽으로 들어와 복제값 \(1{,}072\)개에 기대므로 사정이 낫다(\(\pm 0.039\)).
그러므로 BCa를 쓸 때는 \(B\)를 백분위수법보다 넉넉히 잡아야 한다. 어림으로는 조정된 꼬리 확률 \(\min(\alpha_1, 1-\alpha_2)\)에 \(B\)를 곱한 값이 수백은 되도록 하는 것이 좋고, 여기서는 \(B = 10{,}000\)이 겨우 \(25\)개를 남기므로 \(B = 100{,}000\) 쪽이 알맞다.
BCa 가 바꾸는 것은 분포가 아니라 읽는 자리¶
여기서 짚어 둘 것이 있다. BCa는 붓스트랩 복제값을 하나도 건드리지 않는다. 백분위수법과 똑같은 히스토그램을 놓고, 어느 지점을 끝점으로 삼을지만 바꾼다.

실제 자료로 보자. \(\text{Exp}(1)\)에서 \(n = 20\)을 뽑아 분산을 추정하면 \(s^2 = 1.615\)가 나왔고, 이 표본의 두 보정계수는 \(\hat{z}_0 = 0.146\), \(\hat{a} = 0.097\)이다. 왼쪽 두 칸이 공식을 그림으로 옮긴 것이다. 가속 \(\hat{a}\)가 커질수록 두 절단점이 모두 위로 올라가는데, 위 칸의 상한은 \(97.5\%\)에서 \(99.7\%\)로, 아래 칸의 하한은 \(2.5\%\)에서 \(8.1\%\)로 옮겨 간다. 점선은 \(\hat{z}_0 = 0\)일 때의 자리이고 실선과의 간격이 편향보정의 몫이다.
오른쪽 칸이 그 결과다. 백분위수 구간은 \([0.44,\ 2.70]\), BCa 구간은 \([0.70,\ 3.12]\)로 통째로 오른쪽으로 밀렸다. 하한이 \(0.44\)에서 \(0.70\)으로 올라간 것이 특히 크다. 참값 \(\sigma^2 = 1\)이 두 구간 모두에 들어 있지만, 연습문제 2에서 확인하듯 이런 이동이 \(n = 20\)에서 포함확률을 \(0.686\)에서 \(0.756\)으로 끌어올린다. 오른쪽으로 치우친 \(s^2\)의 표본분포에서는 참값이 추정값보다 아래에 있는 경우가 훨씬 많고, BCa는 그쪽에 여유를 더 주는 것이 아니라 위쪽 꼬리를 길게 잡아 구간 전체를 참값이 놓인 방향으로 재배치한다.
그러므로 BCa의 효과는 두 계수의 크기가 결정한다. 왼쪽 그림에서 \(\hat{a} = 0\)이고 \(\hat{z}_0 = 0\)이면 두 점이 정확히 회색 점선(\(2.5\%\), \(97.5\%\)) 위에 앉아 백분위수 구간이 된다. 연습문제 1의 지침 "\(|\hat{z}_0|\)과 \(|\hat{a}|\)가 모두 \(0.05\) 아래면 잭나이프를 아껴도 좋다"는 말은, 그 경우 그림의 두 점이 점선에서 거의 떨어지지 않는다는 뜻이다.
연습문제¶
연습문제 1. 백분위수 구간과 BCa 구간의 차이를 설명하라. BCa 구간이 백분위수 구간과 크게 달라지는 것은 언제인가?
풀이
두 구간은 같은 붓스트랩 복제값에서 다른 분위수를 읽는다는 점만 다르다.
| 하한 분위수 | 상한 분위수 | |
|---|---|---|
| 백분위수 | \(\alpha/2\) | \(1 - \alpha/2\) |
| BCa | \(\alpha_1 = \Phi\!\left(\hat z_0 + \frac{\hat z_0 + z_{\alpha/2}}{1 - \hat a(\hat z_0 + z_{\alpha/2})}\right)\) | \(\alpha_2\) (같은 꼴, \(z_{1-\alpha/2}\) 사용) |
따라서 \(\hat z_0\)과 \(\hat a\)가 모두 \(0\)이면 두 구간이 정확히 같다.
크게 달라지는 조건은 셋이다.
-
\(\hat{z}_0\)이 클 때. 붓스트랩 분포의 중앙값이 \(\hat{\theta}\)에서 멀 때이다. 편향된 추정량(예: \(\bar{X}^2\)으로 \(\mu^2\) 추정)이나 경계 근처의 모수에서 발생한다.
-
\(\hat{a}\)가 클 때. 표준오차가 \(\theta\)에 강하게 의존할 때이다. 분산, 비율, 오즈비처럼 척도 모수가 관여하는 통계량에서 흔하다.
-
꼬리 밀도가 낮을 때. 같은 \(\alpha_1\) 변화라도 붓스트랩 분포의 꼬리가 평평하면 끝점이 크게 움직인다.
조정의 크기를 어림하려면 \(z\) 척도에서의 이동을 보는 것이 편하다. 본문 보기에서 하한이 \(z = -1.96\)에서 \(-1.24\)로 \(0.72\)만큼 이동했다. 표준정규 척도에서 \(0.72\sigma\)는 큰 이동이다.
import numpy as np
from scipy.stats import norm
for z0 in (0.0, 0.1, 0.3):
for a in (0.0, 0.05, 0.15):
zl = -1.959964
v = z0 + (z0 + zl) / (1 - a * (z0 + zl))
print(f"z0={z0:.1f} a={a:.2f} -> alpha1={norm.cdf(v):.4f}")
출력:
z0=0.0 a=0.00 -> alpha1=0.0250
z0=0.0 a=0.05 -> alpha1=0.0371
z0=0.0 a=0.15 -> alpha1=0.0649
z0=0.1 a=0.00 -> alpha1=0.0392
z0=0.1 a=0.05 -> alpha1=0.0546
z0=0.1 a=0.15 -> alpha1=0.0878
z0=0.3 a=0.00 -> alpha1=0.0869
z0=0.3 a=0.05 -> alpha1=0.1088
z0=0.3 a=0.15 -> alpha1=0.1517
| \(\hat z_0\) | \(\hat a\) | \(\alpha_1\) | 백분위수 대비 |
|---|---|---|---|
| 0.0 | 0.00 | 0.0250 | 동일 |
| 0.0 | 0.05 | 0.0371 | 조금 위 |
| 0.0 | 0.15 | 0.0649 | 뚜렷이 위 |
| 0.1 | 0.00 | 0.0392 | 조금 위 |
| 0.1 | 0.15 | 0.0878 | 크게 위 |
| 0.3 | 0.00 | 0.0869 | 크게 위 |
| 0.3 | 0.15 | 0.1517 | 매우 크게 위 |
\(\hat z_0 = 0.3\), \(\hat a = 0.15\)이면 하한 분위수가 \(2.5\%\)에서 \(15.2\%\)로 6배 넘게 이동한다. 두 계수가 각각 \(0.1\)과 \(0.05\) 정도만 되어도 \(2.5\% \to 5.5\%\)로 두 배가 된다.
실무 지침: \(|\hat z_0| < 0.05\)이고 \(|\hat a| < 0.05\)이면 BCa와 백분위수의 차이가 무시할 만하다. 잭나이프 계산을 아끼고 백분위수를 써도 좋다. 그보다 크면 BCa를 쓴다.
연습문제 2. BCa가 백분위수보다 실제로 나은지 모의실험으로 확인하라. \(\text{Exp}(1)\) 자료의 분산(참값 \(=1\))에 대해 \(n = 20, 50, 200\)에서 포함확률을 비교하라.
풀이
import numpy as np
from scipy import stats
rng = np.random.default_rng(1)
def ci_pct_bca(x, stat, B=800, alpha=0.05):
n = len(x); th = stat(x)
idx = rng.integers(0, n, (B, n))
bs = np.array([stat(x[i]) for i in idx])
pct = np.percentile(bs, [100*alpha/2, 100*(1-alpha/2)])
z0 = stats.norm.ppf(np.clip((bs < th).mean(), 1e-6, 1-1e-6))
jk = np.array([stat(np.delete(x, i)) for i in range(n)])
d = jk.mean() - jk
den = ((d**2).sum())**1.5
a = (d**3).sum() / (6*den) if den > 0 else 0.0
zl, zu = stats.norm.ppf(alpha/2), stats.norm.ppf(1-alpha/2)
a1 = stats.norm.cdf(z0 + (z0+zl)/(1 - a*(z0+zl)))
a2 = stats.norm.cdf(z0 + (z0+zu)/(1 - a*(z0+zu)))
return pct, np.percentile(bs, [100*a1, 100*a2])
for n in (20, 50, 200):
M = 800; cp = cb = 0
for _ in range(M):
x = rng.exponential(1, n)
pct, bca = ci_pct_bca(x, lambda v: v.var(ddof=1))
cp += pct[0] <= 1 <= pct[1]
cb += bca[0] <= 1 <= bca[1]
print(n, round(cp/M, 3), round(cb/M, 3))
출력:
20 0.686 0.756
50 0.82 0.852
200 0.892 0.904
| \(n\) | 백분위수 | BCa | 개선폭 |
|---|---|---|---|
| 20 | 0.686 | 0.756 | \(+0.070\) |
| 50 | 0.820 | 0.852 | \(+0.032\) |
| 200 | 0.892 | 0.904 | \(+0.012\) |
BCa가 세 표본크기에서 모두 낫다. 개선폭이 \(n\)이 커질수록 줄어드는 것도 이론과 부합한다. 두 방법의 오차 차수가 \(O(n^{-1/2})\)와 \(O(n^{-1})\)이므로 차이가 \(O(n^{-1/2})\)로 사라져야 한다. 실제로 \(0.070 \to 0.032 \to 0.012\)로 대략 \(\sqrt{n}\)에 반비례하여 줄어든다.
BCa도 만능은 아니다
\(n = 20\)에서 BCa의 포함확률이 \(0.756\)이다. 백분위수의 \(0.686\)보다 낫지만 여전히 명목값 \(0.95\)에 크게 못 미친다.
지수분포 분산이 어려운 문제이기 때문이다. \(s^2\)의 표본분포가 4차 적률에 지배되고 지수분포의 초과첨도가 \(6\)이라, \(n = 20\)에서는 어떤 붓스트랩 방법도 잘 작동하지 않는다. 이런 경우에는 로그변환 후 구간을 만들거나 모수적 방법을 쓰는 것이 낫다.
연습문제 3. BCa의 가속 \(\hat{a}\)는 잭나이프로 추정한다. 잭나이프가 실패하는 통계량에서는 어떻게 되는가? 중앙값에 대해 \(\hat{a}\)를 계산해 보라.
풀이
붓스트랩 방법 연습문제 2에서 보았듯, \(n\)이 홀수일 때 중앙값의 잭나이프 값 \(\hat\theta_{(-i)}\)는 세 가지 값만 갖는다.
import numpy as np
rng = np.random.default_rng(0)
x = rng.normal(0, 1, 41)
jk = np.array([np.median(np.delete(x, i)) for i in range(41)])
print(len(np.unique(np.round(jk, 10)))) # 3
출력:
3
이 세 값 중 두 개는 \(n\)번의 제거 중 각각 \((n-1)/2\)번씩 나타나고 나머지 하나는 한 번만 나타난다. 그 결과 잭나이프 분포가 거의 완벽하게 대칭이 되고, 3차 적률이 \(0\)에 가까워진다.
rng2 = np.random.default_rng(0)
def acc(gen, stat, n=41, M=2000):
out = []
for _ in range(M):
z = gen(n)
jk = np.array([stat(np.delete(z, i)) for i in range(n)])
d = jk.mean() - jk
den = ((d**2).sum())**1.5
out.append((d**3).sum() / (6*den) if den > 0 else 0.0)
return np.array(out)
a_med = acc(lambda n: rng2.exponential(1, n), np.median)
a_mean = acc(lambda n: rng2.exponential(1, n), np.mean)
print("median: %.5f +- %.5f" % (a_med.mean(), a_med.std()))
print("mean : %.5f +- %.5f" % (a_mean.mean(), a_mean.std()))
출력:
median: 0.00001 +- 0.00089
mean : 0.04130 +- 0.01584
\(\text{Exp}(1)\) 자료, \(n = 41\):
| 통계량 | \(\hat{a}\)의 평균 | \(\hat{a}\)의 표준편차 |
|---|---|---|
| 중앙값 | \(0.00001\) | \(0.00089\) |
| 평균 | \(0.04130\) | \(0.01584\) |
문제는 \(\hat a\)가 흔들린다는 것이 아니다. 언제나 \(0\)이라는 것이다.
지수분포의 중앙값은 표본분포가 분명히 오른쪽으로 치우쳐 있으므로 참 가속이 \(0\)이 아니어야 한다. 실제로 같은 자료의 평균에서는 \(\hat a = 0.041\)이 나온다. 그런데 중앙값에서는 잭나이프 분포가 대칭이라 치우침 신호를 전혀 잡아내지 못한다.
결과. 중앙값에 BCa를 적용하면 \(\hat a \approx 0\)이므로 사실상 BC 구간(편향보정만)이 된다. 잭나이프를 \(n\)번 계산하는 비용을 치르고도 가속 보정의 이득을 전혀 얻지 못한다.
권고. 매끄럽지 않은 통계량(중앙값, 분위수, 최댓값)에는
- delete-\(d\) 잭나이프를 쓴다. 하나가 아니라 \(d\)개씩 제거하면 중앙값에서도 일치추정량이 된다.
- 또는 \(\hat{a} = 0\)임을 인정하고 BC 구간으로 부른다. 결과는 같지만 무엇을 하고 있는지 분명해진다.
- 또는 그냥 백분위수 구간을 쓴다. 붓스트랩 방법 연습문제 1에서 보았듯 지수분포 중앙값에서 백분위수 구간의 포함확률이 이미 \(0.943\)으로 충분히 좋다.
연습문제 4. BCa 구간의 변환 불변성을 수치로 확인하라. \(\hat z_0\)과 \(\hat a\)가 변환에 따라 어떻게 되는가?
풀이
\(\theta\)의 BCa 구간이 \([L, U]\)이면 \(\phi = m(\theta)\)의 BCa 구간은 \([m(L), m(U)]\)여야 한다.
import numpy as np
from scipy import stats
rng = np.random.default_rng(6)
n = 40
x = rng.exponential(1, n)
# 두 호출이 같은 재표본을 쓰도록 인덱스를 미리 뽑아 공유한다
idx = rng.integers(0, n, (20000, n))
def bca(x, stat, idx, alpha=0.05):
n = len(x); th = stat(x)
bs = np.array([stat(x[i]) for i in idx])
z0 = stats.norm.ppf((bs < th).mean())
jk = np.array([stat(np.delete(x, i)) for i in range(n)])
d = jk.mean() - jk
a = (d**3).sum() / (6 * ((d**2).sum())**1.5)
zl, zu = stats.norm.ppf(alpha/2), stats.norm.ppf(1-alpha/2)
a1 = stats.norm.cdf(z0 + (z0+zl)/(1 - a*(z0+zl)))
a2 = stats.norm.cdf(z0 + (z0+zu)/(1 - a*(z0+zu)))
return np.percentile(bs, [100*a1, 100*a2]), z0, a
ci1, z01, a1_ = bca(x, np.mean, idx) # 평균
ci2, z02, a2_ = bca(x, lambda v: np.log(v.mean()), idx) # 로그 평균
print(np.round(ci1, 6), round(z01, 4), round(a1_, 4))
print(np.round(np.exp(ci2), 6), round(z02, 4), round(a2_, 4))
출력:
[0.624251 1.161511] 0.0392 0.0421
[0.624594 1.164064] 0.0392 0.044
두 호출이 같은 붓스트랩 재표본을 쓰도록 인덱스를 미리 뽑아 공유해야 비교가 공정하다.
위 코드가 idx를 공유하는 이유이며, 아래 표는 그 출력이다.
| 대상 | 구간 | \(\hat z_0\) | \(\hat a\) |
|---|---|---|---|
| \(\mu\) 직접 | \([0.624251, \; 1.161511]\) | \(0.0392\) | \(0.0421\) |
| \(\log\mu\) 후 지수변환 | \([0.624594, \; 1.164064]\) | \(0.0392\) | \(0.0440\) |
구간이 소수점 셋째 자리까지 일치한다. 상한이 \(1.1615\)와 \(1.1641\)로 \(0.2\%\) 차이 난다.
완전히 일치하지 않는 이유는 \(\hat{a}\)에 있다. 두 보정계수를 나누어 보자.
- \(\hat{z}_0\)은 정확히 같다(\(0.0392\)). \(\#\{\hat\theta^{*} < \hat\theta\}/B\)에만 의존하는데, \(\log\)가 단조증가이므로 \(\log\hat\theta^{*} < \log\hat\theta \iff \hat\theta^{*} < \hat\theta\)이고 비율이 바뀌지 않는다.
- \(\hat{a}\)는 \(0.0421\)과 \(0.0440\)으로 다르다. 잭나이프 값의 3차 적률과 2차 적률의 비인데, 이 비는 단조변환 아래에서 1차 근사로만 보존된다.
따라서 BCa의 변환 불변성은 이론적으로는 정확하지만 실제 구현에서는 근사적이다. 이론적 진술은 참 가속 상수 \(a\)에 대한 것이고, 잭나이프 추정값 \(\hat a\)가 그 성질을 정확히 물려받지는 않는다.
실무적으로 이 차이는 무시할 만하다. 여기서 \(0.2\%\)인데, 붓스트랩의 몬테카를로 오차보다 작다.
기본 구간과 비교하면 차이가 확연하다.
# 위와 같은 자료·같은 난수열로 평균의 붓스트랩 복제값을 만든다
rng = np.random.default_rng(6)
n = 40
x = rng.exponential(1, n)
th = x.mean()
idx = rng.integers(0, n, (20000, n))
bs = x[idx].mean(axis=1)
# 같은 붓스트랩 복제값으로 기본 구간을 두 척도에서 만든다
lo, hi = np.percentile(bs, [2.5, 97.5])
print(np.round([2*th - hi, 2*th - lo], 5)) # 원척도
llo, lhi = np.percentile(np.log(bs), [2.5, 97.5])
lth = np.log(th)
print(np.round(np.exp([2*lth - lhi, 2*lth - llo]), 5)) # 로그척도 후 되돌림
출력:
[0.56742 1.08876]
[0.63586 1.18821]
기본 구간은 하한이 \(0.567\)과 \(0.636\)으로 12% 차이 난다. BCa의 \(0.2\%\)와 비교하면 두 자릿수 차이이다. 어느 척도에서 반사하느냐가 결과를 크게 바꾸기 때문이다.
정리: 변환 불변성의 강도는 BCa \(>\) 백분위수 \(\gg\) 기본 구간 순이다. 백분위수 구간은 정확히 불변이고(연습문제 1, 백분위수법 참조), BCa는 \(\hat a\)의 오차만큼 근사적으로 불변이며, 기본 구간과 붓스트랩-\(t\)는 불변이 아니다.
정리하며¶
BCa 방법은 두 보정계수로 분위수 절단점을 조정하여 백분위수 구간을 개선한다. 편향보정 \(\hat{z}_0\)은 붓스트랩 분포의 중앙값 편향을, 가속 \(\hat{a}\)는 표준오차가 모수에 따라 변하는 정도를 잭나이프로 추정하여 반영한다. 이 보정으로 2차 정확도를 갖는 변환 불변 신뢰구간을 얻는다. 추가적인 잭나이프 계산을 감당할 수 있다면 BCa 구간이 권장 기본값이다.