Day 2 lecture: Building dashboards in Tableau¶
This morning you learned what makes a chart and a dashboard clear. This afternoon you build them in Tableau: connecting to data, dimensions and measures, aggregation, the Marks card, filters, calculated fields, sheets, dashboards, actions, formatting and printing to PDF. Short Python cells show the same ideas in pandas, so you can see that both tools do the same arithmetic.
Open Tableau Desktop and this notebook side by side. Follow the numbered steps in Tableau; run the Python cells in JupyterLab with Shift + Enter.
What you will be able to do¶
- Connect Tableau to a saved data source and to a CSV file, and check the data types.
- Tell dimensions from measures and discrete (blue) from continuous (green) fields.
- Explain how Tableau aggregates a measure for each mark, and change the aggregation.
- Use the Marks card (Color, Size, Label, Detail, Tooltip) and the Filters shelf.
- Write a calculated field, including a ratio KPI computed as a ratio of sums.
- Assemble sheets into a dashboard, add a filter action, and format it for a reader.
- Save a packaged workbook and print a dashboard to PDF for submission.
Reading: Tableau Software (n.d.), What is data visualization? and Tableau for students (licence and download).
| Part | Topic |
|---|---|
| 1 | The Tableau workspace |
| 2 | Connecting to data |
| 3 | Dimensions and measures, discrete and continuous |
| 4 | Aggregation and the level of detail |
| 5 | The Marks card |
| 6 | Filters |
| 7 | Calculated fields and a ratio KPI |
| 8 | From sheets to a dashboard |
| 9 | Actions: making the dashboard interactive |
| 10 | Formatting for the reader |
| 11 | Saving, printing to PDF and publishing |
| 12 | Assignment 1 briefing |
Field names differ between versions
Tableau has changed its Sample – Superstore data several times: dates, field names (State or State/Province) and some values differ between versions. Use the names you see in your Data pane. When a CSV column is called
total_bill, Tableau may display it as Total Bill; it is the same field.
import pandas as pd
pd.set_option("display.float_format", "{:,.2f}".format)
tips = pd.read_csv("data/tips.csv")
print("Rows (bills):", len(tips))
print("Columns:", list(tips.columns))
Rows (bills): 244
Columns: ['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size']
Part 1: The Tableau workspace¶
When you open a worksheet, the screen has the same parts every time:
| Area | Where | What it does |
|---|---|---|
| Data pane | Left | Lists the fields of the connected data, dimensions above the line, measures below |
| Columns and Rows shelves | Top | Fields placed here define the chart's axes and headers |
| Marks card | Left of the view | Chooses the mark type and what drives colour, size, labels, detail and tooltips |
| Filters shelf | Above the Marks card | Fields placed here limit which rows are used |
| View (canvas) | Centre | The chart itself |
| Show Me | Top right | Suggests chart types for the fields you have selected |
| Sheet tabs | Bottom | Worksheets, dashboards and stories; plus the Data Source tab |
The three icons next to the sheet tabs create a new worksheet, a new dashboard and a new story.
How Tableau thinks
You never write "draw a bar chart". You drag fields onto shelves, and Tableau decides the chart from what you placed where. A measure on Rows and a dimension on Columns gives a bar chart; a date on Columns gives a line. Show Me is a shortcut, not a different method.
Part 2: Connecting to data¶
The built-in sample¶
- Open Tableau Desktop. On the start page, under Saved Data Sources (left side), click Sample – Superstore.
- A blank worksheet, Sheet 1, opens. The Data pane lists tables such as Orders, People and Returns.
A CSV file¶
- On the start page, under Connect → To a File, click Text file.
- Go to the course folder
notebooks/China_Classroom/data/and opentips.csv. - The Data Source page shows a preview of the rows. Above each column an icon shows the data type Tableau guessed:
| Icon | Type | Example in tips |
|---|---|---|
| # | Number | total_bill, tip, size |
| Abc | Text (string) | sex, smoker, day, time |
| Calendar | Date | (none in tips) |
| Globe | Geographic | (none in tips) |
- Click a type icon to change it if the guess is wrong. Then click the Sheet 1 tab at the bottom to start building.
Check what one row means
As in Python, look at the preview and say what one row is before you build anything. In
tips.csvone row is one bill (one table served). In Superstore's Orders table one row is one order line (one product in one order), not one order.
The Python version of "connect and preview" is the cell you ran above, plus head():
tips.head()
| total_bill | tip | sex | smoker | day | time | size | |
|---|---|---|---|---|---|---|---|
| 0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
| 1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
| 2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
| 3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
| 4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
Part 3: Dimensions and measures, discrete and continuous¶
Tableau sorts every field in two ways.
| Meaning | Examples | Typical use | |
|---|---|---|---|
| Dimension | A field you group by: categories, names, dates | Category, Region, day, smoker |
Headers, colours, filters |
| Measure | A number you aggregate (add up, average) | Sales, Profit, total_bill, tip |
Axes, bar lengths, sizes |
And separately, by colour of the pill:
| Pill colour | Kind | What it draws |
|---|---|---|
| Blue | Discrete: separate values | Headers and separate panes (Jan, Feb, Mar as labels) |
| Green | Continuous: a range of values | An axis (a time line from 2023 to 2026) |
Usually dimensions are discrete and measures continuous, but not always. A date can be either: YEAR(Order Date) as a blue pill gives one column per year; a green continuous month gives one long timeline. In Lab 2 you will change a date pill from discrete to continuous to draw a monthly trend.
A number that is really a category
In
tips.csv,size(the number of people at the table) arrives as a measure, so Tableau would add it up. To compare party sizes, right-clicksizein the Data pane → Convert to Dimension, and Tableau treats each size as a group. In Python the same decision is whether you writegroupby("size")or["size"].sum().Check your understanding
For each Superstore field, say dimension or measure, and discrete or continuous in a typical chart: Region, Sales, Order Date on a monthly trend line, Quantity, Customer Name.
Part 4: Aggregation and the level of detail¶
When you drag a measure into the view, Tableau does not draw one mark per row. It draws one mark for each combination of the dimensions in the view, and aggregates the measure inside each mark. The default aggregation is SUM.
In Tableau (with Sample – Superstore):
- Drag Region to Columns and Sales to Rows. The pill reads
SUM(Sales): one bar per region, each the total of its order lines. - Right-click the
SUM(Sales)pill → Measure (Sum) → Average. The pill becomesAVG(Sales)and each bar shows the average order line, a different question. - Drag Segment to Color. Now there is one mark for each region and segment: the level of detail got finer, and each mark aggregates fewer rows.
The same three steps in pandas, on the tips data (grouping by smoker instead of Region):
# Step 1: SUM, one value per group (Tableau's default).
print("SUM(total_bill) by smoker:")
print(tips.groupby("smoker")["total_bill"].sum().round(2))
# Step 2: AVG, the same groups, a different question.
print("\nAVG(total_bill) by smoker:")
print(tips.groupby("smoker")["total_bill"].mean().round(2))
# Step 3: a finer level of detail, one value per smoker AND time.
print("\nSUM(total_bill) by smoker and time:")
print(tips.groupby(["smoker", "time"])["total_bill"].sum().round(2))
SUM(total_bill) by smoker:
smoker
No 2,897.43
Yes 1,930.34
Name: total_bill, dtype: float64
AVG(total_bill) by smoker:
smoker
No 19.19
Yes 20.76
Name: total_bill, dtype: float64
SUM(total_bill) by smoker and time:
smoker time
No Dinner 2,130.14
Lunch 767.29
Yes Dinner 1,530.16
Lunch 400.18
Name: total_bill, dtype: float64
| Tableau | pandas |
|---|---|
| Dimensions in the view (Columns, Rows, Color, Detail…) | The columns in groupby([...]) |
SUM(Sales) |
["sales"].sum() |
AVG(Sales) |
["sales"].mean() |
CNT(Sales) / COUNTD(Order ID) |
.size() / ["order_id"].nunique() |
| One mark | One row of the grouped result |
Predict, then reveal
In Tableau you put Category on Rows and
SUM(Sales)on Columns: 3 bars. Then you drag Order ID onto Detail. How many marks do you expect now, and why does the chart stop making sense? (Try it.)
Part 5: The Marks card¶
The Marks card controls how each mark looks.
| Button | What it encodes | Good use | Watch out |
|---|---|---|---|
| Mark type (dropdown) | Bar, Line, Circle, Square, Text, Map, Pie… | Leave on Automatic until you need a specific type | Pie only for two or three parts |
| Color | A dimension (categories) or measure (scale) | Highlight one thing, or profit (diverging) | Rainbow palettes; colour alone |
| Size | A measure | Secondary information | Area is read inaccurately (this morning, Part 2) |
| Label | Writes values on marks | Direct labels instead of an axis | Too many labels clutter |
| Detail | Splits marks without encoding them | Needed for maps and for tooltip fields | Makes the level of detail finer |
| Tooltip | Text shown on hover | Exact values, definitions | Hover does not exist on a printed PDF |
Try it (Superstore): on a sheet with Sub-Category on Rows and SUM(Sales) on Columns, drag Profit onto Color. Tableau uses a diverging orange–blue palette centred on zero: orange for losses, blue for profits. This is this morning's diverging palette, chosen automatically because Profit can be negative. Click Color → Edit Colors to check where the centre sits.
Part 6: Filters¶
A filter limits which rows reach the view.
- Drag a dimension (for example Region) to the Filters shelf, tick the values to keep, click OK.
- Right-click the pill on the Filters shelf → Show Filter to put a filter control next to the chart for the reader.
- To make one filter change several sheets: on the filter control's menu (small arrow) → Apply to Worksheets → All Using This Data Source (or Selected Worksheets).
- Measures can be filtered too: drag Sales to Filters and choose a range of
SUM(Sales).
In pandas, a filter is a boolean condition applied before grouping:
dinner = tips[tips["time"] == "Dinner"] # filter: keep dinner bills only
print("Bills after the filter:", len(dinner), "of", len(tips))
dinner.groupby("smoker")["tip"].sum().round(2) # then aggregate, as Tableau does
Bills after the filter: 176 of 244
smoker
No 331.45
Yes 214.62
Name: tip, dtype: float64
State the filter on the page
A filtered chart answers a narrower question. Put the filter in the title or the scope note ("Dinner bills only"), or the reader will assume the whole dataset.
Part 7: Calculated fields and a ratio KPI¶
A calculated field is a new field defined by a formula. Create one with Analysis → Create Calculated Field, or right-click in the Data pane → Create → Calculated Field. Give it a name, type the formula, and check that the message under the box says The calculation is valid.
Row-level versus aggregate calculations¶
| Formula | What Tableau does | Result in the view (with AVG) |
|---|---|---|
[Tip] / [Total Bill] |
Computes the ratio for every row, then you aggregate it (Tableau suggests SUM; you would choose AVG) | The mean of ratios: each bill's tip % averaged |
SUM([Tip]) / SUM([Total Bill]) |
Adds up the tips and the bills for each mark, then divides | The ratio of sums: the share of all spending that went to tips |
This morning (Part 11) you saw that these answer different questions, and that a KPI tile should normally use the ratio of sums. In Tableau, the SUM(...) / SUM(...) form is how you write it. Superstore's standard KPI is:
Profit Ratio = SUM([Profit]) / SUM([Sales])
After creating it, right-click the field → Default Properties → Number Format → Percentage so it always displays as a percentage.
Here are both versions in pandas, for each value of smoker:
kpi = tips.groupby("smoker").agg(tips_sum=("tip", "sum"), bills_sum=("total_bill", "sum"))
kpi["ratio of sums %"] = 100 * kpi["tips_sum"] / kpi["bills_sum"] # SUM([Tip]) / SUM([Total Bill])
kpi["mean of ratios %"] = 100 * (tips["tip"] / tips["total_bill"]).groupby(tips["smoker"]).mean() # AVG([Tip] / [Total Bill])
kpi
| tips_sum | bills_sum | ratio of sums % | mean of ratios % | |
|---|---|---|---|---|
| smoker | ||||
| No | 451.77 | 2,897.43 | 15.59 | 15.93 |
| Yes | 279.81 | 1,930.34 | 14.50 | 16.32 |
The two percentage columns in the output above differ. Look closely: do they even rank smokers and non-smokers in the same order? If a dashboard tile said "Tip rate" without saying which one, two analysts could report different numbers from the same data. Name the formula in the tile or the scope note.
Other useful calculations
IF SUM([Profit]) < 0 THEN "Loss" ELSE "Profit" END: a label you can put on Color.DATEDIFF('day', [Order Date], [Ship Date]): days to ship, per row.COUNTD([Order ID]): distinct orders, because Superstore has several rows per order.Check your understanding
In Superstore, why does
AVG([Profit] / [Sales])give a different number fromSUM([Profit]) / SUM([Sales])for the whole company? Which one would you put on an executive dashboard, and why?
Part 8: From sheets to a dashboard¶
A dashboard is built from worksheets: build and title each chart on its own sheet first, then arrange them.
Plan before you build¶
Use this morning's principles. On paper, sketch:
- The decision and the audience (for example: a regional manager deciding which products to review).
- The 3–5 views, each answering one part of the question.
- The layout: headline and KPIs at the top left, detail below and to the right.
Build¶
- Build each sheet and rename its tab (double-click the tab), for example Sales trend, Profit by state, Sales by sub-category. The tab name becomes the default title on the dashboard.
- Click the New Dashboard icon at the bottom.
- On the left, under Size, choose Automatic so the dashboard fills the screen (or a Fixed size such as A4 landscape if you will print it).
- Under Sheets, drag each sheet onto the canvas. Grey shaded areas show where it will land: top, bottom, left or right of what is already there.
- Leave objects Tiled (the default) so they line up in a grid; Floating objects sit on top and are easy to misalign.
- Add a Text object (under Objects) for the headline message and a small one for the scope note (period, data source, units).
- Remove legends you do not need (click the legend → x), and delete repeated titles.
Check your understanding
Your dashboard has six sheets and three legends and still has no headline. Using this morning's principles, name three changes.
Part 9: Actions: making the dashboard interactive¶
Actions let a click on one view change the others.
The quick way: Use as Filter¶
- On the dashboard, click once on a view (for example a map) to select it. A grey border appears with small icons at its top right.
- Click the funnel icon (Use as Filter). Now clicking a mark in that view (a state) filters every other view on the dashboard.
- To clear the selection, click the same mark again, or click an empty part of the view, or press Esc.
The full way: Dashboard → Actions¶
Dashboard → Actions → Add Action offers:
| Action | Effect |
|---|---|
| Filter | Clicking a mark keeps only the matching rows in the target views (this is what Use as Filter creates) |
| Highlight | Clicking a mark highlights matching marks elsewhere and greys the rest, without removing them |
| Go to Sheet / URL / parameter / set | Navigation and advanced uses |
For each action choose the source sheet, target sheets, and whether it runs on Hover, Select (click) or Menu, and what happens when the selection is cleared (Show all values is usually what a reader expects).
In Python there is no clicking, but the idea of one filter driving every view is a function with a parameter. Every number below is recomputed for the chosen group:
def mini_dashboard(data, smoker=None):
"""Recompute every KPI for one selection, as a dashboard filter action does."""
view = data if smoker is None else data[data["smoker"] == smoker]
return pd.Series({
"bills": len(view),
"total spend (dollars)": view["total_bill"].sum(),
"tip rate, sum/sum (%)": 100 * view["tip"].sum() / view["total_bill"].sum(),
"average party size": view["size"].mean(),
}).round(2)
pd.DataFrame({
"All bills": mini_dashboard(tips),
"Click: smoker = Yes": mini_dashboard(tips, "Yes"),
"Click: smoker = No": mini_dashboard(tips, "No"),
})
| All bills | Click: smoker = Yes | Click: smoker = No | |
|---|---|---|---|
| bills | 244.00 | 93.00 | 151.00 |
| total spend (dollars) | 4,827.77 | 1,930.34 | 2,897.43 |
| tip rate, sum/sum (%) | 15.15 | 14.50 | 15.59 |
| average party size | 2.57 | 2.41 | 2.67 |
Actions do not print
A PDF shows one state of the dashboard. Before printing, clear all selections so the reader sees the whole picture, unless the assignment asks for a filtered view; then say in a caption which state is selected.
Part 10: Formatting for the reader¶
Apply this morning's design rules inside Tableau:
| Goal | Where in Tableau |
|---|---|
| Message titles | Double-click a sheet title on the dashboard and type the finding; keep units in the axis title |
| Sort bars by value | The Sort Descending button in the toolbar, or right-click the dimension → Sort |
| Number formats (thousands separators, %) | Right-click the measure pill or axis → Format → Numbers |
| Colour-blind-safe colours | Color → Edit Colors → choose the Color Blind palette for categories; keep orange–blue for profit and loss |
| Remove clutter | Format → Lines: set grid lines to None; right-click an axis → uncheck Show Header when bars are labelled |
| Direct labels | Click Label on the Marks card → Show mark labels |
| Readable tooltips | Click Tooltip on the Marks card and rewrite it as a sentence |
| Scope note | A small Text object at the bottom: data source, period, filters, units |
Bars still start at zero in Tableau
Tableau's bar axes start at zero by default. If you right-click an axis → Edit Axis and untick Include zero, you have built this morning's misleading truncated bar chart.
Part 11: Saving, printing to PDF and publishing¶
| Task | How | Notes |
|---|---|---|
| Save your work | File → Save As → Tableau Packaged Workbook (.twbx) |
A .twbx contains the data, so it opens on another computer. A .twb does not |
| Print to PDF (for GAP) | File → Print to PDF → Range: Active Sheet (with the dashboard tab open) → OK → choose a file name | Choose landscape paper; check that nothing is cut off |
| Export an image | Dashboard → Export Image | Useful for a Word report |
| Publish to the web | Server → Tableau Public → Save to Tableau Public | Anyone on the internet can see it. Never publish confidential or personal data; course submissions go to GAP as PDF |
Before you print, check
- The dashboard tab is the active sheet.
- No mark is selected and no filter is left on by accident.
- Every title is a message, every axis has units, and the scope note is visible.
- Open the PDF and read it as the reader will: is anything cut off or too small?
Summary¶
- Tableau draws one mark per combination of dimensions in the view and aggregates each measure in that mark (SUM by default), exactly like
groupbyin pandas. - Blue pills are discrete (headers), green pills are continuous (axes). Dates can be either.
- The Marks card maps fields to colour, size, label, detail and tooltip; the Filters shelf limits rows.
- Ratio KPIs are calculated fields written as
SUM([numerator]) / SUM([denominator]). - A dashboard is a few titled sheets, laid out for one decision, connected by actions, with a headline and a scope note.
- Submit dashboards as a PDF from File → Print to PDF; keep a
.twbxcopy of your work.
Closing quiz¶
- What is the difference between a dimension and a measure? Give one example of each from Superstore.
- A pill is green. What does that tell you, and what will it draw?
- You put
SUM(Sales)on Rows and Region on Columns, then add Segment to Color. What happens to the number of marks? - Write the calculated field for a profit ratio KPI. Why not
AVG([Profit] / [Sales])? - What does the funnel (Use as Filter) icon on a dashboard view do?
- Name two things to check before Print to PDF.
Part 12: Assignment 1 briefing¶
Assignment 1: Data visualisation and descriptive analytics report (6%, CLO 1–3)
Due Sunday 15 November 2026, 23:59 China Standard Time, on GAP (Moodle).
You act as an analytics consultant for Bistro Analytics, advising a busy restaurant. Using the tips data (244 bills), you run a Python notebook that produces two summary tables and three figures, then write a short business report with four fixed sections:
- Executive summary
- Descriptive data overview
- Visual insights and graphical trends
- Strategic business recommendations
The restaurant owner wants help with two decisions: when to schedule the strongest staff and how to raise revenue and server income through upselling.
Everything you learned today applies: message titles, honest axes, ratio of sums, and turning findings into a recommendation for a specific audience.
Start here: Assignment 1 brief and starter notebook. Open D2_assignment1_tips_report.ipynb in JupyterLab, read the brief, and save your own copy as YourLastName_DATA300_Assignment1.ipynb.
After this briefing, the lab starts with Applied Lab 2: Dashboard creation. If you finish the lab early, start Assignment 1.