Skip to main content

Decipher Support

All the topics, resources needed for Decipher.

FocusVision Knowledge Base

Creating a Rotated Dataset

Overview

When programming brand/product test studies or multi-country projects, it can often be the case that you are working with a very large list of answer options. Lists which include 50-100 answer options can add significant length to a survey, especially if question looping is required, and can sometimes cause issues when collecting data or loading reports.

In cases like these, you can implement a "rotated dataset" to help you capture data without bulking up your survey. Rather than adding a loop for each available answer option, rotated datasets allow you to loop through the maximum number of answer options that a respondent can choose to answer.

The rotated dataset trades the ease of reading your dataset for more efficient survey scripting, allowing the system to load the survey without facing issues such as running out of memory when running reports or generating data exports. This approach is recommended for large studies with 30+ answer options that will be looped through multiple times.

How it Works

The standard approach for looping through brand lists involves creating the same number of looped rows as the total number of answer options you have.

For example, if you would like to allow respondents to pick the five most popular brands they know from a list of 30, you might use the following setup to loop respondents through the brands they selected at a previous question (q1):

<loop label="l1" builderSource="q1" vars="q1">
 <title>Brands Loop</title>
 <block label="b1" builder:title="default loop block">
   <radio
   label="q2_[loopvar: label]"
   optional="0"
   randomize="0">
     <title>Have you used [loopvar: q1]?</title>
     <comment>Select one</comment>
     <row label="r1">Yes</row>
     <row label="r2">No</row>
   </radio>

   <suspend/>

   <radio
   label="q3_[loopvar: label]"
   optional="0">
     <title>Have you purchased [loopvar: q1] in the past 6 months?</title>
     <comment>Select one</comment>
     <row label="r1">Yes</row>
     <row label="r2">No</row>
   </radio>

   <suspend/>

   <textarea
   label="q4_[loopvar: label]"
   height="10"
   optional="0"
   width="50">
     <title>What do you think of  [loopvar: q1]?</title>
     <comment>Be specific</comment>
   </textarea>
 </block>

 <looprow label="r1" cond="(q1.r1)">
   <loopvar name="q1">Brand 1</loopvar>
 </looprow>

 <looprow label="r2" cond="(q1.r2)">
   <loopvar name="q1">Brand 2</loopvar>
 </looprow>

 <looprow label="r3" cond="(q1.r3)">
   <loopvar name="q1">Brand 3</loopvar>
 </looprow>

 <looprow label="r4" cond="(q1.r4)">
   <loopvar name="q1">Brand 4</loopvar>
 </looprow>

 <looprow label="r5" cond="(q1.r5)">
   <loopvar name="q1">Brand 5</loopvar>
 </looprow>

 <looprow label="r6" cond="(q1.r6)">
   <loopvar name="q1">Brand 6</loopvar>
 </looprow>

 <looprow label="r7" cond="(q1.r7)">
   <loopvar name="q1">Brand 7</loopvar>
 </looprow>

 <looprow label="r8" cond="(q1.r8)">
   <loopvar name="q1">Brand 8</loopvar>
 </looprow>

 <looprow label="r9" cond="(q1.r9)">
   <loopvar name="q1">Brand 9</loopvar>
 </looprow>

 <looprow label="r10" cond="(q1.r10)">
   <loopvar name="q1">Brand 10</loopvar>
 </looprow>

....
<looprow label="r30" cond="(q1.r10)">
   <loopvar name="q1">Brand 30</loopvar>
 </looprow>
</loop>

This setup will expand each looped variable (the <looprow> tags) into three separate questions per brand, resulting in the survey including 30 questions and 90 datapoints for the loop alone (30 x 3 = 90).

While looped datasets can make reading the collected data easier, it can cause issues when loading the survey for respondents and when loading or exporting data. With a rotated dataset, the number of loop rows will instead be set to the maximum number of brands the respondent can select (in this case, five).

Creating a Rotated Dataset

There are two ways to create a rotated dataset. You can either program the loop setup from scratch or you can use the rotated dataset loop template.

Programming the Dataset

Storing the Selected Items

To create a rotated dataset, you must first find out which brands respondents selected. This can be done by storing the text of each selected row in your main brand question in a persistent Python list:

<exec>
p.eligibleBrands=[] #initialize the python list
for eachRow in q1.rows: #traverse the rows within our brands question
 if eachRow: #if the row is selected
   p.eligibleBrands.append(eachRow.text) #we add its text to our python list
</exec>

Once you have created the list, you can use this to pipe the selected brands into your brand loop.

Setting up the Brand Loop

With a rotated dataset, you should set the number of loop rows to the maximum number of brands that can be selected. Using the above example, since the atmost attribute of the main brand question is set to 5, the loop will have 5 loop rows:

<loop label="l2" builderSource="q1" randomizeChildren="1" vars="q1">
 <title>Brands Loop</title>
 <block label="b2" builder:title="default loop block">
   <radio
   label="q5_[loopvar: label]"
   optional="0"
   randomize="0">
     <title>Have you used [loopvar: q1]?</title>
     <comment>Select one</comment>
     <row label="r1">Yes</row>
     <row label="r2">No</row>
   </radio>

   <suspend/>

…

 </block>

 <looprow label="1">
   <loopvar name="q1">Brand 1</loopvar>
 </looprow>

 <looprow label="2">
   <loopvar name="q1">Brand 2</loopvar>
 </looprow>

 <looprow label="3">
   <loopvar name="q1">Brand 3</loopvar>
 </looprow>

 <looprow label="4">
   <loopvar name="q1">Brand 4</loopvar>
 </looprow>

 <looprow label="5">
   <loopvar name="q1">Brand 5</loopvar>
 </looprow>
</loop>

Note that the looprow labels have been changed from r1 / r2 / r3 to 1 / 2 / 3 , etc. This will facilitate the finalized setup that includes a tracking question for which brand is displayed at each loop row.

Modifying the Piped Variables

To dynamically pipe in a selected brand, you will need to modify the text inside your <loopvar> tags. Using the same example where respondents are always asked about exactly 5 brands, and those are always brands 1 through 5, this would appear per the following:

 <looprow label="1">
   <loopvar name="q1">${p.eligibleBrands[0]}</loopvar>
 </looprow>

 <looprow label="2">
   <loopvar name="q1">${p.eligibleBrands[1]}</loopvar>
 </looprow>

 <looprow label="3">
   <loopvar name="q1">${p.eligibleBrands[2]}</loopvar>
 </looprow>

 <looprow label="4">
   <loopvar name="q1">${p.eligibleBrands[3]}</loopvar>
 </looprow>

 <looprow label="5">
   <loopvar name="q1">${p.eligibleBrands[4]}</loopvar>
 </looprow>

Note that each loopvar tag is now accessing an element from the p.eligibleBrands list.

From here, you can keep adding more looprows and loopvars referencing ${p.eligibleBrands[n]}, where n is the index of each item in your brand list that starts at 0 and is incremented by 1 for each consecutive piped variable. Although this setup will enforce dynamic piping of selected brands only, you may still see an issue if a respondent does not select exactly five brands.

For example, if a respondent selects three brands, the survey will still execute loop rows 4 and 5, even though there are no eligible brands for those loop rows. Because these rows now reference non-existent elements, the survey will throw a fatal error.

To avoid this, you can add conditions to each loop row to check whether there are enough brands selected to execute it. These conditions can be based on the length of your p.eligibleBrands list (e.g., len(p.eligibleBrands)).

 <looprow cond="len(p.eligibleBrands) gt 0" label="1">
   <loopvar name="q1">${p.eligibleBrands[0]}</loopvar>
 </looprow>

 <looprow cond="len(p.eligibleBrands) gt 1" label="2">
   <loopvar name="q1">${p.eligibleBrands[1]}</loopvar>
 </looprow>

 <looprow cond="len(p.eligibleBrands) gt 2" label="3">
   <loopvar name="q1">${p.eligibleBrands[2]}</loopvar>
 </looprow>

 <looprow cond="len(p.eligibleBrands) gt 3" label="4">
   <loopvar name="q1">${p.eligibleBrands[3]}</loopvar>
 </looprow>

 <looprow cond="len(p.eligibleBrands) gt 4" label="5">
   <loopvar name="q1">${p.eligibleBrands[4]}</loopvar>
 </looprow>

The conditions added above will check if there are at least N brands selected, where N is the index of the brand being piped. This will ensure that the survey does not pipe in a brand item that is not a part of your list.

Using the Dataset Template

If desired, you can use the rotated dataset loop template to create a customizable rotated dataset. To use the template, copy the below code into your project’s XML where the standard loop programming would be. This code sets up the loop and dynamic assignment necessary for the rotated dataset to work.

Once inserted, you can modify the template to match your survey's individual loop requirements.

<loop label="l2" builderSource="q1" randomizeChildren="1" vars="q1">
 <title>Brands Loop</title>
 <block label="b2" builder:title="default loop block">

   <radio
   label="q5_[loopvar: label]"
   optional="0"
   randomize="0">
     <title>Have you used [loopvar: q1]?</title>
     <comment>Select one</comment>
     <row label="r1">Yes</row>
     <row label="r2">No</row>
   </radio>

   <suspend/>

   <radio
   label="q6_[loopvar: label]"
   optional="0">
     <title>Have you purchased <span>[</span><span>loopvar: q1] in the past 6 months</span>?</title>
     <comment>Select one</comment>
     <row label="r1">Yes</row>
     <row label="r2">No</row>
   </radio>

   <suspend/>

   <textarea
   label="q7_[loopvar: label]"
   height="10"
   optional="0"
   width="50">
     <title>What do you think of <span>[</span><span>loopvar: q1]</span>?</title>
     <comment>Be specific</comment>
   </textarea>

   <suspend/>
 </block>

 <looprow cond="len(p.eligibleBrands) gt 0" label="1">
   <loopvar name="q1">${p.eligibleBrands[0]}</loopvar>
 </looprow>

 <looprow cond="len(p.eligibleBrands) gt 1" label="2">
   <loopvar name="q1">${p.eligibleBrands[1]}</loopvar>
 </looprow>

 <looprow cond="len(p.eligibleBrands) gt 2" label="3">
   <loopvar name="q1">${p.eligibleBrands[2]}</loopvar>
 </looprow>

 <looprow cond="len(p.eligibleBrands) gt 3" label="4">
   <loopvar name="q1">${p.eligibleBrands[3]}</loopvar>
 </looprow>

 <looprow cond="len(p.eligibleBrands) gt 4" label="5">
   <loopvar name="q1">${p.eligibleBrands[4]}</loopvar>
 </looprow>
</loop>

Creating a Tracking Question

As mentioned earlier, the rotated dataset approach for looping through questions sacrifices ease of use on the reporting side for more efficient survey scripting. For this reason, you may want to consider adding a tracking question to track the rotation of the data in your reports.

The tracking question should be a hidden question that will display exactly which brand was shown at each loop iteration. It should contain each brand from the main brand list as its rows, and each loop iteration as its column.

For example, if your main brand list contains 10 brands and five loop iterations, it would appear as follows:

<radio
 label="q8"
 where="execute,survey,report">
 <title>Hidden to track displayed brands</title>
 <comment>Select one</comment>
 <row label="r1">Brand 1</row>
 <row label="r2">Brand 2</row>
 <row label="r3">Brand 3</row>
 <row label="r4">Brand 4</row>
 <row label="r5">Brand 5</row>
 <row label="r6">Brand 6</row>
 <row label="r7">Brand 7</row>
 <row label="r8">Brand 8</row>
 <row label="r9">Brand 9</row>
 <row label="r10">Brand 10</row>
....
 <col label="c1">1</col>
 <col label="c2">2</col>
 <col label="c3">3</col>
 <col label="c4">4</col>
 <col label="c5">5</col>
</radio>

Once you have created your hidden question, you will need to add some exec code to get it working. To start tracking rotations in your hidden question, first add the following exec block before the beginning of your loop:

<exec>
p.loopCount = 0 #we initialize a counter variable to keep track of each displayed loop iteration
</exec>

Then, add the following exec block inside the loop to punch the hidden question:

<exec>
for eachRow in q8.rows: #traverse the rows of our tracking question
 for eachItem in p.eligibleBrands: #traverse the stored brands in our array
   if eachRow.text == p.eligibleBrands[int('[loopvar: label]')-1]: #check if the current piped variable text matches the text of the row we’re iterating over
     eachRow.val = p.loopCount #if yes, we punch the column corresponding to the current loop iteration
p.loopCount = p.loopCount+1 #we move on to the next loop iteration
</exec>

This final exec block checks which brand from the list is currently being displayed and punches the column corresponding to the current loop iteration. Once added, you can increment your loop counter to move on to the next loop iteration when punching the hidden question.

The screenshot below shows what the tracking question for the above example will look like in Crosstabs:

dec_create_rotated_dataset_001.png

In addition to tracking which brands are shown and how often, the tracking question will allow you to split Crosstabs reports based on whether or not brands were displayed using the exact position of each brand within the loop. The data from this table can also be exported to view and reference brand information directly from the raw data.

  • Was this article helpful?