*! survcollapse.ado *! Collapse surveillance data over a chosen time variable and complete the series *! v 1.0 17Jan2026 Leonelo Bautista program define survcollapse, rclass version 15.1 // timevar REQUIRED; keepvars OPTIONAL syntax [if] [in] , /// DATEVAR(name) /// CASEVAR(name) /// TIMEVAR(name) /// SAVING(string) /// [ KEEPVARS(varlist) /// REPLACE ] // ------------------------------------------------------------ // Checks // ------------------------------------------------------------ confirm variable `datevar' confirm variable `casevar' capture confirm numeric variable `datevar' if (_rc) { di as err "`datevar' must be a numeric Stata daily date." exit 198 } capture confirm numeric variable `casevar' if (_rc) { di as err "`casevar' must be numeric." exit 198 } if ("`saving'" == "") { di as err "saving() is required." exit 198 } preserve marksample touse quietly keep if `touse' // missing cases -> 0 quietly replace `casevar' = 0 if missing(`casevar') // ------------------------------------------------------------ // keepvars(): confirm + must be constant (only if provided) // Store constants before collapse; add back after collapse. // ------------------------------------------------------------ if ("`keepvars'" != "") { // Confirm variables exist local __kvl "`keepvars'" while ("`__kvl'" != "") { gettoken __kv __kvl : __kvl confirm variable `__kv' } // Check constancy + store value/type local __kvl "`keepvars'" while ("`__kvl'" != "") { gettoken __kv __kvl : __kvl capture confirm numeric variable `__kv' if (_rc == 0) { quietly summarize `__kv', meanonly if (r(min) != r(max)) { di as err "keepvars() must be constant within the if/in sample when collapsing by timevar only." di as err "Variable `__kv' takes multiple values in the sample." exit 198 } local __kv_type_`__kv' "num" local __kv_val_`__kv' = r(min) } else { quietly levelsof `__kv', local(__lev) local __n : word count `__lev' if (`__n' != 1) { di as err "keepvars() must be constant within the if/in sample when collapsing by timevar only." di as err "Variable `__kv' takes multiple values in the sample." exit 198 } local __kv_type_`__kv' "str" local __kv_val_`__kv' "`__lev'" } } } // ------------------------------------------------------------ // Normalize timevar name // ------------------------------------------------------------ local __t = lower(trim("`timevar'")) if ("`__t'" == "day") local __t "__day" if !inlist("`__t'","__day","stata_week","iso_week","cdc_week") { di as err "timevar() must be one of: __day stata_week iso_week cdc_week." exit 198 } // ------------------------------------------------------------ // DAILY: collapse by __day (existing behavior) // ------------------------------------------------------------ if ("`__t'" == "__day") { // Build __day from datevar (overwrite if exists) capture confirm variable __day if (!_rc) drop __day quietly gen long __day = `datevar' label var __day "Day (Stata daily date from `datevar')" quietly collapse (sum) `casevar', by(__day) tempfile __collapsed __times quietly save `"`__collapsed'"', replace quietly summarize __day, meanonly local __tmin = r(min) local __tmax = r(max) clear quietly set obs `=(`__tmax' - `__tmin' + 1)' quietly gen long __day = `__tmin' + _n - 1 quietly save `"`__times'"', replace use `"`__times'"', clear quietly merge 1:1 __day using `"`__collapsed'"' quietly gen byte created = (_merge != 3) label var created "1=time unit created (filled-in); 0=observed in data" drop _merge quietly replace `casevar' = 0 if missing(`casevar') // Add back keepvars as constants if ("`keepvars'" != "") { local __kvl "`keepvars'" while ("`__kvl'" != "") { gettoken __kv __kvl : __kvl if ("`__kv_type_`__kv''" == "num") gen double `__kv' = `__kv_val_`__kv'' else gen strL `__kv' = "`__kv_val_`__kv''" } order `keepvars' __day `casevar' created } else { order __day `casevar' created } quietly save "`saving'", `replace' di as txt "File saved: `saving'" restore exit } // ------------------------------------------------------------ // WEEKLY: collapse by a continuous week-start date, then output year+week // ------------------------------------------------------------ tempvar __wstart quietly gen long `__wstart' = . // Define week-start date depending on week type if ("`__t'" == "stata_week" | "`__t'" == "cdc_week") { // Sunday-based weeks: start = Sunday quietly replace `__wstart' = `datevar' - dow(`datevar') } else if ("`__t'" == "iso_week") { // Monday-based weeks: start = Monday quietly replace `__wstart' = `datevar' - mod(dow(`datevar') + 6, 7) } label var `__wstart' "Week start date (daily date; internal)" // Collapse by continuous weekly axis quietly collapse (sum) `casevar', by(`__wstart') // Complete missing weeks using step=7 tempfile __collapsed __times quietly save `"`__collapsed'"', replace quietly summarize `__wstart', meanonly local __wmin = r(min) local __wmax = r(max) clear quietly set obs `=(floor((`__wmax' - `__wmin')/7) + 1)' quietly gen long `__wstart' = `__wmin' + 7*(_n - 1) quietly save `"`__times'"', replace use `"`__times'"', clear quietly merge 1:1 `__wstart' using `"`__collapsed'"' quietly gen byte created0 = (_merge != 3) label var created "1=time unit created (filled-in); 0=observed in data" drop _merge quietly replace `casevar' = 0 if missing(`casevar') // ------------------------------------------------------------ // Generate year + week corresponding to selected week type // ------------------------------------------------------------ quietly gen int year = . quietly gen int week = . if ("`__t'" == "stata_week") { // Stata/Sunday-based week index within calendar year quietly gen double __wdate = wofd(`__wstart') quietly replace year = year(`__wstart') quietly replace week = __wdate - wofd(mdy(1,1,year)) + 1 drop __wdate label var year "Year (Stata/Sunday-based weeks)" label var week "Week (Stata/Sunday-based)" } else if ("`__t'" == "cdc_week") { // CDC/MMWR: Sun–Sat; week-year = year(Thursday); week1 contains Jan 4 tempvar __thu __y __jan4 __wk1 quietly gen long `__thu' = `__wstart' + 4 quietly gen int `__y' = year(`__thu') quietly gen long `__jan4' = mdy(1,4,`__y') quietly gen long `__wk1' = `__jan4' - dow(`__jan4') // Sunday of week containing Jan 4 quietly replace year = `__y' quietly replace week = 1 + floor((`__wstart' - `__wk1')/7) drop `__thu' `__y' `__jan4' `__wk1' label var year "Year (CDC/MMWR)" label var week "Week (CDC/MMWR)" } else if ("`__t'" == "iso_week") { // ISO: Mon-based; week-year = year(Thursday); week 1 contains Jan 4 tempvar __thu __y __jan4 __jan4_wday __thu_wk1 quietly gen long `__thu' = `__wstart' + 3 quietly gen int `__y' = year(`__thu') quietly gen long `__jan4' = mdy(1,4,`__y') // ISO weekday of Jan 4: Mon=1..Sun=7 quietly gen byte `__jan4_wday' = mod(dow(`__jan4') + 6, 7) + 1 quietly gen long `__thu_wk1' = `__jan4' + (4 - `__jan4_wday') quietly replace year = `__y' quietly replace week = 1 + floor((`__thu' - `__thu_wk1')/7) drop `__thu' `__y' `__jan4' `__jan4_wday' `__thu_wk1' label var year "Year (ISO 8601)" label var week "Week (ISO 8601)" } // Add back keepvars as constants (if provided) if ("`keepvars'" != "") { local __kvl "`keepvars'" while ("`__kvl'" != "") { gettoken __kv __kvl : __kvl if ("`__kv_type_`__kv''" == "num") gen double `__kv' = `__kv_val_`__kv'' else gen strL `__kv' = "`__kv_val_`__kv''" } order `keepvars' year week `casevar' created } else { order year week `casevar' created } // Drop internal week-start date drop `__wstart' quietly save "`saving'", `replace' di as txt "File saved: `saving'" restore end