* version 1.4.1 26Jan2020 Mauricio Caceres Bravo, mauricio.caceres.bravo@gmail.com *! implementation -egen- using C for faster processing /* * syntax: * gegen [type] varname = fun(args) [if] [in], [options] * passed to fun are * [type] varname = fun(args) [if] [in], [options] */ /* * stata's egen does not parse types correctly. If the requested result is * a sum, stata will happily create a float, despite the risk of overflow. * If the source variable is a double, stata will also create a float, even * though that might cause a loss in precision. I do not imitate this behavior * because I consider it flawed. I upgrade types whenever necessary. * */ /* * TODO: implement label, lname, and truncate for group */ capture program drop gegen program define gegen, byable(onecall) rclass version 13.1 local 00 `0' qui syntax anything(equalok) [if] [in] [aw fw iw pw], [by(str) *] local byvars `by' local 0 `00' * Parse weights * ------------- local wgt = cond(`"`weight'"' != "", `"[`weight' `exp']"', "") * Parse egen call * --------------- gettoken type 0 : 0, parse(" =(") gettoken name 0 : 0, parse(" =(") if ( `"`name'"' == "=" ) { local name `"`type'"' local type : set type local retype = 1 local btype double } else { gettoken eqsign 0 : 0, parse(" =(") if ( `"`eqsign'"' != "=" ) { error 198 } local btype `type' local retype = 0 } confirm name `name' gettoken fcn 0: 0, parse(" =(") gettoken args 0: 0, parse(" ,") match(par) local fcn `fcn' if ( `"`par'"' != "(" ) exit 198 if ( `"`fcn'"' == "total" ) local fcn sum if ( `"`fcn'"' == "var" ) local fcn variance if ( `"`fcn'"' == "sem" ) local fcn semean if ( `"`fcn'"' == "seb" ) local fcn sebinomial if ( `"`fcn'"' == "sep" ) local fcn sepoisson if ( `"`fcn'"' == "kurt" ) local fcn kurtosis if ( `"`fcn'"' == "skew" ) local fcn skewness if ( `"`fcn'"' == "sum" ) local type `btype' if ( regexm(`"`fcn'"', " ") ) local fcn: subinstr local fcn " " "|", all if ( regexm(`"`fcn'"', "_") ) local fcn: subinstr local fcn "_" "|", all * Parse by call * ------------- local warnby = 0 if ( _by() ) { local byvars `_byvars' local warnby = 1 } * Pre-compiled functions * ---------------------- local funcs tag /// group /// total /// sum /// nansum /// mean /// geomean /// sd /// variance /// cv /// max /// min /// range /// count /// median /// iqr /// percent /// first /// last /// firstnm /// lastnm /// semean /// sebinomial /// sepoisson /// nunique /// pctile /// select /// nmissing /// skewness /// gini /// gini|dropneg /// gini|keepneg /// kurtosis * gegen aliases for other gtools functions * ---------------------------------------- * NOTE: With retype, let the captured functions determine type local transforms rank /// standardize /// normalize /// demean /// demedian // local direct winsorize /// winsor /// residualize /// hdfe // * NOTE(mauricio): Though you would want to allow by as prefix, it's * difficult because the user can try to pass inputs assuming by: * will produce a particular result, and it might not e.g. * * by var: gegen x = fcn(y[_n - 2]) * * fails with these (in this example, for non-transforms y[_n - 2], * or whichever expression is passed, is generated into a variable * with the correct by: predix. if ( `"`fcn'"' == "xtile" ) { if ( _by() ) { * Note I leave this here because I want to allow expressions. So * I don't allow by... disp as err "by: prefix not allowed with `fcn'" exit 198 } if ( `retype' ) { disp as err "warning: type ignored with gegen function xtile" } cap noi fasterxtile `name' = `args' `if' `in' `wgt', by(`byvars') `options' exit _rc } local shift = regexm(`"`fcn'"', "^shift[ |]*([+-]?[0-9]+)?[ |]*$") local cumsum = regexm(`"`fcn'"', "^cumsum(.*)$") local moving = regexm(`"`fcn'"', "^moving[ |]+([^ |]+)[ |]*([^ |]+)?[ |]*([^ |]+)?$") local range = regexm(`"`fcn'"', "^range[ |]+([^ |]+)[ |]*([^ |]+)?[ |]*([^ |]+)?[ |]*([^ ]+)?$") if ( `:list fcn in transforms' | `moving' | `range' | `cumsum' | `shift' ) { cap confirm var `args' if ( _rc ) { disp as err `"`fcn' requires single variable input"' exit _rc } unab args: `args' if ( `:list sizeof args' != 1 ) { disp as err `"`fcn' requires single variable input"' exit 198 } if ( _by() ) { disp as txt "performance wrning: -by- prefix may be slower than -by()-" * disp as err "by: prefix not allowed with `fcn'" * exit 198 } if ( `retype' ) local type local options types(`type') `options' cap noi gstats transform (`fcn') `name' = `args' `if' `in' `wgt', by(`byvars') `options' exit _rc } if ( `:list fcn in direct' ) { if ( `"`fcn'"' == "winsorize" ) local fcn winsor if ( `"`fcn'"' == "residualize" ) local fcn hdfe cap confirm var `args' if ( _rc ) { disp as err `"`fcn' requires single variable input"' exit _rc } unab args: `args' if ( `:list sizeof args' != 1 ) { disp as err `"`fcn' requires single variable input"' exit 198 } if ( _by() ) { disp as txt "performance wrning: -by- prefix may be slower than -by()-" * disp as err "by: prefix not allowed with `fcn'" * exit 198 } if ( `retype' ) { disp as err "warning: type ignored with gegen function `fcn'" } local options gen(`name') `options' cap noi gstats `fcn' `args' `if' `in' `wgt', by(`byvars') `options' exit _rc } * If function does not exist, fall back on egen * --------------------------------------------- if !( `:list fcn in funcs' ) { confirm new variable `name' if ( `"`c(adoarchive)'"' == "1" ) { capture qui _stfilearchive find _g`fcn'.ado if ( _rc ) { di as error "`fcn'() is neither a gtools nor an egen function" exit 133 } } else { capture qui findfile _g`fcn'.ado if ( `"`r(fn)'"' == "" ) { di as error "`fcn'() is neither a gtools nor an egen function" exit 133 } } if ( `"`weight'"' != "" ) { di as txt "`fcn'() is not a gtools function; falling back on egen" di as err "weights are not allowed for egen-only functions" exit 101 } if ( `"`args'"' == "_all" ) | ( `"`args'"' == "*" ) { unab args : _all } local gtools_args HASHmethod(passthru) /// oncollision(passthru) /// Verbose /// _subtract /// _CTOLerance(passthru) /// compress /// forcestrl /// NODS DS /// Parse - as varlist (ds) or negative (nods) BENCHmark /// BENCHmarklevel(passthru) /// gtools_capture(str) syntax [if] [in] [, `gtools_args' *] if ( "`byvars'" == "" ) { di as txt "`fcn'() is not a gtools function and no by(); falling back on egen" cap noi egen `type' `name' = `fcn'(`args') `if' `in', `options' `gtools_capture' exit _rc } else { di as txt "`fcn'() is not a gtools function; will hash and use egen" local gopts `hashmethod' `oncollision' `verbose' `_subtract' `_ctolerance' local gopts `gopts' `compress' `forcestrl' `benchmark' `benchmarklevel' `ds' `nods' local popts _type(`type') _name(`name') _fcn(`fcn') _args(`args') _byvars(`byvars') * NOTE(mauricio): I don't think you need to do anything special if by() * here because L50 to L67 of egen.ado just pass by() as an argument. cap noi egen_fallback `if' `in', kwargs(`gopts') `popts' `options' `gtools_capture' exit _rc } } FreeTimer local t97: copy local FreeTimer gtools_timer on `t97' global GTOOLS_CALLER gegen if ( `warnby' ) { disp as txt "performance warning: -by- prefix may be slower than -by()-" } * Parse syntax call if function is known * -------------------------------------- * gegen [type] varname = fun(args) [if] [in], [options] syntax /// Main call was parsed manually [if] [in] /// [if condition] [in start / end] [aw fw iw pw] , /// [weight type = exp] [ /// by(str) /// Collapse by variabes: [+|-]varname [[+|-]varname ...] /// p(real 50) /// Percentile to compute, #.# (only with pctile). e.g. 97.5 n(int 0) /// nth smallest to select (negative for largest) /// missing /// for group(), tag(); does not get rid of missing values counts(passthru) /// for group(), tag(); create `counts' with group counts fill(str) /// for group(), tag(); fills rest of group with `fill' /// replace /// Replace target variable with output, if target already exists noinit /// Do not initialize targets with missing values /// compress /// Try to compress strL variables forcestrl /// Force reading strL variables (stata 14 and above only) NODS DS /// Parse - as varlist (ds) or negative (nods) Verbose /// Print info during function execution _subtract /// (Undocumented) Subtract result from source variable _CTOLerance(passthru) /// (Undocumented) Counting sort tolerance; default is radix BENCHmark /// print function benchmark info BENCHmarklevel(int 0) /// print plugin benchmark info HASHmethod(passthru) /// Hashing method: 0 (default), 1 (biject), 2 (spooky) oncollision(passthru) /// error|fallback: On collision, use native command or throw error gtools_capture(passthru) /// Ignored (captures fcn options if fcn is not known) /// /// Unsupported egen options /// ------------------------ /// Label /// lname(passthru) /// Truncate(passthru) /// ] if ( `benchmarklevel' > 0 ) local benchmark benchmark local benchmarklevel benchmarklevel(`benchmarklevel') local keepmissing = cond("`missing'" == "", "", "keepmissing") foreach opt in label lname truncate { if ( `"``opt''"' != "" ) { di as txt "Option -`opt'- is not implemented." exit 198 } } if ( "`gtools_capture'" != "" ) { di as txt ("option -gtools_capture()- ignored with supported function `fcn')" } local bench = ( "`benchmark'" != "" ) if ( ("`ds'" != "") & ("`nods'" != "") ) { di as err "-ds- and -nods- mutually exclusive" exit 198 } * Parse weights * ------------- if ( `:list posof "variance" in fcn' > 0 ) { if ( `"`weight'"' == "pweight" ) { di as err "variance not allowed with pweights" exit 135 } } if ( `:list posof "cv" in fcn' > 0 ) { if ( `"`weight'"' == "pweight" ) { di as err "cv not allowed with pweights" exit 135 } } if ( `:list posof "sd" in fcn' > 0 ) { if ( `"`weight'"' == "pweight" ) { di as err "sd not allowed with pweights" exit 135 } } if ( `:list posof "select" in fcn' > 0 ) { if ( inlist(`"`weight'"', "iweight") ) { di as err "select not allowed with `weight's" exit 135 } } if ( `:list posof "semean" in fcn' > 0 ) { if ( inlist(`"`weight'"', "pweight", "iweight") ) { di as err "semean not allowed with `weight's" exit 135 } } if ( `:list posof "sebinomial" in fcn' > 0 ) { if ( inlist(`"`weight'"', "aweight", "iweight", "pweight") ) { di as err "sebinomial not allowed with `weight's" exit 135 } } if ( `:list posof "sepoisson" in fcn' > 0 ) { if ( inlist(`"`weight'"', "aweight", "iweight", "pweight") ) { di as err "sepoisson not allowed with `weight's" exit 135 } } if ( `"`weight'"' != "" ) { tempvar w touse qui gen double `w' `exp' `if' `in' local wgt `"[`weight'=`w']"' local weights weights(`weight' `w') local anywgt anywgt mark `touse' `if' `in' `wgt' local ifin if `touse' `in' } else { local wgt local weights local anywgt mata st_local("ifin", st_local("if") + " " + st_local("in")) } * Parse quantiles * --------------- local ofcn `fcn' if ( "`fcn'" == "pctile" ) { local quantbad = !( (`p' < 100) & (`p' > 0) ) if ( `quantbad' ) { di as error "Invalid quantile: `p'; p() should be in (0, 100)" cap timer clear `t97' global GTOOLS_CALLER "" exit 110 } local fcn p`p' } else if ( `p' != 50 ) { di as err "Option {opt p()} not allowed" cap timer clear `t97' global GTOOLS_CALLER "" exit 198 } * Parse selection * --------------- if ( "`fcn'" == "select" ) { if ( `n' == 0 ) { di as error "n() should be a positive or negative integer" cap timer clear `t97' global GTOOLS_CALLER "" exit 110 } local fcn select`n' } else if ( `n' != 0 ) { di as err "Option {opt n()} not allowed" cap timer clear `t97' global GTOOLS_CALLER "" exit 198 } * Target and stats * ---------------- if ( "`replace'" == "" ) { confirm new variable `name' tempvar dummy local rename rename `dummy' `name' local addvar qui mata: st_addvar("`type'", "`dummy'") local noobs "" local retype = `retype' & 1 } else { * NOTE: Addvar should be "" with replace; the problem was that * the internals did not empty the variable before writing to * it. With if/in conditions, this caused problems because the * variable was not set to missing outside the range, as it * should. * * As a quickfix I thought I could just empty it before calling * internals. However, this causesd two issues: The variable * would be missing on error, and if the target is also a source, * the source would be all misssing when read by the plugin! * * The easiest fix was to require the target to not be in the * sources, but there was an easier fix! I already empty the * targets fot gcollapse, so I simply set that boolean to true * (init_targ) when gegen was called with replace! This impacts * the check in lines 489-492. cap confirm new variable `name' if ( _rc ) { local dummy `name' local rename "" local addvar "" local retype = `retype' & 0 if "`init'" == "" local noobs qui replace `dummy' = . else local noobs "" } else { tempvar dummy local rename rename `dummy' `name' local addvar qui mata: st_addvar("`type'", "`dummy'") local retype = `retype' & 1 local noobs "" } } local targets targets(`dummy') local stats stats(`fcn') * If tag or group requested, then do that right away * -------------------------------------------------- local opts `compress' `forcestrl' `_subtract' `_ctolerance' local opts `opts' `verbose' `benchmark' `benchmarklevel' local opts `opts' `oncollision' `hashmethod' `ds' `nods' local sopts `counts' if ( inlist("`fcn'", "tag", "group") | (("`fcn'" == "count") & ("`args'" == "1")) ) { if ( "`fill'" != "" ) local fill fill(`fill') if ( `"`weight'"' != "" ) { di as txt "(weights are ignored for egen function {opt `fcn'})" } gtools_timer info `t97' `"Plugin setup"', prints(`bench') off if ( "`fcn'" == "tag" ) { local action tag(`type' `dummy') gfunction(hash) unsorted local noobs qui replace `dummy' = 0 } if ( inlist("`fcn'", "group", "count") ) { if ( `=_N' < maxbyte() ) { * All types are OK } else if ( `=_N' < `=2^24' ) { if inlist("`type'", "byte") { * byte is no longer OK; int, float still OK local upgraded = cond(`retype', "", "`type'") local type int } } else if ( `=_N' < maxint() ) { if inlist("`type'", "byte", "float") { * byte and float no longer OK; int still OK local upgraded = cond(`retype', "", "`type'") local type int } } else if ( `=_N' < maxlong() ) { if inlist("`type'", "byte", "int", "float") { * byte, float, int no longer OK; must upgrade to long local upgraded = cond(`retype', "", "`type'") local type long } } else { if ( "`type'" != "double" ) { * Only double can maintain precision local upgraded = cond(`retype', "", "`type'") local type double } } } if ( "`upgraded'" != "" ) { disp "(warning: user-requested type '`upgraded'' upgraded to '`type'')" } if ( "`fcn'" == "group" ) { local action gen(`type' `dummy') gfunction(hash) countmiss if ( `=_N' > 1 ) local s s local noobs qui replace `dummy' = . local notxt di as txt "(`=_N' missing value`s' generated)" } if ( "`fcn'" == "count" ) { local missing missing local fill fill(group) local action counts(`type' `dummy') gfunction(hash) countmiss unsorted if ( `=_N' > 1 ) local s s local noobs qui replace `dummy' = . local notxt di as txt "(`=_N' missing value`s' generated)" } if ( ("`byvars'" != "") & inlist("`fcn'", "tag", "group") ) { di as err "egen ... `fcn'() may not be combined with with by" global GTOOLS_CALLER "" exit 190 } if ( ("`byvars'" == "") & inlist("`fcn'", "tag", "group") ) { local byvars `args' } cap noi _gtools_internal `byvars' `ifin', `opts' `sopts' `action' `missing' `replace' `init' `fill' local rc = _rc global GTOOLS_CALLER "" if ( `rc' == 17999 ) { local gtools_args `hashmethod' /// `oncollision' /// `verbose' /// `_subtract' /// `_ctolerance' /// `compress' /// `forcestrl' /// `nods' `ds' /// `benchmark' /// `benchmarklevel' /// * `gtools_capture' local gtools_opts `counts' fill(`fill') `replace' `init' p(`p') `missing' collision_fallback, gtools_call(`"`type' `name' = `fcn'(`args') `ifin'"') `gtools_args' `gtools_opts' exit 0 } else if ( `rc' == 17001 ) { if ( "${GTOOLS_DUPS}" == "" ) { if ( `=_N' > 0 ) { `noobs' `notxt' } `rename' exit 0 } else { error 2000 } } else if ( `rc' ) { exit `rc' } return scalar N = `r(N)' return scalar J = `r(J)' return scalar minJ = `r(minJ)' return scalar maxJ = `r(maxJ)' `rename' exit 0 } * Parse source(s) * --------------- unab memvars: _all local rc = 0 if ( !((`:list sizeof args' == 1) & (`:list args in memvars')) ) { tempvar exp if ( _by() ) { cap by `_byvars': gen double `exp' = `args' } else { cap gen double `exp' = `args' if ( (_rc == 0) & ("`byvars'" != "") ) { mata printf("{bf:warning}: gegen is {bf:NOT} parsing the expression '%s' by group.\n", st_local("args")) mata printf("To parse this expression by group, call gegen using the -by:- prefix.\n") } } local rc = _rc } if ( ((`:list sizeof args' == 1) & (`:list args in memvars')) | `rc' ) { cap ds `args' if ( _rc ) { global GTOOLS_CALLER "" di as error "Invalid call; please specify {opth `ofcn'(varlist)} or {opth `ofcn'(exp)}." exit 198 } else { local sametype 1 local sources `r(varlist)' cap confirm numeric v `sources' if ( _rc ) { global GTOOLS_CALLER "" di as err "{opth `ofcn'(varlist)} must call a numeric variable list." exit _rc } * See notes in lines 294-310 * if ( "`:list sources & dummy'" != "" ) { * if ( "`replace'" != "" ) local extra " even with -replace-" * di as error "Variable `dummy' canot be a source and a target`extra'" * exit 198 * } } } else if ( `rc' == 0 ) { local sources `exp' local sametype 0 } if ( `"`ofcn'"' == "nunique" ) { if ( `:list sizeof sources' != 1 ) { global GTOOLS_CALLER "" disp as err `"`fcn' requires single variable input"' exit 198 } } * cap ds `args' * if ( _rc == 0 ) { * local sametype 1 * local sources `r(varlist)' * cap confirm numeric v `sources' * if ( _rc ) { * global GTOOLS_CALLER "" * di as err "{opth `ofcn'(varlist)} must call a numeric variable list." * exit _rc * } * } * else { * local sametype 0 * tempvar exp * cap gen double `exp' = `args' * if ( _rc ) { * global GTOOLS_CALLER "" * di as error "Invalid call; please specify {opth `ofcn'(varlist)} or {opth `ofcn'(exp)}." * * exit 198 * } * local sources `exp' * } * Parse target type * ----------------- * if ( ("`addvar'" != "") & `retype' ) { if ( `retype' ) { parse_target_type `sources', fcn(`ofcn') sametype(`sametype') `anywgt' local type = "`r(retype)'" local addvar qui mata: st_addvar("`type'", "`dummy'") } * Parse counts into freq for gfunction call * ----------------------------------------- if ( "`counts'" != "" ) { local 0, `counts' syntax, [counts(str)] gettoken ftype fname: counts if ( "`fname'" == "" ) { local fname `ftype' if ( `=_N < maxlong()' ) local ftype long else local ftype double } cap confirm new variable `fname' if ( _rc ) { local rc = _rc if ( "`replace'" == "" ) { global GTOOLS_CALLER "" di as err "Variable `fname' exists; try a different name or run with -replace-" exit `rc' } else if ( ("`replace'" != "") & ("`addvar'" != "") ) { qui replace `fname' = . local replace "" } } else { if ( "`addvar'" == "" ) { local addvar qui mata: st_addvar("`ftype'", "`counts'") } else { local addvar qui mata: st_addvar(("`type'", "`ftype'"), ("`name'", "`counts'")) local replace "" } } local counts freq(`counts') } * Call the plugin * --------------- local unsorted = cond("`fill'" == "data", "", "unsorted") gtools_timer info `t97' `"Plugin setup"', prints(`bench') off `addvar' local action sources(`sources') `targets' `stats' fill(`fill') `counts' countmiss cap noi _gtools_internal `byvars' `ifin', `unsorted' `opts' `action' `weights' missing `keepmissing' `replace' `init' local rc = _rc global GTOOLS_CALLER "" if ( `rc' == 17999 ) { if ( `"`weight'"' != "" ) { di as err "Cannot use fallback with weights." exit 17000 } local gtools_args `hashmethod' /// `oncollision' /// `verbose' /// `_subtract' /// `_ctolerance' /// `compress' /// `forcestrl' /// `nods' `ds' /// `benchmark' /// `benchmarklevel' /// `gtools_capture' local gtools_opts `counts' fill(`fill') `replace' `init' p(`p') `missing' collision_fallback, gtools_call(`"`type' `name' = `fcn'(`args') `ifin'"') `gtools_args' `gtools_opts' exit 0 } else if ( `rc' == 17001 ) { if ( "${GTOOLS_DUPS}" == "" ) { `noobs' `rename' exit 0 } else { error 2000 } } else if ( `rc' ) exit `rc' return scalar N = `r(N)' return scalar J = `r(J)' return scalar minJ = `r(minJ)' return scalar maxJ = `r(maxJ)' `rename' exit 0 end capture program drop egen_fallback program egen_fallback, sortpreserve syntax [if] [in], /// [ /// _type(str) /// _name(str) /// _fcn(str) /// _args(str) /// _byvars(str) /// by(passthru) /// kwargs(str) /// * /// ] tempvar dummy global EGEN_Varname `_name' global EGEN_SVarname `_sortindex' local cvers = _caller() if ( "`_fcn'" == "mode" | "`_fcn'" == "concat" ) { local vv : display "version " string(`cvers') ", missing:" } if ( "`: sortedby'" == "`_byvars'" ) { local byid `: sortedby' } else { tempvar byid hashsort `_byvars', gen(`byid') sortgen skipcheck `kwargs' } capture noisily `vv' _g`_fcn' `_type' `dummy' = (`_args') `if' `in', by(`byid') `options' global EGEN_SVarname global EGEN_Varname if ( _rc ) exit _rc quietly count if missing(`dummy') if ( `r(N)' ) { local s = cond(r(N) > 1, "s", "") di in bl "(" r(N) " missing value`s' generated)" } rename `dummy' `_name' exit 0 end capture program drop gtools_timer program gtools_timer, rclass syntax anything, [prints(int 0) end off] tokenize `"`anything'"' local what `1' local timer `2' local msg `"`3'; "' * If timer is 0, then there were no free timers; skip this benchmark if ( `timer' == 0 ) exit 0 if ( inlist("`what'", "start", "on") ) { cap timer off `timer' cap timer clear `timer' timer on `timer' } else if ( inlist("`what'", "info") ) { timer off `timer' qui timer list return scalar t`timer' = `r(t`timer')' return local pretty`timer' = trim("`:di %21.4gc r(t`timer')'") if ( `prints' ) { di `"`msg'`:di trim("`:di %21.4gc r(t`timer')'")' seconds"' } timer off `timer' timer clear `timer' timer on `timer' } if ( "`end'`off'" != "" ) { timer off `timer' timer clear `timer' } end capture program drop parse_target_type program parse_target_type, rclass syntax varlist, fcn(str) sametype(int) [anywgt] gettoken var restvars: varlist local maxtype: type `var' encode_vartype `maxtype' local maxcode `r(typecode)' foreach var in `restvars' { local stype: type `var' encode_vartype `stype' local scode `r(typecode)' if ( `scode' > `maxcode' ) { local maxtype `stype' local maxcode `scode' } } if ( `sametype' ) local retype_A `maxtype' else local retype_A: set type if ( "`maxtype'" == "double" ) local retype_B double else local retype_B: set type if ( `=_N < maxlong()' & ("`anywgt'" == "") ) local retype_C long else local retype_C double if ( `"`maxtype'"' == "byte" ) { local retype_D int } else if ( `"`maxtype'"' == "int" ) { local retype_D long } else if ( `"`maxtype'"' == "long" ) { local retype_D double } else if ( `"`maxtype'"' == "float" ) { local retype_D double } else if ( `"`maxtype'"' == "double" ) { local retype_D double } if ( "`fcn'" == "tag" ) return local retype = "byte" if ( "`fcn'" == "group" ) return local retype = "`retype_C'" if ( "`fcn'" == "total" ) return local retype = "double" if ( "`fcn'" == "sum" ) return local retype = "double" if ( "`fcn'" == "nansum" ) return local retype = "double" if ( "`fcn'" == "mean" ) return local retype = "`retype_B'" if ( "`fcn'" == "geomean" ) return local retype = "`retype_B'" if ( "`fcn'" == "sd" ) return local retype = "`retype_B'" if ( "`fcn'" == "variance" ) return local retype = "`retype_B'" if ( "`fcn'" == "cv" ) return local retype = "`retype_B'" if ( "`fcn'" == "max" ) return local retype = "`retype_A'" if ( "`fcn'" == "min" ) return local retype = "`retype_A'" if ( "`fcn'" == "range" ) return local retype = "`retype_D'" if ( "`fcn'" == "select" ) return local retype = "`retype_A'" if ( "`fcn'" == "count" ) return local retype = "`retype_C'" if ( "`fcn'" == "median" ) return local retype = "`retype_B'" if ( "`fcn'" == "iqr" ) return local retype = "`retype_B'" if ( "`fcn'" == "percent" ) return local retype = "`retype_B'" if ( "`fcn'" == "first" ) return local retype = "`retype_A'" if ( "`fcn'" == "last" ) return local retype = "`retype_A'" if ( "`fcn'" == "firstnm" ) return local retype = "`retype_A'" if ( "`fcn'" == "lastnm" ) return local retype = "`retype_A'" if ( "`fcn'" == "semean" ) return local retype = "`retype_B'" if ( "`fcn'" == "sebinomial" ) return local retype = "`retype_B'" if ( "`fcn'" == "sepoisson" ) return local retype = "`retype_B'" if ( "`fcn'" == "pctile" ) return local retype = "`retype_B'" if ( "`fcn'" == "nunique" ) return local retype = "`retype_C'" if ( "`fcn'" == "nmissing" ) return local retype = "`retype_C'" if ( "`fcn'" == "skewness" ) return local retype = "`retype_B'" if ( "`fcn'" == "kurtosis" ) return local retype = "`retype_B'" if ( "`fcn'" == "gini" ) return local retype = "`retype_B'" if ( "`fcn'" == "gini|dropneg" ) return local retype = "`retype_B'" if ( "`fcn'" == "gini|keepneg" ) return local retype = "`retype_B'" end capture program drop encode_vartype program encode_vartype, rclass args vtype if ( "`vtype'" == "byte" ) return scalar typecode = 1 else if ( "`vtype'" == "int" ) return scalar typecode = 2 else if ( "`vtype'" == "long" ) return scalar typecode = 3 else if ( "`vtype'" == "float" ) return scalar typecode = 4 else if ( "`vtype'" == "double" ) return scalar typecode = 5 else return scalar typecode = 0 end capture program drop collision_fallback program collision_fallback local gtools_args HASHmethod(passthru) /// oncollision(passthru) /// Verbose /// _subtract /// _CTOLerance(passthru) /// compress /// forcestrl /// NODS DS /// BENCHmark /// BENCHmarklevel(passthru) /// gtools_capture(str) syntax, [`gtools_args' gtools_call(str) counts(str) fill(str) replace *] foreach opt in counts fill replace { if ( `"``opt''"' != "" ) { di as err "Cannot use fallback with option {opt `opt'}." exit 17000 } } egen `gtools_call', `options' end capture program drop FreeTimer program FreeTimer qui { timer list local i = 99 while ( (`i' > 0) & ("`r(t`i')'" != "") ) { local --i } } c_local FreeTimer `i' end