Why “avoid sketchy neighborhoods” apps keep failing, and how a vibes map can avoid that
SketchFactor, Ghetto Tracker and their successors failed the same way: they mapped people instead of conditions. Here are the design rules we used for CruzLens and CityLens, and why normalizing by exposure changes the map.
Every few years someone builds an app to help people avoid "bad" neighborhoods, and every few years it goes the same way. The pattern is consistent enough to learn from, and we tried to learn from it before building CruzLens, a "vibes map" of Santa Cruz County, and CityLens, a template for doing the same in any city.
The short version: the map should describe conditions of places (crashes, spills, polluted water, dumping), never the presence of people, and it should be honest about what its numbers are counts of.
Source: State Water Board Beach Watch · Data: cruzlens.json · Code: CruzLens
Show the data (10 rows)
| Station | Exceedances | Samples | Rate | From | Through |
|---|---|---|---|---|---|
| Cowell Beach — Cowell Beach West of Wharf | 413 | 1,114 | 37% | 2006-06-13 | 2026-03-02 |
| Capitola City Beach — Capitola Beach west of Jetty | 393 | 1,766 | 22% | 1995-01-03 | 2026-03-02 |
| Main Beach — Main Beach 25 yrds east of wharf | 140 | 427 | 33% | 2006-06-20 | 2025-05-22 |
| Main Beach — East side of municipal wharf | 70 | 268 | 26% | 2018-03-05 | 2023-10-30 |
| Main Beach — 150 feet east of wharf | 70 | 269 | 26% | 2018-03-05 | 2023-10-30 |
| Main Beach — 215 feet east of wharf | 63 | 268 | 24% | 2018-03-05 | 2023-10-30 |
| Cowell Beach — 75 feet west of wharf | 61 | 268 | 23% | 2018-03-05 | 2023-10-30 |
| Neary Lagoon mouth — Neary Lagoon at Mouth | 59 | 115 | 51% | 2008-01-15 | 2024-05-21 |
| Cowell Beach — 225 feet west of wharf | 49 | 268 | 18% | 2018-03-05 | 2023-10-30 |
| Cowell Beach — Cowell Beach at Cave (Collins) | 26 | 108 | 24% | 2006-06-16 | 2015-07-28 |
Source: State Water Board Beach Watch · Data: cruzlens.json · Code: CruzLens
A short history of sketchy-area apps
Ghetto Tracker, 2013. A crowdsourced site where locals rated parts of town for travelers to avoid. Within a day of the criticism it was renamed "Good Part of Town"; it was then taken offline with a note that it was "not worth the trouble", and CNN reported it came back again soon after.1 Its ratings came only from users’ impressions, with no underlying records.
SketchFactor, 2014. A navigation app that combined public data sets with anonymous user reports to show the "sketchiness" of an area. Critics argued that a subjective word like "sketchy" would, in practice, mean poor neighborhoods and people of color, and users began filing satirical reports to make the point.2 An organization the founders cited as a supporter said it had never endorsed the app.3
Nextdoor, 2015–2016. Crime and Safety posts on the neighborhood network were repeatedly reported for racial profiling: vague "suspicious person" warnings about Black residents walking, driving or knocking on doors. In 2016 Nextdoor replaced the free-text box with a structured form that, if a post mentions race, requires other specific descriptors, and said the change cut profiling posts by 75% in testing.4 Reporting the following year found gaps remained, including on the mobile apps.5
Vigilante, later Citizen, 2016–2021. A real-time crime alert app, pulled from Apple’s App Store within a week of its 2016 launch over concerns it would encourage vigilantism, relaunched in 2017 as Citizen.6 In May 2021, Citizen pushed a photo of a man it described as a suspect in the Palisades fire, with a $30,000 reward. He was not the arsonist; police questioned and released him.7
What went wrong, in data terms
These failures share three mechanisms:
- The unit was a person, not a place. A report of a "suspicious" person, a homeless encampment or a gathering is a report about who was present, filtered through the reporter’s assumptions. Aggregate thousands of them and the map shows where certain people are, then labels those places dangerous.
- The data measured attention, not risk. Crowdsourced reports come from whoever uses the app. Even official crime data reflects where police look and what residents report: a well-known simulation of predictive policing on Oakland drug arrests showed a model trained on arrest records sending officers back to the same neighborhoods, regardless of where drug use actually was.8 Later work formalized this as a runaway feedback loop.9
- Nothing was shown. A red zone with no records behind it can’t be checked, corrected or argued with.
The design rules we used
1. Map conditions, never people. CruzLens and CityLens layers describe places: traffic crashes, sewage spills, beach bacteria, toxic cleanup sites, illegal dumping, potholes, graffiti. They contain no demographic or population layers. CruzLens uses only the pollution-burden part of CalEnviroScreen, not its population-characteristics score.
2. People-targeting categories are removed or weighted zero. Encampment reports, welfare checks, mental-health and suicide calls, and missing-person calls are removed before scoring. In the San Diego example, curfew, loitering and vagrancy offenses carry a weight of 0, because they record enforcement against people, not a condition of a place. A crisis call means someone needed help. It does not make a block "bad". scripts/check.py in CityLens enforces this on every bundled city config.
3. Every number is clickable. Each hex shows which layers contributed and the individual records behind them, each linked to its publisher. A Sources panel lists retrieval times, coverage windows, record counts and failures. Missing data is shown as unknown, never as zero.
4. Crime is one layer, and a small one. In CruzLens, reported crime has a default weight of 0.5 against 1.5 for traffic crashes, and users can turn any layer off or reweight it.
Exposure normalization: count of what, per what?
The charts at the top show why the denominator matters. Ranked by the raw number of failed bacteria samples, Cowell Beach and Capitola lead. Ranked by failures per sample, Neary Lagoon’s mouth is worst: 59 failures in only 115 samples, a 51% failure rate. Capitola has more failures because it has been sampled 1,766 times since 1995. A raw-count map rewards places that are watched more.
So for every layer we asked: what is the exposure, and can we measure it?
- Beach bacteria have a clean denominator, samples, so CruzLens scores them as an exceedance rate.
- Crashes, complaints and dumping don’t. The obvious per-capita fix uses residents, but crashes happen to commuters and visitors on highways where nobody lives, so dividing by residents would make empty highway corridors look extreme. Without traffic volume at 250 m resolution, CruzLens shows raw record density and says so on the map.
- Each layer is scaled to its own busiest places (the 95th-percentile hex reaches 100%) before layers are combined, so one high-volume source can’t drown out the rest, and records fade with an adjustable half-life so a decade-old spill counts less than last month’s.
We also show what a raw count can hide. In the CityLens San Francisco example, the city’s incident-report dataset had no usable coordinates, so the map uses priority police calls instead and says so in the Sources panel. A green hex means few listed records nearby. It does not mean safe.
Methods
The rules above are implemented in the CruzLens and CityLens repositories (scoring in web/model.mjs, category exclusions in each adapter and city config, enforced by scripts/check.py). The beach figures cover every Beach Watch sample CruzLens loaded for each station (each station’s dates are in the data table); a sample "exceeds" if any analyte is above the AB 411 single-sample standard.
Limits
Removing people-targeting categories does not make the remaining data neutral: crash reports, code cases and 311 requests also depend on who reports and who responds. The rules lower the risk of building a profiling tool; they don’t remove it. Feedback and corrections are welcome as issues on either repository.
Notes
- "‘Ghetto Tracker’ site offends, dies and returns", CNN, September 6, 2013. https://www.cnn.com/2013/09/06/tech/web/ghetto-tracker-controversy ↩
- "SketchFactor App Responds to Accusations of Racism", NBC News, August 12, 2014. https://www.nbcnews.com/news/asian-america/sketchfactor-app-responds-accusations-racism-n178136 ↩
- "SketchFactor Lost Its Anti-Racial Profiling ‘Partner’ as Soon as It Launched", The Wire via Yahoo News, August 2014. https://www.yahoo.com/news/sketchfactor-lost-anti-racial-profiling-partner-soon-launched-143800120.html ↩
- "Social Network Nextdoor Moves To Block Racial Profiling Online", NPR, August 23, 2016. https://www.npr.org/sections/alltechconsidered/2016/08/23/490950267/social-network-nextdoor-moves-to-block-racial-profiling-online; the 75% figure is Nextdoor’s own: "How Nextdoor reduced racist and discriminatory posts by 75%", Inman, September 14, 2016. https://www.inman.com/2016/09/14/how-nextdoor-reduced-racist-posts-by-75/ ↩
- Caroline O’Donovan, "Racial Profiling Is Still A Problem On Nextdoor", BuzzFeed News, 2017. https://www.buzzfeednews.com/article/carolineodonovan/racial-profiling-is-still-a-problem-on-nextdoor ↩
- "Instant crime alert app Vigilante removed from App Store after concerns expressed", 9to5Mac, November 2, 2016. https://9to5mac.com/2016/11/02/vigilante-app-new-york-city/ ↩
- "Citizen app sets off manhunt for Palisades wildfire arson suspect — but they had the wrong guy", FOX 11 Los Angeles, May 17, 2021. https://www.foxla.com/news/citizen-app-sets-off-manhunt-for-man-falsely-accused-of-starting-palisades-fire; see also Vice, "LAPD Emails Reveal Fallout of Citizen’s Botched Manhunt". https://www.vice.com/en/article/lapd-emails-citizen-palisades-wildfire-manhunt/ ↩
- Kristian Lum and William Isaac, "To predict and serve?", Significance 13(5), October 2016. https://doi.org/10.1111/j.1740-9713.2016.00960.x ↩
- Danielle Ensign et al., "Runaway Feedback Loops in Predictive Policing", 2017. https://arxiv.org/abs/1706.09847 ↩
Data and code
Every number and chart in this post is generated from these files:
The projects that collected the data:
- CruzLens source · live demo
- CityLens (source not published yet)