Guide to choice filters: examples to copy and learn from (part 3)

This is the third part of this feature guide on choice_filter expressions. To get started, read the first part of this guide by clicking here. You can also read the second part on additional properties and consideration for choice_filter expressions by clicking here.


SurveyCTO core documentation has some great choice filtering examples, which I have linked you to from this section but have discussed a little further here, to give a larger account of those examples. I've also included some additional examples, to help expand your understanding of the possibilities. Each of these examples appear in this working sample form design which you should study and test on your server while you read these guidelines for context.

Example 1: Cascading selects

Generally, this is when the value entered in one field is used to filter another field. SurveyCTO's main documentation has a good example of this: Cascading selects: Filtering multiple-choice option lists. In this example, one first selects a region of the world which is used as the filter criteria for filtering a list of countries down to countries in that region. The answer to the country question is in turn used as filter criteria for a list of cities which is filtered down to the cities in the selected country. This is exactly the same form programming approach illustrated in the first example in this guide, except applied repeatedly to a series of interrelated choice lists. You can apply the same method in many contexts but a country's sub-divisions are probably the most common application (e.g. Province > District > Village > Household).

A more basic version of cascading selects was featured in part 1 of this guide and features as example 1 in the associated sample form.

Example 2: Present a list of household members

This example exists in SurveyCTO's core product documentation outside this guide, so please refer to this article and review the sample form: Rosters: Choosing among earlier entries. It doesn't matter if you're looking at variation 1 or 2 of this design. In both cases the choice_filter expression is (filtering down to actual size):


In the image to the right, the select_one field's choice list has 5 choices but only 3 family members were recorded, so there are two blanks, not filtered out.



This sample form illustrates a solution to the problem of how to present a choice list of prior entries collected in text fields. It is simple enough to refer to the value stored in a field and display the contents in the label of a choice list option by placing the name of that field in curly brackets with a $ sign in front (e.g. "${fieldname}") in the label. The challenge is that if you don't filter this list down to the actual number of prior selections, any unused choices will appear as blank choices with missing labels (see the screenshot above). That's confusing, so best avoided. You can use choice filtering to logically filter down to the actual number of household members:


This example won't be found in the sample form associated with this guide.

Example 3: Filter out prior selections in a select_one field

Let's say that in a follow-up question, you only want to ask about the options that were not selected in a prior question. Perhaps, "Of these remaining crops that you don't grow at home, do you buy any of them at the market?". The way to do that is using the functions, "not()" and "selected()" in your choice_filter expression. The function "selected()" is necessary in case the first question is a select_multiple field, but it will also work if it was a select_one field, so this syntax is generally good for choice_filter expressions:

not(selected(${crops_home}, filter))

The filter for each choice should match its value. If I wanted to extend to always allow "Refuse" as a response in the same list of choices, even though it had not been selected in "crops_home", the following would work (if "Refused" had the choice list filter value 96):

not(selected(${crops_home}, filter)) or filter = 96 

Example 4: Rank items through filtering out prior responses from the same list

Similar to the above example, it is possible to accumulate choice_filter expression conditions across several fields to successively filter out prior selections, in order to rank them. We happen to have a dedicated help topic with sample form that illustrates, here.

This example won't be found in the sample form associated with this guide.

Example 5: Filter out prior selections in a repeated select_one field

It is possible that you have a set of items you wish to ask the same set of questions about, over and over in an exercise inside which you'll have no idea on the best order for the items. For example, you might be asking for market prices for a list of products where it would be overly restrictive to be forced to enquire on each item in a preconfigured order. Another example is a survey section where you'll ask about the wellbeing of each child, where it is more convenient to ask about the first available children rather than the first in the order they were originally captured.

Such form or survey sections are typically prepared using a repeat group. There is already a sample form and accompanying product documentation article that illustrates how to ask follow-up questions on a number of items selected in a select_multiple field. This is a similar mechanic which would be useful to familiarise yourself with.

The solution is as follows:

  • Create a select_one field with the items you wish to ask follow-up questions about inside a repeat group (let's call it "choice" in this example).
  • Give all choice list items unique filter values equal to the choice list values.
  • Give the repeat group an appropriate repeat_count value, most likely one equal to the number of items in the choice list for the "choice" field.
  • Create a calculate field outside the repeat group and name it "calc_join" (for example), with the expression, "join(' ', ${choice})". This will create a space-separated value list of the selections made in the repeated field, "choice".
  • Give "choice" the choice_filter expression, "${choice} = filter or not(selected(${calc_join}, filter))''. The part of this expression before "or" is critical, as it tells SurveyCTO to always regard the current selection in "choice" to be one of the displayed choices, even if the second part of the expression would otherwise filter it out.

Note: In choice_filter expressions like this one, you cannot use "." in place of a direct reference to "${choice}" like you can in other types of expressions that refer to the value stored in the current field. If you do, this design will not work correctly.

There is a dedicated sample form to see how this works that you can view here.

This example won't be found in the sample form associated with this guide.

Example 6: Using repeated fields and indexed-repeat()

Sometimes the criteria for filtering choices you will want to use is stored in a field inside a repeat group. As discussed in this article, a repeated field has many instances, so without giving SurveyCTO additional instructions using the indexed-repeat() function as to which instance of a repeated field you want to take a value from, your form design won't work properly. 

Household rosters are a common scenario where you have repeated fields and you may wish to recall some of the data and filter it based on criteria stored in repeated fields. To use a concrete example, you may want to present a list of household members who are 18 and older, in order to choose a respondent for an interview. In this example, your roster repeat group has an integer field named "age" which stores the age of each household member. 

This form programming approach actually has a strong resemblance to the example above, about presenting a list of household members, and also solves the problem that you must plan for a maximum number of household members, because any members that you would plan for that might otherwise appear as choice options without labels, won't have an age value at all, so can be filtered out. It also goes a step further, filtering out any intervening members in a haphazardly ordered roster list who are under 18.

To understand this example, you must first understand the syntax for indexed-repeat() and at least one other use case, not discussed here. Also take a look at the sample form for this example. In this implementation of indexed-repeat(), the filter parameter for the choice list in question is used as the index value in indexed-repeat() (parameter 3 of the function). This expression looks like this:

 indexed-repeat(${age}, ${g_example_6_roster}, filter) >= 18

What this expression does is return each "age" value in each repeat instance and compare it to 18, filtering out the choice list items with filter values that match repeat instances where "age" is not greater than or equal to 18. Take a look at the example and test it to see it working in practice.

This example is also discussed as part of this webinar.

Example 7: Filter out household head after being selected

Similar to example 5 above, perhaps you have a list of items which you'll follow-up on and you would like to allow some to be selected repeatedly while others (or just one) should be filtered out once selected once. The example here is the option to indicate that a household member is the head of household. While it is possible that more than one person shares the duty of household head, the case of enforcing a single household head will be illustrated (it would also be possible to record only to heads and only then remove the option through filtering). 

The solution here is much like the one above for filtering out prior selections except for one point, so please do first review the solution above carefully. That point is the filter values. Rather than a unique filter value per choice matching the choice value in each case, you'll give the household head a choice value equal to its value in the choice list but give all other choices values not equal to their choice value. The behaviour will change. To add another unique category to be selected just once, give that category its own unique value too.

Note that this might be a case where you might add a second filter column, to give the same choice list a separate, unique set of filter values. The required choice list values for this method could prove too inflexible for other filtering requirements.

See this dedicated sample form here.

This example won't be found in the sample form associated with this guide.

Example 8: Static choice filters

It is possible to have fewer choice lists when choice lists have a lot of overlap through using choice filtering. For example, it could be a requirement of your study that you always give the respondent the opportunity to refuse to answer any question. To facilitate this, you will have to include "Refuse" as a choice list option in each choice list. However, in the question where you ask for consent, it doesn't make sense to include "Refused" as well as "Yes" and "No" as response options (informed consent requires an explicit answer, and you cannot interview someone who refuses to answer an opening question about informed consent). 

You could solve this challenge by having more than one choice list:


Here, you could use the "yesno" choice list when you want to only present the response options, "Yes" and "No", and use the "yesno_refused" choice list when you want to present "Yes", "No" and "Refused" as possible responses. Alternatively, you could also use just one choice list with all three response options and statically filter out "Refused" when you only want "Yes" and "No" as response options. Note the filter values in the above screenshot (1 for "Yes" and "No", and 0 for "Refused". Statically filter out "Refuse" for fields where you only want "Yes" and "No" with the choice_filter expression, "filter = 1", or alternatively "filter != 0" ("!=" is the logical operator for "does not equal").

This programming approach is not necessarily better than including more than one choice list but a key advantage of SurveyCTO's choice list functionality is that choice lists can be reused. Statically filtering out choices to create different versions of the same choice list can help make the same choice list more widely usable. As your forms get more complex, with longer choice lists, being able to re-purpose similar choice lists in this way can be useful.

Example 9: Cross-category cascading select filtering

Imagine a scenario where you need to do something like cascading select choice filtering, like in the first example in this article - but categories overlap. Typically a district sits discreetly in a province in a country, so administrative regions are not a good example. However, perhaps in a monitoring and evaluation scenario in a program that is being evaluated, some organizations work in partnership with other organizations on the same projects which fall under that program. To understand better, imagine the following:

Org 1's projects

Org 2's projects

Org 3's projects

Project A

Project B

Project C

Project A

Project B

Project C

See that Org 1 and 2 both coordinate on project A? Also that Project B and C overlaps between Org 1 and Org 2? Conventional cascading selection would not be able to display Project C if either Org 1 or Org 3 were selected. However, SurveyCTO's choice filtering functionality is flexible enough to solve this requirement as well.

The solution we propose is to use a comma-separated list of values as the choice filter for each project, where each value in the list represents an organization it is associated with. Each value in the filter list should be a choice value of an organization. Then, the item-present() function can be used to search each choice filter for the organization value, filtering out unneeded choices.

Here's an example of the choices sheet:


With the above choice list, the following choice_filter expression would work for the "project" select_one field:

item-present(',', filter, ${org})

The first parameter says the list separator is a comma ,, the second parameter says the list to be searched will be stored in the filter column, and the third parameter says to search for the value of the field "org" in the list. For example, if “org” is 2, the choice list is filtered down to choices with filter values that include '2' in their comma-separated list; in the "projects" choice list, this would mean choices 2 and 3 are filtered out, and only choice 1 would be shown, since that is the only choice with a filter including '2' in the comma-separated list.

Example 10: Multiple filter columns

Choice lists can take more than one discrete filter value, offering greater flexibility and more options in terms of how to express your specifications in a choice_filter expression. Additional filter columns are added to the choices sheet in the spreadsheet form design template (they cannot be added in the online designer). Please see "Multiple filter columns" in part 2 of this guide for more on this.


In this example, the choice list "health_attitudes" is being filtered based on both the prior response in the "adult" select_one field, and on the random number that's being generated in the "calc_random" calculate field. This random number is being scaled up to the range of between 1 and 2 in the second part of the choice_filter expression after "and" (if you would like the random number to have a greater probability range than between 1 to 2, replace the 2 with a different upper bound, such as with 4 to get an integer between 1 and 4 inclusive):

filter = ${adult} and randomization = int(${calc_random}*2)+1

This expression is saying that for a choice to show on screen for a field that uses the "health_attitudes" choice list, the value of "adult" has to match the choice's filter value, and the value of "int(${calc_random}*2)+1" has to match the choice's randomization value. For example, let's say the value of "adult" is "0" (indicating the respondent is under 16), and the value of the int() expression is 2 (so "calc_random" is a value between .5 and 1). This means the value of filter is 0, and randomization is 2, so only choices that match those values will appear in the form.

From the example discussed in the above paragraph, the parts of this logic are:

  • ${adult} = 0
  • ${calc_random} = 0.5
  • filter = ${adult} and randomization = int(${calc_random}*2)+1
  • filter = 0 and randomization = int(0.5*2)+1
  • filter = 0 and randomization = 2

In this case, the choices in row 41, 45, and 49 on the choices sheet each have a filter value of 0 and a randomization value of 2, so they would appear to the enumerator as they fill out the form; no other choice has both a filter and randomization value that match, so they will not appear. If the value of "adult" was 1 instead, then a choice's filter value would also need to be 1 to appear, so rows 43, 47, and 51 would appear to the enumerator instead.

Using this choice_filter expression and choice list with more than one filter value, the choice list is both purposefully filtered, and a random element is introduced in the filtering of the "health_attitudes" choice list.

Beyond the examples

Choice filtering is incredibly flexible in SurveyCTO. These examples are just a range of commonly applicable choice filter options. Our hope is that you'll benefit from the direct application of these examples but also that you'll experiment and adapt these ideas to new requirements! Happy filtering!

Do you have thoughts on this support article? We'd love to hear them! Feel free to fill out this feedback form.


Article is closed for comments.