Data wrangling

In tap SQL is how you can manipulate the data. In this tutorial, we'll explore some common data wrangling tasks you may want to perform: cleaning a messy dataset, joining it with another dataset, and then enriching it with H3 geospatial indexing to enable performant geospatial filtering.

Get set up

open tap and sign in or create your (free) account. This could take a minute to set up your storage bucket and domain if it's your first time signing in.

Next, download the required datasets:

Add data

After signing in you'll be on the Data page. Click Create data + to open the data creation form.

Data are the "sources" for all data processing in tap. You can upload any CSV, JSON(L), Parquet, XLSX, or ODS document. JSON documents will be converted into a single-line JSONL file, and XLSX and ODS documents will be extracted into separate CSV sheets.

Note: already have a bunch of data in object storage? Tap works with any S3-compatible object store so talk to us about bringing your own data lake.

Let's call the data:

  • scotland_hospitals
  • ons_postcode_lookup

For each, click Upload file and select the downloaded CSV. Once the upload completes, tap will automatically try to preview the file. For now, click Save to import the selected data.

Once saved, you'll see tap "materialising" the data. This creates an optimised copy of the data in Parquet format to accelerate queries. Materialisation should only take a few seconds.

Once you've created both data, navigate to the Models page. This is where we will do all our data wrangling.

Clean it

Notice the incosistent Postcode spacing and extra whitespace around AddressLine1 in the scotland_hospitals data:

PostcodeUtf8
AddressLine1Utf8
AddressLine2Utf8
"KA278LF""Lamlash ""Isle of Arran"
"KA128SS"" Kilwinning Road"" Irvine"
"KA280HF""College St ""Millport"
"KA2 0BE"" Kilmarnock Road""Kilmarnock"
"KA120DP"" Warrix Avenue""Irvine"
"KA6 6AB""Dalmellington Road ""Ayr"
"KA9 2HQ""Biggart Road ""Prestwick"
"KA6 6DX"" Dalmellington Road""Ayr"
"KA7 4DW""10 Doonfoot Road ""Ayr"
"KA215RF""Nelson Road""Saltcoats"

We can clean it with the following query:

SELECT
regexp_replace("Postcode", '\s*(.3$)', ' \1', 'g') AS "Postcode",
trim("AddressLine1") AS "AddressLine1",
trim("AddressLine2") AS "AddressLine2"
FROM data.scotland_hospitals

Let's save this model with the name scotland_hospitals_cleaned. Our data should no look like this:

PostcodeUtf8
AddressLine1Utf8
AddressLine2Utf8
"KA27 8LF""Lamlash""Isle of Arran"
"KA12 8SS""Kilwinning Road""Irvine"
"KA28 0HF""College St""Millport"
"KA2 0BE""Kilmarnock Road""Kilmarnock"
"KA12 0DP""Warrix Avenue""Irvine"
"KA6 6AB""Dalmellington Road""Ayr"
"KA9 2HQ""Biggart Road""Prestwick"
"KA6 6DX""Dalmellington Road""Ayr"
"KA7 4DW""10 Doonfoot Road""Ayr"
"KA21 5RF""Nelson Road""Saltcoats"

Join it

We can join it with the ons_postcode_lookupdata to map postcodes to latitude and longitude with the following query in a new model:

SELECT model.example_cleaned.*,
data.ons_postcode_lookup.latitude AS "Latitude",
data.ons_postcode_lookup.longitude AS "Longitude"
FROM model.scotland_hospitals_cleaned
JOIN data.ons_postcode_lookup ON "Postcode" = postcode

Let's save this model with the name scotland_hospitals_with_lat_long. Our data should no look like this:

PostcodeUtf8
LatitudeFloat64
LongitudeFloat64
AddressLine1Utf8
AddressLine2Utf8
"KA27 8LF"55.54312-5.115521"Lamlash""Isle of Arran"
"KA12 8SS"55.635056-4.676106"Kilwinning Road""Irvine"
"KA28 0HF"55.761296-4.922755"College St""Millport"
"KA2 0BE"55.61394-4.539409"Kilmarnock Road""Kilmarnock"
"KA12 0DP"55.611863-4.660189"Warrix Avenue""Irvine"
"KA6 6AB"55.434174-4.593108"Dalmellington Road""Ayr"
"KA9 2HQ"55.492576-4.604862"Biggart Road""Prestwick"
"KA6 6DX"55.430332-4.595538"Dalmellington Road""Ayr"
"KA7 4DW"55.449149-4.639765"10 Doonfoot Road""Ayr"
"KA21 5RF"55.63957-4.772409"Nelson Road""Saltcoats"

Enrich it

To support performant geospatial querying through the H3 filter we will add H3 indexing with the use of our Latitude and Longitudecolumns.

SELECT
model.scotland_hospitals_with_lat_long.*,
h3_latlng_to_cell("Latitude", "Longitude", 15::INT) AS "H3Res15"
FROM model.scotland_hospitals_with_lat_long

Let's save this model with the name scotland_hospitals_enriched. Our data should no look like this:

PostcodeUtf8
H3Res15Utf8
LatitudeFloat64
LongitudeFloat64
AddressLine1Utf8
AddressLine2Utf8
"KA27 8LF""8f19569a8c5c648"55.54312-5.115521"Lamlash""Isle of Arran"
"KA12 8SS""8f19092e6775410"55.635056-4.676106"Kilwinning Road""Irvine"
"KA28 0HF""8f19092b0d119ad"55.761296-4.922755"College St""Millport"
"KA2 0BE""8f190925084439b"55.61394-4.539409"Kilmarnock Road""Kilmarnock"
"KA12 0DP""8f19092e3d467a6"55.611863-4.660189"Warrix Avenue""Irvine"
"KA6 6AB""8f1954598d46a49"55.434174-4.593108"Dalmellington Road""Ayr"
"KA9 2HQ""8f1954598c58ac3"55.492576-4.604862"Biggart Road""Prestwick"
"KA6 6DX""8f1954598c58ac3"55.430332-4.595538"Dalmellington Road""Ayr"
"KA7 4DW""8f19545932d3a66"55.449149-4.639765"10 Doonfoot Road""Ayr"
"KA21 5RF""8f19092f28342c1"55.63957-4.772409"Nelson Road""Saltcoats"

That's it!

You can then serve it via a Data API and start building data-centric applications right away! Or, just download the data in a CSV, Parquet or JSONL format for further analysis.

Looks good? Missing something you need? We need your feedback.

Let us know if you need to get your data and applications talking, or you want to save money on complex, inefficient data platforms and integration tooling. We'd love to discuss and see how tap could help.

tap is built by DigitalSociety – we build bespoke digital tools for complex requirements in web applications, data engineering and cloud.