Guides

Build your first model

Start from an empty workbook and build a working staff-cost model — dimensions, series, formulas and your own data — then use it to find something the company's own summary hides.

A company has three years of headcount and salary history and a hiring plan for next year, both split by department. Salaries rise each year by an approved percentage. You have been asked to model the staff budget for 2024 to 2026 and forecast 2027.

You will build it from an empty workbook. Nothing is pre-loaded, and by the end the model will have told you something about the company that its own summary hides. If you would rather see the finished model first — or compare yours against it afterwards — it is published in Demo models as Walkthrough demo.

Start a new workbook, click Axmo Model, then Show panel.

The Axmo Model tab open on the Excel ribbon of an empty Book1, showing seven groups of buttons — Model, Analyse, Data, Worksheet, View, Share and Learn & Improve — with the Axmo Data Modelling pane docked on the right on its Status tab, reading “No model found — starting fresh”.
The Axmo ribbon and the panel it opens. Everything in this guide is driven from one or the other.

Part A — Dimensions

A dimension is an axis your figures vary along. This model has two: who, and when.

Company

Click + Categorical. Rename it Company — the top layer and its consolidation element follow the name.

Add a layer: type Directorate, then + Add Layer.

The Dimensions dialog with DIMENSION NAME set to Company and one layer present, Layer: Top_Company holding the element All_Company. Below it the word Directorate has been typed into the new-layer field beside the + Add Layer button, and both the name field and that pair are ringed.
The Company dimension with its Directorate layer added. The top layer and its consolidation element took their names from the dimension.

Add its first element with the green + on the layer row: choose the parent, type Alpha, press Enter. Add Beta the same way.

The Dimensions dialog with two layers: Layer: Top_Company holding All_Company, and Layer: Directorate below it, where the name Alpha is being typed into an element field. The green + on the Directorate layer’s own row is ringed, and empty Node name and New layer name fields sit underneath with their + Add Node and + Add Layer buttons.
Adding the first element to a layer. The parent is chosen before the element is named.

Now a Department layer, and five elements — Alpha-1 and Alpha-2 under Alpha, Beta-1, Beta-2 and Beta-3 under Beta. Choose the parent each time. The picker offers every element above the layer you are adding to, not only the layer directly above it, because Axmo lets a child hang from any higher layer.

The buttons to the right of each element rename it, move it, re-parent it or delete it. Save whenever you like. Check the shape against the figure below and Save.

The finished Company dimension in the Dimensions dialog, three layers deep: Top_Company holding All_Company; Directorate holding Alpha and Beta, each shown as ← All_Company; and Department holding Alpha-1 and Alpha-2 under Alpha and Beta-1, Beta-2 and Beta-3 under Beta. A Parent dropdown reading All_Company sits above the Department elements with Add and Cancel beside it.
The finished Company dimension — two directorates, five departments. Compare yours before saving.

Time

Click + Sequential. Name it Time, kind Time, level Year, start 2024, end defined by end point, end 2027. Preview — four elements — then Confirm and Save.

The sequential-dimension wizard showing 2024 as the first element, End defined by set to end_point, End set to 2027, and the Opening element (BEFORE) and Closing element (AFTER) boxes both unticked. Under DIMENSION ELEMENTS it previews Top_Time: All_Time and Year: 2024, 2025, 2026, 2027, with a Time_Weight weight dataseries and Cancel, Preview and Confirm buttons.
The Time dimension's preview. Nothing is created until you confirm what it shows.

A sequential dimension is generated rather than typed, and its elements have a known order. That order is what [Year[-1]] will step along in Part B.

Your Data Series list will also show Time_Weight. Axmo creates it automatically whenever a Time dimension is built — it holds the length of each period in days. You did not add it, and nothing in this model uses it.

Part B — Data series

A data series is a named quantity. Input series hold values you type or load. Calculated series hold a formula, evaluated separately at every coordinate.

The four inputs

Click + Input, name it Joiners Persons, leave both dimensions connected, roll-up SUM, Save.

The Data Series dialog with five series listed — Joiners Persons, Joiners Salary, Leavers Persons, Leavers Salary and a locked Time_Weight, all INPUT — and Leavers Salary open on the right. Its Type is Input and under Dimension Bindings both Company and Time are ticked, each with Roll-up “SUM — sum of children” and Split-down NONE.
An input series connected to both dimensions, rolling up with SUM. The other three differ only in the name.

Three more the same way: Leavers Persons, Joiners Salary, Leavers Salary.

Roll-up is chosen per series and per dimension, and it is how the total gets made. SUM is right for people arriving and money paid out. Nothing about the data tells Axmo that, which is why you say it.

One input that is not per department

Year End Salary Increase, % — untick Company, and set the Time roll-up to NONE.

The Data Series dialog with six series listed and Year End Salary Increase, % open on the right as an Input. Under Dimension Bindings the Company box is unticked and Time is ticked, and Time’s Roll-up reads “NONE — do not roll up; the parent stays …” with Split-down NONE.
The salary policy series: Company unticked, Time roll-up NONE. The only series in the model that is not per department.

Worth pausing on. The policy is one number a year, the same in every department, so the series has no reason to vary by Company. Read at any department it gives the same figure. And NONE, because there is no meaningful total of a percentage across four years.

The calculated series

Click + Calculated, name it Headcount, bindings Company SUM and Time AVG, and the leaf formula:

Nz(Headcount[Year[-1]], 0) + Joiners Persons - Leavers Persons

Read it as a sentence: this year's headcount is last year's, plus joiners, minus leavers. [Year[-1]] steps back one year. Nz supplies 0 for 2024, where there is no previous year to step to. A series that refers to its own earlier value is a recurrence, and Axmo works out that it has to be computed year by year.

Time roll-up is AVG here because headcount is a stock. Adding four years of it would count every person four times.

The rest are the same three steps — name, settings, formula — so only the settings are given:

NameCompanyTimeFormula
Staff CostSUMAVGNz(Staff Cost[Year[-1]] * (1 + [Year End Salary Increase, %][Year[-1]]), 0) + Joiners Salary - Leavers Salary
Average SalaryFORMULAFORMULAStaff Cost / Headcount
Staff Cost ShareFORMULAFORMULAStaff Cost / Staff Cost[All_Company]
Staff Cost Change To Previous Year, %FORMULAAVGIF(Year@ <> 1, (Staff Cost - Staff Cost[Year[-1]]) / Staff Cost[Year[-1]])
Average Salary Change To Previous Year, %FORMULAAVGIF(Year@ <> 1, (Average Salary - Average Salary[Year[-1]]) / Average Salary[Year[-1]])

Two of these are worth stopping on.

Staff Cost reads the increase at [Year[-1]]. A year-end rise lands on the following year's payroll. Read it at the current year instead and 2027 would quietly get no rise at all, because the policy table ends in 2026 — the model would still compute, and the forecast would be wrong.

FORMULA roll-up means the formula runs at the total as well as at the leaves. Average Salary at Alpha is Alpha's cost divided by Alpha's headcount — not the average of its two departments' averages, which is a different and meaningless number. A ratio is never summed.

Check your list against the figure below.

The Data Series dialog headed Total: 12, its list at the top and beginning Average Salary, Average Salary Change To Previ… and Headcount. Staff Cost Change To Previous Year, % is open on the right with Company roll-up FORMULA, Time roll-up AVG, and a formula reading IF(Year@ <> 1, (Staff Cost - Staff Cost[Year[-1]]) / Staff Cost[Year[-1]]).
All twelve series — six inputs and six calculated. You added five of the inputs; Time_Weight came with the Time dimension.

Part C — Connecting to data

Make a new sheet called InputData. Paste the first table into A1 and the second into I1.

One thing to notice in the data. 2024 has joiners in every department and no leavers anywhere, and its joiner salaries are far larger than any other year's. That is the opening position arriving: the model has no separate series for the workforce that already existed, so 2024 loads it through Joiners. From 2025 the columns mean what they say.

YearDepartmentJoiners PersonsLeavers PersonsJoiners SalaryLeavers Salary
2024Alpha-120010000000
2024Alpha-21508100000
2024Beta-11207200000
2024Beta-21006400000
2024Beta-3805440000
2025Alpha-152250000104000
2025Alpha-242216000112000
2025Beta-133225000186000
2025Beta-222164000132000
2025Beta-322172000140000
2026Alpha-152245000106000
2026Alpha-242220000114000
2026Beta-133240000201000
2026Beta-222174000144000
2026Beta-322188000154000
2027Alpha-162294000108000
2027Alpha-242220000118000
2027Beta-133258000219000
2027Beta-222186000154000
2027Beta-322200000168000
YearYE Salary Increase
20243%
20254%
20265%

Select the larger table, right-click, and run Create Input Range from the Axmo group at the bottom of the menu. (The Input Ranges editor does the same thing: + Add, then Use selected.) Name it Department Year data, Has headers on, Add row number off. Save.

Click + Series Loader. Target series Joiners Persons, value column Joiners Persons, duplicate handling Block. Then + Add beside Target series three more times, for Leavers Persons, Joiners Salary and Leavers Salary — the column names match the series names.

Under Dimension mappings, Company comes from the Department column and Time from the Year column. Save.

The Input Ranges dialog with one range, Department Year data at InputData!A1:F21, Has headers ticked and Add row number unticked. Its series loader, loader_1, lists four target series — Joiners Persons, Joiners Salary, Leavers Persons and Leavers Salary — each taking the column of the same name with Duplicate handling Block, and two dimension mappings: Company from the Department column and Time from the Year column.
The series loader: four targets, and both dimension mappings filled in.

The mapping is how a spreadsheet row becomes a coordinate. Alpha-1 in the Department column is matched to the element you created in Part A, so the spelling has to agree.

Now the second table, the same way: input range Salary Increase Data, one target Year End Salary Increase, % from the YE Salary Increase column, duplicate handling Block, Time mapped to the Year column. Save and close.

The Input Ranges dialog now listing two ranges, Department Year data and Salary Increase Data, with the second selected at Excel reference InputData!I1:J4, payload shape table and Has headers ticked. Its loader, loader_2, carries one target series, Year End Salary Increase, %, taking the YE Salary Increase column with Duplicate handling Block, and one dimension mapping, Time from the Year column.
The second input range. The same shape as the first, with one target and one mapping.

Part D — Does it work?

Press Update on the panel. The status becomes Up to date. If something is wrong, the panel tells you what and where — nothing is silently skipped.

In any empty cell with a little room to its right and below:

=DS.DATA("SC_YtY_Change := [Staff Cost Change To Previous Year, %][All_Company]; AS_YtY_Change := [Average Salary Change To Previous Year, %][All_Company]; YE_Increase := [Year End Salary Increase, %][Year[-1]]", "Year", "2024")

Format the result as a percentage with one decimal. The third argument drops 2024, which has no previous year to compare against.

A DS.DATA formula in cell A1 spilling into a four-row block on the worksheet: columns headed 2025, 2026 and 2027, with rows SC_YtY_Change at 12.5%, 12.3% and 13.3%, AS_YtY_Change at 4.5%, 4.8% and 4.9%, and YE_Increase at 3.0%, 4.0% and 5.0%.
One formula, spilled into a small table. The names before := become the row labels.
202520262027
SC_YtY_Change12.5%12.3%13.3%
AS_YtY_Change4.5%4.8%4.9%
YE_Increase3.0%4.0%5.0%

Staff cost is growing at roughly three times the pay policy, while pay per head tracks the policy within a point. So the growth is people, not pay — and at company level the rises look exactly as approved.

That second line is the one worth doubting.

Part E — A report, and what it shows

Building it

Find a free area six columns wide and forty-nine rows deep. Right-click its top-left cell and run Create Output Range. (Or open the editor and use Use Selection, or type the anchor as Sheet!Cell.)

Row definition: Dimension → Company → whole dimension → Add. Column definition: Dimension → Time → Year → Add.

Then add the series to the row definition. Series → Pick → Headcount → Add, then click the f button on that row and enter 0 as the format. The others follow: Staff Cost and Average Salary as #,##0, Staff Cost Share as 0%, and both change series as 0.0%.

Give every series a format. An output cell with no format keeps whatever format was there before it, so a report that grows by a row will show yesterday's percentages under today's headcount.

Check newLine is on for the series row, leave the output type as Report, and make sure Show column headers, Show row labels and Show data are all on. Save, close, and Update.

The Output Ranges dialog for a range named Staff Cost report anchored at Report!A1. Its Row Definition holds Company (all layers) and a Series item with newLine ticked, listing Headcount formatted 0, Staff Cost and Average Salary as #,##0, Staff Cost Share as 0%, and Staff Cost Change To Previous Year, % and Average Salary Change To Previous Year, % as 0.0%. The Column Definition holds Time / Year.
The output range settings, with a format set on every series row.

Reading it

The company view said pay per head is tracking policy. Test it one level down.

On the panel, open Active Sheet (or Outputs), find Staff Cost report and click Filt. Remove the Department layer — the red cross on its row, or deselect the children of Alpha and Beta in the Trees panel — and Apply.

The Filters dialog for the Staff Cost report, headed Company — 5 excluded. Its Layers panel lists Top_Company at 1, Directorate at 2, and Department at 5/5 excl with its red remove cross ringed; the Trees panel shows All_Company, Alpha and Beta all ticked with their children boxes cleared and ringed. Time reads all included, and behind the dialog the Report sheet shows All_Company, Alpha and Beta blocks of six series each.
The report's filters. Removing the Department layer leaves the two directorates and the company total.
Average salary, year on year202520262027
policy3.0%4.0%5.0%
Alpha2.2%2.7%3.1%
Beta8.4%9.0%9.6%

Neither directorate is anywhere near the policy, and their errors nearly cancel. Beta's headcount is 30 in every one of the four years — the directorate that is not growing at all is the one whose pay per head is running at nearly twice the approved rise.

Put the departments back and the same split appears inside each directorate.

Asking a further question without building anything

You can add a calculation to a report without adding a series to the model. In the Output Ranges editor, in the row definition, choose Series → Expression and type:

AS_Joiners := IF(Joiners Persons <> 0, Joiners Salary / Joiners Persons)

Add, format #,##0. Then the same for:

AS_Leavers := IF(Leavers Persons <> 0, Leavers Salary / Leavers Persons)

Save and Update.

2025joinersleavers
Alpha51,77854,000
Beta80,14365,429

There it is. Alpha replaces the people who leave with people costing slightly less. Beta replaces them at a 22% premium — and by 2027 Alpha is 9% below its leavers while Beta is still 19% above.

Neither directorate is doing anything visible at company level. One is buying growth by hiring below its own average; the other is holding headcount flat while its cost per head climbs. The summary said the pay rises were as approved, and it was right — by coincidence.

One check worth noticing

For Beta and its departments, Staff Cost Change and Average Salary Change are identical. For Alpha they are far apart. That is not a quirk: where headcount is flat the two must agree, and where it grows the gap between them is the growth. You could have found Beta's flat headcount from those two rows alone.

Where to go next

The model has room for more: headcount change year on year, staff turnover, cost per joiner. Each is one more calculated series and no new data.