Lists and statistics
A list keeps several values together under one name, such as a week of step counts, a set of exam marks or a month of receipts. Once the values are in a list, you can total them, average them, measure how spread out they are, and pick out the ones that matter.
This page covers writing and indexing lists, summary statistics, adding up the lines above,
labelling lines with tags so you can total them wherever they are, sorting and slicing,
transforming a list with map and filter, and arithmetic on whole lists. Reach for it whenever
you have a column of figures to summarise. Lists of rows can also act as matrices, which have
their own page.
Writing a list
Put the values in square brackets, separated by commas:
scores = [72, 85, 91, 64, 88] gives [72, 85, 91, 64, 88]scores[0] gives 72scores[4] gives 88length(scores) gives 5A value’s position in the list is its index, written in square brackets after the list.
Indexes start at zero, so scores[0] is the first mark and scores[4] is the fifth. The last
element is always at length(scores) - 1. A negative, fractional or too-large index is an error;
indexes don’t wrap around from the end.
Elements can be calculations, quantities with units, or other lists:
[2 * 45, 60 + 15] gives [90, 75][1.5 kg, 750 g] gives [1.5 kg, 750 g][[1, 2], [3, 4]][1][0] gives 3With nested lists, the first index picks the inner list and the second picks a value from it:
[1] selects [3, 4], then [0] selects 3.
Thousands separators work inside a list, so you can paste figures as they’re written. Put a space after each comma between values, and a comma followed by three digits stays part of the number:
salaries = [£32,000, £41,500, £38,250] gives [£32,000.00, £41,500.00, £38,250.00]average(salaries) gives £37,250.00A few things to watch for:
- Without spaces, every comma separates, so
[8200,10400]has two values. A list such as[100,200,300]could be one number or three, so Varlig shows an error that spells out both. - Answers show thousands separators and a space after each comma, so
[8,200, 10,400]is a list of two values. - A list holds at most 1,024 values.
Summary statistics
scores = [72, 85, 91, 64, 88]sum(scores) gives 400average(scores) gives 80median(scores) gives 85min(scores) gives 64max(scores) gives 91count(scores) gives 5mean and avg are other names for average, total for sum, stdev for stddev and
count for length, so spreadsheet habits carry over. product multiplies the values together.
For the most common value, a percentile or a quartile, see below.
With a named list, you can also write the statistic in words:
scores = [72, 85, 91, 64, 88]average of scores gives 80total of scores gives 400sum of, mean of, median of, min of, max of and count of work the same way. After of,
write a name or the values themselves, separated by commas, with or without round brackets. For
values in square brackets, use the function form, as in average([72, 85, 91]).
Most statistics, including sum, product, average, median, min, max, count and
stddev, also accept separate values instead of a list:
max(72, 85, 91) gives 91sum(1, 2, 3, 4) gives 10average of 70, 85, 91 gives 82mean of (1,2,3) gives 2average(1,200, 1,800) gives 1,500Adding up the lines above
A line that says only sum, total, subtotal, grand total, average, max or min works
out that figure for the answers above it. It covers its own section, which starts at the previous
total, a --- divider or a # heading, so each part of a note gets its own figure as you type.
It works like the Subtotal option in a line’s
context menu:
# Groceriesbread = £1.20milk = £1.45eggs = £2.30total gives £4.95# Householdbin bags = £3.50washing up liquid = £1.80total gives £5.30The word can carry a label, take a name of its own, convert the answer or have a comment after it, so the line reads like the rest of your note:
tickets = £64train = £41.20Estimated total: total gives £105.20est = total stores the figure under a name, and total // still to book adds a note beside it.
total in kg converts before it shows, which is handy when a section mixes grams and kilograms.
A statistic word beside an operator is the figure its own line would show, so £5 + subtotal
adds a fiver to the running total.
# Parcels12 kg gives 12 kg4 kg gives 4 kg850 g gives 850 gtotal in kg gives 16.85 kgmax gives 12 kgIf you’ve given a value the name sum or total, the line shows your value instead. When
there’s nothing above to work with, the line says Unsupported: no amounts above to add up
rather than showing a blank or a zero, which usually means a heading you’d forgotten was there.
Each amount is counted once. When a later line in the section works something out from a named amount, the total counts that line rather than both, so a shopping list of unit prices and quantities adds up to what you’ll actually pay:
shirt = £18.50shorts = £99 * shirt gives £166.5010 * shorts gives £90.00fee = £40vat = fee * 20% gives £8.00total gives £304.50shirt and shorts are counted inside the two lines that use them, while fee stays in the
total because vat only takes a share of it.
That follows a chain of names as far as it goes. Where a note splits an amount into shares and then adds the shares back up, the line that adds them counts, and so does the amount they were shares of:
pot = £9001st = 50% of pot gives £450.002nd = 30% of pot gives £270.003rd = 20% of pot gives £180.001st + 2nd + 3rd gives £900.00total gives £900.00Only like amounts are added. Percentages, rates and plain counts are left out when there’s money or a quantity above, and so is a line with an error. Plain numbers pasted in a column are added when they’re written to the penny, the way the amounts are; one carrying more decimals, such as an exchange rate or a factor, is left out. The total’s hint names everything it left out, such as “Leaves out line 3 (people = 2)” or “Leaves out line 2 (an error)”, so you can see at a glance what’s in the figure.
A list of two or more amounts written without names, straight above the total and below a line worked out from names, is added on its own. That way a budget worked out at the top of a note isn’t counted again in the list below it:
budget = £18,000remaining = budget - £6,388.25 gives £11,611.75- skip hire £250 gives £250.00- deep clean £120 gives £120.00subtotal gives £370.00To total lines that aren’t next to each other, label them with a tag instead.
Most common values
mode gives the value that turns up more often than any other, and modes gives every value tied
for the most common, as a list:
marks = [3, 4, 4, 5, 5, 5, 6]mode(marks) gives 5modes([1, 1, 2, 2, 3]) gives [1, 2]mode answers only when one value is strictly the most common. If nothing repeats, or several
values tie, it says so rather than picking one:
mode([1, 2, 3]) gives Unsupported: No value repeats, so this list has no mode; use median or average insteadPercentiles and quartiles
percentile(list, rank) takes a rank from 0 to 100 and gives the value at that point, interpolating
between the two nearest observations. quartile(list, k) is the same thing at quarters, with k
from 0 to 4, so quartile(list, 1) is percentile(list, 25):
scores = [72, 85, 91, 64, 88]percentile(scores, 50) gives 85percentile(scores, 25) gives 72quartile(scores, 3) gives 88quartile(scores, 4) gives 91percentile([1, 2, 3, 4], 25) gives 1.75quartile([1, 2, 3, 4], 3) gives 3.25The 50th percentile is the median, and the 0th and 100th — quartile(list, 0) and
quartile(list, 4) — are the smallest and largest values, the same as min and max. A rank
outside its range is an error rather than the nearest value that would fit:
percentile([1, 2, 3, 4], 101) gives Unsupported: A percentile is between 0 and 100quartile([1, 2, 3, 4], 5) gives Unsupported: A quartile is 0, 1, 2, 3 or 4Spread: variance and standard deviation
scores = [72, 85, 91, 64, 88]variance(scores) gives 132.5stddev(scores) to 1 dp gives 11.5Both are sample statistics: they divide by one less than the number of values (n - 1),
which is the usual choice when your figures are a sample from something larger. They need at
least two values. Add to 1 dp after any statistic to round it.
When your list is the whole population rather than a sample, use the population forms, which divide
by n and need only one value:
# Train delays in minutesdelays = [2, 4, 4, 4, 5, 5, 7, 9]sample_variance(delays) gives 4.5714285714population_variance(delays) gives 4sample_stddev(delays) gives 2.1380899353population_stddev(delays) gives 2| Call | Divides the squared differences by | Needs |
|---|---|---|
variance, sample_variance |
n - 1 |
Two values |
population_variance |
n |
One value |
stddev, stdev, sample_stddev |
n - 1 |
Two values |
population_stddev |
n |
One value |
sample_variance and sample_stddev are longer names for variance and stddev, so you can spell
out which one you mean. The short names have always been the sample forms and still are.
Lists with units
sum, average, median, mode, modes, percentile, quartile, min, max, stddev and
population_stddev keep units, and convert between compatible ones. The values are read in the
first one’s unit, and the answer comes back in it:
commutes = [24 min, 31 min, 27 min, 22 min]sum(commutes) gives 1 hour 44 minaverage(commutes) gives 26 minmedian(commutes) gives 25.5 minpercentile(commutes, 25) gives 23.5 minpopulation_stddev(commutes) gives 3.3911649916 minsum([1 kg, 500 g]) gives 1.5 kgmodes([1 kg, 500 g, 1 kg, 500 g]) gives [0.5 kg, 1 kg]The variances are the exception: they square their observations, and no unit survives that, so they take plain numbers only.
sample_variance([1 kg, 2 kg]) gives Unsupported: A variance squares its observations, so it takes plain numbers; use stddev for amounts with a unitsort also only works on plain numbers. A statistic on values with incompatible units, such as
sum([1 kg, 3 m]), gives an error rather than quietly skipping values.
The exactness rules behind all of these are in What the engine guarantees.
Empty lists
| Call on an empty list | Result |
|---|---|
sum([]) |
0 |
product([]) |
1 |
count([]), length([]) |
0 |
average([]), median([]), min([]), max([]) |
Error |
variance([]), stddev([]) |
Error |
An empty list most often turns up as the result of a filter that matched nothing, so an
average of a filtered list can fail even when the original list has values.
Tags
A tag is a # with a word joined straight on to it, such as #food. Write one anywhere on a
line to label that line. The tag takes no part in the sum, so the line still shows its own
answer, and it groups that line with every other line carrying the same tag. total of #food
then adds those lines up:
£12.50 lunch #food gives £12.50£8 coffee #food gives £8.00flights = £420 #trip gives £420.00total of #food gives £20.50That’s what a tag gives you that a subtotal can’t: the lines don’t have to sit next to each other. A week of spending, a trip and a shopping list can share one block, each with its own figure at the bottom. Hold the pointer over a tag to see what it adds up to so far.
Every statistic works on a tag. of is optional, the tag can come before or after the word, and
the answer takes a label or a name of its own, as any other line does:
#food £12.50#food £8£420 #triptotal of #food gives £20.50average of #food gives £10.25count of #food gives 2min of #food gives £8.00max of #food gives £12.50#food total gives £20.50Food this week: sum of #food gives £20.50eaten = avg #food gives £10.25sum, total, average, avg, mean, count, min and max all read this way.
A tag statistic stands where an amount does, so a calculation can carry on from it. That’s how you take a deposit off a running total, scale one up, or set two tags against each other:
£20 shop #food gives £20.00£30 meal out #food gives £30.00total of #food - £10 gives £40.002 * total of #food gives £100.00Cooking kit: total of #food gives £50.00A line can carry as many tags as you like, and each one counts it. Name two tags in one phrase and you get the lines that carry both:
£50 dinner #trip #food gives £50.00£30 taxi #trip gives £30.00£9 sandwich #food gives £9.00total of #trip gives £80.00total of #food gives £59.00total of #trip #food gives £50.00What a tag total covers
A tag phrase looks over exactly the same lines a plain total would, and follows the same
rules:
- It covers its own section, back to the previous total, a
---divider or a#heading. - It adds only like amounts. Percentages, rates, plain counts and lines with errors are left out, and the hint names them, such as “Leaves out line 2 (people = 2)”.
- Each amount is counted once, so a tagged price that a later tagged line multiplies is counted in that line rather than in both.
So a heading is all it takes to keep one week’s #food apart from the next:
# Week 1£20 lunch #food gives £20.00total of #food gives £20.00# Week 2£30 lunch #food gives £30.00total of #food gives £30.00When there’s nothing to add up, the line says so rather than showing a blank or a zero, which usually means a typo in the tag or a heading you’d forgotten was there:
£12.50 lunch #food gives £12.50total of #drinks gives Unsupported: no lines tagged #drinks abovering the plumber #jobstotal of #jobs gives Unsupported: no amounts tagged #jobs aboveWriting tag names
A tag name can hold letters, digits, _, - and /, so #q1-2026 and #trip/lisbon are each
one name. Capitals make no difference:
£20 flight #trip/lisbon gives £20.00£35 hotel #Trip/Lisbon gives £35.00total of #trip/lisbon gives £55.00A # that isn’t joined to a word keeps its old meaning, so nothing you already write changes:
| You write | What it is |
|---|---|
# Weekly shop |
A comment, because of the space after the hash |
5 + 5 # comment |
The same: everything after the hash is ignored |
C#4 in Hz |
A musical sharp, because the hash is joined to the note |
#1, Invoice #123 |
A number, not a tag: a tag name can’t start with a digit |
#ff0000, #fff |
A colour code |
varlig.app/docs#tags |
A web address with a fragment |
A tag name has to start with a letter, and three, four, six or eight hex digits after the hash
are read as a colour, so #fee is a colour while #fees is a tag.
Tags in a note and tags in a block
The tags that organise your notes in the sidebar are described in
Tags, links and backlinks. Those stop at the edge of a calc block: a
#food you write inside a block labels that line for its total and never appears in the
sidebar, and a #food in the note around the block files the note without touching your sums.
Write the tag in both places when you want both, and note that the two differ in one detail:
a tag in a note can hold spaces when you close it with a second #, while a tag in a calc block
always ends at the first space.
List reference
| Call | What it does |
|---|---|
range(start,end) |
Whole numbers from start to end, including both ends |
range(start,end,step) |
Count by step, which can be negative or fractional but not zero |
length(list), count(list) |
Number of values |
sort(list) |
Smallest to largest; plain numbers only |
reverse(list) |
The same values in reverse order; any values |
unique(list) |
Keep the first occurrence of each value |
slice(list,start,end) |
Values from index start up to, but not including, index end |
map(expression,variable,list) |
Work out the expression once for each value |
filter(condition,variable,list) |
Keep the values for which the condition is true |
range(1, 5) gives [1, 2, 3, 4, 5]range(0, 100, 25) gives [0, 25, 50, 75, 100]range(10, 0, -2) gives [10, 8, 6, 4, 2, 0]scores = [72, 85, 91, 64, 88]sort(scores) gives [64, 72, 85, 88, 91]reverse(sort(scores)) gives [91, 88, 85, 72, 64]slice(sort(scores), 0, 3) gives [64, 72, 85]unique([4, 2, 4, 6, 2]) gives [4, 2, 6]reverse(sort(…)) gives largest first, and slice(sort(…), 0, 3) picks out the three lowest.
Slice bounds must satisfy 0 <= start <= end <= length. Equal start and end give an empty
list. There is no scores[0:2] shorthand; write slice(scores, 0, 2).
unique compares values exactly as they are stored, so 1 kg and 1000 g count as two
different values, and two results that differ in the tenth decimal place aren’t merged.
Map and filter
map works out an expression for each value in a list. filter keeps the values that pass a
test. In both, the expression comes first, then a name to stand for each value, then the list:
scores = [72, 85, 91, 64, 88]map(s + 5, s, scores) gives [77, 90, 96, 69, 93]filter(s >= 80, s, scores) gives [85, 91, 88]count(filter(s >= 80, s, scores)) gives 3average(filter(s >= 80, s, scores)) gives 88Read filter(s >= 80, s, scores) as “the values s in scores where s >= 80”. Both work with
units and money:
map(p * 1.2, p, [£10, £25, £40]) gives [£12.00, £30.00, £48.00]spend = [£42.50, £18.20, £65.00, £23.30]filter(x > £30, x, spend) gives [£42.50, £65.00]sum(filter(x > £30, x, spend)) gives £107.50The name you choose only exists inside the call. It doesn’t change a value of the same name elsewhere in the note:
x = 10map(x * 2, x, [1, 2, 3]) gives [2, 4, 6]x gives 10The condition in filter must be a comparison or another true/false value. A plain number isn’t
treated as true, so filter(x, x, [0, 1, 2]) is an error. Mapping or filtering an empty list
gives an empty list.
List arithmetic
You can multiply or divide every value by a single number, and add or subtract two lists of the same length value by value:
[30, 36, 22] / 40 * 100 gives [75, 90, 55]2 * [3, 1, 4] gives [6, 2, 8]-[5, -3] gives [-5, 3]mocks = [62, 70, 60]finals = [72, 78, 69]finals - mocks gives [10, 8, 9]average(finals - mocks) gives 9[1 kg, 2 kg] + [500 g, 500 g] gives [1.5 kg, 2.5 kg][72, 85] == [72, 85] gives trueThe first line turns marks out of 40 into percentages. The finals - mocks line pairs each
student’s mock mark with their final mark.
Some operations aren’t supported on lists:
- Adding a single number to a list, as in
scores + 5. Usemap(s + 5, s, scores)instead. - Adding lists of different lengths. Values are never repeated to make lengths match.
- Multiplying two flat lists with
*. For the sum of pairwise products, usedot:dot([2, 3, 1], [4, 5, 10])is33, for example two coffees at 4, three teas at 5 and one cake at 10. Between lists of rows,*is matrix multiplication, covered in Vectors and matrices.
Lists of rows
A list of rows works like a small table. average and sum work down the columns, and map can
work across the rows:
# Each row is one student: [mock, final]results = [[62, 72], [70, 78], [60, 69]]average(results) gives [64, 73]sum(results) gives [192, 219]map(average(r), r, results) gives [67, 74, 64.5]median, min, max and stddev don’t accept lists of rows. Use map over the rows, as in the
last line, or pull out a column into its own list.
The exact rules for all of these operations are in the advanced mathematics reference.
Putting it together
Here is a week of step counts from a watch, with a daily goal:
# Steps, Monday to Sundaysteps = [8200, 10400, 6300, 12000, 9100, 7400, 9600]goal = 9000sum(steps) gives 63,000average(steps) gives 9,000median(steps) gives 9,100Days on target: count(filter(s >= goal, s, steps)) gives 4Spread: max(steps) - min(steps) gives 5,700# Roughly 0.75 m per stepwalked = sum(steps) * 0.75 mwalked in km gives 47.25 kmThe average lands exactly on the goal, but only four days reached it, and there are 5,700 steps
between the best and worst days. Change goal or paste in next week’s counts and every line
updates.