Dear Stata users,
I recently ask AI to check and evaluate Stephen Jenkins' samplepps command, and AI give me the following answer. I want to know whether that's true?
Critical Evaluation of the samplepps Command
1. Syntax and Compatibility Issues
Issue: The command uses ge as an abbreviation for generate.
ge `randu' = uniform() if `touse'
In Stata 8 (which the command targets), ge is not a valid abbreviation for generate. This abbreviation was only introduced in later versions. The command will fail with an "unrecognized command" error.
Issue: The command uses recast byte on a variable that may not yet exist.
recast byte `varlist'
If the user specifies a new variable name via generate(), the variable does not exist in the dataset when recast is executed. This will produce an error. The correct approach is to first generate the variable, then recast it.
2. Logic Error: Without-Replacement Sampling Algorithm
Issue: The systematic sampling implementation for without-replacement PPS is incorrect.
local draw = uniform() local R = 0 forvalues i = 0/`=`ncases'-1' { local pick = `draw' + `R' replace `varlist' = 1 if inrange(`pick',`lb',`ub') & `touse' local R = `R' + 1 }
The algorithm attempts to implement systematic PPS sampling by:
What should happen: In correct without-replacement PPS sampling (e.g., Brewer's method, Sampford's method, or the systematic method with proper handling of "certainty" units), units with selection probability > 1 must be treated as certainty units (selected with probability 1) and removed from the sampling frame before selecting the remaining units.
3. Logic Error: The Probability Check is Misguided
count if (`size'/`sumsize' >= 1/`ncases' & `touse') if `r(N)' > 0 { di as error "`r(N)' cases have " di as error "size/(total size) > 1/(no. cases)" error 197 }
Issue: The command checks whether any unit's selection probability (under the assumption of equal probability SRS) exceeds 1/ncases. However:
4. Logic Error: Without-Replacement Sampling with Replacement Check
Issue: The condition size/total_size >= 1/ncases is a sufficient but not necessary condition for problems in systematic PPS sampling. Even if no unit exceeds this threshold, the systematic method can still fail if the interval boundaries cause certain units to be "skipped" or "double-counted." The code does not check for or handle these edge cases.
5. Efficiency Issue: With-Replacement Sampling
Issue: The with-replacement PPS implementation uses a loop over each draw:
forvalues i = 1/`ncases' { local draw = uniform() replace `varlist' = `varlist' + 1 if inrange(`draw',`lb',`ub') & `touse' }
While this logic is correct, it is highly inefficient for large sample sizes. Each iteration loops over all observations to check the inrange() condition. For ncases = 1000 and N = 10,000, this performs 10 million comparisons.
Better approach: Use Stata's _n indexing or Mata for vectorized operations. Alternatively, use the cumulative sum approach with sum() and ceil() for much faster execution.
6. Issue: Inconsistent Variable Type
Issue: The command attempts to store the selection indicator as a byte variable:
recast byte `varlist'
However, in with-replacement sampling, varlist stores the count of times each unit is selected (which could be > 1). A byte variable can only store values from -127 to 100 (or 0-255 if unsigned). If a unit is selected more than 100 times, the value will overflow. For without-replacement sampling, a byte is sufficient (values are 0 or 1), but the command applies the same type to both cases.
7. Issue: lb Initialization
ge `lb' = 0 in 1 replace `lb' = `ub'[_n-1] if missing(`lb') & `touse'
Issue: The in 1 syntax sets lb = 0 only for the first observation. However, if the first observation is not in touse (i.e., excluded by the if or in condition), then lb remains missing for the first valid observation. The subsequent replace statement uses _n-1 referencing, which can produce incorrect cumulative boundaries when observations are excluded.
8. Issue: Missing Handling of the seed() Option
Note: The code snippet provided removed the seed() option from the syntax, but the original version of the command (as shown in the first message) included:
[ WITHrepl seed(string)]
If seed() is re-added, the command uses set seed but does not restore the original seed after execution, which can affect subsequent random number generation in the user's session.
9. Issue: No Design-Effect or Weight Calculation
Issue: The command returns only the selected indicator variable. It does not compute or return:
10. Issue: Poor Error Messages
di as error "{p 0 0 4}number cases to select " di as error "> number of valid obs{p_end}"
Issue: The error messages are cryptic and do not explain:
Summary Table of Issues
Conclusion
The samplepps command cannot be recommended for production use. It contains:
Recommendation: Do not use samplepps. Use gsample with [aw=size] for PPS sampling, and if you need within-cluster sampling (e.g., 50 students per school), implement it as a two-stage design with appropriate weight calculations.
I recently ask AI to check and evaluate Stephen Jenkins' samplepps command, and AI give me the following answer. I want to know whether that's true?
Critical Evaluation of the samplepps Command
1. Syntax and Compatibility Issues
Issue: The command uses ge as an abbreviation for generate.
ge `randu' = uniform() if `touse'
In Stata 8 (which the command targets), ge is not a valid abbreviation for generate. This abbreviation was only introduced in later versions. The command will fail with an "unrecognized command" error.
Issue: The command uses recast byte on a variable that may not yet exist.
recast byte `varlist'
If the user specifies a new variable name via generate(), the variable does not exist in the dataset when recast is executed. This will produce an error. The correct approach is to first generate the variable, then recast it.
2. Logic Error: Without-Replacement Sampling Algorithm
Issue: The systematic sampling implementation for without-replacement PPS is incorrect.
local draw = uniform() local R = 0 forvalues i = 0/`=`ncases'-1' { local pick = `draw' + `R' replace `varlist' = 1 if inrange(`pick',`lb',`ub') & `touse' local R = `R' + 1 }
The algorithm attempts to implement systematic PPS sampling by:
- Generating cumulative probability intervals (lb and ub)
- Starting with a random point (draw)
- Adding 1 sequentially (draw, draw+1, draw+2, ...)
What should happen: In correct without-replacement PPS sampling (e.g., Brewer's method, Sampford's method, or the systematic method with proper handling of "certainty" units), units with selection probability > 1 must be treated as certainty units (selected with probability 1) and removed from the sampling frame before selecting the remaining units.
3. Logic Error: The Probability Check is Misguided
count if (`size'/`sumsize' >= 1/`ncases' & `touse') if `r(N)' > 0 { di as error "`r(N)' cases have " di as error "size/(total size) > 1/(no. cases)" error 197 }
Issue: The command checks whether any unit's selection probability (under the assumption of equal probability SRS) exceeds 1/ncases. However:
- This check is meaningless for without-replacement PPS. The condition for a "certainty unit" in PPS is size_i / total_size > 1/ncases—which is exactly what the check identifies. But instead of handling these units appropriately (by setting them aside as certainty units), the command simply aborts with an error.
- This means the command cannot handle any dataset that contains a "large" unit—even though such units are the very reason PPS is used (to give large units appropriate representation).
4. Logic Error: Without-Replacement Sampling with Replacement Check
Issue: The condition size/total_size >= 1/ncases is a sufficient but not necessary condition for problems in systematic PPS sampling. Even if no unit exceeds this threshold, the systematic method can still fail if the interval boundaries cause certain units to be "skipped" or "double-counted." The code does not check for or handle these edge cases.
5. Efficiency Issue: With-Replacement Sampling
Issue: The with-replacement PPS implementation uses a loop over each draw:
forvalues i = 1/`ncases' { local draw = uniform() replace `varlist' = `varlist' + 1 if inrange(`draw',`lb',`ub') & `touse' }
While this logic is correct, it is highly inefficient for large sample sizes. Each iteration loops over all observations to check the inrange() condition. For ncases = 1000 and N = 10,000, this performs 10 million comparisons.
Better approach: Use Stata's _n indexing or Mata for vectorized operations. Alternatively, use the cumulative sum approach with sum() and ceil() for much faster execution.
6. Issue: Inconsistent Variable Type
Issue: The command attempts to store the selection indicator as a byte variable:
recast byte `varlist'
However, in with-replacement sampling, varlist stores the count of times each unit is selected (which could be > 1). A byte variable can only store values from -127 to 100 (or 0-255 if unsigned). If a unit is selected more than 100 times, the value will overflow. For without-replacement sampling, a byte is sufficient (values are 0 or 1), but the command applies the same type to both cases.
7. Issue: lb Initialization
ge `lb' = 0 in 1 replace `lb' = `ub'[_n-1] if missing(`lb') & `touse'
Issue: The in 1 syntax sets lb = 0 only for the first observation. However, if the first observation is not in touse (i.e., excluded by the if or in condition), then lb remains missing for the first valid observation. The subsequent replace statement uses _n-1 referencing, which can produce incorrect cumulative boundaries when observations are excluded.
8. Issue: Missing Handling of the seed() Option
Note: The code snippet provided removed the seed() option from the syntax, but the original version of the command (as shown in the first message) included:
[ WITHrepl seed(string)]
If seed() is re-added, the command uses set seed but does not restore the original seed after execution, which can affect subsequent random number generation in the user's session.
9. Issue: No Design-Effect or Weight Calculation
Issue: The command returns only the selected indicator variable. It does not compute or return:
- Sampling weights (1 / inclusion_probability)
- The design effect (deff)
- The sum of the size variable for variance estimation
- The finite population correction (fpc)
10. Issue: Poor Error Messages
di as error "{p 0 0 4}number cases to select " di as error "> number of valid obs{p_end}"
Issue: The error messages are cryptic and do not explain:
- What the user should do to fix the problem
- Why the problem occurred
- The distinction between sampling units and population units
Summary Table of Issues
| ge abbreviation invalid in Stata 8 | High | Syntax |
| recast before variable creation | High | Syntax |
| Without-replacement algorithm does not handle multiple selections of same unit | Critical | Logic |
| Aborts when encountering "large" units (prob > 1/n) | Critical | Logic |
| No handling of certainty units | Critical | Methodology |
| No calculation of sampling weights | High | Missing Feature |
| Inefficient loop for with-replacement sampling | Medium | Performance |
| Inconsistent variable type (byte vs count) | Medium | Logic |
| lb initialization fails when first observation excluded | Medium | Logic |
| Poor error messages | Low | Usability |
Conclusion
The samplepps command cannot be recommended for production use. It contains:
- Critical algorithmic errors that produce incorrect selection probabilities in without-replacement PPS
- Inability to handle "certainty units" (large units with selection probability > 1/n), which are common in real-world data
- Syntax errors that will cause it to fail in the stated Stata version
- Missing essential features such as sampling weight calculation
Recommendation: Do not use samplepps. Use gsample with [aw=size] for PPS sampling, and if you need within-cluster sampling (e.g., 50 students per school), implement it as a two-stage design with appropriate weight calculations.
Code:
*! version 1.0.1 24june2005 Stephen Jenkins
*! Draw random sample, proportional to size, of n cases
program define samplepps, rclass sortpreserve
version 8
syntax newvarname(generate) [if] [in], Size(varname numeric) ///
Ncases(integer) [ WITHrepl ]
quietly {
recast byte `varlist'
replace `varlist' = 0
marksample touse
markout `touse' `size'
count if `touse'
local nobs = r(N)
if `nobs' == 0 {
capture drop `varlist'
di as error "no valid observations"
error 2000
}
tempvar randu ub lb
ge `randu' = uniform() if `touse'
sort `randu' // random ordering of clusters
// with replacement
if "`withrepl'" != "" {
ge `ub' = sum(`size') if `touse'
su `size' if `touse', meanonly
replace `ub' = `ub'/r(sum) if `touse'
ge `lb' = 0 in 1
replace `lb' = `ub'[_n-1] if missing(`lb') & `touse'
replace `varlist' = 0 if `touse'
forvalues i = 1/`ncases' {
local draw = uniform()
replace `varlist' = `varlist' + 1 ///
if inrange(`draw',`lb',`ub') & `touse'
}
}
// without replacement
if "`withrepl'" == "" {
if (`nobs' < `ncases') {
capture drop `varlist'
di as error "{p 0 0 4}number cases to select "
di as error "> number of valid obs{p_end}"
error 197
}
su `size' if `touse', meanonly
local sumsize = r(sum)
count if (`size'/`sumsize' >= 1/`ncases' & `touse')
if `r(N)' > 0 {
capture drop `varlist'
di as error "{p 0 0 4}`r(N)' cases have "
di as error "size/(total size) > 1/(no. cases){p_end}"
error 197
}
ge `ub' = sum(`size') if `touse'
replace `ub' = `ncases'*`ub'/`sumsize' if `touse'
ge `lb' = 0 in 1
replace `lb' = `ub'[_n-1] if missing(`lb') & `touse'
local draw = uniform()
local R = 0
forvalues i = 0/`=`ncases'-1' {
local pick = `draw' + `R'
replace `varlist' = 1 ///
if inrange(`pick',`lb',`ub') & `touse'
local R = `R' + 1
}
}
replace `varlist' = . if `touse' == 0
lab var `varlist' "=1:selected case"
return scalar ncases = `ncases'
return scalar nobs = `nobs'
return local sizevar = "`size'"
return local sample "`varlist'"
return scalar withrepl = ("`withrepl'" != "")
}
end

Comment