Bohrium
robot
新建

空间站广场

论文
Notebooks
比赛
课程
Apps
我的主页
我的Notebooks
我的论文库
我的足迹

我的工作空间

任务
节点
文件
数据集
镜像
项目
数据库
公开
为什么你感觉公交车总是要等?Waiting Time Paradox
python
statistics
中文
bohrium
pythonstatistics中文bohrium
flyingdwarf
发布于 2023-07-10
赞 5
1
AI4SCUP-CNS-BBB(v1)

【初稿】为什么你感觉公交车总是要等?Waiting Time Paradox

©️ Copyright 2023 @ Authors
作者: liutao@dp.tech 📨
日期:2023-07-09
共享协议:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
快速开始:点击上方的 开始连接 按钮,选择 bohrium-notebook:2023-05-31镜像 和任意配置机型即可开始。

代码
文本

Open In Bohrium

代码
文本

想象如下情景:

  • 情景一:你在赶地铁,一顿操作后,成功在关门前一刻赶上了每6分钟一班的2号线。
  • 情景二:你想看看市容,于是决定坐公交,算好了时间走到公交站台,结果发现每10分钟一班的10路车晚点了,等着等着发现这趟车被取消了,结果你等了足足10分钟。
代码
文本

经常坐公交车的朋友也许和我有类似的感觉:坐公交车似乎总是要在站台上等一阵子。无论是因为提前太早到,还是因为卡着点到站台发现公交车早到了半分钟,刚刚扬长而去。有时候晚点的公交车会直接从手机导航APP上消失,于是“哦,公交车晚了3分钟”变成了“得,直接等下一班吧”。我第一次想这个问题的时候,以为公交车的时间间隔是经过深思熟虑后确定的,特别是考虑了当地的交通拥堵状况,最终确定了一个相较之下比较长的时间间隔(比如10分钟)从而让乘客更加容易接受“等待下一班”的可能性。毕竟嘛,来都来了。

代码
文本

其实仔细一想,为什么地铁和公交的乘坐会有这样的差异体验呢?地铁和公交有啥本质的区别吗?显然不是有些公交车能开窗,而地铁是个闷罐子哈哈。是的,你可能已经想到了:地铁线上没有堵车,因此(几乎)不会晚点或取消。因此说是每6分钟一班,实际(几乎)就是每6分钟一班。均等的时间间隔,依次沿着时间轴排开:

metro_schedule.png

代码
文本

而公交车,受限于地面交通的各种复杂情况(修路、堵车等等),其时刻表的不确定性更大。说是每10分钟一班,实际可能是前后两班隔了20分钟,或者是“你方唱罢我登场”般的前脚跟后脚: bus_schedule.png 甚至极端特殊情况下,整整一天只来了两班:早晨第一班车和晚上最后一班车。原因可能是有个活动开了一天,路给封了。

代码
文本

有个东西叫泊松过程(Poisson Process)

公交车的计划时刻表是经过某个固定时长后,预期会有一辆车,但实际有不确定性。换句话说,某件事在一段时间内以恒定的概率发生,但是实际发生的时刻是随机的。这个过程可以用统计学里的泊松过程来近似(泊松过程要求事件的发生是相互独立的,而严格来说前后两辆公交车的到达时刻并不完全独立)。在泊松过程里,如果此随机变量在一段区间内的每个点都以同等概率出现,比如这里的公交车到达时刻,那么这个变量连续两次所取的值之间的差则符合指数分布,比如这里的前后两辆公交车之间的间隔。接下来,我们一起通过模拟来看看这个公交车等待时间的问题。在文末笔者还准备了一个可交互式的界面,所以要看到最后哇 :)

代码
文本
[1]
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import poisson
代码
文本
[2]
num_bus = 1000000 # 某短时间内(一小时或一天等),到达此站台的公交车数量
tau = 10 # 公交时刻表上显示的公交车到达间隔(分钟),即平均间隔时间
# 在这个模型里,我们可以说:经过 `num_bus` * `tau`分钟以后,全部 `num_bus`辆公交车都到达过此站台。

# 但公交车实际上并不总能按时到达,而是有随机性
rand = np.random.RandomState(42) # 宇宙的终极答案是42 ;)

# 假设公交车的到达时刻是一个随机变量,且符合均匀分布(uniform distribution)。
# 这个假设意味着我们假设公交车在任何时刻到达的概率都相同。
bus_arrival_time = num_bus * tau * np.sort(rand.rand(num_bus))
代码
文本
[3]
# 实际上前后两辆公交车的到达间隔为:
bus_intervals = np.diff(bus_arrival_time)

# 小检查,看看模型给出的平均到达间隔是否等于模型参数 tau。如果不相等,那么模型有问题。
print('Sanity check: the following two values should be close: \n')
print('(1) Time interval between scheduled bus arrivals (tau): {:.2f} minutes'.format(tau))
print('(2) Average time interval between two bus arrivals from simulation: {:.2f} minutes \n'.format(bus_intervals.mean()))
Sanity check: the following two values should be close: 

(1) Time interval between scheduled bus arrivals (tau): 10.00 minutes
(2) Average time interval between two bus arrivals from simulation: 10.00 minutes 

代码
文本

模型给出的平均到达间隔和模型参数相等,耶,可以继续下去。接下来,我们将在公交车的实际到达时刻下,模拟乘客们的等待时长。

代码
文本
[4]
# 来到此站台的乘客数量
num_ppl = 10000

# 假设他们也是随机到达此站台,那么他们的到达时刻也可以用均匀分布模拟
ppl_arrival_time = num_bus * tau * np.sort(rand.rand(num_ppl))
代码
文本
[5]
# 对每个乘客,根据他们到达站台的时刻和公交车实际到达站台的时刻表,找出他们乘坐的公交车序号bus_indx
bus_indx = np.searchsorted(bus_arrival_time, ppl_arrival_time, side='left')
# `left`: 假设卡着点赶到站台的乘客刚好能上车,不需要等下一班

# 有些不幸运的乘客错过了最后一班车
unlucky_ppl = np.where(bus_indx == bus_arrival_time.shape[0])[0]

# 剩下的乘客们等待了一段时间后都上了车
num_lucky_ppl = num_ppl - len(unlucky_ppl)
waiting_time = bus_arrival_time[bus_indx[:num_lucky_ppl]] - ppl_arrival_time[:num_lucky_ppl]
代码
文本

有了上了车的乘客们的等待时间waiting time,我们可以算出全部乘客的平均等待时间

代码
文本
[6]
print('Average waiting time for all passengers: {:.2f} minutes'.format(waiting_time.mean()))
Average waiting time for all passengers: 10.15 minutes
代码
文本

是的,可以发现,乘客们的平均等待时间非常接近公交车时刻表上显示的公交车到达间隔(tau

代码
文本
[7]
# 画些图可视化我们的模拟数据
plt.rcParams["figure.dpi"] = 120
%matplotlib inline

plt.hist(bus_intervals, bins=50, density=True, edgecolor='k')
plt.axvline(bus_intervals.mean(), color='black', linestyle='dashed', label=r'mean (i.e., $\tau$)')

plt.xlabel('Interval between bus arrivals (minutes)')
plt.ylabel('Probability density')
plt.legend()
plt.show()
代码
文本

上图是公交车的实际到达间隔。如泊松过程描述的一样,公交车的实际到达间隔近似指数分布。这里可以细说一下影响乘客平均等待时间的“两股力量”:

  1. 如果前后两班车间隔了很远,那么车站上的积累的乘客很多(不考虑上下班高峰期,乘客们被假设在不同时刻以同等概率出现),而且他们都等了很长时间。不过出现这样长间隔的概率很小(如上图)。因此他们的“长”等待时间不会过分影响”平均等待时间“。
  2. 相反的,如果前后两班车间隔很短,那么每个在此间隔内上车的乘客都只等了一点点时间。但是这样的幸运乘客并不多,因为此间隔很短。因此他们的“短”等待时间也不会过分影响”平均等待时间“。

最终的平均等待时间是“公交车到达间隔”和“出现此间隔的概率”两股力量博弈的结果。

代码
文本
[8]
plt.rcParams["figure.dpi"] = 120
%matplotlib inline
plt.plot(waiting_time)
plt.xlabel('Passenger index')
plt.ylabel('Waiting time (min)')
plt.show()
代码
文本

我们可以看到,不同的乘客等待了不同时长,有些乘客等了很久很久,超过了一小时。这是因为他们不幸运地碰上了两班相隔很远公交车。

代码
文本

根据指数函数的公式,我们可以算出平均等待时间。巧合的是,平均等待时间和平均到达时间间隔是一样的 :)这意味着,如果你经常坐那趟公交车,你预计需要等10分钟,无论你是提前很多去,还是卡着点去。

所以下次如果赶时间,并且出于某些原因打算坐公交 ,比如看市容,建议不要极限操作 :) 当然,笔者觉得最好的办法是有B方案,比如如果公交车没来,那就去附近的地铁站吧,市容可以改天看。再说了,都在赶时间了,还要什么自行车…不,还看什么市容 ;)

代码
文本

最后的最后,试试这个可交互的界面的吧!

代码
文本
[1]
def simulate_wait_time(num_bus_textbox,
tau_textbox,
num_ppl_textbox,
nbins_bus_textbox,
nbins_wait_textbox):

num_bus = num_bus_textbox.value
tau = tau_textbox.value
num_ppl = num_ppl_textbox.value
nbins_bus = nbins_bus_textbox.value
nbins_wait = nbins_wait_textbox.value
# num_bus = 10000
# # average time in minutes between two bus arrivals
# tau = 10

rand = np.random.RandomState(42) # the answer ;)

# at the end of the day, all `num_bus` buses arrive and the total time is num_bus * tau
# the actual arrival times can be modeled by a uniform distribution among the entire time span
# rand gives a uniform distribution over [0, 1)
bus_arrival_time = num_bus * tau * np.sort(rand.rand(num_bus))

# actual interval between two arrivals
bus_intervals = np.diff(bus_arrival_time)

# # sanity check
# print('Sanity check: the following two values should be close: \n')
# print('(1) Time interval between scheduled bus arrivals (tau): {:.2f} minutes'.format(tau))
# print('(2) Average time interval between two bus arrivals from simulation: {:.2f} minutes \n'.format(bus_intervals.mean()))


# # simulate passengers' waiting time based on the actual arrival time `bus_arrival_time`
# num_ppl = 100000
# randomly arrive at the bus stop throughout the day (num_bus * tau)
ppl_arrival_time = num_bus * tau * np.sort(rand.rand(num_ppl))


# find the arrival time of the bus for each passenger
bus_indx = np.searchsorted(bus_arrival_time, ppl_arrival_time, side='left') # `left`: assumes ppl can get on the bus when they arrive simultaneously as the bus
# some ppl missed the last bus
unlucky_ppl = np.where(bus_indx == bus_arrival_time.shape[0])[0]
num_lucky_ppl = num_ppl - len(unlucky_ppl)
waiting_time = bus_arrival_time[bus_indx[:num_lucky_ppl]] - ppl_arrival_time[:num_lucky_ppl]
print('Average waiting time from all the passengers: {:.2f} minutes \n'.format(waiting_time.mean()))

# plot
# plt.clf()
fig, ax = plt.subplots(1,2,figsize=(12,4))
# nbins_bus = 50
# nbins_wait = 50

ax[0].hist(bus_intervals, bins=nbins_bus, density=True, edgecolor='k')
ax[0].axvline(bus_intervals.mean(), color='black', linestyle='dashed', label='mean')

ax[0].set_xlabel('Interval between bus arrivals (minutes)')
ax[0].set_ylabel('Probability density')
ax[0].legend()

ax[1].hist(waiting_time, bins=nbins_wait, density=True, edgecolor='k')
ax[1].axvline(waiting_time.mean(), color='black', linestyle='dashed', label='mean')

ax[1].set_xlabel('Waiting time (minutes)')
ax[1].set_ylabel('Probability density')
ax[1].legend()
plt.show()
代码
文本
[2]
# !pip install ipywidgets -q # quiet
import ipywidgets as widgets
from IPython.display import display
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import poisson
from IPython.display import clear_output

num_bus_textbox = widgets.IntText(value=10000, description='Num of bus:', disabled=False)
tau_textbox = widgets.FloatText(value=10.0, description=r'tau (min):', disabled=False)
num_ppl_textbox = widgets.IntText(value=10000, description='Num of ppl:', disabled=False)
nbins_bus_textbox = widgets.IntText(value=50, description='nbins_bus:', disabled=False)
nbins_wait_textbox = widgets.IntText(value=50, description='nbins_wait:', disabled=False)

display(num_bus_textbox)
display(tau_textbox)
display(num_ppl_textbox)
display(nbins_bus_textbox)
display(nbins_wait_textbox)

button = widgets.Button(description="Run!")
output = widgets.Output()

def on_button_clicked(b):
# Display the message within the output widget.
with output:
clear_output(wait=True)
print("Running the code...")
simulate_wait_time(num_bus_textbox,
tau_textbox,
num_ppl_textbox,
nbins_bus_textbox,
nbins_wait_textbox)
button.on_click(on_button_clicked)
display(button, output)
代码
文本
[ ]
# 这里的交互式界面这里没有正常显示,但当我在我的文件中心编辑notebook的时候是可以呈现的
代码
文本

更多阅读

  1. 泊松过程:https://zhuanlan.zhihu.com/p/146726798
代码
文本
python
statistics
中文
bohrium
pythonstatistics中文bohrium
已赞5
本文被以下合集收录
1
Be quiet,please
更新于 2024-06-06
2 篇0 人关注
推荐阅读
公开
12. 时间型操作
Pandas
Pandas
xuxh@dp.tech
更新于 2024-08-06
公开
Uni-ELF APP Hands-on
Uni-ELF
Uni-ELF
Letian
更新于 2024-08-14
3 赞
评论
 # 【初稿】为什么你感觉公交车总是要等?...

Roger

2023-07-10
这个题目好有趣

flyingdwarf

作者
2023-07-10
嘿嘿谢谢~ 《经常等不靠谱的公交车有感》

MileAway

2023-07-10
回复 Roger +1
评论
 ## 想象如下情景: - 情景一:你...

镜影映照

2023-07-19
有没有可能让读者模拟一下等公交的时间,达到一种类似算命的效果。同时读者也可以自己统计一下,自己平均需要等多久。

flyingdwarf

作者
2023-07-19
这是个挺好的主意,“算命”哈哈。下面的代码实例里的确统计了每个乘客的等待时间,并且用histogram可视化,也因此设置了个`nbins_wait`的输入来控制histogram的形状

镜影映照

2023-07-19
回复 flyingdwarf 咦,直方图在是在没有显示出来的交互部分吗
评论
 ## 有个东西叫泊松过程(Poisson...

深势科技-冯宇辰

2023-07-17
要是能够给出泊松过程的公式就更好了。

镜影映照

2023-07-19
借楼问一下,为什么这个近似是合理的

flyingdwarf

作者
2023-07-19
好,我这里再扩充一下

flyingdwarf

作者
2023-07-19
回复 镜影映照 如果考虑实际情况,每辆公交车到达同一个站台的时间是有相互影响的,原因之一是公交车司机会听从公交中心的调配。这里作为问题简化的一部分,假设“不同车次到达站台”是独立的。如果有兴趣去掉这个假设进一步建模,欢迎讨论或私戳liutao@dp.tech!
展开

镜影映照

2023-07-19
sorry,我没说清楚,我的意思是,为什么等公交车的过程可以用泊松过程来近似。现实生活当然很复杂,我们需要用各种简单的模型去近似,关键在于这种近似为什么是合理的,还是尝试了各种随机过程去近似等公交车,然后发现泊松过程恰好比较对得上。
展开
评论
 # 画些图可视化我们的模拟数据 plt....

深势科技-冯宇辰

2023-07-17
能更加详细的解释一下为什么公交车的实际到达间隔接近指数分布吗?我之前看的泊松过程都是用来讨论某一时间发生的次数的分布的。

flyingdwarf

作者
2023-07-19
这里我觉得需要再琢磨一下,尤其是模型的假设部分。我自己学的时候也对“指数分布”这个结果有些疑惑。如果是完全符合指数分布,那么”间隔时间=0“的概率密度最大,这个有些反直觉。可以一起讨论!
评论