Quota Management
Table of Contents
- Introduction
- Defining the targets and report options in quotadef.pl
- Monitoring the quotas
- Managing quotas in the ROUTE section
- Utilities
- Examples
Introduction
On some surveys there is a requirement to get a preset number of completed interviews for particular groups of respondents. For example to get a predefined number of interviews in each region or from each age group/gender combination.
The CAWI system provides tools to define quotas (target numbers of interviews) with particular groups and to manage the survey so that interviews are terminated for respondents in groups whose targets have been met. On termination respondent can be directed to another page using the STOP command.
The targets are defined using perl hash lists. The examples below illustrate the perl syntax so no in depth knowledge of perl is required to use the quota features.
Defining the targets and report options in quotadef.pl
The targets are defined in a perl hash table in file quotadef.pl in the survey .rsc directory. (Note: Defining the targets in SCROLL is described in the Scroll User Manual)
The structure of the hash table is:
use utf8;
our %OBJECTIFS = (
''=> ...,
''=> ...
);
The ' ' => . . . lines above to be replaced by quota lists as in the examples below:
Global quota
# QUOTA global
'Global' => 1000,
Example of simple quotas:
# QUOTA sex
'QSex' => {
'Sex' => {
1 => 480,
2 => 520
}
},
# QUOTA age
'QAge' => {
'Age_group' => {
1 => 100,
2 => 400,
3 => 400,
4 => 100
}
},
# QUOTA region
'QReg' => {
'Region' => {
1 => 100,
2 => 100,
3 => 200,
4 => 100,
5 => 200,
7 => 100,
7 => 100,
8 => 100
}
}
NB genquota is a useful tool for generating these lists.
Sex, Age_group and Region are the names of the variables/questions defined in the survey dsc file. QSex, QAge and QReg are the names of the quotas.
- It is not necessary to define a quota for each element. By default, if an element is not declared the quota is open.
- A quota of 0 is not allowed.
- The sum of quotas for each element does not have to correspond to the global quota.
Example of cross quotas:
'QSexAndAge' => {
'Sex' => {
1 => { # Male
'Age_group' => {
1 => 50, # 18-34 years
2 => 190, # 35-49 years
3 => 190, # 50-64 years
4 => 50 # 65 years and over
}
},
2 => { # Female
'Age_group' => {
1 => 50, # 18-34 years
2 => 210, # 35-49 years
3 => 210, # 50-64 years
4 => 50 # 65 years and over
}
}
}
}
Display options for use in the quota report
Labels can be added to the quotadef.pl file to be used in the online quota report:
our %OBJLABELS = (
"Global" => "Number completed",
"QSex" => "Quota for Sex",
"QAge" => "Quota for Age group",
"QReg" => "Quota for Region"
);
The order quotas are displayed can also be specified in quotadef.pl. Example
our %OBJVORDER = (
'QAge' => 1,
'QSex' => 2,
'QReg' => 3,
'Global' => 4
);
Syntax check
To test whether the syntax is right, use the command:
perl -wc quotadef.pl
Monitoring the quotas
Quotas can be monitored with an on-line reporting tool.
Setting up the Quota Report
Compiling the survey will automatically create the file quotaReport.dsc. Example quotaReport.dsc file:
quotaReport [
Global "Number completed",
QSex "Quota for Sex",
QAge "Quota for Age group",
QReg "Quota for Region",
QSexAndAge "Cross quota for Sex by Age"
] "Per quota report after CHECKALLQUOTA call" : {
block (0) "BLOCK",
ok (1) "OK"
}
This file should be inserted in the QUESTIONS section of the the main survey dsc file so that quotas can be monitored.
The array question, quotaReport, is automatically filled when the function CHECKALLQUOTA is used. The responses in this question are then used for quota monitoring.
Accessing the Quota Report
Quota reports are generated showing the targets and the number of interviews carried out.
The quota follow up tables can be inspected online with a link like that shown below:
http://<host>/cgi-bin/xcawi/Q/<project>/quotastats.pl
Example quota report with 3 quotas defined:
NB Test serial numbers can be excluded from the quotareport using %EXCID% in the project.ini file.
Managing quotas in the ROUTE section
The following functions can be used in the ROUTE section of the questionnaire to manage quotas.
- CHECKQUOTA()
- CHECKALLQUOTA()
- INCALLQUOTA()
- QUOTAASSIGNVALUE()
- INCQUOTAFULL()
They have to be declared as functions in the project .dsc file:
FUNCTION CHECKQUOTA RETURNS INTEGER
FUNCTION CHECKALLQUOTA RETURNS INTEGER
FUNCTION INCALLQUOTA RETURNS INTEGER
FUNCTION QUOTAASSIGNVALUE RETURNS INTEGER
FUNCTION INCQUOTAFULL RETURNS INTEGER
FUNCTION GETQUOTA RETURNS INTEGER
CHECKQUOTA()
Use after having asked the question(s) used in the definition of a quota.
This function returns 0 if the quota is full.
For example :
urlqf="quotafull"
IF (CHECKQUOTA("QSex") == 0) THEN {
qstatus=v4
STOP(urlqf) // display quotafull.blk HTML page
}
Notes:
The CHECKQUOTA function causes an Internal Server error if a variable being checked does not have an answer. Always verify that the variable concerned has an answer before doing a CHECKQUOTA. The CHECKALLQUOTA function does not have this problem.
Note that it does not make sense to use The CHECKQUOTA function after a QUOTAASSIGNVALUE. If a variable is not filled in at the exit of a QUOTAASSIGNVALUE, it is because a priori the quotas are filled. Therefore, after a k=QUOTAASSIGNVALUE("QUOTA","variable") use IF NOANSWER(variable) or IF k==0 (rather than CHECKQUOTA).
Good practice:
Add a global quota check at the start of the questionnaire :
urlqf="url=http://survey.xxxx.com/quotasfull"
IF (CHECKQUOTA("Global") == 0) THEN {
qstatus=v5
STOP(urlqf) //redirect to url
}
This saves the respondent from having to answer other questions once the overall target has been achieved.
CHECKALLQUOTA()
Function to be called after asking all the questions used to define quotas This function returns 0 if at least one quota defined in %OBJECTIFS is full. For example :
IF (CHECKALLQUOTA() == 0) THEN {
qstatus=v4
STOP(urlqf)
}
Notes:
The functions CHECKQUOTA and CHECKALLQUOTA automatically populate the variable quotaReport[QuotaName] (see above) to indicate which quota(s) is/are full. For example: quotaReport[QSexAndAge] is set to 0 if the interview causes the quota to be exceeded and set to 1 otherwise.
The CHECKALLQUOTA function only checks the number of completes collected for quotas in which the respondent has a value recorded for the variable concerned. So, in the case of crossed quotas, if the respondent does not have a value recorded for both of the variables involved, the CHECKALLQUOTA function will not check this quota.
INCALLQUOTA()
Function to be used at the end of the ROUTE section to increment all of the quotas. (Note: The function uses an internal function INCQUOTA)
k=INCALLQUOTA()
The identifiers contributing to each quota are stored in files on the server. Suppose an interview appears in the data (the .dat file) with :
Sex=1
Age_group=2
Region=4
The quota files where the identifiers are added after the call to INCALLQUOTA would be:
- qt_QSexAndAge_Sex_1_Age_group_2.txt
- qt_QSex_Sex_1.txt
- qt_QAge_Age_group_2.txt
- qt_QReg_Region_4.txt
These files are used by the quotastats.pl program. Duplicate identifiers within each of these files are only counted once.
QUOTAASSIGNVALUE()
This function sets the response category of a quota variable/question for which the quota is least complete with regard to percentage completed. So the quota variable value is set by this function rather than coming from a respondent answer.
It can be used to allocate respondents to groups where the percentage ending up in each group is defined as a quota. For example to make sure the same number of people are asked about each of a list of products/topics.
Usage:
k=QUOTAASSIGNVALUE("arg1","arg2","arg3","arg4","arg5")
Arguments:
- The name of the quota in %OBJECTIFS in quotadef.pl.
- The quota variable to be set to the value associated with the least complete quota.
- Optional. Can be used to specify the rule for when two or more categories have the same percentage:
- 0 => the first category in alphanumeric order is chosen
- 1 => the category is chosen at random
- Optional. A filter to restrict the categories that can be selected.
- Optional. Set to 1 to for the attribution to be made according to the profile.
The function returns 1 if a value for the quota variable can be assigned, 0 if all the quotas are full.
Example:
k=QUOTAASSIGNVALUE("QT_colour","qcolour","1","colour_tmp")
Suppose the categories of qcolour and colour_tmp are lists of colours. The function will only check the quotas for colours in colour_tmp and will set qcolour to the colour with the least complete quota. Random selection to be used if more than one colour has the same completion rate.
If the 5th argument is needed, then the 3rd and the 4th arguments must also be included. If the 4th argument is not needed, then use function QUOTAASSIGNVALUEP.
QUOTAASSIGNVALUEP()
QUOTAASSIGNVALUEP is the same as QUOTAASSIGNVALUE but with the 5th argument preset to 1 instead of being specified.
QUOTAASSIGNVALUEP("arg1", "arg2", "arg3")
See Example 2 for full example using QUOTAASSIGNVALUEP
Notes
- With QUOTAASSIGNVALUE and QUOTAASSIGNVALUEP only one item can be selected. To select several items multiple calls must be made and the values stored in temporary variables before being recoded back into the quota variable. So, for example, code could be written to select the least full 4 items out of 7. See Example 3 below.
- The choice of the percentage rather than the count was made to facilitate the advancement of the fieldwork. But note: that in some cases, this may involve having to change objectives in the field.
INCQUOTAFULL()
This function can be used to keep a record of the number of times a quota question is given a response for which the quota is already full.
Example usage:
IF(CHECKALLQUOTA() == 1) THEN {
NEWPAGE()
MESSAGE("NO QUOTAS COMPLETE")
ENDPAGE()
}
ELSE {
k=INCQUOTAFULL()
STOP
}
The function updates column F on the right-hand side of the quota monitoring table. For example if there were 3 new interviews with people in Age_group 1 after that quota was full the report would show:
A file qt_full _ quotaname _ variable _ element .txt on the server is used to record cases started after the quota is full. For example:
qt_full_QAge_Age_group_1.txt
Note that INCQUOTAFULL only works when it is associated with the CHECKALLQUOTA function and not with the CHECKQUOTA function.
GETQUOTA()
This function allows you to retrieve the number of respondents recorded in a quota file.
Example: quota control for completed surveys for a specific country
cmpde=GETQUOTA('QT_COMPLETE_COUNTRY_1_qstatus_2')
IF COUNTRY IN {1} AND cmpde>=800 THEN { qstatus=4; k=INCQUOTAFULL(); STOP(urlqf) }
Note: Be careful with the quota name after GETQUOTA (without the "qt_" prefix or ".txt" suffix). If it's written incorrectly, it won't trigger an Internal Server Error (ISE), but will return 0 instead.
Cross quotas
To implement cross quotas, use the functions described above. Example 2 uses QUOTAASSIGNVALUEP to manage cross quotas.
Utilities
- genquota
- quotaredo
genquota
genquota is a command that can be used in the dsc directory to generate the Perl structure to be used in quotadef.pl.
Arguments are:
- project name
- quota name
- variable used for quota or list of variables for cross quotas.
Examples (assuming a project named p1234):
genquota p1234 QSex Sex
genquota p1234 QSexAndAge Sex,Age_group
Note that genquota uses the project.xml file.
quotaredo
As described above, for each quota objective, a file qt_ quotaname _ variable _ element .txt is created on the server.
For example : qt_QSex_Sex_1.txt, qt_QSex_Sex_2.txt, qt_QSexAndAge_Sex_1_Age_group_2.txt .
Quotaredo can be used to regenerate these .txt files if the structure of a quota variable is modified (not the objectives). If a quota is removed, the .txt files are not removed.
A backup directory is created in the project data directory when the script starts. For each data file, quotaredo reports the actions carried out.
To regenerate the quota completely just remove all the files qt_*.txt in the data directory and run quotaredo.
In the cgi-bin directory for the survey (For example /data/www/cawi2/cgi-bin/xcawi/Q/project_name ) run :
./quotaredo.pl
Basic Example
To implement a global quota of 1000 respondents, made up of 400 men and 600 women.
quotadef.pl
our %OBJECTIFS=(
'Global' => '1000',
'QT_Sex' => {
'Sex' => {
'1' => 400,
'2' => 600
}
}
);
dsc file
QUESTIONS
qstatus "questionnaire_status" : {
v1 (1) "running",
v2 (2) "complete",
v3 (3) "screenout",
v4 (4) "quotafull"
}
Sex "Demographic question for quotas" : {1 "Male", 2 "Female"} REQUIRED
q1 "Quota not full - continue to the survey" : {Continue}
qend "Final question" : {Continue}
VARIABLES
urlcp : TEXT[40]
urlqf : TEXT[40]
urlsc : TEXT[40]
k : INTEGER
FUNCTION CHECKQUOTA RETURNS INTEGER
FUNCTION CHECKALLQUOTA RETURNS INTEGER
FUNCTION INCALLQUOTA RETURNS INTEGER
ROUTE
// quotafull.blk in project rsc folder for message to be displayed when quota full
urlqf = "quotafull"
// If global quota full then prevent respondent having to answer quota questions
IF (CHECKQUOTA("Global") == 0) THEN {
qstatus=v4
STOP(urlqf)
}
ELSE qstatus = v1
NEWPAGE()
MESSAGE("Global quota not complete so ask demographic question")
ASK(Sex)
ENDPAGE()
IF (CHECKALLQUOTA() == 0) THEN {
STOP(urlqf)
}
NEWPAGE()
ASK(q1)
ENDPAGE()
NEWPAGE()
ASK(qend)
ENDPAGE()
//Update the quotas
qstatus=v2
k=INCALLQUOTA()
Example 2
In this example quotas are defined for each of 4 age groups and in each interview one of 2 products is asked about. QUATOASSIGNVALUEP is used to ensure that respondents in each age group are asked about each of the 2 products the same number of times.
quotadef.pl
our %OBJECTIFS = (
'Global'=>500,
'QT_Product_Age' => {
'product' => {
1 => { # Product 1
'Age_group' => {
1 => 50, # 18-34 years
2 => 100, # 35-49 years
3 => 50, # 50-64 years
4 => 50 # 65 years and over
}
},
2 => { # Product 2
'Age_group' => {
1 => 50, # 18-34 years
2 => 100, # 35-49 years
3 => 50, # 50-64 years
4 => 50 # 65 years and over
}
}
}
}
);
dsc file
QUESTIONS
@<"quotaReport.dsc"
product "Variable used for quotas": {1 "Product 1",2 "Product 2"}
Age_group :{
1 "18-34 years",
2 "35-54 years",
3 "55-65 years",
4 "More than 65 years"
} REQUIRED
VARIABLES
k : INTEGER
urlcp: TEXT[40]
urlso: TEXT[40]
urlqf: TEXT[40]
FUNCTION CHECKQUOTA RETURNS INTEGER
FUNCTION CHECKALLQUOTA RETURNS INTEGER
FUNCTION INCALLQUOTA RETURNS INTEGER
FUNCTION INCQUOTAFULL RETURNS INTEGER
FUNCTION QUOTAASSIGNVALUE RETURNS INTEGER
FUNCTION QUOTAASSIGNVALUEP RETURNS INTEGER
ROUTE
urlqf="quotafull"
IF (CHECKQUOTA("Global") == 0) THEN {
STOP(urlqf)
}
NEWPAGE()
MESSAGE("Question for Product and Age group quotas")
ASK(Age_group)
ENDPAGE()
IF (CHECKALLQUOTA() == 0) THEN {
// quota report to record the number over quota")
k=INCQUOTAFULL()
STOP(urlqf)
}
k=QUOTAASSIGNVALUEP("QT_Product_Age","product","1")
IF (product IN {1}) THEN {
NEWPAGE()
MESSAGE("Now some questions about Product 1")
ENDPAGE()
}
ELSE {
NEWPAGE()
MESSAGE("Now some questions about Product 2")
ENDPAGE()
}
//Update the quota records
k=INCALLQUOTA()
Example quotastats report
So the next respondent aged 55-65 will be asked about Product 2.
Example 3
Imagine we want to ask detailed questions about 7 products/topics but budget/respondent burden constraints only allow each respondent to be asked about a maximum of 4. QUOTAASSIGNVALUE can be used to ensure that each of the 7 products/topics is asked about the same number of times.
The example program below asks question colour and displays 4 colours from the full list of 7 colours. At the end of the survey each colour to be displayed the same number of times. The quota is satisfied when each colour has been displayed 100 times. Towards the end of the survey fewer than 4 colours will be displayed as quotas are filled.
quotadef.pl
our %OBJECTIFS = (
'Global'=>700,
# QUOTA colours
'QT_colour' => {
'qcolour' => {
1 => 100,
2 => 100,
3 => 100,
4 => 100,
5 => 100,
6 => 100,
7 => 100
}
}
);
our %OBJLABELS = (
"Global" => "Number completed",
"QT_colour" => "Quota for Colour"
);
dsc file
DEFINITIONS
TRainbow = {1 "Red",2 "Orange",3 "Yellow",4 "Green",5 "Blue",6 "Indigo",7 "Violet"}
QUESTIONS
//@<"quotaReport.dsc"
quotaReport [
Global "Number completed",
QT_colour "Quota for Colour"
] "For quota report" : {
block (0) "BLOCK",
ok (1) "OK"
}
qcolour "Variable used for quotas": TRainbow {4}
colour "What is your favourite colour?" : TRainbow
VARIABLES
k : INTEGER
i: INTEGER
tmp[4] : TRainbow
colour_tmp "Used as filter to only include colours not selected already" : TRainbow {7}
nselected : INTEGER
rand_colour : TRainbow
FUNCTION QUOTAASSIGNVALUE RETURNS INTEGER
FUNCTION INCALLQUOTA RETURNS INTEGER
ROUTE
// Initialise variables
nselected=0; FOR i=0 TO 3 DO tmp[i]={}
qcolour = {}
colour_tmp = (TRainbow)
// Check quota to select the 4 least selected colours and store in tmp[] array. colour_tmp used to prevent same colour being selected twice.
FOR i=1 TO 7 DO {
IF COUNT(colour_tmp)>0 AND nselected<4 THEN {
k=QUOTAASSIGNVALUE("QT_colour","qcolour","1","colour_tmp")
tmp[nselected]=qcolour; nselected=nselected+1
colour_tmp = (colour_tmp \ qcolour)
}
}
// Copy selections into qcolour
qcolour = {}
FOR i=0 TO 3 DO {
qcolour = (qcolour | tmp[i])
}
IF (NOANSWER(qcolour)) THEN STOP
// Randomise the order that the 4 selected colours are presented
IF (NOANSWER(rand_colour)) THEN rand_colour = SHUFFLE(TRainbow & qcolour)
SETQLIST(colour,rand_colour)
NEWPAGE()
ASK(colour)
ENDPAGE()
//Update the quota records with the 4 colours selected for display.
k=INCALLQUOTA()
Example quotastats report
So green, indigo and 2 other colours chosen at random will be displayed in the next interview.