The avg() and median() functions in Attio: what a typical deal actually looks like
"Average deal size" is on every dashboard and trusted by nobody who's looked at the underlying deals. One enterprise contract and the average doubles; one free pilot and it halves. avg(), mean(), and median() put the typical value on the record as a live rollup — and the choice between them is the whole story, because the average tells you about the total and the median tells you about the deal you're most likely to see next.
This is part of our function-by-function series on Attio formula attributes — previously: if(), timeSpentIn(), dateDiff(), the ?? operator, count(), hasBeenIn(), valueSetAt(), contains(), sum(), previousValue(), dateAdd(), formatDate(), min()/max(), valueAt(), and replace()/replaceAll(). This one covers the three "typical value" functions together.
Table of contents
- What avg(), mean(), and median() do
- The rollup: typical deal size per account
- avg() vs. median(): the whale problem
- The skew signal: run both
- Blank-proofing
- Composing with the rest of the library
- CRM use cases that earn their keep
- Copy-paste formulas
- Gotchas
- Final thoughts
What avg(), mean(), and median() do
Three array functions, each returning one value:
avg(values)
mean(values)
median(values)avg([1.5, 2.3, 4.7]) returns 2.8333. mean([10, 20, 90]) returns 40 — the arithmetic mean, same idea as avg(), documented as working on numbers, currency, or dates. median([10, 20, 90]) returns 20 — the middle value once sorted, and notice it's half the mean on the same three numbers. That gap is the entire reason median() exists.
Force the output type explicitly — Currency for deal values, Number for counts and days — rather than leaving it on Auto.
The rollup: typical deal size per account
Like every array function, these pair with relationship and multi-value attributes. On a Company, type { in the editor, pick the deal-value attribute exposed through your deals relationship, and wrap it:
avg({Deal value})That's the average deal across everything linked to the account — the same graph-walk that powers sum({Deal value}) and max({Deal value}), but answering "what's a normal deal here" instead of "what's the total" or "what's the biggest".
avg() vs. median(): the whale problem
Take an account with five closed deals: $8K, $10K, $12K, $15K, and one $200K enterprise expansion. The average is $49K. The median is $12K. Which one should the rep use to size the next proposal?
The average is true and useless — no deal at this account has ever been near $49K. The median is what a typical deal at this account looks like, and it's the number the next proposal should be anchored to. The mean gets pulled toward outliers because it counts *how big* they are; the median only counts *where they sit in the order*, so one whale moves it by at most one position.
Rule of thumb: use avg() when you're going to multiply it back out (average × count = total, so it's the right input for capacity and revenue math); use median() when you're describing what to expect from the next one.
The skew signal: run both
The most useful thing about having both is the gap between them. Put avg({Deal value}) and median({Deal value}) side by side and:
- Roughly equal — the account's deals are consistent; either number is safe to plan on.
- Average well above median — one or two big deals are carrying the account; the "typical" deal is smaller than the total suggests, and the expansion story is really one contract.
- Average below median — a cluster of small deals or pilots is dragging the average down; the account buys bigger than its average implies.
That's a one-line account diagnostic that no single number gives you.
Blank-proofing
An account with no linked deals feeds an empty array, and every one of these returns null. Guard with ?? where a number is needed downstream:
avg({Deal value}) ?? 0Think first about whether zero is honest. "Average deal size: $0" on an account that has never bought reads as a fact, not an absence. Often the cleaner move is to leave the rollup blank and put the guard on the comparison that uses it, so no-deal accounts stay visibly empty instead of silently reporting zero.
Composing with the rest of the library
Route on the typical value with if(): a "buying below potential" flag compares the account's median deal to what its size suggests.
if((median({Deal value}) ?? 0) < 10000, "Below potential", "On target")Because 13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">mean() and median() accept dates, they also work on the date attributes exposed through a relationship — the median close date across an account's deals is a rough "when does this account tend to buy" marker, and dateDiff() from the [dateDiff() article turns per-deal cycle lengths into something you can median across if you keep the cycle length as its own attribute on the deal.
CRM use cases that earn their keep
- Typical deal size per account —
median({Deal value})— important because proposals should be anchored to what the account usually signs, not to an average one contract distorted. - Average deal size for revenue math —
avg({Deal value})— important because average × deal count is the total, so it's the right input for capacity and quota planning. - Skew diagnostic —
avg({Deal value})next tomedian({Deal value})— important because the gap tells you whether an account's number is one whale or a pattern. - Below-potential flag —
if((median({Deal value}) ?? 0) < 10000, "Below potential", "On target")— important because accounts consistently buying small are the expansion list nobody pulls. - Typical sales cycle —
median({Cycle days})across an account's deals — important because "how long does this account take to close" should be the median, not an average that one stalled deal doubled. - Typical contract length —
median({Contract months})— important because renewal planning wants the term the customer usually signs, not one three-year outlier blended in.
Copy-paste formulas
Swap in your attribute names and these work as-is:
Typical deal size, outlier-proof (Currency output):
median({Deal value})Average deal size, blank-safe (Currency output):
avg({Deal value}) ?? 0Below-potential flag (Text output):
if((median({Deal value}) ?? 0) < 10000, "Below potential", "On target")Typical sales cycle in days (Number output):
median({Cycle days})Typical contract term (Number output):
median({Contract months})Gotchas
- avg() and mean() are the same idea. Both return the arithmetic mean;
mean()is the one documented for dates as well as numbers and currency. Don't expect them to differ on the same input. - Median with an even count. With four values there's no single middle — expect the conventional midpoint between the two central values, and test on a record with an even number of deals if the exact behavior matters for a threshold.
- Blank in, blank out. Empty arrays return null on all three. Guard with
??and decide whether zero is honest. - Which deals count? The rollup includes every linked deal — open, won, and lost — unless your relationship is scoped. If "typical deal size" should mean won deals only, aggregate an attribute that's only populated on won deals.
- Nesting limit. Three formula attributes deep. Keep the rollup as its own attribute and reference it from the flag rather than stacking further.
Final thoughts
avg() is the number every dashboard shows; median() is the number the rep should actually use. Ship them together — avg({Deal value}) for the revenue math, median({Deal value}) for the proposal anchor — and the gap between them becomes a diagnostic you get for free. Add the below-potential flag and you have an expansion list that doesn't depend on anyone remembering to look.
For the rest of the library — every history, logic, math, date, and text function with CRM use cases — see the complete guide to Attio formula attributes.
And if you'd rather have your account rollups, deal-size diagnostics, and expansion signals designed and shipped for you, that's literally what we do. Get a free workspace audit or see the AI-native Attio sprint.
Official sources
Attio documentation used to verify this guide:
Need help with your Attio setup?
We migrate teams, build data models, wire automations, and train Claude agents inside your workspace. Discovery call is free.
Book a free discovery callReady when you are.
Two ways in. Pick the friction that fits.