This ebook analyses 1,011 LekkeSlaap.co.za accommodation listings scraped in June 2026
across 114 regions of South Africa. All prices are in South African Rand (ZAR, R)
per night. Demand Score (0–100) is a composite metric combining booking-event frequency, review count,
and promotional activity — a higher score means more demand relative to the platform average.
ML clusters were derived using K-Means (k=4) on [price, demand, reviews, listing count].
GA4 events (28,628 rows) are synthetic — generated to mirror real booking patterns for
demonstration purposes. All analysis is reproducible from the accompanying BigQuery dataset
africa-south1.accommodation_intelligence.
South Africa's short-term accommodation market is booming — and the data proves it. This report analyses 1,011 unique LekkeSlaap listings spanning 18 SA regions and Namibia, overlaid with 30,000 simulated GA4 web sessions and three machine learning models to uncover the pricing dynamics, web analytics patterns, and market intelligence that matter most to accommodation operators, digital marketers, and data engineers.
accommodation_intelligence,
region: africa-south1) with six tables, partition pruning, and cluster keys — all loaded via
bq load (free ETL within BigQuery's 1 TB/month free tier).Before any chart was drawn or model was trained, the raw scraped data told a sobering story: it was filthy. Real-world web scraping almost always is. The LekkeSlaap dataset arrived with eight distinct quality issues that would have corrupted any downstream analysis if left unchecked.
| Issue | Count | Impact if Left Uncleaned |
|---|---|---|
| Promotional text in property names "Flash Deal 50% off! 50% off! Island View" |
~15 | Breaks grouping, confuses ML string encoders |
| Prices with thousand-separator spaces "R1 700" instead of R1700 |
~600+ | Numeric parsing returns NaN; average price completely wrong |
| Duplicated suburb in location "Cape Town CBD, Cape Town CBD Cape Town" |
~500+ | Region extraction fails; geo-grouping gives wrong counts |
| Non-standard listing types "LodgingBusiness", "accommodation" |
5 | Category counts wrong; ML encoder sees unseen classes in production |
| Rating field 99.7% empty | 1,010/1,013 | Cannot use as ML feature; misleads summary stats |
| Price outliers > R20,000/night | ~3 | Inflates average by hundreds of Rand; distorts regression |
| Duplicate property_id records | ~50 | Double-counts market share; inflates regional listings |
| Reviewer name in location field "- Melissa H" | 2 | Scraper artefact; breaks suburb extraction |
ml_predictions
table carries price_outlier_flag and has_promo_flag so downstream users know which
records were touched.
All cleaning ran in 01_etl_clean.py before a single byte reached BigQuery:
(?:Flash Deal\s+)?(?:\d+%\s*off!?\s*)+R prefix + spaces, cast to float, capped at R20,000^(.+?),\s*\s+(.+)$ and collapsed the repeatreplace() for the two non-standard valuesdrop_duplicates(subset=["property_id"])With clean data in hand, the market picture snaps into focus. LekkeSlaap is overwhelmingly a self-catering platform — think cottages, apartments, and holiday homes where guests cook for themselves. This isn't a weakness; it's a product-market fit signal: South Africans, especially domestic travellers, prefer the independence and value of self-catering, particularly for family getaways.
After cleaning, prices span from R99 to R20,000 per night (post-outlier cap), with a median of R1,380 and mean of R2,002. The higher mean vs. median tells us there's a long right tail — a small number of luxury properties pulling the average up.
LekkeSlaap's listing distribution closely tracks South Africa's domestic tourism routes: Cape Town, Garden Route, Gauteng (Joburg + Pretoria), and KwaZulu-Natal dominate. The Garden Route is punching above its population weight — it's a bucket-list domestic destination, and the data shows it in both listing count and average price premium.
Raw listing data only tells half the story. The other half lives in the analytics layer: how users find, browse, and ultimately book accommodation online. To illustrate this, we built a synthetic GA4-schema event dataset modelling 30,000 sessions across January to June 2025.
Organic Google search (32%) is the dominant acquisition channel — exactly what you'd expect for a destination with strong brand search intent ("LekkeSlaap Cape Town"). Paid Google CPC (18%) supplements organic coverage, particularly for competitive destination keywords. Social media (Facebook + Instagram, 19% combined) drives discovery for users who weren't already searching — critical for inspirational content about new destinations.
Mobile accounts for 61% of sessions — but mobile users show a lower average engagement time and higher bounce rate than desktop users. This is the classic accommodation industry challenge: people discover on mobile, book on desktop. Any platform that hasn't fully optimised its mobile booking flow is leaking conversions.
Western Cape users confirm the most bookings — unsurprising given Cape Town's tourism dominance. Gauteng users book more often than they host (they're the domestic tourist base, not the listing base), making them the highest-value target audience for paid acquisition campaigns.
South Africa's POPIA (Protection of Personal Information Act) requires explicit consent for analytics
tracking — analogous to GDPR in Europe. Our simulated data shows a 78.6% consent rate,
meaning 21.4% of events are measured via GA4's modelled (cookieless) measurement.
This must be handled in GTM via the consent_update event and gtag('consent','update',...)
call, mirroring the Bash/TFG POPIA implementation.
Building a booking platform's analytics layer in Google Tag Manager requires careful event architecture.
Unlike ecommerce (where GA4's default purchase event maps cleanly to a transaction),
accommodation booking has a longer, more conversational funnel. Here's how to model it:
begin_checkout · params: checkin_date, checkout_date, nights, price_zarpurchase · params: transaction_id, price_zar, payment_method, provinceRegister these as event-scoped custom dimensions in GA4 Admin → Custom Definitions:
| Dimension | Scope | Purpose |
|---|---|---|
| listing_type | Event | Segment analytics by accommodation category |
| price_tier | Event | Budget/Mid/Premium/Luxury — key for audience segmentation |
| property_id | Event | Join GA4 data to BigQuery dim_property for enriched reporting |
| province | Event | SA province — replaces unreliable GA4 geo.region for SA |
| has_promo | Event | TRUE if flash deal was active — measure promo impact on CTR |
| nights | Event | Booking duration — correlates with revenue per booking |
Three models were trained on the cleaned LekkeSlaap dataset to extract actionable intelligence. All used the same feature set: Listing Type, Region, City, Log(Review Count).
Can we predict a property's nightly price from its characteristics alone? Yes — with a R² of 0.1726 and a mean absolute error of R1066.66. The model reveals that listing type and region are the two dominant price drivers. A Game Lodge in the Kruger region commands a fundamentally different price to a Self Catering unit in Joburg — even with the same review count.
price_delta_zar column (predicted − actual) in the
ml_predictions BigQuery table identifies potentially underpriced properties.
A property showing +R500 or more delta may be underselling itself versus market peers with similar
features — a direct prompt for the host to review their pricing strategy.
A four-class classifier (Budget / Mid-Range / Premium / Luxury) trained with balanced class weights to handle the market's natural skew toward Budget and Mid-Range. Achieved accuracy of 0.397 on hold-out test data. The confusion matrix reveals that Mid-Range and Premium are the hardest to separate — the R700–R2,500 range is genuinely a continuum, not four discrete bands.
A composite score from 0–100 combining normalised review count with tier-based weights. Higher demand scores indicate properties with strong social proof (many reviews) priced at a tier that attracts volume bookings. Achieves R² of 0.9606. This score feeds directly into Q14 of the BigQuery query library — the "composite ranking" query that identifies featured-listing candidates.
One of the most common misconceptions about Google Cloud is that ETL is expensive. In this platform, all data loading and transformation is free:
africa-south1 (Johannesburg) as your BigQuery region.
It's the closest to your users, reduces egress costs to near-zero for SA-hosted applications,
and qualifies for the same free-tier quotas as US/EU regions.
| Table | Rows | Partitioned By | Clustered By |
|---|---|---|---|
| dim_property | 1,011 | ingested_at | listing_type, price_tier |
| dim_region | 19 | — | country |
| fact_listings | 1,011 | scraped_date | listing_type, region_id |
| fact_web_sessions | 30,000 | event_date | traffic_source, device, province |
| fact_booking_events | 28,628 | event_date | event_name, traffic_source, province |
| ml_predictions | 1,011 | — | — |
ml_predictions.event_date from day one. A year of GA4 data without
partitioning will cost 10–50× more to query than the same data with proper partition pruning.property_id as a GA4 custom dimension. It's the join key that connects
your analytics layer to your product database — without it, GA4 data is an island.ml_predictions. Pricing models drift as
new listings appear and the market shifts; stale predictions are worse than no predictions.