@@ -26,73 +26,173 @@ exclude rows that are invalid for variance-covariance estimator.
2626"""
2727const CheckVcov = StatsStep{:CheckVcov , typeof (checkvcov!), true }
2828
29+ # Get esample and aux directly from CheckData
2930required (:: CheckVcov ) = (:data , :esample , :aux )
3031default (:: CheckVcov ) = (vce= Vcov. robust (),)
3132copyargs (:: CheckVcov ) = (2 ,)
3233
34+ """
35+ parsefeterms!(xterms)
36+
37+ Extract any `FixedEffectTerm` or interaction of `FixedEffectTerm` from `xterms`
38+ and determine whether any intercept term should be omitted.
39+ See also [`ParseFEterms`](@ref).
40+ """
41+ function parsefeterms! (xterms:: TermSet )
42+ feterms = Set {FETerm} ()
43+ has_fe_intercept = false
44+ for t in xterms
45+ result = _parsefeterm (t)
46+ if result != = nothing
47+ push! (feterms, result)
48+ delete! (xterms, t)
49+ end
50+ end
51+ if ! isempty (feterms)
52+ if any (t-> isempty (t[2 ]), feterms)
53+ has_fe_intercept = true
54+ for t in xterms
55+ t isa Union{ConstantTerm,InterceptTerm} && delete! (xterms, t)
56+ end
57+ push! (xterms, InterceptTerm {false} ())
58+ end
59+ end
60+ return (xterms= xterms, feterms= feterms, has_fe_intercept= has_fe_intercept)
61+ end
62+
63+ const ParseFEterms = StatsStep{:ParseFEterms , typeof (parsefeterms!), true }
64+
65+ required (:: ParseFEterms ) = (:xterms ,)
66+
67+ """
68+ groupfeterms(feterms)
69+
70+ Return the argument without change for allowing later comparisons based on object-id.
71+ See also [`GroupFEterms`](@ref).
72+ """
73+ groupfeterms (feterms:: Set{FETerm} ) = (feterms= feterms,)
74+
75+ """
76+ GroupFEterms <: StatsStep
77+
78+ Call [`InteractionWeightedDIDs.groupfeterms`](@ref)
79+ to obtain one of the instances of `feterms`
80+ that have been grouped by equality (`hash`)
81+ for allowing later comparisons based on object-id.
82+
83+ This step is only useful when working with [`@specset`](@ref) and [`proceed`](@ref).
84+ """
85+ const GroupFEterms = StatsStep{:GroupFEterms , typeof (groupfeterms), false }
86+
87+ required (:: GroupFEterms ) = (:feterms ,)
88+
89+ """
90+ makefes(args...)
91+
92+ Construct `FixedEffect`s from `data` (the full sample).
93+ See also [`MakeFEs`](@ref).
94+ """
95+ function makefes (data, allfeterms:: Vector{FETerm} )
96+ # Must use Dict instead of IdDict since the same feterm can be in multiple feterms
97+ allfes = Dict {FETerm,FixedEffect} ()
98+ for t in allfeterms
99+ haskey (allfes, t) && continue
100+ if isempty (t[2 ])
101+ allfes[t] = FixedEffect ((getcolumn (data, n) for n in t[1 ]). .. )
102+ else
103+ allfes[t] = FixedEffect ((getcolumn (data, n) for n in t[1 ]). .. ;
104+ interaction= _multiply (data, t[2 ]))
105+ end
106+ end
107+ return (allfes= allfes,)
108+ end
109+
110+ """
111+ MakeFEs <: StatsStep
112+
113+ Call [`InteractionWeightedDIDs.makefes`](@ref)
114+ to construct `FixedEffect`s from `data` (the full sample).
115+ """
116+ const MakeFEs = StatsStep{:MakeFEs , typeof (makefes), false }
117+
118+ required (:: MakeFEs ) = (:data ,)
119+ combinedargs (:: MakeFEs , allntargs) = (FETerm[t for nt in allntargs for t in nt. feterms],)
120+
33121"""
34122 checkfes!(args...)
35123
36- Extract any `FixedEffectTerm` from `xterms`,
37- drop singleton observations for any fixed effect
38- and determine whether intercept term should be omitted.
124+ Drop any singleton observation from fixed effects over the relevant subsample.
39125See also [`CheckFEs`](@ref).
40126"""
41- function checkfes! (data, esample :: BitVector , xterms :: TermSet , drop_singletons :: Bool )
42- fes, fenames, has_fe_intercept = parse_fixedeffect! (data, xterms )
127+ function checkfes! (feterms :: Set{FETerm} , allfes :: Dict{FETerm,FixedEffect} ,
128+ esample :: BitVector , drop_singletons :: Bool )
43129 nsingle = 0
44- if ! isempty (fes)
130+ nfe = length (feterms)
131+ if nfe > 0
132+ fes = Vector {FixedEffect} (undef, nfe)
133+ fenames = Vector {String} (undef, nfe)
134+ # Loop together to ensure the orders are the same
135+ for (i, t) in enumerate (feterms)
136+ fes[i] = allfes[t]
137+ fenames[i] = getfename (t)
138+ end
139+ # Determine the unique order based on names
140+ order = sortperm (fenames)
141+ fes = fes[order]
142+ fenames = fenames[order]
143+
45144 if drop_singletons
46145 for fe in fes
47146 nsingle += drop_singletons! (esample, fe)
48147 end
49148 end
149+ sum (esample) == 0 && error (" no nonmissing data" )
150+
151+ for i in 1 : nfe
152+ fes[i] = fes[i][esample]
153+ end
154+ return (esample= esample, fes= fes, fenames= fenames, nsingle= nsingle)
155+ else
156+ return (esample= esample, fes= FixedEffect[], fenames= String[], nsingle= 0 )
50157 end
51- sum (esample) == 0 && error (" no nonmissing data" )
52- return (xterms= xterms, esample= esample, fes= fes, fenames= fenames,
53- has_fe_intercept= has_fe_intercept, nsingle= nsingle)
54158end
55159
56160"""
57161 CheckFEs <: StatsStep
58162
59163Call [`InteractionWeightedDIDs.checkfes!`](@ref)
60- to extract any `FixedEffectTerm` from `xterms`
61- and drop singleton observations for any fixed effect.
164+ to drop any singleton observation from fixed effects over the relevant subsample.
62165"""
63- const CheckFEs = StatsStep{:CheckFixedEffects , typeof (checkfes!), true }
166+ const CheckFEs = StatsStep{:CheckFEs , typeof (checkfes!), true }
64167
65- required (:: CheckFEs ) = (:data , :esample , :xterms )
168+ required (:: CheckFEs ) = (:feterms , :allfes , :esample )
66169default (:: CheckFEs ) = (drop_singletons= true ,)
67- copyargs (:: CheckFEs ) = (2 , 3 )
170+ copyargs (:: CheckFEs ) = (3 , )
68171
69172"""
70- makefesolver! (args...)
173+ makefesolver(args...)
71174
72175Construct `FixedEffects.AbstractFixedEffectSolver`.
73176See also [`MakeFESolver`](@ref).
74177"""
75- function makefesolver! (fes:: Vector{FixedEffect} , weights:: AbstractWeights ,
76- esample:: BitVector , nfethreads:: Int )
178+ function makefesolver (fes:: Vector{FixedEffect} , weights:: AbstractWeights , nfethreads:: Int )
77179 if ! isempty (fes)
78- fes = FixedEffect[fe[esample] for fe in fes]
79180 feM = AbstractFixedEffectSolver {Float64} (fes, weights, Val{:cpu }, nfethreads)
80- return (feM= feM, fes = fes )
181+ return (feM= feM,)
81182 else
82- return (feM= nothing , fes = fes )
183+ return (feM= nothing ,)
83184 end
84185end
85186
86187"""
87188 MakeFESolver <: StatsStep
88189
89- Call [`InteractionWeightedDIDs.makefesolver! `](@ref) to construct the fixed effect solver.
190+ Call [`InteractionWeightedDIDs.makefesolver`](@ref) to construct the fixed effect solver.
90191"""
91- const MakeFESolver = StatsStep{:MakeFESolver , typeof (makefesolver! ), true }
192+ const MakeFESolver = StatsStep{:MakeFESolver , typeof (makefesolver), true }
92193
93- required (:: MakeFESolver ) = (:fes , :weights , :esample )
194+ required (:: MakeFESolver ) = (:fes , :weights )
94195default (:: MakeFESolver ) = (nfethreads= Threads. nthreads (),)
95- copyargs (:: MakeFESolver ) = (1 ,)
96196
97197function _makeyxcols! (yxterms:: Dict , yxcols:: Dict , yxschema, data, t:: AbstractTerm )
98198 ct = apply_schema (t, yxschema, StatisticalModel)
@@ -291,6 +391,7 @@ transformed(::MakeTreatCols, @nospecialize(nt::NamedTuple)) = (nt.tr.time,)
291391# Obtain the relative time periods excluded by all tr
292392# and the treatment groups excluded by all pr in allntargs
293393function combinedargs (:: MakeTreatCols , allntargs)
394+ # exc cannot be IdDict for comparing different types of one
294395 exc = Dict {Int,Int} ()
295396 notreat = IdDict {ValidTimeType,Int} ()
296397 for nt in allntargs
0 commit comments