*-------------------------------------------------------------------------------
*
*	"rrcalc: Stata program to calculate AAPOR compliant response rates"
*	Version 1.1, 29th October 2015
*	Authors: Kai Willem Weyandt (GESIS) & Lars Kaczmirek (GESIS)
*
*-------------------------------------------------------------------------------

cap program drop rrcalc
program define rrcalc, rclass
version 12
syntax varlist(min=1 max=1 numeric) [using] [if] ,[GENerate(string) Rates(string) replace]

//------------------------------------------------------------------------------
//	define sample to use

marksample touse, novarlist

//------------------------------------------------------------------------------
//	tempvar with 8 categories corresponding to I, P, R, NC, O, UH, UO, and NE

tempvar tempaapor mirror
qui gen byte `tempaapor'=.
qui tostring `varlist', gen(`mirror')

capture confirm variable `varlist'
if !_rc {

	qui replace `tempaapor' = 1 if regexm(`mirror', "^1$")
	qui replace `tempaapor' = 1 if regexm(`mirror', "^10") | regexm(`mirror', "^1[.]0")
	qui replace `tempaapor' = 1 if regexm(`mirror', "^11") | regexm(`mirror', "^1[.]1")

	qui replace `tempaapor' = 2 if  regexm(`mirror', "^12") | regexm(`mirror', "^1[.]2")

	qui replace `tempaapor' = 3 if regexm(`mirror', "^21") | regexm(`mirror', "^2[.]1")
	qui replace `tempaapor' = 4 if regexm(`mirror', "^22") | regexm(`mirror', "^2[.]2")

	qui replace `tempaapor' = 5 if regexm(`mirror', "^23") | regexm(`mirror', "^2[.]3")
	qui replace `tempaapor' = 5 if regexm(`mirror', "^20") | regexm(`mirror', "^2[.]0")

	qui replace `tempaapor' = 6 if regexm(`mirror', "^31") | regexm(`mirror', "^3[.]1")

	qui replace `tempaapor' = 7 if regexm(`mirror', "^32") | regexm(`mirror', "^3[.]2")
	qui replace `tempaapor' = 7 if regexm(`mirror', "^33") | regexm(`mirror', "^3[.]3")
	qui replace `tempaapor' = 7 if regexm(`mirror', "^34") | regexm(`mirror', "^3[.]4")
	qui replace `tempaapor' = 7 if regexm(`mirror', "^35") | regexm(`mirror', "^3[.]5")
	qui replace `tempaapor' = 7 if regexm(`mirror', "^39") | regexm(`mirror', "^3[.]9")

	qui replace `tempaapor' = 8 if regexm(`mirror', "^4")

	qui levelsof `varlist'
	tokenize `r(levels)'
	local length = wordcount(r(levels))
	local undefinedValues = ""

	forval x=1(1)`length' {
		if ///
		regexm("``x''", "^1$")!=1 & ///
		regexm("``x''", "^10")!=1 & ///
		regexm("``x''", "^11")!=1 & ///
		regexm("``x''", "^12")!=1 & ///
		regexm("``x''", "^21")!=1 & ///
		regexm("``x''", "^22")!=1 & ///
		regexm("``x''", "^23")!=1 & ///
		regexm("``x''", "^20")!=1 & ///
		regexm("``x''", "^31")!=1 & ///
		regexm("``x''", "^32")!=1 & ///
		regexm("``x''", "^33")!=1 & ///
		regexm("``x''", "^34")!=1 & ///
		regexm("``x''", "^35")!=1 & ///
		regexm("``x''", "^39")!=1 & ///
		regexm("``x''", "^4")!=1 & ///
		///
		regexm("``x''", "^1[.]0")!=1 & ///
		regexm("``x''", "^1[.]1")!=1 & ///
		regexm("``x''", "^1[.]2")!=1 & ///
		regexm("``x''", "^2[.]1")!=1 & ///
		regexm("``x''", "^2[.]2")!=1 & ///
		regexm("``x''", "^2[.]3")!=1 & ///
		regexm("``x''", "^2[.]0")!=1 & ///
		regexm("``x''", "^3[.]1")!=1 & ///
		regexm("``x''", "^3[.]2")!=1 & ///
		regexm("``x''", "^3[.]3")!=1 & ///
		regexm("``x''", "^3[.]4")!=1 & ///
		regexm("``x''", "^3[.]5")!=1 & ///
		regexm("``x''", "^3[.]9")!=1 ///
		{
			local undefinedValues = "`undefinedValues' " + "``x''"
			qui replace `tempaapor'=. if `mirror'=="``x''"
		}
	}
}

//------------------------------------------------------------------------------
/*	[defining scalars for rate calculation]

I	=	Complete interviews (1.1)
P	=	Partial interviews (1.2)
R	=	Refusal and break-off (2.1)
NC	=	Non-contact (2.2)
O	=	Other (2.0, 2.3)

UH	=	Unknown if household/occupied HU (3.1)
UO	=	Unknown, other (3.2-3.9)

NE	=	Not eligible (4)

E	= 	e is the estimated proportion of cases of unknown
		eligibility that are eligible
*/

qui sum `tempaapor' if `touse'
scalar NSEN =	r(N)

qui count if `touse' & `tempaapor'==1
scalar I	=	r(N)

qui count if `touse' & `tempaapor'==2
scalar P	=	r(N)

qui count if `touse' & `tempaapor'==3
scalar R	=	r(N)

qui count if `touse' & `tempaapor'==4
scalar NC	=	r(N)

qui count if `touse' & `tempaapor'==5
scalar O	=	r(N)

qui count if `touse' & `tempaapor'==6
scalar UH	=	r(N)

qui count if `touse' & `tempaapor'==7
scalar UO	=	r(N)

qui count if `touse' & `tempaapor'==8
scalar NE	=	r(N)

qui count if `touse' & `tempaapor'==1 | `tempaapor'==2 | `tempaapor'==3 | ///
`tempaapor'==4 | `tempaapor'==5
scalar EE	=	r(N)

scalar E	=	EE / (EE+NE)

//------------------------------------------------------------------------------
//	[calculation of response rates]

*response rate 1:
local RR1	= 		I 		/ 	( (I+P) + (R+NC+O) + (UH+UO) )
*response rate 2:
local RR2	= 		(I+P) 	/ 	( (I+P) + (R+NC+O) + (UH+UO) )
*response rate 3:
local RR3	= 		I 		/	( (I+P) + (R+NC+O) + E*(UH+UO) )

local outputRR1 = `RR1' * 100
local outputRR1 = string(`outputRR1',"%9.2f") //format [using filename]
local outputRR3 = `RR3' * 100
local outputRR3 = string(`outputRR3',"%9.2f") //format [using filename]


*response rate 4:
local RR4	= 		(I+P) 	/ 	( (I+P) + (R+NC+O) + E*(UH+UO) )
*response rate 5:
local RR5	= 		I 		/ 	( (I+P) + (R+NC+O) )
*response rate 6:
local RR6	=		(I+P) 	/ 	( (I+P) + (R+NC+O) )

*cooperation rate 1:
local COOP1	= 		I 		/ 	( (I+P) + R + O )
*cooperation rate 2:
local COOP2	= 		(I+P) 	/ 	( (I+P) + R + O )
*cooperation rate 3:
local COOP3	= 		I 		/ 	( (I+P) + R )
*cooperation rate 4:
local COOP4	= 		(I+P) 	/ 	( (I+P) + R )

*refusal rate 1:
local REF1	= 		R 		/ 	( (I+P) + (R+NC+O) + (UH+UO) )
*refusal rate 2:
local REF2	= 		R 		/ 	( (I+P) + (R+NC+O) + E*(UH + UO) )
*refusal rate 3:
local REF3	= 		R 		/ 	( (I+P) + (R+NC+O) )

*contact rate 1:
local CON1	= 		( (I+P) + R + O ) 	/ 	( (I+P) + R + O + NC + (UH+UO) )
*contact rate 2:
local CON2	= 		( (I+P) + R + O ) 	/ 	( (I+P) + R + O + NC + E*(UH+UO) )
*contact rate 3:
local CON3	= 		( (I+P) + R + O ) 	/ 	( (I+P) + R + O + NC )

//------------------------------------------------------------------------------
//	[output]: default overview

//define columns for output
local fircol 45
local seccol 49
local linlen 60

di _n
di as result _dup(`linlen') "_"
di _n
di as result "Overall cases (N)" _col(`fircol')": " _col(`seccol') NSEN
di ""
di as result "Complete interview (1.1 - I)" _col(`fircol')": " _col(`seccol') I
di as result "Partial interview (1.2 - P)"	_col(`fircol')": " _col(`seccol') P
di as result "Refusal and break-off (2.1 - R)" _col(`fircol')": " _col(`seccol') R
di as result "Non-contact (2.2 - NC)" _col(`fircol')": " _col(`seccol') NC
di as result "Other (2.0, 2.3 - O)" _col(`fircol')": " _col(`seccol') O

di as result "Unknown if household/occupied HU (3.1 - UH)"	_col(`fircol')": " _col(`seccol') UH
di as result "Unknown, other (3.2-3.5, 3.9 - UO)"	_col(`fircol')": " _col(`seccol') UO

di ""
di as result "e is the estimated proportion" _col(`fircol')": " _col(`seccol') %5.4f E
di as result "of cases of unknown eligibility"
di as result "that are eligible"
di _n

di as result _dup(`linlen') "_"

//------------------------------------------------------------------------------
//	[rates()]: display options: rr, coop, con, ref, all

if "`rates'" != "" {
	if regexm("`rates'", "rr")==1 | regexm("`rates'", "all")==1 {
		di ""
		di as result "AAPOR response rate 1" _col(`fircol') ": " _col(`seccol') %5.4f `RR1'
		di as result "AAPOR response rate 2" _col(`fircol')": " _col(`seccol') %5.4f `RR2'
		di as result "AAPOR response rate 3" _col(`fircol')": " _col(`seccol') %5.4f `RR3'
		di as result "AAPOR response rate 4" _col(`fircol')": " _col(`seccol') %5.4f `RR4'
		di as result "AAPOR response rate 5" _col(`fircol')": " _col(`seccol') %5.4f `RR5'
		di as result "AAPOR response rate 6" _col(`fircol')": " _col(`seccol') %5.4f `RR6'
	}

	if regexm("`rates'", "coop")==1 | regexm("`rates'", "all")==1  {
		di ""
		di as result "AAPOR cooperation rate 1" _col(`fircol')": " _col(`seccol')  %5.4f `COOP1'
		di as result "AAPOR cooperation rate 2" _col(`fircol')": " _col(`seccol')  %5.4f `COOP2'
		di as result "AAPOR cooperation rate 3" _col(`fircol')": " _col(`seccol')  %5.4f `COOP3'
		di as result "AAPOR cooperation rate 4" _col(`fircol')": " _col(`seccol')  %5.4f `COOP4'
	}

	if regexm("`rates'", "ref")==1 | regexm("`rates'", "all")==1 {
		di ""
		di as result "AAPOR refusal rate 1" _col(`fircol')": " _col(`seccol')  %5.4f `REF1'
		di as result "AAPOR refusal rate 2" _col(`fircol')": " _col(`seccol')  %5.4f `REF2'
		di as result "AAPOR refusal rate 3" _col(`fircol')": " _col(`seccol')  %5.4f `REF3'
	}

	if regexm("`rates'", "con")==1 | regexm("`rates'", "all")==1 {
		di ""
		di as result "AAPOR contact rate 1" _col(`fircol')": " _col(`seccol')  %5.4f `CON1'
		di as result "AAPOR contact rate 2" _col(`fircol')": " _col(`seccol')  %5.4f `CON2'
		di as result "AAPOR contact rate 3" _col(`fircol')": " _col(`seccol')  %5.4f `CON3'
	}

	di ""
	di as result _dup(`linlen') "_"
}

//------------------------------------------------------------------------------
//	[notifications]: display undefined values

if "`undefinedValues'"!="" {
	local worder=wordcount("`undefinedValues'")
	if "`worder'"=="1" {
		di _n
		di in red "Value `undefinedValues' does not fit the AAPOR Standards Definition and has been set to missing!"
		di _n
	}
	if "`worder'"!="1" {
		di _n
		di in red "Values `undefinedValues' do not fit the AAPOR Standards Definition and have been set to missing!"
		di _n
	}

}

//------------------------------------------------------------------------------
//	[generate(newvar)]: generate aapor variable with value label

if "`generate'"!="" {
	qui gen byte `generate' = `tempaapor' if `touse'

	lab def `generate' ///
	1 "1 - Complete interview" ///
	2 "2 - Partial interview" ///
	3 "3 - Refusal and break-off" ///
	4 "4 - Non-contact" ///
	5 "5 - Other" ///
	6 "6 - Unknown if household/occupied HU" ///
	7 "7 - Unknown, other" ///
	8 "8 - Not eligible" ///
	, modify

	lab val `generate' `generate'
}

//------------------------------------------------------------------------------
//	[r()]: put results in scalars stored in r()

return scalar CON3= `CON3'
return scalar CON2 = `CON2'
return scalar CON1 = `CON1'
return scalar REF3= `REF3'
return scalar REF2 = `REF2'
return scalar REF1 = `REF1'
return scalar COOP4 = `COOP4'
return scalar COOP3 = `COOP3'
return scalar COOP2 = `COOP2'
return scalar COOP1 = `COOP1'
return scalar RR6= `RR6'
return scalar RR5= `RR5'
return scalar RR4= `RR4'
return scalar RR3 = `RR3'
return scalar RR2 = `RR2'
return scalar RR1 = `RR1'
return scalar E=E
return scalar EE=EE
return scalar NE=NE
return scalar UO=UO
return scalar UH=UH
return scalar O=O
return scalar NC=NC
return scalar R=R
return scalar P=P
return scalar I=I
return scalar N=NSEN

//------------------------------------------------------------------------------
// [using filename]: produce text file

if `"`using'"'!="" {
	qui cap file close myfile
	if "`replace'"=="" {
		qui file open myfile `using', write
	}
	if "`replace'"=="replace" {
		qui file open myfile `using', write replace
	}
	qui set more off
	qui file write myfile "You may use the following text in your publications which is based on text in AAPOR (2015, p. 52f.):" _n
	qui file write myfile `"`=char(34)'"'"Response rate 1 (RR1), or the minimum response rate, was `outputRR1'%. Response rate 3 (RR3) was `outputRR3'%. RR3 estimates what proportion of cases of unknown eligibility is actually eligible. All response rates are calculated with the formulae in AAPOR (2015). In addition, a table showing the final disposition codes for all cases is available upon request." _n
	qui file write myfile "References: The American Association for Public Opinion Research. 2015. Standard Definitions: Final Dispositions of Case Codes and Outcome Rates for Surveys. 8th edition. AAPOR."`"`=char(34)'"' _n
	qui cap file close myfile
	qui set more on
}

end