abstreet icon indicating copy to clipboard operation
abstreet copied to clipboard

Mode shift

Open dabreegster opened this issue 3 years ago • 21 comments

Quoting some docs: The travel demand model is extremely fixed; the main effect of a different random number seed is currently to initially place parked cars in specific spots. When the player makes changes to the map, exactly the same people and trips are simulated, and we just measure how trip time changes. This is a very short-term prediction. If it becomes much more convenient to bike or bus somewhere, then more people will do it over time. How can we transform the original demand model to respond to these changes?

Here's a strawman proposal for how this could work: after the player edits the map, re-evaluate every trip in the scenario. Calculate a cost for driving/walking/transit/biking for each one. Change the mode specified by the input to the best choice.

  • From a gameplay/UI perspective, I'd vote for explicitly opting into this new "long-term" behavior vs the current short-term mode.
  • We may want to do this upfront before any map changes to avoid the first edit causing many spurious changes unrelated to the edit. If the Soundcast model predicts a particular mode for someone and our calculation changes it, what should we do -- go ahead and change it?
  • If the person doesn't own a car according to the input, it might be odd to assume player edits would cause them to buy one. But going the other direction is much easier for people financially, of course.
  • How well this idea works completely depends on how we cost each possible route. Do we express the cost just as an estimated time? If we wanted to account for some kind of safety/convenience, how do we compare these -- some linear combination with parameters we have to somehow tune? Or a lexicographic comparison of each factor, arbitrarily choosing that a slight safety benefit warrants any increase in expected time?
  • Currently the pathfinding costs for all modes are quite primitive -- they don't account for waiting at traffic signals, unprotected left or right turns onto an arterial, any other measure of how nice a road is to cycle/walk on, etc. It's becoming increasingly urgent to improve this, not just for this issue.
  • A person's individual preferences differ, which should likely affect how they cost each mode. How should we encode these preferences? Does Soundcast contain them?

Thinking a little about UX of switching modes.

  • Even with contraction hierarchies, calculating 4 paths for every trip will be slow -- depends on the map and scenario size, but could be 30-90s sometimes. Likely need to think carefully about aggregating similar trips to speed this up.
  • #237 has some ideas for helping the player understand these long-term changes.

dabreegster avatar Jan 11 '21 18:01 dabreegster

https://github.com/byu-transpolab/modechoice https://www.ce.utexas.edu/prof/bhat/COURSES/LM_Draft_060131Final-060630.pdf

dabreegster avatar Mar 21 '21 03:03 dabreegster

screencast Here's a new toy. Access it by launching a scenario, opening the dashboard, then selecting "mode shift" from the top-left dropdown.

This looks for all driving trips in the map and shows the expected time to drive vs bike. Also shows the distance and elevation gain/loss of the trip. You can filter by different things and count the number of matching trips. So it's a super rudimentary interactive PCT-ish query. These're good candidate trips that could be swayed to cycle. Maybe a next step would be to figure out what kind of infrastructure changes (better signal timing, more protected cycle lanes along the route) would convince the person to switch.

@Robinlovelace Any feedback on this approach? Anything you'd like to see in this kind of dashboard? Handing you a blank cheque here. ;)

dabreegster avatar Jul 14 '21 19:07 dabreegster

This looks great, a solid basis for implementing mode shift models in A/B Street by the looks of it! Wish list for things I'd like to see:

  • [x] Enable the user to specify cut of distances and hilliness levels for mode switch targets (already implemented I guess from the vid, not tried it though)
  • [ ] Include pre-made scenarios of cycling uptake, like the Go Dutch scenario shown below (reproducible code that could be ported to Rust available if interested)
  • [ ] Enable users to back their own uptake functions, e.g. by drawing distance decay curves (this is very ambitious but thinking outloud)

There may well be some lower hanging fruit. Good news is: there's lots of literature on mode shift and much of it revolves around logit models. Not so great news: not much of the research is reproducible or based on open code.

Here are some figures showing the concepts of distance decay and hilliness decay in action.

image

image

Robinlovelace avatar Jul 14 '21 20:07 Robinlovelace

Ideas from a call with Michael & Robin:

  • Look into existing multinomial logit models; they're already for individual people
  • From the dashboard with distance/elevation filters, draw the potential cycling routes used and look for patterns. Prioritize changes there.
  • Or focus on "the stick." For car trips < 5 miles, what're the common roads used? So what if we road diet there?
  • From the dashboard, don't just use a uniform 30% probability. Let people specify the distance decay curves, with presets.
  • Key ideas: make tools to explore different combinations of infrastructure/behavior changes. Not just one prescriptive "apply score function" button. But also have some preset scenarios of change.
  • For reconciling with existing Soundcast models, we could first draw the distance decay function from that data, then maybe think about it as a baseline and apply relative changes.

dabreegster avatar Jul 22 '21 21:07 dabreegster

Alright, starting to put a few things together... Screenshot from 2021-07-26 10-50-34 Screenshot from 2021-07-26 10-48-41 They're not easy to see in this initial view, but the maps have a heatmap of roads. Based on the current filters that find short driving trips, we calculate what route that trip might take if it biked instead. Then we look at all road segments those routes cross, filter for high-stress roads (the definition of this is extremely preliminary), and count the most popular segments. So this is kind of PCTish. We look for trips that're good candidate to shift onto cycling, then look for reasons why the direct cycling route might be scary, and find the most common problem areas. It's a really quick+interactive way to focus attention on certain roads.

dabreegster avatar Jul 26 '21 17:07 dabreegster

Very good being able to link trips filtered by length and mode to the map.

Robinlovelace avatar Jul 26 '21 22:07 Robinlovelace

filter for high-stress roads (the definition of this is extremely preliminary)

If you wanted to leverage the work of others in this department, there exists: https://bna.peopleforbikes.org/#/places/0ea909ec-0b15-4bae-b593-a40da8a72312/

e.g. here's seattle low vs high stress streets: Screen Shot 2021-07-26 at 5 07 27 PM

They have many cities and towns in the US (including Seattle), and a handful of international cities. It looks like their scoring logic is also based on OSM tagging and census data.

Also, I came across their nice bike-centric OSM tagging guide: https://docs.google.com/document/d/1HuAXQUnCEcv9aLZyIDHkLTJ5ZSKfB-U4MlJSmN-1BLk/edit

The BNA score seems to come from this repository: https://github.com/azavea/pfb-network-connectivity

After a cursory review, I think the analysis is primarily driven from here(?): https://github.com/azavea/pfb-network-connectivity/blob/develop/src/analysis/scripts/run_analysis.sh

and largely driven by a string of SQL mutations: https://github.com/azavea/pfb-network-connectivity/tree/develop/src/analysis/stress

If not literally using their results, it might be worth checking some of their methods, e.g. in https://github.com/azavea/pfb-network-connectivity/blob/develop/src/analysis/stress/stress_tertiary_ints.sql#L14

michaelkirk avatar Jul 27 '21 00:07 michaelkirk

I absolutely want to avoid reinventing the wheel on defining high-stress. Thank you for the detailed links! I'll figure out tradeoffs between importing their data separately vs trying to replicate their classifier. (An argument for the latter being: the user will probably be changing roads to make things less stressful)

dabreegster avatar Jul 27 '21 01:07 dabreegster

https://osmus.slack.com/archives/C2VJAJCS0/p1630599064085200 Discussion of tagging levels of traffic stress in OSM directly

dabreegster avatar Sep 07 '21 18:09 dabreegster

That sounds good to me. Couldn't see the Slack (do I need to join the group?) but interested in the idea. After chatting with Martin about tagging 'LTN' I tentatively agree with the 'keep it objective' approach, unless there is a very clear definition of traffic stress, including things like surface quality that may not be captured in OSM. Stress is key to prioritising effort where it's most needed though and likely levels of use so great to see more potential data sources on it.

Robinlovelace avatar Sep 07 '21 19:09 Robinlovelace

Try https://osmus-slack.herokuapp.com/ to join the Slack. Luckily it's public and has permanent archiving (afaict), but you do need to sign up. :(

Note that tagging level of cycling stress is different than tagging an LTN, but they're definitely related.

dabreegster avatar Sep 07 '21 21:09 dabreegster

I'm going to take some more steps on this, but in the context of the bike network tool for #743. The end goal is to predict which trips from an input scenario will switch from driving to biking due to the user's proposed map edits.

The "differential" approach needs to avoid the "spurious" changes mentioned in the original message of this issue. So on an unedited map, given the chosen parameters for distance/time/elevation thresholds, calculate which trips would switch "baseline." Then after edits, repeat, and see how many more trips match.

But... I'm not sure this makes sense. If all those trips would have switched without the user's edits, why hasn't that happened yet? One of the main barriers from the real human perspective is safety. So I think we can "score" the user's network by running mode shift and seeing how many trips will make use of new infrastructure. In other words, take the existing "find the most important gaps" layer, and just overlay that with the proposed network.

So some concrete steps to get there, as a gift to my less clear-headed future self in the morning:

  1. Add a 4th tab to the ungap tool. Put the 4 thresholds as the "input" parameters there. On the map, show the most important gaps by default. Figure out what to cache and where. Evaluate the gaps against the unedited map, always!
  2. Hover on a road segment to see the number of potential trips that would switch -- and maybe not just number, but total distance of those trips?
  3. Add a button to open up the table of individual trips. Clicking will switch to the individual route tool.
  4. Add some kind of "score", the number of trips that would switch, further filtered by those whose route leads through improved infrastructure.

Then more speculative steps:

  • Add the carbon emissions estimate
  • Instead of the fixed parameters for max distance / time / hilliness, implement probability functions from https://github.com/a-b-street/abstreet/issues/448#issuecomment-880184069

dabreegster avatar Sep 20 '21 03:09 dabreegster

Alright, some progress: Screenshot from 2021-09-20 17-47-46

This new page works as follows: 0) Start with an existing travel demand model, which predicts the trips people take daily, and the mode they use.

  1. Search for all driving trips which might consider biking instead. There are filters to select maximum time, distance, and elevation gain for the potential biking trip. As a possible next step, this could use a decay function instead of being a hard cut-off.
  2. For all of those trips, calculate the path they would take if biking, assuming no major preferences to avoid hills or stressful roads. (I'm considering changing this to avoid hills, or making it configurable.)
  3. Count the number of these potential biking trips that pass through every segment of a high-stress road. That's the red heatmap; tooltips show exact counts.
  4. Then, based on the current proposed bike network extensions, decide if each potential trip would be positively impacted by the new bike lanes. For now that's just a simple test -- does the path make use of at least 1 new bike lane on a previously stressful road?
  5. That's our guess for which driving trips will convert to biking, if this new infrastructure is built. From that, we can sum up the total mileage of the driving trip that we're converting, and calculate the annual CO2 savings, assuming a simple 404 grams per mile and assuming these trips happen 5 times a week, 52 weeks a year.

I took a video showing the end-to-end workflow of the tool so far, but the GIF is too big for github; I'll record a youtube video talking through it later. But the gist is this:

  1. Use the explore mode to just understand the city's current bike network. There are layers to explore elevation. Ideally I'll have time to add a layer showing historic collision data.
  2. Use the route tool to draw a particular route from A to B -- like my commute to work, for example. See what the biking route probably looks like, and score how much it's forced to use high-stress roads. Adjust preferences to avoid those stressful roads or to avoid hills, and see the trade-offs between the two.
  3. Armed with that knowledge, use the quick-sketch tool to expand the bike network.
  4. Return to the "individual test cases" in the routing tool. The user can add protected infrastructure along the ideal, flat route that they want to use.
  5. But now, the user can also evaluate their changes against a large set of "test cases" and see how many people might be convinced to stop driving, and the subsequent CO2 savings. There are many assumptions that go into that calculation, but the UI lets the user tune those parameters.

dabreegster avatar Sep 21 '21 00:09 dabreegster

This looks like a really useful development. The approach sounds sensible. Looking forward to giving it a spin!

Robinlovelace avatar Sep 21 '21 07:09 Robinlovelace

Screenshot from 2021-10-06 12-15-38 This is where we're at. The 3 colored bars work as a kind of "funnel".

  1. The first shows all the driving trips in the area
  2. After you specify the max time and hills somebody's willing to bike, we filter down and show how many of those trips would switch if stuff was safer
  3. After you make map edits, the third further filters that down to the trips who cross at least one improved section

I'm about to add another filter, either "what percentage of someone's trip has to be on low-stress roads for them to switch?" or "what's the total distance someone's willing to spend on their trip on high-stress roads?" (where they might choose to gingerly walk their bike on a sidewalk or something else). That'll further restrict the 3rd colored bar.

My question is, should we express that new safety requirements filter as a new thing in the third box? Or should it be part of the other 2 filters? If the latter, do we even need this 3-step funnel view? We could just have one bar, showing the total driving trips in the area, then the final results based on all the filters. Originally splitting it up was meant to show gradual barriers to mode shift (first just intrinsic to the geography, then based on stuff that could be fixed with new infra). But if all the filters are slideable, then the user can explore this themselves.

dabreegster avatar Oct 06 '21 19:10 dabreegster

I think would great for the user to be able to explore different scenarios of behaviour (e.g. max % people willing to cycle, which could be cultural and slow to change) and willingness to cycle on busy infrastructure. 2D plots showing 'distance decay', 'hilliness decay', 'business decay' and other variables could help people understand what moving these slides does, perhaps by moving things inside a 2D plotting window. Sketchy idea below, thinking you could represent each of those variables (max elevation gain, max distance) inside the graph and have linear or curved 'ramp up' and 'ramp down' lines showing that it's not about hard limits but %s dropping...

image

Robinlovelace avatar Oct 06 '21 20:10 Robinlovelace

The thing is that the links between the different factors are not yet know. I'm confident that for distance the 5 slides enable the user to capture most possible variability, e.g. by shifting max distance (and right plateau) vertical dotted lines right to represent E-Bike adoption. Interactive sketch: https://excalidraw.com/#room=11d6a02f5445a16de228,DadvVTIbaYcHi4tsQG86KQ

Robinlovelace avatar Oct 06 '21 20:10 Robinlovelace

(In the above plots Distance could be switched for Time and the curves would be similar, I think time is more realistic but distance is easier to model due to high variability of cycling speeds.)

Robinlovelace avatar Oct 06 '21 20:10 Robinlovelace

moving things inside a 2D plotting window

I need to think about this a little more to guess how hard it'd be to implement. I think it's possibly not so bad. Plenty of ideas about requirements in this thread...

The thing is that the links between the different factors are not yet know.

My probability knowledge is super fuzzy right now. Would it be reasonable to start by assuming these 3 factors are independent, so we could multiply the probabilities? The logic would be something like:

for each candidate trip:
  calculate hilliness, distance, and stress
  p1 = plug hilliness into the appropriate plot
  p2 = ... distance
  p3 = ... stress
  final_probability = p1 * p2 * p3
  if random coinflip, weighted by final_probability is true:
    this trip will mode-shift

dabreegster avatar Oct 06 '21 20:10 dabreegster

Yes, that's reasonable. An actual working function, published in the R package pct, that calculates probability of cycling if citizens switch to Dutch likelihood of cycling is as follows:

uptake_pct_godutch = function(distance,
                              gradient,
                              alpha = -3.959 + 2.523,
                              d1 = -0.5963 - 0.07626,
                              d2 = 1.866,
                              d3 = 0.008050,
                              h1 = -0.2710,
                              i1 = 0.009394,
                              i2 = -0.05135,
                              verbose = FALSE) {
  distance_gradient = check_distance_gradient(distance, gradient, verbose)
  distance = distance_gradient$distance
  gradient = distance_gradient$gradient
  logit_pcycle = alpha + (d1 * distance) +
    (d2 * sqrt(distance)) + (d3 * distance ^ 2) +
    (h1 * gradient) +
    (i1 * distance * gradient) +
    (i2 * sqrt(distance) * gradient)
  boot::inv.logit(logit_pcycle)
}

I don't actually like that function - too complex - functional forms based on the Beta distribution are neater and require fewer parameters, but the point is many different functions can be used. I think keeping it simple as you suggest is the way forward.

Robinlovelace avatar Oct 06 '21 20:10 Robinlovelace

Discussing this with Fred, Lucas and others today.

Robinlovelace avatar Apr 08 '22 14:04 Robinlovelace