CAP 2791C Power BI: Data Preparation · Miami Dade College · group final project

Florida licensed drivers, 2006 to 2026

A ten-minute walkthrough of the Power BI file for the colleague who will maintain it: where the data comes from, how 21 PDFs in three layouts become one table through Power Query steps, how the result is checked after every refresh, and how the model is built on top of it.

Spoken text about 1,060 words Target 9:30 including screen changes Presenters 5 parts, everyone rehearses all five Fact table 43,080 rows Reconciled 21 of 21 years

Setup before the session

Outline

TimePartPresenterTopicScreen
0:00–1:201Javier AlvarezThe business problem: 21 PDFs in three layoutsBrowser: FLHSMV page, PDFs
1:20–3:152Gabriel Ponce Nino De GuzmanSourcePath, the year parameters, SourceFilesPower Query: Parameters, Staging
3:15–5:153Jose SalamancaParsers for the long and wide layouts, step by stepPower Query: Walkthrough group
5:15–7:304Fed Sendy Jn BaptisteThe stacked parser, the hand edits, QA ReconciliationPower Query: Walkthrough; Table view
7:30–9:305Jan ZikaThe model: star schema, age brackets, measures; adding a yearTable view, Model view, README
1

The business problem

0:00–1:20
Javier Alvarez
0:00Browser · FLHSMV reports page, then the three PDFs

You are taking over a dataset of Florida's licensed drivers from 2006 to 2026. The state motor vehicle department, FLHSMV, publishes one PDF a year with drivers by county, age bracket and sex. Anyone who wants a trend, such as an insurer pricing young drivers, a county planning a service office, or a driving school sizing its market, would otherwise retype 21 PDFs. The PDFs come in three layouts: wide tables with the ages across the columns, the same with F and M rows stacked under each county, and from 2019 a long layout with one row per county and age. Our file turns all of them into one table of 43,080 rows that refreshes in about a minute.

We will go through the file in the order you will work with it: where the files are read from, how each layout is parsed, how the result is checked, and the model built on top of it.

2

One parameter, one source

1:20–3:15
Gabriel Ponce Nino De Guzman
1:20Power Query · Parameters group · Manage Parameters on SourcePath

Start with the setup, because it is what you touch first. One parameter, SourcePath, holds the start of the path to the PDFs, and it is the only value you change. Pointed at the project's data folder, the model reads a local copy of the reports: refresh takes about a minute and works offline. Pointed at the FLHSMV web folder, https://www.flhsmv.gov/pdf/driver-vehiclereports/, the same refresh reloads every report from the original source. Below it, 21 parameters, File_2006 to File_2026, hold each year's file name. The names are the same in both places, so changing the path changes the source and nothing else.

Manage Parameters dialog for SourcePath: type Text, required, with a description naming the local folder and the FLHSMV web folder.
Fig. 1The parameter's description names both values it accepts.
2:15Staging · SourceFiles

SourceFiles turns the parameters into a table: year, file name, layout and the file itself. If the path starts with http it uses Web.Contents; otherwise it reads the folder once with Folder.Files. Either way the model has one data source, so combining 21 files raises no firewall errors. The first web refresh asks for the privacy level of flhsmv.gov; choose Public. Source Location shows where each year was read from.

The Layout column decides which parser reads each file, and the parsers come next.

Power Query Editor with SourcePath set to the FLHSMV web folder; SourceFiles lists 21 years with file name, layout (Wide, Stacked, Long), binary content and the FLHSMV URL of each report.
Fig. 2SourceFiles with SourcePath set to the FLHSMV web folder. The Layout column picks the parser: Wide for 2006-2011 and 2018, Stacked for 2012-2017, Long from 2019.
Power BI Privacy levels dialog for https://www.flhsmv.gov/ with Public selected.
Fig. 3One-time prompt on the first web refresh. FLHSMV is a public government site, so the level is Public.
3

The long and wide layouts

3:15–5:15
Jose Salamanca
3:15Walkthrough · Sample Long 2026

Each layout has a parser, and each parser has a sample query in the Walkthrough group that runs the same steps on one PDF, so you can click through them in Applied Steps. The long layout is the class ADA query: filter to tables, expand, remove and rename columns, extract the text after "Age", replace " County", unpivot, change type. Two filters, Column3 equals Female and AgeBracket does not contain "to", replace the class version's list of one year's title and total labels, so the same steps work for every year.

For 2019 to 2025 we read the accessible versions, the files ending in _ada. In the wide 2020 and 2021 files, the PDF connector merges the page header into the first row of each page and that row's numbers are lost.

Sample Long 2026 at the Filtered Rows1 step: columns Column1 to Column8 of the 2026 report, county, age bracket, Female and Male counts; Applied Steps list on the right.
Fig. 4Sample Long 2026 at the filter Column3 = Female. The Applied Steps list on the right is the class ADA query with the two general filters.
4:10Walkthrough · Sample Wide 2006 · click four steps

The wide layout of 2006 shows why the others need more steps. After Expanded Data, the county name is printed only on the F row, with null on the M row. Remove Empty on the last column drops the title, Use First Row as Headers names the age columns, Fill Down copies the county onto the M row, and Unpivot Other Columns turns fifteen age columns into one. Replace Values handles the dash the state uses for zero and spells out F and M.

  1. Expanded Data
    Expanded Data step: rows of the 2006 table with County printed on F rows and null on M rows.
    The PDF table as Power Query reads it: the county appears on the F row only.
  2. Promoted Headers
    Promoted Headers step: columns named County, Sex, 15, 16 and so on.
    After Remove Empty and Use First Row as Headers, the age brackets are column names.
  3. Filled Down
    Filled Down step: every row now carries its county name; the formula bar shows Table.FillDown.
    Fill Down copies each county onto its M row.
  4. Changed Type with Locale
    Final step of Sample Wide 2006: columns County, Sex, AgeBracket, Drivers and Year.
    After Unpivot Other Columns, Replace Values and the type change: one row per county, sex and age bracket.
4

The stacked layout, hand edits, reconciliation

5:15–7:30
Fed Sendy Jn Baptiste
5:15Walkthrough · Sample Stacked 2012 · Removed Errors, Added Conditional Column

The stacked layout of 2012 to 2017 puts F and M rows under each county's total row. A conditional column turns F and M into the Sex column, a second one keeps the county name on total rows, and Fill Down spreads it. In 2017 the header repeats on every page; Change Type on the row total turns those rows into errors, and Remove Errors drops them.

Sample Stacked 2012 at Added Conditional Column1: county total rows followed by F and M rows; the formula bar shows the conditional column.
Fig. 5The stacked layout: each county's total row, then its F and M rows. The formula bar shows the conditional column that keeps the county name on total rows.
5:50Replaced Value2 · hover the info icon on an "M edit" step

These parsers are longer than anything we wrote in class, so here is exactly what is typed by hand. Every step is a ribbon command except at most three per parser, and those are marked "M edit" in each step's description. They are one-line changes in the formula bar: rename columns by position, because their headers change from year to year; take the year from the function's input; label the statewide total row before Fill Down; and, in the 2012 case you see here, relabel a column printed as 21-30 that really holds ages 22 to 30. The functions are the same steps with the PDF and the year as inputs, so if you change a step, change it in the sample and in the function.

ParserYearsStepsTyped by hand (M edit)
fnParseLong2019–202614Added Custom: the year column
fnParseWide2006–2011, 201819Renamed Columns by position; Replaced Value2: label the statewide row; Added Custom
fnParseStacked2012–201721Renamed Columns by position; Replaced Value2: 2012 "21-30" to "22-30"; Added Custom
Sample Stacked 2012 at Replaced Value2: the formula bar shows if ReportYear = 2012 then Table.ReplaceValue(..., "21-30", "22-30", ...) else the previous step; the data shows 21 and 22-30 as separate brackets.
Fig. 6One of the hand edits: a Replace Values step wrapped in if ReportYear = 2012 then ... else ..., so it applies only to the 2012 report.
6:45Table view · QA Reconciliation

LicensesStaging runs the right parser for each year and keeps the statewide total printed in each PDF. QA Reconciliation compares that total with the sum of the county rows: all 21 years reconcile to zero. Check this table after every refresh. The Counties column matters too: it should read 69 until 2013 and 68 after, when the Unknown line ends; any other count means a county name was split or left blank.

Table view of QA Reconciliation: for each year from 2006 to 2026, rows, counties, age brackets, sum of county detail, published statewide total, difference 0, status Reconciled, Florida counties, Out of State and Unknown County.
Fig. 7QA Reconciliation in Table view. Difference is 0 in every year; Out of State drops between 2022 and 2023.
5

The model, and adding a year

7:30–9:30
Jan Zika
7:30Table view · QA Reconciliation, Out of State column · then Model view

The same table shows the data's one break: Out of State falls from 849,151 in 2022 to 73,144 in 2023, a change in how the report counts that line. Florida counties alone grew from 14.6 to 18.7 million over the 21 years.

The model is a star. Licenses is the fact table: year, county, age bracket, sex and drivers, with its keys hidden. County, Age Bracket and Year are built from the fact table in Power Query, so a new county spelling or bracket shows up as a new row.

Model view: Licenses related many-to-one to Year, County and Age Bracket; QA Reconciliation has no relationships.
Fig. 8Star schema. QA Reconciliation is a standalone check table.
8:10Table view · Year, then Age Bracket

The age brackets changed twice: until 2012 single years to 21, then 22 to 30; in 2013, 21 to 30; from 2014, 21 to 29. Those schemes cannot be mapped onto each other above 20, so we keep every bracket as published and add an Age Group column with the only groups identical in all years: 15 to 17, 18 to 20, and 21 plus.

Table view of Year: source file, layout, source URL, the age brackets each year published and the bracket count.
Fig. 9The Year table lists the brackets each report published: three schemes, 2006-2012, 2013, and 2014 onward.
Table view of Age Bracket: 28 brackets with minimum and maximum age, age group, and first and last year used.
Fig. 10Age Bracket: First Year and Last Year show which scheme each label belongs to.
8:45Model view · select the Licensed Drivers measure

Each report is a snapshot, so the main measure, Licensed Drivers, takes the latest year in the filter context instead of adding years together. Florida County Drivers excludes Out of State and Unknown.

Licensed Drivers =
VAR LatestYear = MAX ( 'Year'[Year] )
RETURN
    CALCULATE ( SUM ( Licenses[Drivers] ), 'Year'[Year] = LatestYear )
9:05README · Adding a new year

To add 2027: save the new report into the data folder, create a File_2027 parameter with its file name, add one row to SourceFiles with its layout, refresh, and check QA Reconciliation. New county spellings go into CountyAliases. The README records every decision and its reason. Thank you; we are happy to take questions.

Likely questions

Why is the local folder the default rather than the web, as in the class example?
The local copy refreshes offline in about a minute and keeps the exact files the analysis used. The original source is one parameter change away.
Why three parsers instead of one?
Each layout needs a different set of UI steps. Three short chains of ribbon commands are easier to read and fix than one query full of conditions.
Why keep the sample queries if the functions already exist?
A function shows no Applied Steps. The samples make every step visible and testable on a real PDF.
What happens if a file is missing?
The refresh stops with an error that names the file: "Not found" and the path from the local folder, or the HTTP error and the URL from the web.
How do you know the 2012 column is 22-30?
In 2012, age 21 plus "21-30" for Alachua totals 46,002; the 2013 21-30 bracket is 45,813. A column holding 21-30 would double-count age 21.
What does Out of State mean?
The reports do not define it. The model keeps it as its own county, and Florida County Drivers excludes it.