1- function eventreg (@nospecialize (df),
2- @nospecialize (formula:: FormulaTerm ),
3- @nospecialize (rel_varlist:: Vector{Symbol} ),
1+ function _nonzero_relmask (df:: DataFrame , nvarlist:: Vector{Symbol} )
2+ isempty (nvarlist) && return Bool[]
3+ X = Matrix {Float64} (df[:, nvarlist])
4+ return vec (any (.! iszero .(X), dims = 1 ))
5+ end
6+
7+ function _estimable_relmask (vcov_mat:: AbstractMatrix , nrel_times:: Integer , ncohort:: Integer )
8+ nrel_times == 0 && return Bool[]
9+ interaction_dim = nrel_times * ncohort
10+ diag_v = diag (Matrix (vcov_mat)[1 : interaction_dim, 1 : interaction_dim])
11+ keep_rel = falses (nrel_times)
12+ for i in 1 : nrel_times
13+ block = ((i - 1 ) * ncohort + 1 ): (i * ncohort)
14+ keep_rel[i] = any (.! isnan .(diag_v[block]))
15+ end
16+ return keep_rel
17+ end
18+
19+ function _zero_omitted_rowscols (vcov_mat:: AbstractMatrix )
20+ VV = Matrix {Float64} (vcov_mat)
21+ omitted = isnan .(diag (VV))
22+ kept = .! omitted
23+
24+ if any (kept) && any (isnan .(VV[kept, kept]))
25+ throw (ArgumentError (" Non-omitted entries of the interacted-regression covariance matrix contain NaN values." ))
26+ end
27+
28+ if any (omitted)
29+ VV[omitted, :] .= 0.0
30+ VV[:, omitted] .= 0.0
31+ end
32+
33+ return VV
34+ end
35+
36+ function _reinsert_rel_outputs (coef_reduced:: AbstractVector , vcov_reduced:: AbstractMatrix , keep_rel:: AbstractVector{Bool} , full_size:: Integer )
37+ coef_full = zeros (Float64, full_size)
38+ vcov_full = fill (NaN , full_size, full_size)
39+
40+ keep_idx = findall (keep_rel)
41+ coef_full[keep_idx] .= coef_reduced
42+ vcov_full[keep_idx, keep_idx] .= Matrix (vcov_reduced)
43+
44+ return coef_full, Symmetric (vcov_full)
45+ end
46+
47+ function eventreg (@nospecialize (df),
48+ @nospecialize (formula:: FormulaTerm ),
49+ @nospecialize (rel_varlist:: Vector{Symbol} ),
450 @nospecialize (control_cohort:: Symbol ),
551 @nospecialize (cohort:: Symbol ),
652 @nospecialize (vcov:: CovarianceEstimator = Vcov. simple ());
@@ -43,21 +89,29 @@ function StatsAPI.fit(::Type{EventStudyInteract},
4389 if dof_add != 0
4490 @warn " The dof_add keyword is no longer forwarded to FixedEffectModels.reg on version 1.13 and is currently ignored."
4591 end
46- # Prepare the varlists
47- nvarlist = Symbol[]
48- dvarlist = Symbol[]
49- for l in rel_varlist
92+ # Prepare the varlists
93+ nvarlist = Symbol[]
94+ dvarlist = Symbol[]
95+ for l in rel_varlist
5096 push! (dvarlist,l)
5197 n_l = Symbol (" n" ,l)
5298 df[! ,n_l] = df[! ,l]
53- df[! ,n_l][df[! ,control_cohort]. == 1 ] .= 0
54- push! (nvarlist,n_l)
55- end
99+ df[! ,n_l][df[! ,control_cohort]. == 1 ] .= 0
100+ push! (nvarlist,n_l)
101+ end
102+ full_dvarlist = copy (dvarlist)
56103
57- cohort_list = unique (df[! ,cohort][df[! ,control_cohort]. == 0 ])
58- cohort_list = sort (cohort_list)
59- cohort_list = Int .(cohort_list)
60- nrel_times = length (nvarlist)
104+ prekeep_rel = _nonzero_relmask (df, nvarlist)
105+ if ! all (prekeep_rel)
106+ nvarlist = nvarlist[prekeep_rel]
107+ dvarlist = dvarlist[prekeep_rel]
108+ end
109+ isempty (nvarlist) && error (" No estimable relative-time indicators remain after removing unsupported columns." )
110+
111+ cohort_list = unique (df[! ,cohort][df[! ,control_cohort]. == 0 ])
112+ cohort_list = sort (cohort_list)
113+ cohort_list = Int .(cohort_list)
114+ nrel_times = length (nvarlist)
61115 ncohort = length (cohort_list)
62116
63117 # Prepare interaction terms for the interacted regression
@@ -97,11 +151,21 @@ function StatsAPI.fit(::Type{EventStudyInteract},
97151 progress_bar = progress_bar,
98152 subset = subset)
99153
100- # Caculate the weights.
101-
102- df = df[result. esample .== 1 , :]
103-
104-
154+ postkeep_rel = _estimable_relmask (result. vcov, nrel_times, ncohort)
155+ if ! all (postkeep_rel)
156+ nvarlist = nvarlist[postkeep_rel]
157+ dvarlist = dvarlist[postkeep_rel]
158+ end
159+ isempty (nvarlist) && error (" No estimable relative-time indicators remain after fitting the interacted regression." )
160+
161+ nrel_times = length (nvarlist)
162+ rel_keep_mask = falses (length (rel_varlist))
163+ rel_keep_mask[findall (prekeep_rel)] .= postkeep_rel
164+
165+ # Caculate the weights.
166+
167+ df = df[result. esample .== 1 , :]
168+
105169 if ncohort == 1
106170 ff_w = ones (1 , nrel_times)
107171 Sigma_ff = zeros (nrel_times, nrel_times)
@@ -127,40 +191,25 @@ function StatsAPI.fit(::Type{EventStudyInteract},
127191 Sigma_ff = _share_vcov (stage2df, e, X, vcov)
128192 end
129193
130- b = result. coef
131-
132- V = diag (result. vcov)
133-
134- replace! (V, NaN => 0 )
135-
136- # Convert the delta estimate vector to a matrix where each column is a relative time
137- end_ = 0
138- evt_bb = zeros (ncohort,0 )
139- evt_VV = zeros (ncohort,0 )
140-
141- for i in 1 : nrel_times
142- start = end_ + 1
143- end_ = start + ncohort - 1
144- b_i = b[start: end_]
145- evt_bb = hcat (evt_bb, b_i)
146- V_i = V[start: end_]
147- evt_VV = hcat (evt_VV, V_i)
148- end
149-
150- # Take weighted average for IW estimators
151- w = ff_w
152- delta = evt_bb
153- b_iw = sum (w .* delta, dims= 1 )
154- nc, nr = size (w)
155-
156- # Ptwise variance from cohort share estimation and interacted regression
157- VV = result. vcov # VCV from the interacted regression
158- replace! (VV, NaN => 0 )
159-
160- VV = VV[1 : nr* nc, 1 : nr* nc] # in case reports _cons
161-
162- wlong = w' .* repeat (ee (1 , nr)' , 1 , nc)
163-
194+ interaction_keep = repeat (postkeep_rel, inner = ncohort)
195+ interaction_idx = findall (interaction_keep)
196+ b = result. coef[interaction_idx]
197+
198+ # Convert the interacted coefficients to a matrix where each column is a relative time
199+ evt_bb = reshape (b, ncohort, nrel_times)
200+
201+ # Take weighted average for IW estimators
202+ w = ff_w
203+ delta = evt_bb
204+ b_iw = sum (w .* delta, dims= 1 )
205+ nc, nr = size (w)
206+
207+ # Ptwise variance from cohort share estimation and interacted regression
208+ VV_full = _zero_omitted_rowscols (result. vcov) # VCV from the interacted regression
209+ VV = VV_full[interaction_idx, interaction_idx]
210+
211+ wlong = w' .* repeat (ee (1 , nr)' , 1 , nc)
212+
164213 for i in 2 : nrel_times # loop from 2 to nrel_times
165214 wlong = [wlong (w' .* repeat (ee (i, nr)' , 1 , nc))] # concatenate
166215 end
@@ -179,10 +228,9 @@ function StatsAPI.fit(::Type{EventStudyInteract},
179228 end
180229 end
181230
182- coef_iw = vec (b_iw' )
183-
184- vcov_iw = Symmetric (V_iw)
185- vcov_iw_diag = diag (vcov_iw)
231+ coef_iw_reduced = vec (b_iw' )
232+ vcov_iw_reduced = Symmetric (V_iw)
233+ coef_iw, vcov_iw = _reinsert_rel_outputs (coef_iw_reduced, vcov_iw_reduced, rel_keep_mask, length (rel_varlist))
186234
187235
188236
@@ -213,7 +261,7 @@ function StatsAPI.fit(::Type{EventStudyInteract},
213261 nclusters = result. nclusters
214262 esample = result. esample
215263 fekeys = result. fekeys
216- coef_names = [string (x) for x in dvarlist ]
264+ coef_names = [string (x) for x in full_dvarlist ]
217265 # coef_names = coefnames
218266 response_name = responsename (result)
219267 # response_name, coef_names = coefnames(formula_schema)
0 commit comments