Quota Management

Search:

Table of Contents

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 } } } }

Back to start

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

Back to start

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.

Back to start

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.

Back to start

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:

  1. The name of the quota in %OBJECTIFS in quotadef.pl.
  2. The quota variable to be set to the value associated with the least complete quota.
  3. 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
  4. Optional. A filter to restrict the categories that can be selected.
  5. 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.

Back to start

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

Back to start

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()

Back to start

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.

Back to start

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.

Back to start