Maps Business Coverage Gap Finder
Pricing
from $2.00 / 1,000 underserved cell results
Maps Business Coverage Gap Finder
Identify category-versus-area gaps in owned place datasets, with sample limitations made explicit. For Location intelligence and market research teams. Check the live Pricing tab and input limits.
Pricing
from $2.00 / 1,000 underserved cell results
Rating
0.0
(0)
Developer
Practical AI Lab
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Security
Limited permissionsShare
Compare business listing data you already have across postal areas and categories, and get a ranked list of category-area combinations that look underserved relative to a transparent statistical baseline.
This Actor is an analysis tool. It does not scrape Google Maps or any other website, makes no network requests to third-party sites, and does not read or output phone numbers, email addresses, personal names or any other contact data.
Who it is for
- Local-market analysts and consultants who already hold listing exports and want a quick first-pass screen of where categories look thin.
- Franchise, retail-site and service-planning teams who compare several existing datasets (for example two exports collected at different times or by different tools).
- Data teams who want a reproducible, explainable gap metric rather than a black-box score.
What makes it different from a plain listing export
- It works on your existing data (inline JSON or Apify datasets in your account), so you control sourcing and compliance.
- It ranks area x category cells, not individual businesses.
- Two baselines: category-mix independence (default) or per-capita using a population or demand proxy you supply. With population supplied, areas with zero listings are also detected.
- Multi-dataset comparison: results show per-source counts for every flagged cell, and the summary shows how many distinct places appear in each source and in each combination of sources.
- Each row carries an expected count, deficit, z-score and a conservative confidence label.
Input
| Field | Meaning |
|---|---|
listings | Inline records. Needs a category (category, categoryName, categories, type) and a postal code (postalCode, zip, postcode, ...) or city when regionMode is city. Optional: placeId/id or name for de-duplication, rating, reviewsCount, source (a label for comparing datasets). |
datasetIds | Up to 20 existing Apify dataset IDs holding such records. Labelled dataset_1, dataset_2, ... unless a record has its own source. |
regionMode, postalPrefixLength | How areas are defined (default: first 3 postal characters). |
regionPopulation | Optional {"area": number} demand proxy; switches to the per-capita baseline. |
categoryAliases | Optional map to merge category names. |
minCategoryListings, minExpectedListings, maxObservedToExpectedRatio, minDeficit | Flagging thresholds. |
maxListings, maxRegions, maxCategories, maxResults | Safety and size limits. |
Sample input (copy and run)
The tiny sample is only for checking the output shape; the thresholds are deliberately lowered so it returns a result.
{"listings": [{"placeId": "a1", "category": "Cafe", "postalCode": "100-0001", "rating": 4.4},{"placeId": "a2", "category": "Cafe", "postalCode": "100-0002", "rating": 4.1},{"placeId": "a3", "category": "Cafe", "postalCode": "100-0003", "rating": 4.6},{"placeId": "a4", "category": "Gym", "postalCode": "100-0004", "rating": 4.2},{"placeId": "a5", "category": "Gym", "postalCode": "100-0005", "rating": 3.9},{"placeId": "b1", "category": "Cafe", "postalCode": "200-0001", "rating": 4.0},{"placeId": "b2", "category": "Cafe", "postalCode": "200-0002", "rating": 4.3},{"placeId": "b3", "category": "Cafe", "postalCode": "200-0003", "rating": 4.5},{"placeId": "b4", "category": "Gym", "postalCode": "200-0004", "rating": 4.0},{"placeId": "b5", "category": "Gym", "postalCode": "200-0005", "rating": 4.4},{"placeId": "c1", "category": "Cafe", "postalCode": "300-0001", "rating": 4.2},{"placeId": "c2", "category": "Cafe", "postalCode": "300-0002", "rating": 4.0},{"placeId": "c3", "category": "Cafe", "postalCode": "300-0003", "rating": 4.7}],"postalPrefixLength": 3,"minCategoryListings": 1,"minExpectedListings": 0.5,"minDeficit": 0.5}
By the arithmetic of the method, area 300 has no gyms while the category mix of the other areas implies roughly 0.9 expected, so it should appear as one low-confidence row. Real analyses need far more data (see limitations).
Output
One dataset row per flagged cell, ranked by deficit (expected minus observed):
rank, region, category, observedListings, expectedListings, deficit, observedToExpectedRatio, zScore, confidence (low/medium/high), regionListings, categoryListings, regionPopulation, averageRating, totalReviews, countsBySource, model.
The key-value store record SUMMARY holds run totals: records read, accepted, duplicates, invalid records by reason, listings outside the analysed areas, source overlap, warnings and limitations.
Method
- Records are normalised (case-folded category, postal prefix) and de-duplicated by place ID, or by name + area + category.
- Counts are aggregated per area and category.
- Expected count for a cell:
area_total * category_total / grand_total(independence baseline), orcategory_total * area_population / total_population(per-capita baseline). - A cell is flagged when expected >=
minExpectedListings, deficit >=minDeficitand observed <= expected xmaxObservedToExpectedRatio. zScore = (observed - expected) / sqrt(expected)is a rough Poisson-style measure. Confidence ishighonly with expected >= 20 and z <= -3,mediumwith expected >= 8 and z <= -2, otherwiselow.
Limitations and sample-size caveats
- A gap means "fewer listings than the baseline predicts in your data". It is not proof of unmet demand, profitability or an open market. Source data may be incomplete, stale or biased.
- The independence baseline assumes every area has the same category mix. Business districts, residential areas and rural areas differ, so false gaps and missed gaps are expected.
- The per-capita baseline is only as good as the population or demand proxy you provide. Residents are not the same as visitors, workers or buyers.
- Small samples are unreliable. With fewer than about 200 usable listings, or fewer than 5 areas, treat results as exploratory. Cells with expected counts below about 8 are labelled low confidence.
- Postal prefixes of different length or country systems are not comparable to each other; areas of very different size are compared as if equal.
- Ratings and review counts are descriptive only and are not used for flagging.
- Records with no place ID and no name cannot be de-duplicated and may be double counted.
- Input is limited to 100,000 inline records and 500,000 accepted records per run; areas and categories are capped by
maxRegions/maxCategories(largest first). - No benchmark results, accuracy figures or revenue claims are provided with this Actor.
Data and compliance
- You are responsible for having the right to use the listing data you supply.
- Only aggregate area-category results are output. Business names are used in memory for de-duplication only and are not written to the dataset.
- Contact fields in your records are ignored.
Pricing
The code defines the pay-per-event name underserved-cell-result (one event per returned result row). Events are charged only after results are stored; failed runs are not charged. No pricing plan is configured in this repository.
Local checks
python -m unittest discover -s tests