Repository navigation
Expand file tree
/
Copy pathcode_2cases.py
More file actions
151 lines (121 loc) · 5.35 KB
/
Copy pathcode_2cases.py
File metadata and controls
151 lines (121 loc) · 5.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
import statsmodels.api as sm
from tqdm import tqdm
import warnings
warnings.filterwarnings('ignore')
# ---------------------------------------------------------
# Core Estimators (Mapping estimating equations to sequential GLMs)
# ---------------------------------------------------------
def fit_glm(formula, data):
"""Helper to fit logistic regression."""
return smf.glm(formula, data=data, family=sm.families.Binomial()).fit(disp=False)
def est_case1(df):
"""Table 1: Case 1 Estimators"""
res = {}
df_s1 = df[df['S'] == 1]
# 1. Complete-case analysis (naïve)
y1_naive = df_s1[df_s1['A'] == 1]['Y'].mean()
y0_naive = df_s1[df_s1['A'] == 0]['Y'].mean()
res['Complete-case analysis (naïve)'] = y1_naive - y0_naive
# 2 & 3. Standard and Modified g-computation
m = fit_glm("Y ~ A * W", df_s1)
df1, df0 = df.assign(A=1), df.assign(A=0)
y1_hat, y0_hat = m.predict(df1), m.predict(df0)
res['Standard g-computation'] = y1_hat.mean() - y0_hat.mean()
res['Modified g-computation'] = y1_hat[df['A'] == 1].mean() - y0_hat[df['A'] == 0].mean()
# 4. Inverse probability weighting
# Selection model based on A and W (treatment induced selection)
m_s = fit_glm("S ~ A * W", df)
pi_s = m_s.predict(df)
ipw = df['S'] / pi_s
# Weighted means
wt1, wt0 = df['A'] * ipw, (1 - df['A']) * ipw
res['Inverse probability weighting'] = (wt1 * df['Y']).sum() / wt1.sum() - (wt0 * df['Y']).sum() / wt0.sum()
return res
def est_case2(df):
"""Table 2: Case 2 Estimators"""
res = {}
df_s1 = df[df['S'] == 1]
df1, df0 = df.assign(A=1), df.assign(A=0)
# 1. Complete-case analysis (naïve)
y1_naive = df_s1[df_s1['A'] == 1]['Y'].mean()
y0_naive = df_s1[df_s1['A'] == 0]['Y'].mean()
res['Complete-case analysis (naïve)'] = y1_naive - y0_naive
# 2, 3, 4. Standard G-computation variants
for name, formula in [('Standard g-computation,{Z}', "Y ~ A + Z"),
('Standard g-computation,{X}', "Y ~ A + X"),
('Standard g-computation,{X, Z}', "Y ~ A + X + Z")]:
m = fit_glm(formula, df_s1)
res[name] = m.predict(df1).mean() - m.predict(df0).mean()
# 5. Iterated g-computation (Nested)
m_inner = fit_glm("Y ~ A + X + Z", df_s1)
df_temp = df.copy()
df_temp['y1_inner'] = m_inner.predict(df1)
df_temp['y0_inner'] = m_inner.predict(df0)
m_out1 = fit_glm("y1_inner ~ A + Z", df_temp)
m_out0 = fit_glm("y0_inner ~ A + Z", df_temp)
res['Iterated g-computation'] = m_out1.predict(df1).mean() - m_out0.predict(df0).mean()
# 6. Inverse probability weighting
# Selection depends on X, Treatment depends on Z
m_s = fit_glm("S ~ X", df)
m_a = fit_glm("A ~ Z", df)
pi_s = m_s.predict(df)
pi_a = m_a.predict(df)
# Calculate weights exactly as defined in estfun.py psi_ipw_case2
ipmw = df['S'] / pi_s
iptw = df['A'] / pi_a + (1 - df['A']) / (1 - pi_a)
ipw = ipmw * iptw
wt1, wt0 = df['A'] * ipw, (1 - df['A']) * ipw
res['Inverse probability weighting'] = (wt1 * df['Y']).sum() / wt1.sum() - (wt0 * df['Y']).sum() / wt0.sum()
return res
# ---------------------------------------------------------
# Bootstrap Wrapper & Table Generation
# ---------------------------------------------------------
def run_comparison_table(df, est_func, table_name, B=500):
n = len(df)
m = int(n * 0.9) # m-out-of-n size
d = int(n * 0.1) # delete-d size
pt_ests = est_func(df)
np_boots = {k: [] for k in pt_ests.keys()}
m_boots = {k: [] for k in pt_ests.keys()}
jk_boots = {k: [] for k in pt_ests.keys()}
np.random.seed(42)
print(f"\nRunning Bootstraps for {table_name} (B={B})...")
for _ in tqdm(range(B)):
# 1. Non-parametric
df_np = df.sample(n=n, replace=True)
for k, v in est_func(df_np).items(): np_boots[k].append(v)
# 2. m-out-of-n
df_m = df.sample(n=m, replace=True)
for k, v in est_func(df_m).items(): m_boots[k].append(v)
# 3. Delete-d Jackknife
df_jk = df.sample(n=n-d, replace=False)
for k, v in est_func(df_jk).items(): jk_boots[k].append(v)
# Compile Table Data
records = []
for k in pt_ests.keys():
pt = pt_ests[k]
np_se = np.std(np_boots[k])
m_se = np.std(m_boots[k]) * np.sqrt(m/n)
jk_se = np.std(jk_boots[k]) * np.sqrt((n-d)/d)
records.append({
'Estimator': k,
'Point Estimate': pt,
'NP-Boot SE': np_se,
'NP 95% CI': f"[{pt - 1.96*np_se:.3f}, {pt + 1.96*np_se:.3f}]",
'm-Boot SE': m_se,
'm-Boot CI': f"[{pt - 1.96*m_se:.3f}, {pt + 1.96*m_se:.3f}]",
'Jackknife SE': jk_se,
'Jackknife CI': f"[{pt - 1.96*jk_se:.3f}, {pt + 1.96*jk_se:.3f}]"
})
df_res = pd.DataFrame(records).set_index('Estimator')
print(f"\n=== {table_name} ===")
print(df_res.round(3).to_markdown())
# Execution
if __name__ == "__main__":
d1 = pd.read_csv("data/example1.csv")
d2 = pd.read_csv("data/example2.csv")
run_comparison_table(d1, est_case1, "Table 1: Case 1 Results", B=500)
run_comparison_table(d2, est_case2, "Table 2: Case 2 Results", B=500)