When and Why to Use
Use this to capture numeric input across a grid of rows and columns. Best for:
- Time allocation or quantity distribution
- Budget breakdowns
- Structured numeric input across categories
Supports autosumming, recoding, custom validation and editable starting values.
Other Names and Formats
A Numeric Grid Question is a numeric matrix, numeric-entry grid, or numeric textbox grid. Budget and time distribution tasks are also called allocation questions, budget allocation, or time allocation questions.
Continuous sum is a related name used for numeric entries summed across items. A constant-sum allocation question additionally requires the survey to enforce a specified total, such as 100 points. Configure and verify that total constraint for the intended survey experience; autosumming alone does not establish a fixed-total requirement.
Chat Style
- Each row is presented with numeric input fields for each column
- Users enter numbers directly
Traditional Style
- Full grid visible with scrollable columns if needed
- Easier comparison across multiple categories
- Autosums are enabled

The traditional layout keeps both value columns visible alongside each row. Respondents can edit the entries before selecting Send.
Configuration Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| question | string | yes | - | The prompt shown to the user |
| rows | List[str | MediaItem] | yes | - | Rows to show in the grid |
| row_name | string | yes | - | Reporting label for rows |
| columns | List[str] | no | - | Column labels |
| column_name | string | no | - | Reporting label for columns |
| image | MediaItem | no | - | Top-level image |
| min_max | Tuple[int, int] | no | (1, 10) | Inclusive range of acceptable numeric values |
| randomize | bool | no | False | Randomize row order |
| randomize_columns | bool | no | False | Randomize column order |
| recodes | Dict[str, str] | no | - | Optional recoding logic |
| default | Dict[str, int | Dict[str, int]] | List[Dict] | no | random | Test-data defaults keyed by row (or by row, then column, when columns are used), or a list of candidate dictionaries — each simulated respondent picks one at random |
| prefill | int | dict[str, int] | dict[str, dict[str, int]] | None | no | None | Editable respondent starting values, separate from test-data defaults |
| autosum_columns | bool | no | False | Require responses to sum correctly by column |
| autosum_rows | bool | no | False | Require responses to sum correctly by row |
| custom_validator | Callable[[Dict[str, int] | int], str | None] | no | straight-line check | Called with the parsed grid dictionary in the traditional experience, or with each parsed number in the chat experience (asked row by row); return an error message to reject |
| image_label_field | str | no | - | Used to label media row items |
| show_image_label | bool | no | True | Show/hide labels for row images |
| image_size | Tuple[int, int] | no | (600, 600) | Bounding box for images |
| number_seconds | int | no | 0 | Seconds to wait before allowing the respondent to continue |
| tags | s.tag() | no | - | Token substitution and reporting group |
| id | str | no | - | Optional stable identifier for this question |
Editable starting values
Use prefill when respondents should see a starting number that they can review and change. This does not submit an answer by itself. It is separate from default, which supplies test/simulation responses and the tester's Auto answer.
- Pass one integer to start every cell at that value, for example
prefill=0with a range that permits zero. - With no
columns, pass a complete dictionary keyed by row. - With
columns, pass a complete row-first dictionary whose values are dictionaries keyed by column. This shape is the same for prefills in every supported survey version.
Every row and column must be represented, and starting values must be integers within min_max. Partial dictionaries and unknown row/column keys are rejected. Normal answer validation still applies when the respondent submits.
s.grid_numeric_question(
"Review your usual daily hours and change any values that differ",
rows=["Work", "Sleep"],
row_name="Activity",
columns=["Monday", "Tuesday"],
column_name="Day",
min_max=(0, 24),
prefill={
"Work": {"Monday": 8, "Tuesday": 8},
"Sleep": {"Monday": 7, "Tuesday": 7},
},
)
A numeric list uses the same function without columns, for example rows=["Food", "Housing"], min_max=(0, 100) and prefill={"Food": 25, "Housing": 75}. Check the range and any total constraint for your questionnaire.
Example Code
Basic usage:
s.grid_numeric_question( "How many hours do you spend per week on the following activities?", row_name="Activity", rows=["Work", "Sleep", "Exercise", "Socializing"], columns=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] )
With recodes and autosum:
s.grid_numeric_question(
"Distribute your budget across categories",
row_name="Category",
rows=["Food", "Housing", "Entertainment"],
columns=["January", "February", "March"],
recodes={
"0-30%": "Low",
"31-70%": "Medium",
"71-100%": "High"
},
autosum_rows=True
)
Response shape by survey version
For version 4 surveys, a multi-column numeric grid returns a row-first DictResponse. The outer keys are row labels and each value is a dictionary keyed by column:
response = s.grid_numeric_question(
"How many hours do you spend per day on each activity?",
rows=["Work", "Sleep"],
row_name="Activity",
columns=["Monday", "Tuesday"],
column_name="Day",
)
for activity, days in response.items():
for day, value in days.items():
s.store_value(
"Stored numeric-grid value",
value,
tags=s.tag(Activity=activity, Day=day),
)
The shape is:
{
"Work": {"Monday": 8, "Tuesday": 8},
"Sleep": {"Monday": 7, "Tuesday": 7},
}
In version 2 and version 3 surveys, the return shape is column-first, with columns outside and rows inside:
{
"Monday": {"Work": 8, "Sleep": 7},
"Tuesday": {"Work": 8, "Sleep": 7},
}
Iterate row then column in version 4, and column then row in versions 2 and 3. The default parameter is row-first — keyed by row and then column — in every supported version.
With custom validation:
s.grid_numeric_question(
"How many units of each product did you sell?",
row_name="Product",
rows=["Item A", "Item B"],
columns=["Online", "In-store"],
custom_validator=lambda d: "Please don't enter the same number for every cell"
if len(dict.fromkeys([v for row in d.values() for v in row.values()])) == 1
else None
)
Notes
- Returns a
DictResponsecontaining the entered numbers; version 4 multi-column grids are row-first, while versions 2 and 3 are column-first - Use autosum_columns or autosum_rows to require entries that sum correctly by column or by row
- Recodes are especially helpful for analysis of numeric ranges
- A straight-line check is applied by default — pass your own custom_validator to replace it
