-
Notifications
You must be signed in to change notification settings - Fork 0
/
impartial.ado
331 lines (303 loc) · 12.2 KB
/
impartial.ado
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/*
Project: UNESCO Institute of Statistics handbook on measuring education equity
Purpose: Calculate basic ratio indices of equity in education
Author: Stuart Cameron
Contact: stuart.cameron@opml.co.uk
*/
capture program drop impartial
program impartial, rclass
//--- Present impartiality (inter-group equity) measures of a given binary outcome
/* TODO:
- Present relevant measures for continuous outcomes too (though gap and ratio should be fine as they are)
? Present other appropriate statistics for a continuous class variable, e.g. Pearson's rho, bivariate regression coefficient and R2
- remove irrelevant measures for continuous outcomes (odds ratio - should it be called something else?)
? Version for multiple categorical measure; ordinal measures?
- Make weights work (pass them to mean)
- allow if, in
? Make svy work
/ Focus is 1st category found if not specified
/ Add adjusted parity index
/ Add odds ratio
/ Add effect size (Phi = Pearson's rho)
? Add something like t-test or proportion test - could add standard error and significance test of each measure
(i.e. whether different from 0 for gap or correlation coefficient, different from 1 for ratio, adj parity ratio, odds ratio)
- alternatively provide confidence intervals
/ Return the values as well as displaying them
? Compare quantiles of a continuous class variable
e.g. impartial EC5=Yes No, over(wscore=Q1 Q5)
/ alternative syntax like impartial, outcome(EC5=Yes No) over(HL4=Female Male)
? allow plausible values
/- to treat outcome as continuous, simply don't provide categories: impartial books, over(HL4=Female Male)
*/
//syntax [fweight aweight iweight pweight], outcome(string) over(string)
//syntax varname [=/exp], over(string) // [fweight aweight iweight pweight], over(string)
version 13.1
syntax anything(name=outcome equalok) [fweight aweight iweight pweight], over(string) [bar] [hbar] [graphoptions(string asis)]
set varabbrev off // alternatively use novarabbrev
// Parse outcome and over into variable name and, if present, labels/values
gettoken outcomevar rest: outcome, parse("=")
gettoken _ outcomevals: rest, parse("=")
gettoken overvar rest: over, parse("=")
gettoken _ overvals: rest, parse("=")
if "`bar'" == "bar" & "`hbar'" == "hbar" {
display as error "Only one type of bar chart may be shown"
exit
}
local graph = "`bar'" == "bar" | "`hbar'" == "hbar"
if `graph' {
local graph_type = "`bar'`hbar'"
}
local noutcomevals: word count `outcomevals'
local novervals: word count `overvals'
local outcomelabel: variable label `outcomevar'
local overlabel: variable label `overvar'
local overhaslabels = `"`: value label `overvar''"' != `""'
local outcomehaslabels = `"`: value label `outcomevar''"' != `""'
quietly levelsof `outcomevar', local(outcomelevels)
quietly levelsof `overvar', local(overlevels)
local binaryoutcome = `: word count `outcomelevels'' == 2
local binaryover = `: word count `overlevels'' == 2
forvalues i = 1/`noutcomevals' {
quietly getlevel `outcomevar', value(`: word `i' of `outcomevals'') local(outcome`i')
}
forvalues i = 1/`novervals' {
quietly getlevel `overvar', value(`: word `i' of `overvals'') local(over`i')
}
/*
// Debugging info
display `"outcomevar: `outcomevar'; outcomevals: `outcomevals' noutcomevals: `noutcomevals'"'
display `"overvar: `overvar'; overvals: `overvals'; novervals: `novervals'"'
display `"levels: `outcomelevels' / `overlevels' "'
display `"values: outcome `outcome1', `outcome2' over `over1', `over2'"'
display `"has labels: outcome `outcomehaslabels' over `overhaslabels'"'
*/
/* Parsing over variable:
1. If 1 label or value is given, compare this to other non-missing.
2. If 2 labels or values are given, compare the two corresponding values
3. If no labels or values given, compare the first category found to other non-missing
(Then later: allow quintiles etc.)
*/
tempvar overrec
if `novervals' == 0 {
local over1: word 1 of `overlevels'
local over2 nonmissing
//recode `overvar' (`over1'=1) (nonmissing=0) (missing=.), gen(`overrec')
local over1_label: label (`overvar') `over1'
local over2_label "other non-missing values"
quietly recode `overvar' (`over1'=1) (`over2'=0) (missing=.), gen(`overrec')
}
else if `novervals' == 1 {
local over2 nonmissing
local over1_label: label(`overvar') `over1'
local over2_label "other non-missing values"
quietly recode `overvar' (`over1'=1) (`over2'=0) (missing=.), gen(`overrec')
}
else if `novervals' == 2 {
local over1_label: label(`overvar') `over1'
local over2_label: label(`overvar') `over2'
//display `"RECODING recode `overvar' (`over1'=1) (`over2'=0) (else=.), gen(`overrec') "'
quietly recode `overvar' (`over1'=1) (`over2'=0) (else=.), gen(`overrec')
}
else {
display as error `"Too many values given for the over variable `over'"'
exit
}
// If the comparison group is 'nonmissing' but there is only one other
// nonmissing value, then label the value appropriately in output
if `binaryover' & `"`over2'"' == `"nonmissing"' {
foreach level of local overlevels {
if `over1' != `level' {
local over2 `level'
local over2_label: label(`overvar') `over2'
continue, break
}
}
}
/*Parsing outcome variable:
1. If no label or value is given, treat the outcome as continuous
2. If 1 label or value is given, treat it as binary, comparing the given category to other non-missing
3. If 2 labels or values are given, treat it as binary, comparing the two given categories (treating the first as 'positive')
*/
tempvar outcomerec
if `noutcomevals' == 0 {
generate `outcomerec' = `outcomevar'
local outcometype continuous
}
else if `noutcomevals' == 1 {
local outcome1_label: label (`outcomevar') `outcome1'
local outcome2 nonmissing
local outcome2_label "other non-missing values"
local outcometype binary
quietly recode `outcomevar' (`outcome1'=1) (nonmissing=0) (missing=.), gen(`outcomerec')
}
else if `noutcomevals' == 2 {
local outcome1_label: label (`outcomevar') `outcome1'
local outcome2_label: label (`outcomevar') `outcome2'
local outcometype binary
quietly recode `outcomevar' (`outcome1'=1) (`outcome2'=0) (else=.), gen(`outcomerec')
}
else {
display as error `"Too many values given for the outcome variable `outcome'"'
exit
}
/*
if "`outcometype'" == "binary" {
//display `"RECODING `outcomevar' (`outcome1'=1) (`outcome2'=0) (else=.), gen(`outcomerec')"'
if `"`outcome2'"' == `"nonmissing"' {
quietly recode `outcomevar' (`outcome1'=1) (nonmissing=0) (missing=.), gen(`outcomerec')
}
else {
quietly recode `outcomevar' (`outcome1'=1) (`outcome2'=0) (else=.), gen(`outcomerec')
}
}
*/
// If the second outcome is 'nonmissing' but there is only one other
// nonmissing value, then label the value appropriately in output
if `binaryoutcome' & `"`outcome2'"' == `"nonmissing"' {
foreach level of local outcomelevels {
if `outcome1' != `level' {
local outcome2 `level'
local outcome2_label: label(`outcomevar') `outcome2'
continue, break
}
}
}
quietly mean `outcomerec' if !missing(`overrec')
matrix M = r(table)
scalar overall = M[1, 1]
quietly mean `outcomerec' if `overrec' == 0
matrix M = r(table)
scalar c = M[1, 1]
quietly mean `outcomerec' if `overrec' == 1
matrix M = r(table)
scalar f = M[1, 1]
local colon = cond(`"`outcomelabel'"' == `""', "", ": ")
display as text _newline `"Comparing `outcometype' outcome `outcomevar'`colon'`outcomelabel'"'
local label = cond(`overhaslabels', ", `over1_label'", "")
display "for " as result `"Group A (`overvar'=`over1'`label')"' _continue
if `"`over2'"' == `"nonmissing"' {
local valuepart "other non-missing values"
}
else if `overhaslabels' {
local valuepart `over2', `over2_label'
}
else {
local valuepart `over2'
}
display as text " compared to " as result `"Group B (`overvar'=`valuepart')"'
display
local graph_labelA = cond(`overhaslabels', "`over1_label'", "`over1'")
local graph_labelB = cond(`overhaslabels', "`over2_label'", "`over2'")
local h display %-40s as text
local r as result %4.3f
if `"`outcometype'"' == `"binary"' {
display as text `"Proportion with outcome `outcome1'"' _continue
local graph_outcome_label "outcomevar=`outcome1'"
if `outcomehaslabels' & `"`outcome1_label'"' != `""' & `"`outcome1_label'"' != `"`outcome1'"' {
display `" (`outcome1_label')"' _continue
local graph_outcome_label `"`outcome1_label'"'
}
display " versus " _continue
if "`outcome2'" == "nonmissing" {
display "other nonmissing values"
}
else {
display `"`outcome2'"' _continue
if `outcomehaslabels' & `"`outcome2_label'"' != `""' & `"`outcome2_label'"' != `"`outcome2'"' {
display `" (`outcome2_label')"'
}
else {
display
}
}
`h' "% in group A or B" `r' (overall * 100)
`h' `"% in group A"' `r' (f * 100)
`h' `"% in group B"' `r' (c * 100)
}
else {
display "Average value of outcome variable"
`h' "In group A or B" `r' overall
`h' `"In group A"' `r' f
`h' `"In group B"' `r' c
}
scalar gap = 100 * (f - c)
scalar ratio = f / c
//scalar gap = abs(vgap)
scalar minratio = min(ratio, 1/ratio)
// scalar odds_ratio = (c * (1 - f)) / (f * (1 - c)) // report the reverse, i.e. odds of good outcome
scalar odds_ratio = (f * (1 - c)) / (c * (1 - f))
scalar adjusted_ratio = cond(f <= c, f / c, 2 - c / f)
quietly correlate `outcomerec' `overrec'
scalar phi = r(rho)
display
`h' "Gap (A - B)" `r' gap
`h' "Ratio (A / B)" `r' ratio
//disp %-40s as text "Absolute gap |F - C|" as result %4.3f gap // No point in displaying this as it can easily be read by removing the sign from the gap
`h' "Min ratio (min(A / B, B / A))" `r' minratio
`h' "Adjusted parity ratio" `r' adjusted_ratio
`h' "Odds ratio" `r' odds_ratio
`h' "Pearson's correlation coefficient" `r' phi
// Display bar charts if requested
if `graph' {
if `"`outcometype'"' == `"binary"' {
tempvar graphover
label variable `outcomerec' `"`outcomelabel'"'
quietly replace `outcomerec' = `outcomerec' * 100
quietly recode `overrec' (0 = 2 "Group B (`graph_labelB')") (1 = 1 "Group A (`graph_labelA')"), gen(`graphover')
local cattitle = cond("`graph_type'" == "bar", "b1title", "l1title")
graph `graph_type' (mean) `outcomerec', over(`graphover') `cattitle'(`"`overlabel'"') ytitle(`"`graph_outcome_label' (%)"') `graphoptions'
}
else {
tempvar graphover
label variable `outcomerec' `"`outcomelabel'"'
quietly recode `overrec' (0 = 2 "Group B (`graph_labelB')") (1 = 1 "Group A (`graph_labelA')"), gen(`graphover')
local cattitle = cond("`graph_type'" == "bar", "b1title", "l1title")
graph `graph_type' (mean) `outcomerec', over(`graphover') `cattitle'(`"`overlabel'"') ytitle(`"`outcomelabel'"') `graphoptions'
}
}
// Return values
return scalar gap = gap
return scalar absgap = abs(gap)
return scalar ratio = ratio
return scalar minratio = minratio
return scalar adjratio = adjusted_ratio
return scalar or = odds_ratio
return scalar rho = phi
end
capture program drop getlevel
program getlevel, rclass
//--- Given a level (value) or label, return the level for a given variable
// optionally return the level in a local macro
// note, if the given value is both a value and a label, the value associated with the label will be returned
version 13.1
syntax varname, value(string) [local(string)]
local v `varlist'
capture confirm number `value'
if _rc == 0 { // If value is a number, then by default just return the same number
local r = `value'
}
local haslabels = `": value label `v'"' != `""'
//display `"`v'; `value'; `local'; `haslabels'"'
if `haslabels' {
// Variable has labels, so first check if value is found there
quietly levelsof `v', local(levels)
/* Note, we end up repeatedly calling levelsof. It would be more optimal
to pass the levels (rather than the variable name) to getlevel to avoid this */
foreach level of local levels {
if `"`value'"' == `"`: label (`v') `level''"' {
local r = `level'
display `"Found `value' in the labels of `v' for value `level'"'
continue, break
}
}
}
if `"`r'"' == `""' {
display as error `"Value `value' is non-numeric and "' _continue
display as error cond(`haslabels', "was not found in labels of `v'", "`v' does not have labels")
exit
}
if `"`local'"' != `""' {
c_local `local' `r'
}
return local level `r'
end