Employee Survey in PowerBi

About the Project

The dashboard was build in powerbi as an employee survey by HR to better comprehend the satisfaction of employees within the organisation. Each employee within each department was surveyed via phone, email, letter and website with this information collated into a csv file. This served as the data source for the visualisation for further analysis and record keeping by the HR team

Live - HR Survey Dashboard in PowerBi

Data Model

The data model from the survey consist of the main table which contain the survey data information. The other tables connected to the main table consist of Survey type,  Manager information, employee information, department (all of which have a primary key linked to the secondary key in the survey table) and master date table used to filter out certain time frames. 

How this dashboard is built

An employee survey dashboard has one job that is easy to get wrong: a department average means nothing without the company average beside it. Seven out of ten sounds fine until you learn the company sits at eight and a half. So every score in this report is paired with its company benchmark, and the comparison between the two is what the page is actually about.

It is a single page by design. HR reviews are conversations, not exploration sessions, and everything needed for one fits on a screen.

The data model

TableGrain and role
Tbl_EmployeeThe response table. One row per employee per survey period, carrying Employee_Id and the scored answers, including job satisfaction and work-life balance.
Tbl_DepartmentDepartment dimension, the axis every comparison is made across.
Master dates tableMarked date table on Month & Year, driving the trend lines.
All MeasuresDisconnected table holding the measures.

The responses were collected by phone, email, letter and website and collated into a single CSV, so the modelling work here was mostly in shaping inconsistent inbound formats into one clean response table before any DAX was written.

Column names below are taken from the model. These are the measure definitions as the model computes them, not a paste of the formula bar.

The measures

The score, and the benchmark

Six measures, and they come in pairs. Each question has a filtered average and a company-wide average, with the second removing the department filter so it stays constant while the first moves.

Avg. how satisfied are you with your current job? =
AVERAGE( Tbl_Employee[JobSatisfaction] )

Avg. how satisfied are you with your current job? - whole company =
CALCULATE(
    AVERAGE( Tbl_Employee[JobSatisfaction] ),
    REMOVEFILTERS( Tbl_Department )
)

Avg. survey score work-life balance? =
AVERAGE( Tbl_Employee[WorkLifeBalance] )

Avg. survey score work-life balance? - whole company =
CALCULATE(
    AVERAGE( Tbl_Employee[WorkLifeBalance] ),
    REMOVEFILTERS( Tbl_Department )
)

REMOVEFILTERS on the department, not on everything. The benchmark still respects the date slicer, so a department is compared against the company in the same period rather than against an all-time average. Using ALL() instead would quietly break that.

Conditional formatting

Two measures exist to colour the work-life balance results against the benchmark, returning a colour rather than a value so the table shades itself.

Conditional formating - work-life balance =
VAR Score     = [Avg. survey score work-life balance?]
VAR Benchmark = [Avg. survey score work-life balance? - whole company]
RETURN
    SWITCH(
        TRUE(),
        Score >= Benchmark * 1.05, "#2E7D32",
        Score <  Benchmark * 0.95, "#C62828",
        "#F9A825"
    )

The thresholds matter more than the colours. A department within five percent of the benchmark is shown as neutral rather than red, because normal variation should not read as a problem. Setting that band deliberately is what stops a heat map turning into an alarm.

Walking the page

The layout runs top to bottom in the order the conversation goes.

  • Headcount cards. Employee counts across four segments, giving the denominator before any score is shown. A department of six and a department of six hundred deserve different confidence in their averages.
  • Score cards with benchmark. Each average sits directly beside its whole-company equivalent, so the comparison needs no mental arithmetic.
  • Two trend lines. Survey score and satisfaction over previous months, which is where a genuine decline separates itself from one bad month.
  • Two tables. The department-level detail, shaded by the conditional formatting measures.
  • A date slicer. One control, so the whole page moves to the same period together.

Build decisions worth calling out

  • Every score is paired with its benchmark. The single most important decision in the file. It turns an absolute number into a judgement.
  • The benchmark respects time but not department. Same period, whole company, which is the only fair comparison.
  • Headcount shown alongside scores. Sample size is context, and small departments produce volatile averages that look like signal.
  • A neutral band in the formatting. Within five percent of benchmark reads as neutral, so ordinary variation is not dressed up as a red flag.
  • One page, one slicer. Built for a meeting rather than for exploration.

What I would change next

The report shows averages, and averages hide distribution. A department averaging seven could be uniformly content or split between very happy and very unhappy people, and those need completely different responses. Adding a distribution view, even a simple count by score band, would surface that. I would also add response rate as a measure, since a department where a third of people answered tells you something in itself, and it qualifies how much weight the score deserves. Beyond that, the natural extension is linking survey scores to attrition, which is the outcome the survey is ultimately a proxy for.

Get in touch!

What type of project are you interested in?
Where can I reach you?
Where would you like to discuss?