Sum Values in an XLSForm Repeat Group
To add up a number collected in a repeat group, put sum(${field}) in a calculate row after the end_repeat row, where field is the question inside the repeat. Outside the repeat, ${field} stands for that answer in every instance, and sum() adds them; count(${repeat_name}) gives the number of instances. Every example below compiles with pyxform and ODK Validate, and works in ODK, KoboToolbox and SurveyCTO.
How do I sum a field from a repeat group?
A trader lists each sale in a repeat. The total goes in a calculate below end_repeat, and a note shows it:
| type | name | label | calculation |
|---|---|---|---|
| begin_repeat | sale | Sale | |
| text | product | Which product was sold? | |
| decimal | sale_amount | What was the total amount for ${product}? | |
| end_repeat | sale | ||
| calculate | total_sales | sum(${sale_amount}) | |
| calculate | n_sales | count(${sale}) | |
| note | show_total | Total of ${n_sales} sales: ${total_sales} |
Two things decide whether this works:
- Position. The calculate sits outside the repeat. Inside the repeat,
${sale_amount}means "this instance's amount", sosum()there adds up one value. - One argument.
sum()takes a single nodeset.sum(${a}, ${b})is rejected; to add two repeated fields, writesum(${a}) + sum(${b}).
count() takes the repeat's name (${sale}), not a question inside it. For reading one specific instance rather than the total, use indexed-repeat().
How do I count the instances that match a condition?
To count children under five in a household roster, mark each member with 1 or 0 inside the repeat, then add up the marks outside it:
| type | name | label | calculation |
|---|---|---|---|
| begin_repeat | member | Household member | |
| text | member_name | What is the member's name? | |
| integer | member_age | How old is ${member_name}? | |
| calculate | is_under5 | if(${member_age} < 5, 1, 0) | |
| end_repeat | member | ||
| calculate | hh_size | count(${member}) | |
| calculate | n_under5 | sum(${is_under5}) | |
| note | show_under5 | ${n_under5} of ${hh_size} members are under five. |
The same pattern counts anything per instance: women of reproductive age, members who attend school, plots that are irrigated. Write the test once, inside the repeat, with an if() statement, and sum() does the counting.
How do I sum only some instances (a conditional sum)?
A conditional sum works the same way: a calculate inside the repeat holds the amount when the condition is met and 0 when it is not. Here, only members who worked for pay in the last month add to household earnings:
| type | name | label | calculation | relevant |
|---|---|---|---|---|
| begin_repeat | earner | Household member | ||
| text | earner_name | What is the member's name? | ||
| select_one yes_no | worked | Did ${earner_name} work for pay in the last 30 days? | ||
| integer | earnings | How much did ${earner_name} earn in the last 30 days? | ${worked} = '1' | |
| calculate | earnings_counted | if(${worked} = '1', coalesce(${earnings}, 0), 0) | ||
| end_repeat | earner | |||
| calculate | hh_earnings | sum(${earnings_counted}) | ||
| note | show_earnings | Household earnings in the last 30 days: ${hh_earnings} |
| list_name | name | label |
|---|---|---|
| yes_no | 1 | Yes |
| yes_no | 0 | No |
For a select_multiple question inside the repeat, use selected(${field}, 'name') as the condition, or count-selected(${field}) to add up how many options were ticked in each instance.
One empty answer blanks the whole total. A skipped or unanswered number is not 0, and sum() over it returns no value at all. Sum a calculate that holds coalesce(${earnings}, 0) (as above) whenever the number has a relevant or is not required.
How do I calculate an average from a repeat?
Divide the sum by the count. XLSForm divides with div, not /:
| type | name | label | calculation |
|---|---|---|---|
| begin_repeat | child | Child | |
| text | child_name | What is the child's name? | |
| decimal | muac_cm | What is ${child_name}'s MUAC in cm? | |
| end_repeat | child | ||
| calculate | n_children | count(${child}) | |
| calculate | mean_muac | if(${n_children} > 0, round(sum(${muac_cm}) div ${n_children}, 1), '') | |
| note | show_mean | Average MUAC for ${n_children} children: ${mean_muac} cm |
The if() stops a division by zero when no child was added. round(..., 1) keeps one decimal place. More arithmetic patterns are in the XLSForm calculation examples.
How do I make the repeat entries add up to 100%?
A farmer lists each crop and the share of land it takes. Sum the shares after the repeat and stop the form until they add up to 100, with a constraint on an acknowledge question:
| type | name | label | calculation | constraint | constraint_message |
|---|---|---|---|---|---|
| begin_repeat | crop | Crop | |||
| text | crop_name | Which crop is grown? | |||
| integer | crop_share | What percentage of the land is under ${crop_name}? | . >= 1 and . <= 100 | Enter a share from 1 to 100. | |
| end_repeat | crop | ||||
| calculate | total_share | sum(${crop_share}) | |||
| acknowledge | check_share | The crop shares add up to ${total_share}%. Confirm to continue. | ${total_share} = 100 | The shares add up to ${total_share}%. Go back and correct them so they total 100%. |
The enumerator cannot finalize the form while the total is wrong, and the message shows the actual total, so they know how far off it is. The constraint examples page has more cross-question checks.
How do I sum a nested repeat?
With a repeat inside a repeat, where the calculate sits decides what it adds. Inside the outer repeat, sum() adds only that instance's inner entries; after both repeats, it adds everything:
| type | name | label | calculation |
|---|---|---|---|
| begin_repeat | hh_member | Household member | |
| text | hm_name | What is the member's name? | |
| begin_repeat | income_source | Income source | |
| text | source_name | What is the income source? | |
| decimal | source_amount | How much did ${hm_name} earn from ${source_name} last month? | |
| end_repeat | income_source | ||
| calculate | member_income | sum(${source_amount}) | |
| note | show_member_income | ${hm_name} earned ${member_income} last month. | |
| end_repeat | hh_member | ||
| calculate | household_income | sum(${source_amount}) | |
| note | show_household_income | Household income last month: ${household_income} |
pyxform writes ${source_amount} as a relative path in member_income (this member's sources) and an absolute path in household_income (every member's sources). You write the same reference both times.
Common repeat sum errors and how to fix them
These are the messages ODK Validate returns, which is what ODK Central, KoboToolbox or the XLSForm converter shows when it rejects the form:
| You wrote (outside the repeat) | Write instead | Error you will see |
|---|---|---|
${sale_amount} * 2 | sum(${sale_amount}) * 2 | This field is repeated: ... You may need to use the indexed-repeat() function to specify which value you want. |
SUM(${sale_amount}) | sum(${sale_amount}) | cannot handle function 'SUM' |
sum(${a}, ${b}) | sum(${a}) + sum(${b}) | cannot handle function 'sum'. Requires 1 arguments but 2 provided. |
sum(${sale_amount} > 5) | a 1/0 calculate inside the repeat, then sum() of it | XPath evaluation: type mismatch not a nodeset |
sum(${sale_amt}) (misspelt name) | the exact name from the survey sheet | Could not find the name 'sale_amt'. |
sum(${amount}) where amount can be skipped | sum() of a coalesce(${amount}, 0) calculate | No error, but the total is blank |
sum(${amount}) inside the repeat | move the calculate below end_repeat | No error, but each instance shows only its own value |
The last two rows compile and look fine in the builder, and fail only once real data arrives. For other messages, see common XLSForm errors or paste the form into the free XLSForm validator.
Does this work the same in ODK, KoboToolbox and SurveyCTO?
Yes. All three use the ODK XPath functions sum() and count(), and the same rule that a repeated field outside its repeat means every instance. What differs is where you type it:
| Platform | Where the sum goes |
|---|---|
| ODK (XLSForm) | calculation column of a calculate row after end_repeat |
| KoboToolbox Formbuilder | A Calculate question placed after the repeat group, with sum(${field}) in its calculation box |
| KoboToolbox (XLSForm upload) | Same as ODK |
| SurveyCTO | calculation column of a calculate field after the repeat (skip logic goes in a column named relevance) |
In the exported data, the totals land on the main sheet, one row per submission, while the repeat's answers go to a separate sheet. That is the main reason to compute them in the form: the analyst gets the household total without merging sheets.
Why totals belong in the form
A household total calculated during the interview can be checked on the spot. A note that says "Household income last month: 0" when three members said they worked tells the enumerator something was skipped, while the respondent is still there to answer. The same total built in Stata a month later only tells you the record is wrong.
You can describe the total in plain language instead: "count the members under five and add up the earnings of members who worked". LooprAI writes the calculates inside and after the repeat, then compiles the form with pyxform before anything is saved, so a calculate in the wrong place never reaches your form.
Next: XLSForm repeat groups →
Frequently Asked Questions
How do I sum a field from a repeat group in XLSForm?
Add a calculate row after the end_repeat row and write sum(${field}) in its calculation column, where field is the number question inside the repeat. Outside the repeat, ${field} refers to every instance, and sum() adds them into one total.
How do I count the number of entries in a repeat?
Use count() with the repeat's own name, for example count(${member}) in a calculate after the repeat. It returns how many instances were added. To count only some instances, add a 1 or 0 calculate inside the repeat and sum it.
Why is my repeat group sum blank?
Usually because one instance has an empty value. A skipped or unanswered number is not zero, and sum() over an empty value returns nothing. Add a calculate inside the repeat with coalesce(${amount}, 0) and sum that calculate instead of the question.
Why does my form say 'This field is repeated' when I use a repeat answer?
You referenced a question from inside a repeat in a calculation outside it without a function that takes many values. Wrap it in sum() for a total, use count() on the repeat for the number of instances, or indexed-repeat() to read one instance.
How do I make repeat entries add up to 100 percent?
Sum the percentages in a calculate after the repeat, then add an acknowledge question with the constraint ${total_share} = 100 and a constraint message that shows the total. The form cannot be finalized until the shares add up to exactly 100.
Does sum() work the same in KoboToolbox and SurveyCTO?
Yes. ODK, KoboToolbox and SurveyCTO all support sum() and count() on repeated fields. In Kobo's Formbuilder you add a Calculate question after the repeat group; in XLSForm and SurveyCTO the expression goes in the calculation column of a calculate row.
No Credit Card Required
Build the form first. Add hosting when you need it.
Start free with LooprAI, then deploy a managed ODK Central server or run DataSnap checks when your project is ready.
Automation handles the mechanical work.
Researchers decide what the evidence means.