1 | **********************************************************************; |
---|
2 | * Program filename: TotalSurvAgeBRFSS.sas ; |
---|
3 | * ; |
---|
4 | * This file is "included" in each BRFSS indicator .def file for AA ; |
---|
5 | * rates. The macro variables (varname, weight) are also set in the ; |
---|
6 | * .def file. The %FocusLevel% variable is set in the IBIS Module.xml ; |
---|
7 | * file as a filter exclude variable. This allows the user to select ; |
---|
8 | * whether to calculate the at-risk percentage (%VarLevel=1) or the ; |
---|
9 | * not-at-risk percentage (%VarLevel=2). ; |
---|
10 | * ; |
---|
11 | * Missing set to . for all indicator and dimension vars ; |
---|
12 | * ; |
---|
13 | **********************************************************************; |
---|
14 | |
---|
15 | OPTIONS MPRINT MLOGIC MLOGICNEST SYMBOLGEN SPOOL SOURCE2 SUMSIZE=138M PAGESIZE=4000 LINESIZE=MAX; |
---|
16 | |
---|
17 | **********************************************************************************************; |
---|
18 | * CODE FOR ADJUSTING FOR MULTIPLE YEARS. ONLY NEEDED IF PRESENTING POPULATION COUNT ESTIMATES.; |
---|
19 | * If cross1 and cross2 are not equal to year, then _llcpwt is divided by the number of years ; |
---|
20 | * the user selects ; |
---|
21 | **********************************************************************************************; |
---|
22 | |
---|
23 | /* proc print data=tmp (obs=10); run; */ |
---|
24 | |
---|
25 | proc freq data=tmp; |
---|
26 | tables year / out=yrfreq noprint; |
---|
27 | run; |
---|
28 | proc print data=yrfreq; |
---|
29 | title1 ' '; title2 'yrfreq'; |
---|
30 | run; |
---|
31 | |
---|
32 | proc summary data=yrfreq; |
---|
33 | class Year; |
---|
34 | output out=yrnum n(year)=nyears; |
---|
35 | proc print data=yrnum; |
---|
36 | title1 ' '; title2 'yrnum'; |
---|
37 | run; |
---|
38 | |
---|
39 | data yrnum; set yrnum; |
---|
40 | if year=.; |
---|
41 | keep year nyears; |
---|
42 | proc print data=yrnum; |
---|
43 | title1 ' '; title2 'yrnum only .'; |
---|
44 | run; |
---|
45 | |
---|
46 | proc sort data=tmp; by year; run; |
---|
47 | |
---|
48 | proc sql; |
---|
49 | create table tmpyrwgt as |
---|
50 | select tmp.*, yrnum.nyears |
---|
51 | from tmp, yrnum |
---|
52 | quit; |
---|
53 | |
---|
54 | data tmp; |
---|
55 | set tmpyrwgt; |
---|
56 | if upcase(%cross1%) ^=YEAR |
---|
57 | ?cross2? and upcase(%cross2%) ^=YEAR |
---|
58 | then |
---|
59 | _llcpwt=_llcpwt/nyears; |
---|
60 | run; |
---|
61 | |
---|
62 | /*proc print data=tmp (obs=10); |
---|
63 | title1 ' '; title2 'checktmp .'; run; |
---|
64 | */ |
---|
65 | |
---|
66 | /**** SURVEYMEANS*************************************************************; |
---|
67 | * Run the SURVEYMEANS proc with Age BRFSS in domain statement. ; |
---|
68 | * No cross1 or cross2, run this first to get the totals. ; |
---|
69 | ******************************************************************************; |
---|
70 | */ |
---|
71 | |
---|
72 | %macro ageadjrt(varname,weight,Focuslevel); |
---|
73 | |
---|
74 | title1 '***************************************************************'; |
---|
75 | title2 'SURVEYMEANS proc (AgeBRFSS in domain statement and no cross vars)'; |
---|
76 | proc surveymeans data=tmp nobs sum mean stderr; |
---|
77 | var &varname.; |
---|
78 | class &varname.; |
---|
79 | strata ststr; |
---|
80 | domain AgeBRFSS |
---|
81 | %surveyvar1% |
---|
82 | ; |
---|
83 | ods output statistics=stats |
---|
84 | summary=summary |
---|
85 | domain=domain ; |
---|
86 | weight &weight. ; |
---|
87 | run; |
---|
88 | |
---|
89 | ***** TOTALTMP ****************************************************************; |
---|
90 | * Read the totals into memory and create the total row flag variables, trow1 ; |
---|
91 | * flags the total row for cross1 and trow2 flags the total row for cross2. ; |
---|
92 | * I think the cross2 with ?s is code that ibisq uses to create the program, ; |
---|
93 | * it checks for cross2 and uses that line in the program only if cross2 exists.; |
---|
94 | * trow1 and trow2 are used in AGEADJUST step and again used in ; |
---|
95 | * redflag step,to recode to appropriate values ; |
---|
96 | *******************************************************************************; |
---|
97 | title2 'TOTALTMP : Stats for total rows (trow1 and trow2 initialized to 1)'; |
---|
98 | data totaltmp; |
---|
99 | set work.domain; |
---|
100 | drop DomainLabel VarLabel; |
---|
101 | /*trow1 and 2 are used later to get aawt*/ |
---|
102 | trow1 = 1; |
---|
103 | ?cross2? trow2 = 1; |
---|
104 | run; |
---|
105 | proc print data=totaltmp noobs; run; |
---|
106 | |
---|
107 | ***** TMP ********************************************************************; |
---|
108 | * Set domain vars to AgeBRFSS, cross1, cross2 and run survey means ; |
---|
109 | ******************************************************************************; |
---|
110 | title2 'TMP: SurveyMeans for domain vars and crosses (AgeBRFSS by cross1 and cross2)'; |
---|
111 | proc surveymeans data=tmp nobs sum mean stderr; |
---|
112 | var &varname.; |
---|
113 | class &varname. ; |
---|
114 | strata ststr; |
---|
115 | domain AgeBRFSS*%cross1% |
---|
116 | ?cross2? *%cross2% |
---|
117 | ; |
---|
118 | ods output statistics=stats |
---|
119 | summary=summary |
---|
120 | domain=domain ; |
---|
121 | weight &weight. ; |
---|
122 | run; |
---|
123 | |
---|
124 | proc sort data=work.domain; |
---|
125 | by %cross1% %cross2% AgeBRFSS ; |
---|
126 | run; |
---|
127 | |
---|
128 | ***** TMPALL *****************************************************************; |
---|
129 | * CONCATENATE TOTAL ROWS ONTO NEW DOMAIN DATASET ; |
---|
130 | * work.domain is the new domain output, totaltemp is the new one with the ; |
---|
131 | * cross1 and cross2 output. (recodes based on var type (char or num) ; |
---|
132 | * determined use of SAS vtype function ; |
---|
133 | ******************************************************************************; |
---|
134 | title2 'TMPALL: stats for domain vars with totals added'; |
---|
135 | |
---|
136 | data tmpall; |
---|
137 | set work.domain totaltmp; |
---|
138 | &varname.=input(VarLevel, $54.); |
---|
139 | drop DomainLabel VarLabel; |
---|
140 | |
---|
141 | if (vtype(%cross1%)="C" and %cross1%='') then %cross1% = 'tot'; |
---|
142 | if (vtype(%cross1%)="N" and %cross1%=.) then %cross1% = -1; |
---|
143 | ?cross2? if (vtype(%cross2%)="C" and %cross2%='') then %cross2% = 'tot'; |
---|
144 | ?cross2? if (vtype(%cross2%)="N" and %cross2%=.) then %cross2% = -1; |
---|
145 | |
---|
146 | proc print data=tmpall noobs; run; |
---|
147 | |
---|
148 | ***** NORMALIZE **************************************************************; |
---|
149 | * Now, go back to work.domain records and compute new weights that are ; |
---|
150 | * normalized. ; |
---|
151 | * What is the problem? Let us say the user filters the dataset to a small ; |
---|
152 | * population, like blacks in Kauai, and that population does not have survey ; |
---|
153 | * records in all 7 AA age groups. The weights will not sum to 1.0. So we do ; |
---|
154 | * this step that finds the age groups that are represented in the filtered ; |
---|
155 | * dataset, and recomputes (normalizes) the weights so that they sum to 1.0 ; |
---|
156 | * AgeBRFSS - NCHS Statnote20 distribution #9 WITH 75+ FOR HI BRFSS ; |
---|
157 | ******************************************************************************; |
---|
158 | title2 'NORMALIZE: grab records in work.domain (actual records)'; |
---|
159 | data norm; |
---|
160 | set work.domain; |
---|
161 | drop DomainLabel VarLabel; |
---|
162 | %ageAdjustedFilter% |
---|
163 | run; |
---|
164 | proc print data=norm noobs; run; |
---|
165 | |
---|
166 | title2 'getwts: grab weights for records actually in dataset, row totals denoted when trow1 and trow2 = 1'; |
---|
167 | data getwts; |
---|
168 | set norm totaltmp; |
---|
169 | |
---|
170 | /* this is just to document the age ranges. |
---|
171 | Age BRFSS is already in the dataset, and is |
---|
172 | used in the domain statement for aarates. |
---|
173 | 18-24 0.128809768 |
---|
174 | 25-34 0.182648111 |
---|
175 | 35-44 0.219076679 |
---|
176 | 45-54 0.181652285 |
---|
177 | 55-64 0.117541734 |
---|
178 | 65-74 0.088966942 |
---|
179 | 75+ 0.081304482 |
---|
180 | */ |
---|
181 | |
---|
182 | aawt=0; /*probably don't need to set these here, but... ??*/ |
---|
183 | if AgeBRFSS="18-24" then aawt=0.128809768; |
---|
184 | if AgeBRFSS="25-34" then aawt=0.182648111; |
---|
185 | if AgeBRFSS="35-44" then aawt=0.219076679; |
---|
186 | if AgeBRFSS="45-54" then aawt=0.181652285; |
---|
187 | if AgeBRFSS="55-64" then aawt=0.117541734; |
---|
188 | if AgeBRFSS="65-74" then aawt=0.088966942; |
---|
189 | if AgeBRFSS="75+" then aawt=0.081304482; |
---|
190 | |
---|
191 | if N=0 then Mean=0; |
---|
192 | |
---|
193 | run; |
---|
194 | proc print data=getwts noobs; run; |
---|
195 | |
---|
196 | ***** SUMWTS *****************************************************************; |
---|
197 | * PRODUCE THE SUM OF THE WEIGHTS BASED ON THE AGE GROUPS THAT ARE PRESENT ; |
---|
198 | ******************************************************************************; |
---|
199 | title2 'SUMWTS: sums of the weights for records in dataset by crossvars'; |
---|
200 | proc summary data=getwts nway; |
---|
201 | var aawt; |
---|
202 | class %cross1% %cross2% VarLevel; |
---|
203 | output out=sumwts sum(aawt)=sumwt; |
---|
204 | proc sort data=sumwts; |
---|
205 | by %cross1% %cross2% VarLevel; |
---|
206 | run; |
---|
207 | proc print data=sumwts noobs; run; |
---|
208 | |
---|
209 | proc sort data=tmpall; |
---|
210 | by %cross1% %cross2% VarLevel; |
---|
211 | run; |
---|
212 | |
---|
213 | ***** AGEADJUST **************************************************************; |
---|
214 | * Merge the sum of the weights onto the dataset and divide aawt by sumwt to ; |
---|
215 | * provide normalized weights. ; |
---|
216 | * AgeBRFSS - NCHS Statnote20 distribution #9 + 75+, for HI BRFSS ; |
---|
217 | ******************************************************************************; |
---|
218 | title2 'AGEADJUST: merge sumwts onto dataset, normalize aawts'; |
---|
219 | data AgeAdjust; |
---|
220 | merge tmpall sumwts; |
---|
221 | by %cross1% %cross2% VarLevel; |
---|
222 | aawt=0; |
---|
223 | if AgeBRFSS="18-24" then aawt=0.128809768; |
---|
224 | if AgeBRFSS="25-34" then aawt=0.182648111; |
---|
225 | if AgeBRFSS="35-44" then aawt=0.219076679; |
---|
226 | if AgeBRFSS="45-54" then aawt=0.181652285; |
---|
227 | if AgeBRFSS="55-64" then aawt=0.117541734; |
---|
228 | if AgeBRFSS="65-74" then aawt=0.088966942; |
---|
229 | if AgeBRFSS="75+" then aawt=0.081304482; |
---|
230 | normwt=aawt/sumwt; *normwt is the "normalized" weight for age-adjusting; |
---|
231 | if trow1=1 then normwt=aawt; |
---|
232 | if trow2=1 then normwt=aawt; |
---|
233 | |
---|
234 | if N=0 then Mean=0; |
---|
235 | |
---|
236 | run; |
---|
237 | proc print data=AgeAdjust noobs; run; |
---|
238 | |
---|
239 | ***** TMP ********************************************************************; |
---|
240 | * Produce cross-products for weighted percentages. ; |
---|
241 | ******************************************************************************; |
---|
242 | title2 'TMP: dataset with cross-products (wgt_percent)'; |
---|
243 | data tmp; |
---|
244 | set AgeAdjust; |
---|
245 | keep AgeBRFSS %cross1% %cross2% VarName VarLevel &varname. N Mean StdErr normwt wgt_percent wgt_var trow1 trow2; |
---|
246 | &varname.=input(VarLevel, $54.); |
---|
247 | wgt_percent=mean*normwt; |
---|
248 | wgt_var=stderr*stderr*normwt*normwt; |
---|
249 | proc print data=tmp noobs; |
---|
250 | run; |
---|
251 | |
---|
252 | ***** TMP1 *******************************************************************; |
---|
253 | * SUM CROSS-PRODUCTS WITHIN CROSS VARS TO GET AGE-ADJUSTED RATES ; |
---|
254 | ******************************************************************************; |
---|
255 | title2 'TMP1: age-adjusted, sum of n and weighted percentages and variances'; |
---|
256 | proc summary data=tmp ; |
---|
257 | var n wgt_percent wgt_var; |
---|
258 | class %cross1% %cross2% &varname.; |
---|
259 | output out=tmp1 sum(n wgt_percent wgt_var)=; |
---|
260 | run; |
---|
261 | proc print data=tmp1 noobs; run; |
---|
262 | |
---|
263 | ***** RATE *******************************************************************; |
---|
264 | * CALCULATE ASYMMETRIC CONFIDENCE INTERVALS ; |
---|
265 | ******************************************************************************; |
---|
266 | title2 'RATE: RATES COMPUTED'; |
---|
267 | data rate; |
---|
268 | set tmp1; |
---|
269 | |
---|
270 | stderr=sqrt(wgt_var); |
---|
271 | f=log(wgt_percent)-log(1-wgt_percent); |
---|
272 | s=stderr/(wgt_percent*(1-wgt_percent)); |
---|
273 | Lf=f-1.96*s; |
---|
274 | Uf=f+1.96*s; |
---|
275 | |
---|
276 | lower=exp(Lf)/(1+exp(Lf)); |
---|
277 | upper=exp(Uf)/(1+exp(Uf)); |
---|
278 | if %cross1%=. then delete; |
---|
279 | else if %cross1%='' then delete; |
---|
280 | ?cross2? if %cross2%=. then delete; |
---|
281 | ?cross2? else if %cross2%='' then delete; |
---|
282 | drop f s Lf Uf wgt_var; |
---|
283 | run; |
---|
284 | |
---|
285 | proc sort data=rate; |
---|
286 | by %cross1% |
---|
287 | ?cross2? %cross2% |
---|
288 | &varname.; |
---|
289 | run; |
---|
290 | |
---|
291 | proc print data=rate noobs; run; |
---|
292 | |
---|
293 | ***** SAMPLEN ****************************************************************; |
---|
294 | * GENERATE UNWEIGHTED SAMPLEN FOR CROSS1, CROSS2 for cell suppression below ; |
---|
295 | ******************************************************************************; |
---|
296 | title2 'SAMPLEN: the n from all the varname totals'; |
---|
297 | proc summary data=tmpall nway; |
---|
298 | var N; |
---|
299 | class %cross1% %cross2%; |
---|
300 | output out=sampleN sum(N)=sampleN; |
---|
301 | run; |
---|
302 | proc sort data=sampleN; |
---|
303 | by %cross1% |
---|
304 | ?cross2? %cross2% |
---|
305 | ; |
---|
306 | |
---|
307 | proc print data=sampleN noobs; run; |
---|
308 | |
---|
309 | title2 'TMP2: SAMPLEN MERGED INTO TMP1 DATA'; |
---|
310 | data tmp2; |
---|
311 | merge rate sampleN; |
---|
312 | by %cross1% |
---|
313 | ?cross2? %cross2% |
---|
314 | ; |
---|
315 | drop _TYPE_ _FREQ_ ; |
---|
316 | run; |
---|
317 | proc print data=tmp2 noobs; run; |
---|
318 | |
---|
319 | *****************************************************************; |
---|
320 | ********WGTCOUNTS: Add wgtsum field to every record *************; |
---|
321 | ***********Used by Hawaii, they want pop num, not samplen********; |
---|
322 | *****************************************************************; |
---|
323 | proc summary data=tmpall nway; /*need to do proc summary across all AA age groups to get weighted counts*/ |
---|
324 | var Sum; |
---|
325 | class %cross1% |
---|
326 | ?cross2? %cross2% |
---|
327 | &varname. |
---|
328 | ; |
---|
329 | output out=wgtcounts sum(Sum)=; |
---|
330 | proc sort data=wgtcounts; by %cross1% |
---|
331 | ?cross2? %cross2% &varname. |
---|
332 | ; |
---|
333 | proc print; title2 'wgtcounts'; |
---|
334 | run; |
---|
335 | data tmp4; *Add weighted counts field to each record; |
---|
336 | merge wgtcounts tmp2 ; |
---|
337 | by %cross1% |
---|
338 | ?cross2? %cross2% |
---|
339 | &varname. |
---|
340 | ; |
---|
341 | drop _TYPE_ _FREQ_ ; |
---|
342 | proc print; title2 'tmp4: Add weighted sample field to every record'; |
---|
343 | run; |
---|
344 | |
---|
345 | proc summary data=wgtcounts nway; /*need to do proc summary across all values of indicator to get weighted denominators*/ |
---|
346 | var sum; |
---|
347 | class %cross1% |
---|
348 | ?cross2? %cross2% |
---|
349 | ; |
---|
350 | output out=weightedN sum(Sum)=wgtsum; |
---|
351 | proc print; title2 'weightedN'; |
---|
352 | run; |
---|
353 | proc sort data=weightedN; |
---|
354 | by %cross1% |
---|
355 | ?cross2? %cross2% |
---|
356 | ; |
---|
357 | run; |
---|
358 | data tmp6; *Add wgtsum field to each record; |
---|
359 | merge tmp4 weightedN ; |
---|
360 | by %cross1% |
---|
361 | ?cross2? %cross2% |
---|
362 | ; |
---|
363 | drop _TYPE_ _FREQ_ ; |
---|
364 | proc print; title2 'tmp6: Add weighted sample denominator to every record'; |
---|
365 | run; |
---|
366 | |
---|
367 | **************************************************************************; |
---|
368 | * redflag is the statistical stability indicator, based on ; |
---|
369 | * the relative standard error (RSE, or coefficient of variation. ; |
---|
370 | * recode total rows to . (char and num) fore ibisq ; |
---|
371 | **************************************************************************; |
---|
372 | data rse; |
---|
373 | set tmp6; |
---|
374 | |
---|
375 | if 0<wgt_percent<.50 then RSE=(StdErr/wgt_percent); |
---|
376 | if .50<=wgt_percent<1 then RSE=(StdErr/(1-wgt_percent)); |
---|
377 | |
---|
378 | /* %%% Need value attribute in DB for Stable, use VALUE_ATTRIBUTE_NAME value here */ |
---|
379 | redflag=put('BRFSSStable', $14.); |
---|
380 | /********************************************************************************************************** |
---|
381 | * Not sure we are using this, just commenting out, and keeping it here; |
---|
382 | * If we do use this we will have to change logic way below where we Convert values for cell suppression; |
---|
383 | *if rse>.3 then redflag=put('Unstable', $14.); |
---|
384 | *if rse>.5 then redflag=put('VeryUnstable', $14.); |
---|
385 | ***********************************************************************************************************/ |
---|
386 | /* may want to comment this out at some future date */ |
---|
387 | /* %%% Need value attribute in DB for No Variance, use VALUE_ATTRIBUTE_NAME value here */ |
---|
388 | if wgt_percent in (0 1) then redflag=put('No Variance', $14.); |
---|
389 | |
---|
390 | if %cross1%=-1 then %cross1%=.; |
---|
391 | else if %cross1%='tot' then %cross1%='.'; |
---|
392 | ?cross2? if %cross2%=-1 then %cross2%=.; |
---|
393 | ?cross2? else if %cross2%='tot' then %cross2%='.'; |
---|
394 | |
---|
395 | /* Since these are esimates HI wants to round to 100's and sum is weighted numerator */ |
---|
396 | |
---|
397 | finalsum=ROUND(sum,100); |
---|
398 | |
---|
399 | /* had this as If 0<sum<50 then finalsum=50;, but in reading emails from around 3/18/2019 |
---|
400 | Katherine want this to be if weighted count is less than 50 show 50, because even if no one |
---|
401 | responded yes to th e particular indicator question, that does not mean no one in population, just sample |
---|
402 | */ |
---|
403 | If sum<50 then finalsum=50; |
---|
404 | |
---|
405 | wgtsum=ROUND(wgtsum,100); |
---|
406 | ageadj_percent=100*wgt_percent; |
---|
407 | lower=100*lower; |
---|
408 | upper=100*upper; |
---|
409 | |
---|
410 | run; |
---|
411 | |
---|
412 | proc sort data=rate; |
---|
413 | by %cross1% |
---|
414 | ?cross2? %cross2% |
---|
415 | ; |
---|
416 | run; |
---|
417 | proc print data=rse noobs; title2 'RSE: RSE computed, totals set to dot'; run; |
---|
418 | |
---|
419 | ***** Hawaii Small Numbers Rule ************************************; |
---|
420 | * This is where the cell suppression takes place - will update once ; |
---|
421 | * Hawaii provides their criteria ; |
---|
422 | * Suppress cells if unweighted samplesize (SampleN) is less than 50 ; |
---|
423 | * ZWs program uses ".A" to identify cells for suppression I have ; |
---|
424 | * co-opted his method so I can use the NM/now HI logic for cell ; |
---|
425 | * suppression instead of the standard IBIS logic. And I need to use ; |
---|
426 | * ZWs program because it will suppress the table marginals that can ; |
---|
427 | * be used to calculate the suppressed cells. Needs ; |
---|
428 | * suppressed_variables code at the end of this file to work. ; |
---|
429 | ********************************************************************; |
---|
430 | title2 'TMP: rate with cell suppression'; |
---|
431 | data tmp; |
---|
432 | set rse; |
---|
433 | if &varname = '%spvar2%'; /*This is the value for the indicator dimension passed in by the URL.;*/ |
---|
434 | if (0<SampleN<50) OR (rse >.3) then do; /* Hawaii cell supression Rule */ |
---|
435 | ageadj_percent = .A; * Age-adjustedpercent who answered yes or no; |
---|
436 | lower = .A; * lower confidence interval; |
---|
437 | upper = .A; * upper confidence interval; |
---|
438 | finalsum = .A; * pop estimated weighted numerator; |
---|
439 | wgtsum = .A; * pop estimated weighted denominator; |
---|
440 | /* %%% Need value attribute in DB for Not Reportable, use VALUE_ATTRIBUTE_NAME value here */ |
---|
441 | redflag=put('Not Reportable', $14.); |
---|
442 | end; |
---|
443 | proc print data=tmp noobs; title2 'final tmp'; run; |
---|
444 | |
---|
445 | %mend; |
---|