You are on page 1of 13

Session 5: CAPI

At the end of this lesson participants will be able to:

 Add question text and help text


 Add fills in question text
 Use multiple languages in question and in the dictionary
 Use occurrence labels and logic variables as fills in question text
 Set occurrence labels from logic
 Use setcaselabel to customize the case listing
 Use dynamic value sets

Question Text
We can add text to each question in our survey by clicking on CAPI Questions in the toolbar. This allows
us to enter literal question text for the questionnaire that the interviewer will read verbatim.
Additionally, we can add interviewer instructions. Let’s start with the first few questions in section B.

For RELATIONSHIP, enter “What is (name’s) relationship to the head of the household?” For SEX enter
“Is (name) a male or a female?”. Run the application on Windows and then on Android to see how the
question text is displayed.

In addition to the text, we can add instructions to the interviewer. For example for AGE we can have:

How old is (name) in completed years?

Followed by the instruction:

Enter age in completed years (“000” for children less than one year old)

To make it clear to the interviewer that this is an instruction, we use italics to distinguish it from the
question. We can also use different colors and fonts as well. You can use whatever scheme you like as
long as it is consistent scheme throughout the questionnaire.

If you have your question text in Word or Excel you can copy and paste into CSPro and it will preserve
the formatting.

Page 1 of 13 05 - CAPI.docx
Help Text
In addition to question text, we can provide additional instructions to the interviewer as “help text”.
Help text is not shown by default but can be displayed by using the F2 key on Windows or tapping the
help icon next to the question text on Android.

Let’s add the following help text to the name field in section B:

Include all persons living in this house who have common arrangements for cooking and dining.

Run the application on both Android and Windows and see how the help text is displayed.

Fills in Question Text


It is possible to have the question include the values of dictionary variables. For example, for SEX,
instead of asking “Is (name) a male or a female?” we can include the respondents name by using
%NAME% inside the question text. At runtime, this will be replaced by the contents of the variable
NAME.

Let’s change the text for RELATIONSHIP, SEX and AGE to use %NAME% in place of “(name)”.

Supporting Multiple Languages


It is possible to have the question text and value set in multiple languages. Language can be specified
for both Question Text and for Dictionary Labels.

Multiple Languages for Question Text


To have multiple languages for question text you first need to define the languages. This is done from
the CAPI Options Menu. Select “Define CAPI Languages”

This will bring up the “Language” dialog box. Click on “Add” to add a language and then on “OK”.
Click on the “Add”
button to add a language

 This simply specifies the names you will be using for the languages. It has no effect on the
translations.
 Language names follow the same rules as names; that is, they must be unique and cannot
contain spaces. Try using abbreviations like ENG (English), SPA (Spanish), FRA (French), or
POR (Portuguese).
Page 2 of13 Language labels can contain any text to describe
05 - CAPI.docx
the language.
 During data entry, the interviewer can easily switch among languages
When creating question text, you enter the question text for each language specified. Let’s look at the
question text for the “Sex” item. There is a Drop Down menu for the languages we specified in the
above steps. We select the language and then enter the question text in that language.

Now that you have created the question text in the specified languages, they can be selected during
data entry.

In Windows, we can go to the “Options” Menu and select “Change Language” and a menu of the
languages we defined will be displayed. Click on the desired language and the CAPI text will be
displayed in that language.

Tap to bring up Tap “Change Language” Tap on the language you The CAPI question is display
On Android:
the menu to select the language wish to display. In this case in the selected language. In
we will select “French”. this case, it is “French”.

Page 3 of 13 05 - CAPI.docx
Multiple Languages for Dictionary Items
To have multiple language in question text you first need to define the languages. This is similar to the
process done for multiple languages for CAPI questions; however, for dictionary items you define
languages from the Edit menu of the dictionary.

 Make sure you are in the


dictionary table
 Select “Edit”
 Select “Languages
 Enter the language names

Using the language drop-down at the top of


the page you can cycle through the
languages you defined in the above step.

This will allow you to add labels for each language specified:

Page 4 of 13 05 - CAPI.docx
Using Occurrence Labels in Question Text
We have the following occurrence labels for the housing unit types in question F04.

 Traditional round hut


 Detached house
 Semi-detached house
 Flat/apartment
 Improvised (kiosk/container)

We can use these occurrence labels in the question text for F04:

How many %getocclabel% units are in this household?

Anytime you use %getocclabel% in question text it is replaced by the occurrence label of the current
occurrence. With the above, the question text for the first occurrence will be “How many traditional
round hut units are in this household?” and the text for the second occurrence will be “How many
detached house units are in this household?” …

We can also use occurrence label fills in

 G01. Household Possessions


 H01. Livestock.

Setting Occurrence Labels in Logic


If you look at the case tree in Android you will see that the roster occurrences are displayed with the
name of the roster and the occurrence number “Names(1), Names (2)…” which is not useful. Using logic
we can set the occurrence labels to the names of the individuals instead. For that we use the command
setocclabel() which takes the name of the group (roster or repeating form) and the string to set it to. For
example, to set the occurrence label of each row of the names roster once the name is entered we can
do the following in the postproc of the name field (NAME):

PROC NAME
setocclabel(NAMES_ROSTER, strip(NAME));

In the same proc we can also set the occurrence labels for the other rosters that use the individual
record (demographics, education, and fertility).

This works fine when we are adding a new case, however we open an existing case in modify mode the
occurrence labels are not set until we get to the person roster even though the occurrences already
exist. In modify mode, while still on section A you can scroll the case tree to see Names(1), Names(2)…
as we had before. In order to prevent this, we need to set the occurrence labels for the rosters as soon
as we open the case. What proc can we use? The preproc of the questionnaire!

Page 5 of 13 05 - CAPI.docx
We will use a loop to go through each occurrence in the names roster and set the labels for the
corresponding rows of the other rosters:

PROC HOUSEHOLDQUESTIONNAIRE_QUEST
preproc

// Fill in occurrence labels in rosters when entering a case that


// has existing data (partial save or modify mode). If we don't this
// then the case tree will not have correct occurrence labels until
// after we pass through person roster.

do numeric i = 1 while i <= totocc(NAMES_ROSTER)


setocclabel(NAMES_ROSTER(i), strip(NAME(i)));
setocclabel(DEMOGRAPHICS_ROSTER(i), strip(NAME(i)));
setocclabel(EDUCATION_ROSTER(i), strip(NAME(i)));
setocclabel(FERTILITY_ROSTER(i), strip(NAME(i)));
enddo;

Now that we have occurrence labels set in the individual rosters we could also use %getocclabel% in the
question text to fill in the names although using the field %NAME% directly will work just as well.

Using Logic Variables in Question Text


In addition to dictionary variables, and occurrence labels it is also possible to use logic variables in
question text.

For question D05 we want to double check that the total of the children living with the women, living
elsewhere and deceased equals the number of total births. We do this by asking the question:

Just to make sure that I have this right, (name) has had in total (total number) births during her life. Is
this correct?

We can use the question text for this but we don’t have a dictionary variable for total births. We only
have the yes/no variable NUMBER_BIRTHS_CORRECT. We could create an additional dictionary variable
but instead we can simply create a logic variable in the program for totalBirths and use that as the fill
value.

We will declare it in the PROC global section so that it is available everywhere. If you declare a logic
variable inside the PROC of a dictionary variable or group, it is only available inside that PROC. Anything
in the PROC global is available in all the PROCs and in the question text.

To view the proc GLOBAL, in the logic view, click on the first item in the form tree. This shows all of the
program logic at once: the proc GLOBAL plus all the other procs. Clicking on any other item in the form
tree shows just the procs for that item.

Page 6 of 13 05 - CAPI.docx
We need to assign a value to totalBirths. Which proc do we do that in? We do that in the onfocus of
NUMBER_BIRTHS_CORRECT since we need to use it when we are in that field. The onfocus is called
every time the field is entered. We cannot do this in the preproc since the preproc is not triggered when
moving backwards through the questions.

PROC NUMBER_OF_BIRTHS_CORRECT
onfocus
// Compute total births to be used as fill in question text
totalBirths = CHILDREN_IN_HOUSEHOLD + CHILDREN_ELSEWHERE + CHILDREN_DECEASED;

What happens when one of the fields in this calculation is skipped? The value becomes notappl which
messes up the entire calculation. We need to be a little smarter in calculating our total to exclude the
skipped values.

PROC NUMBER_OF_BIRTHS_CORRECT
onfocus

// Compute total births to be used as fill in question text


totalBirths = 0;
if CHILDREN_IN_HOUSEHOLD <> notappl then
totalBirths = totalBirths + CHILDREN_IN_HOUSEHOLD;
endif;
if CHILDREN_ELSEWHERE <> notappl then
totalBirths = totalBirths + CHILDREN_ELSEWHERE;
endif;
if CHILDREN_DECEASED <> notappl then
totalBirths = totalBirths + CHILDREN_DECEASED;
endif;

Case Labels
By default, the case listing screen shows the id-items concatenated together. This is not very easy for an
interviewer to read. You can customize the case listing for a case using the setcaselabel command. As an
example let’s set the case label to the string “province-district-ea-household number: name of head of
household”. Since we need to have the name of the head of household to do this, we can add it in the
postproc of RELATIONSHIP.

PROC RELATIONSHIP

if RELATIONSHIP = 1 then
// Set label for case in case listing
// to an easier to read format.
// We do this in the relationship proc since
// that is where we first get the name of the head
// of household.
string caseLabel = maketext("%d-%02d-%03d-%03d: %s",
PROVINCE, DISTRICT, ENUMERATION_AREA,
HOUSEHOLD_NUMBER, strip(NAME(curocc())));
setcaselabel(POPSTAN2020_DICT, caseLabel);
endif;

Page 7 of 13 05 - CAPI.docx
Now after entering a case we have a much friendlier case listing:

Note that setcaselabel only works with data files of type CSPro DB.

Dynamic Value Sets


It is often useful to change the value set for a question from logic. This can be done using the command
setvalueset. Let’s start with a simple example. Currently our value set for relationship in section B has
labels like “Son/Daughter” and “Brother/Sister” to allow for both genders. However, when we show the
relationship value set we already know the gender of the household member so we could show “Son”
for males and “Daughter” for females. To do this we create two new value sets for relationship in the
dictionary: RELATIONSHIP_MALE and RELATIONSHIP_FEMALE. Then in the onfocus of relationship we
choose between the two value sets:

PROC RELATIONSHIP
onfocus
// Show male or female version of value set depending on sex of the person.
if SEX = 1 then
setvalueset(RELATIONSHIP, RELATIONSHIP_MALE);
else
setvalueset(RELATIONSHIP, RELATIONSHIP_FEMALE);
endif;

Dynamic Value Sets from a Roster


For question B13, Line Number of Mother, we would like to create a value set from the names and line
numbers of the eligible women in the household roster. To do this we need the second form of
setvalueset that takes an array of codes and an array of labels. This will allow us to create the list of
names in logic instead of in the dictionary. First, we need to declare the two arrays in the PROC global.

PROC GLOBAL
numeric totalBirths;
array string labels(100);
array numeric codes(100);

An array logic variable is similar to a dictionary item with occurrences. A numeric array of length seven
stores seven numbers, each of which is accessed through subscripts.

We will fill in the two arrays of codes and labels with names and line numbers of the eligible women in
the household. For example, if we have the following household:

Page 8 of 13 05 - CAPI.docx
We would fill in the two arrays as follows:

Subscript Codes Labels


1 2 Mary Brown
2 4 Jane Brown
3 87 Non-resident
4 88 Dead
5 notappl
To do this in logic we need to loop through the section B roster and add an entry into our arrays for each
eligible woman:

PROC MOTHERS_LINE_NUMBER
onfocus
// Create the value set for child mother from all eligible
// women in household roster
numeric indexRoster;
numeric nextEntryValueSet = 1;
do indexRoster = 1 while indexRoster <= totocc(DEMOGRAPHICS_ROSTER)

if SEX(indexRoster) = 2 and AGE(indexRoster) >= 12


and indexRoster <> curocc() then
labels(nextEntryValueSet) = NAME(indexRoster);
codes(nextEntryValueSet) = indexRoster;
nextEntryValueSet = nextEntryValueSet + 1;
endif;
enddo;
Then we need to add the special codes for non-resident and deceased to the arrays:

labels(nextEntryValueSet) = "non-resident";
codes(nextEntryValueSet) = 87;
nextEntryValueSet = nextEntryValueSet + 1;
labels(nextEntryValueSet) = "deceased";
codes(nextEntryValueSet) = 88;
nextEntryValueSet = nextEntryValueSet + 1;

Finally, we need to terminate the array of codes with a notappl to tell CSPro not to use the whole array
and then pass the array of codes and the array labels to the setvalueset command.

codes(nextEntryValueSet) = notappl;
setvalueset(MOTHER_LINE_NUMBER, codes, labels);

Page 9 of 13 05 - CAPI.docx
Group Exercise:

Implement the dynamic value set for father line number (B15). Only show males in the household over
12. Don’t forget to include the codes for non-resident and deceased.

Dynamic Value Sets from Checkboxes


Question B20 (primary language) should only show a subset of the languages chosen in B19 (languages
spoken). We can do this using a dynamic value set as well. The trick is that since B19 uses checkboxes it
will have alpha codes (A, B, C…) while B20 will have numeric codes (1,2,3…) so we need to convert from
numeric to alpha to determine if a given language was selected. We could do this with a series of if then
else statements but an easier approach is to use the string “ABCDEFGH” to convert from numeric to
alpha by looking up the character at the position of the numeric code. For example, numeric code 1
would give us the character at the first position: A. Numeric code 2 would give us the character at
position 2, B etc…

PROC MAIN_LANGUAGE
onfocus

// Create value set from items selected in languages spoken


numeric nextEntry = 1;

// Used to translate from checkbox (alpha codes) to numeric codes


string languageCheckboxCodes = "ABCDEFGH";

// Loop through the numeric codes 1-8 and each selected


// to value set
do numeric languageNumericCode = 1 while languageNumericCode <= 8

// Convert the numeric code to the checkbox alpha code


// by looking it up in the array.
string languageCheckboxCode =
languageCheckboxCodes[languageNumericCode:1];

// Check if the language is selected in the checkbox field


if pos(languageCheckboxCode, LANGUAGES_SPOKEN) > 0 then
// Language is selected. Add it to the value set.
codes(nextEntry) = languageNumericCode;
labels(nextEntry) = getlabel(MAIN_LANGUAGE_VS1, languageNumericCode);
nextEntry = nextEntry + 1;
endif;

enddo;

// Mark end of value set


codes(nextEntry) = notappl;

// Modify value set


setvalueset(MAIN_LANGUAGE, codes, labels);

Page 10 of 13 05 - CAPI.docx
What if the interviewer doesn’t pick any language in B19? Then our dynamic value set is empty. We
should add a check to B19 to ensure that at least one language is chosen.

PROC LANGUAGES_SPOKEN

// Ensure that at least on language is chosen


if length(strip(LANGUAGES_SPOKEN)) = 0 then
errmsg("You must choose at least one language");
reenter;
endif;

Dynamic Checkboxes
Let’s implement a dynamic value set for question G2, “were assets purchased with a loan”. Rather than
a series of yes/no question, we implement this using a single variable with checkboxes. We could have
one checkbox for each of the 12 items in the assets roster but it would be better if we only displayed the
checkboxes for the assets that the household actually possess. How do we know if the household
possess an item? The household possess the item if its quantity is greater than zero. We need to loop
through the rows of the roster and add a checkbox to the value set for each item with quantity greater
than zero. The only tricky part is that since these are checkboxes so we need to use alpha values.

In order to create a value set with alpha values we need a code array of type string. Where do we
declare it? PROC GLOBAL.

PROC GLOBAL
numeric totalBirths;
array string labels(100);
array codes (100);
array string codesString(100);

We build the value set in the onfocus of the checkboxes field. We use the alphabet string trick again to
get the alpha codes from the occurrence number. We also use the function getocclabel() to get the
occurrence label from the assets roster to use in the value set.

Page 11 of 13 05 - CAPI.docx
PROC POSSESSIONS_PURCHASED_WITH_LOAN
onfocus

// Create dynamic value set from assets that have quantity > 0
numeric nextEntry = 1;

string alphabet = "ABCDEFGHIJ";

do numeric assetNumber = 1 while assetNumber <= totocc(POSSESSIONS_ROSTER)


// Check if household possesses this asset
if QUANTITY(assetNumber) > 0 then
// Add to value set
labels(nextEntry) =
getocclabel(POSSESSIONS_ROSTER(assetNumber));
codesString(nextEntry) = alphabet[assetNumber:1];
nextEntry = nextEntry + 1;
endif;
enddo;

// Mark end of array (use "" instead of notappl since field is alphanumeric)
codesString(nextEntry) = "";
setvalueset($, codesString, labels);

Page 12 of 13 05 - CAPI.docx
Exercises
1. Add question text to the rest of section B. Use fills to include the name as we did in the
examples.
2. Add a new language, the language of your choice, to the CAPI text and to the dictionary.
Translate the question text, labels and value sets for section B into the new language.
3. Add question text for section G and use the occurrence labels to fill in the name of the
possessions in the quantity and value fields.
4. Set the occurrence labels in section E to the names of the deceased using the setocclabel
command.
5. In question B06 (date of birth) use a dynamic value set for the day based on the month (January:
1-31, February: 1-28, March: 1-31…) so that the interviewer cannot enter an invalid date like
February 30 or April 31. Bonus if you can correctly handle leap years.
6. For E08 (line number of mother of deceased) use a dynamic value set to list the names of all
eligible women from the household roster.
7. Add a new record and form for section H, Agriculture:
a. for question H1, use a repeating item with occurrence labels like we did for the housing
units (F04). Add the question text using the occurrence labels as fills.
b. for question H2 parts a and b do NOT use repeating items. Use checkboxes instead. In
your dictionary, you will have two singly occurring alpha items instead of multiply
occurring numeric items and on the form you will have two non-repeating checkbox
questions instead of a roster. For H2 part c use a repeating numeric item with 7
occurrences for the ranking (this will be a roster on the form).
c. For H3, use checkboxes and
d. for H4 use a repeating numeric item with 3 occurrences.
8. Skip question H04 if “school fees” was NOT checked in question H03.
9. For question H04 create a dynamic value set of school age (age 5 to 25) members of the
household from the household roster. Add an additional option to the value set for “not a
household member” with code 99.
10. For question H02c (crops sold), use a dynamic value set to limit the options to only those crops
selected in H02b (crops produced).
11. Implement question H02d, crop ranking using a dynamic value set. The first occurrence will be
the top ranked crop, the second occurrence will be the second ranked crop, etc… Use dynamic
value sets to limit the crops for each occurrence so that it is not possible to pick the same crop
twice. In other words, show all crops for the first occurrence but if groundnuts is picked for the
first occurrence then show everything but groundnuts in the second occurrence and then if
maize is picked in the second occurrence show everything but groundnuts and maize in the third
occurrence... Bonus: display the current rankings in the question text using a logic variable and a
fill.

Page 13 of 13 05 - CAPI.docx

You might also like