Python应用分析为什么我等的公交车总是迟到&源码&数据

版权声明:表明出处,欢迎转载 https://blog.csdn.net/cjl503427235/article/details/84933132

如果大家和我一样经常乘坐公交车上下班,你可能会熟悉以下情况:

       你到了公交车站,准备上车:每10分钟就有一趟公交车到了。你瞥了一眼手表,记下了时间……当11分钟后公交车终于来了,你想知道为什么你总是看起来那么不幸?!每次都要等个整点。

你可能会天真地认为,如果公共汽车每10分钟来一班,而你是随机到达的,那么平均等待时间大概是5分钟左右。但实际上,公共汽车并不完全准时到达,所以你可能要等更长的时间。事实证明,在一些合理的假设下,你可以得出一个惊人的结论:

        当你等每10分钟一辆的公交车时,你的平均等待时间是10分钟。这就是所谓的等待时间悖论

我以前就遇到过这种想法,总是怀疑它是否真的是真的……这些“合理的假设”在多大程度上符合现实?这篇文章将从模拟和概率论证的角度来探讨等待时间悖论,然后看一些来自西雅图市的真实巴士到达时间数据,希望能彻底解决这个悖论。

目录:

  1. 检验悖论

  2. 模拟等待时间

  3. 挖掘更深层次的问题:概率&泊松过程( Poisson Processes)

  4. 选择p(T)

  5. 实际等待时间

  6. 数据清理

  7. 公共汽车多晚?

  8. 预定和观察的到达间隔

  9. 构建统一的时间表

  10. 结语

#检验悖论

如果公共汽车每十分钟准时到达,你的平均等待时间将是这个间隔的一半:5分钟。从本质上说,很容易说服您自己,为这些到达添加一些变化将使平均等待时间更长一些,我们将在这里看到。

等待时间悖论是一种更为普遍的现象——检查悖论的一个特例,艾伦•唐尼(Allen Downey)在这篇发人深省的文章中详细讨论了这一现象:检查悖论无处不在。

简而言之,当观察到一个量的概率与被观察到的量相关时,就会出现检验悖论。Allen举了一个调查大学生班级平均人数的例子。虽然学校可能会如实宣传每班平均30名学生,但学生所看到的平均班级规模可能(通常也会)大得多。原因是(当然)大班的学生更多,所以在计算学生的平均数量时,您会过度抽样大班样本。

在名义上10分钟的公交线路上,有时到站时间会超过10分钟,有时会更短,如果你是随机到达的,你会有更多的机会遇到更长的间隔,而不是更短的间隔。因此,乘客所经历的平均时间跨度将比公共汽车之间的平均时间跨度更长,这是有道理的,因为更长的时间跨度被过度采样。

但是等待时间悖论提出了一个更有力的论断:当到达的平均时间间隔为N分钟时,乘客体验的平均时间间隔为2N分钟。这可能是真的吗?

#模拟等待时间

为了让自己相信等待时间悖论是合理的,让我们从模拟平均10分钟到达的一列公共汽车开始。为了数字的准确性,我们将模拟大量到达的巴士:一百万辆巴士(或大约19年的24小时10分钟的车头):

import numpy as np

N = 1000000  # number of buses
tau = 10  # average minutes between arrivals

rand = np.random.RandomState(42)  # universal random seed
bus_arrival_times = N * tau * np.sort(rand.rand(N))

为了确认我们做的是正确的,我们检查一下均值区间是否接近τ=10:

intervals = np.diff(bus_arrival_times)
intervals.mean()
out :9.9999879601518398

通过对这些公交到站的模拟,我们现在可以模拟大量乘客在这段时间内到达公交车站,并计算出他们每个人所经历的等待时间。让我们把它封装到一个函数中,以便以后使用:

def simulate_wait_times(arrival_times,
                        rseed=8675309,  # Jenny's random seed
                        n_passengers=1000000):
    rand = np.random.RandomState(rseed)
    
    arrival_times = np.asarray(arrival_times)
    passenger_times = arrival_times.max() * rand.rand(n_passengers)

    # find the index of the next bus for each simulated passenger
    i = np.searchsorted(arrival_times, passenger_times, side='right')

    return arrival_times[i] - passenger_times

然后我们可以模拟一些等待时间并计算平均值:

wait_times = simulate_wait_times(bus_arrival_times)
wait_times.mean()

out:10.001584206227317

平均等待时间也接近10分钟,正如等待时间悖论中所预测的那样。

#挖掘更深层次的问题:概率&泊松过程( Poisson Processes)

我们怎么能理解这里发生了什么?

从本质上来说,观察到一个值的概率与该值本身有关是检验悖论的一个例子。我们用p(T)表示公共汽车到站时T在公共汽车之间的分布:

在上面的模拟中,我们选择了E[T]=τ= 10分钟。

当一个人在一个随机的时间到达一个公交车站时,他所经历的时间间隔的概率会受到p(T)的影响,同时也会受到T本身的影响:时间间隔越长,乘客所经历的时间间隔的概率越大。

所以我们可以写出乘客体验到的到达时间分布:

比例常数来自于分布的正态化:

与上面相比,我们看到这个化简为

期望等待时间E[W]将是乘客体验到的期望间隔的一半,因此我们可以写:

可以用一种更容易理解的方式重写:

现在剩下的就是选择p(T)的形式然后计算积分。

#选择p(T)

有了以上的推理,p(T)的合理分布是什么?通过绘制到达时间间隔的直方图,我们可以得到模拟到达时间内的p(T)分布的图像:

%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn')

plt.hist(intervals, bins=np.arange(80), density=True)
plt.axvline(intervals.mean(), color='black', linestyle='dotted')
plt.xlabel('Interval between arrivals (minutes)')
plt.ylabel('Probability density');

这里的垂直虚线显示了大约10分钟的平均间隔。这看起来很像一个指数分布,这不是偶然的:我们模拟的公共汽车到达时间作为均匀的随机数非常接近泊松过程,对于这样一个过程,它可以显示到达间隔的分布是指数的。

(这里的垂直虚线显示了大约10分钟的平均间隔。这看起来很像一个指数分布,这不是偶然的:我们模拟的公共汽车到达时间作为均匀的随机数非常接近泊松过程,对于这样一个过程,它可以显示到达间隔的分布是指数的。)

区间的指数分布意味着到达时间遵循泊松过程。为了再次验证这个推理,我们可以确认它与泊松过程的另一个属性相匹配:在固定时间范围内到达的次数将是泊松分布的。让我们通过将模拟到达的车辆按小时分组来验证这一点:

from scipy.stats import poisson

# count the number of arrivals in 1-hour bins
binsize = 60
binned_arrivals = np.bincount((bus_arrival_times // binsize).astype(int))
x = np.arange(20)

# plot the results
plt.hist(binned_arrivals, bins=x - 0.5, density=True, alpha=0.5, label='simulation')
plt.plot(x, poisson(binsize / tau).pmf(x), 'ok', label='Poisson prediction')
plt.xlabel('Number of arrivals per hour')
plt.ylabel('frequency')
plt.legend();

经验值和理论值之间的密切匹配使我们相信我们的解释是正确的:对于大N,我们上面模拟的到达时间是用泊松过程很好地描述的,这意味着指数分布的到达间隔。

这意味着我们可以写出概率分布:

把这个结果代入上面的结果,我们发现一个人经历的平均等待时间是:

对于符合泊松过程的公共汽车到达,乘客的期望等待时间与到达之间的平均间隔相同。

对此的一种补充解释是:泊松过程是一个无记忆的过程,这意味着事件的历史对下一个事件的预期时间没有影响。所以当你到达车站时,到下一辆车的平均等待时间总是一样的:在我们的例子中,它是10分钟,这是不管从上一辆车到现在已经有多长时间了!同样的道理,你已经等待了多长时间并不重要:下一个到达目的地的预期时间总是正好是10分钟:对于泊松过程,你等待的时间不会得到“积分”。

#实际等待时间

如果真实的公共汽车到达是由泊松过程描述的,那么上面的描述是非常正确的,但它们是吗?

Seattle Transit Map

为了确定等待时间悖论是否描述了现实,我们可以深入研究一些数据,具体数据可以在微末找到下载地址,该数据集包含了西雅图市区第三和派克公交车站C、D和E号线的预定和实际到达时间,记录于2016年第二季度(感谢华盛顿州交通中心的Mark Hallenbeck提供的数据!)

import pandas as pd
df = pd.read_csv('arrival_times.csv')
df = df.dropna(axis=0, how='any')
df.head()

out[7]:

	OPD_DATE	VEHICLE_ID	RTE	DIR	TRIP_ID	STOP_ID	STOP_NAME	SCH_STOP_TM	ACT_STOP_TM
0	2016-03-26	6201	673	S	30908177	431	3RD AVE & PIKE ST (431)	01:11:57	01:13:19
1	2016-03-26	6201	673	S	30908033	431	3RD AVE & PIKE ST (431)	23:19:57	23:16:13
2	2016-03-26	6201	673	S	30908028	431	3RD AVE & PIKE ST (431)	21:19:57	21:18:46
3	2016-03-26	6201	673	S	30908019	431	3RD AVE & PIKE ST (431)	19:04:57	19:01:49
4	2016-03-26	6201	673	S	30908252	431	3RD AVE & PIKE ST (431)	16:42:57	16:42:39

我特别从快速乘车路线中获取数据的原因是,在一天的大部分时间里,公交车的时间间隔都在10到15分钟之间。

#数据清理

首先,让我们做一些数据清理,使它成为一个更容易使用的形式:

# combine date and time into a single timestamp
df['scheduled'] = pd.to_datetime(df['OPD_DATE'] + ' ' + df['SCH_STOP_TM'])
df['actual'] = pd.to_datetime(df['OPD_DATE'] + ' ' + df['ACT_STOP_TM'])

# if scheduled & actual span midnight, then the actual day needs to be adjusted
minute = np.timedelta64(1, 'm')
hour = 60 * minute
diff_hrs = (df['actual'] - df['scheduled']) / hour
df.loc[diff_hrs > 20, 'actual'] -= 24 * hour
df.loc[diff_hrs < -20, 'actual'] += 24 * hour
df['minutes_late'] = (df['actual'] - df['scheduled']) / minute

# map internal route codes to external route letters
df['route'] = df['RTE'].replace({673: 'C', 674: 'D', 675: 'E'}).astype('category')
df['direction'] = df['DIR'].replace({'N': 'northbound', 'S': 'southbound'}).astype('category')

# extract useful columns
df = df[['route', 'direction', 'scheduled', 'actual', 'minutes_late']].copy()

df.head()
	route	direction	scheduled	actual	minutes_late
0	C	southbound	2016-03-26 01:11:57	2016-03-26 01:13:19	1.366667
1	C	southbound	2016-03-26 23:19:57	2016-03-26 23:16:13	-3.733333
2	C	southbound	2016-03-26 21:19:57	2016-03-26 21:18:46	-1.183333
3	C	southbound	2016-03-26 19:04:57	2016-03-26 19:01:49	-3.133333
4	C	southbound	2016-03-26 16:42:57	2016-03-26 16:42:39	-0.300000

#公共汽车多晚?

在这个表中有6个不同的数据集:C、D和E线的南北方向。为了了解它们的特性,让我们绘制一个直方图,其中每一个的实际减去预定到达时间:

import seaborn as sns
g = sns.FacetGrid(df, row="direction", col="route")
g.map(plt.hist, "minutes_late", bins=np.arange(-10, 20))
g.set_titles('{col_name} {row_name}')
g.set_axis_labels('minutes late', 'number of buses');

你可能认为附近的公交车接近他们安排每个单程旅行,显示更多的开始传播接近尾声,这是数据证实:该漆C-line和北行的D和E线附近的开始各自的航线,在相反的方向,他们正趋于零。

#预定和观察的到达间隔

接下来让我们看一下这六条路线的观察到的和预定到港时间间隔。我们将从使用panda groupby功能来计算这些间隔开始:

def compute_headway(scheduled):
    minute = np.timedelta64(1, 'm')
    return scheduled.sort_values().diff() / minute

grouped = df.groupby(['route', 'direction'])
df['actual_interval'] = grouped['actual'].transform(compute_headway)
df['scheduled_interval'] = grouped['scheduled'].transform(compute_headway)
g = sns.FacetGrid(df.dropna(), row="direction", col="route")
g.map(plt.hist, "actual_interval", bins=np.arange(50) + 0.5)
g.set_titles('{col_name} {row_name}')
g.set_axis_labels('actual interval (minutes)', 'number of buses');

很明显,这些看起来不太像我们模型的指数分布,但这并没有告诉我们太多:分布可能受到非恒定预定到达间隔的影响。

让我们重复上面的图表,检查预定的而不是观察到的到达时间间隔:

g = sns.FacetGrid(df.dropna(), row="direction", col="route")
g.map(plt.hist, "scheduled_interval", bins=np.arange(20) - 0.5)
g.set_titles('{col_name} {row_name}')
g.set_axis_labels('scheduled interval (minutes)', 'frequency');

由此可见,公共汽车每周的到站时间各不相同,因此我们不能从原始到站时间分布来评价等待时间悖论的准确性。

#构建统一的时间表

即使预定的到达时间间隔不统一,也有一些特定的时间间隔有大量的到达:例如,有近2000辆北行E-line公交车,预定的时间间隔为10分钟。为了探究等待时间悖论是否适用,让我们将数据按行、方向和预定间隔分组,然后将这些类似的到达重新堆叠在一起,就好像它们是按顺序发生的一样。这应该保持原始数据的所有相关特征,同时使其更容易直接与等待时间悖论的预测进行比较。

def stack_sequence(data):
    # first, sort by scheduled time
    data = data.sort_values('scheduled')
    
    # re-stack data & recompute relevant quantities
    data['scheduled'] = data['scheduled_interval'].cumsum()
    data['actual'] = data['scheduled'] + data['minutes_late']
    data['actual_interval'] = data['actual'].sort_values().diff()
    return data

subset = df[df.scheduled_interval.isin([10, 12, 15])]
grouped = subset.groupby(['route', 'direction', 'scheduled_interval'])
sequenced = grouped.apply(stack_sequence).reset_index(drop=True)
sequenced.head()

out[13] :

	route	direction	scheduled	actual	minutes_late	actual_interval	scheduled_interval
0	C	northbound	10.0	12.400000	2.400000	NaN	10.0
1	C	northbound	20.0	27.150000	7.150000	0.183333	10.0
2	C	northbound	30.0	26.966667	-3.033333	14.566667	10.0
3	C	northbound	40.0	35.516667	-4.483333	8.366667	10.0
4	C	northbound	50.0	53.583333	3.583333	18.066667	10.0

利用这些清理后的数据,我们可以绘制出每条路线、每一个方向、每一个到达频率的“实际”到达间隔分布:

for route in ['C', 'D', 'E']:
    g = sns.FacetGrid(sequenced.query(f"route == '{route}'"),
                      row="direction", col="scheduled_interval")
    g.map(plt.hist, "actual_interval", bins=np.arange(40) + 0.5)
    g.set_titles('{row_name} ({col_name:.0f} min)')
    g.set_axis_labels('actual interval (min)', 'count')
    g.fig.set_size_inches(8, 4)
    g.fig.suptitle(f'{route} line', y=1.05, fontsize=14)

      

我们可以看到,对于每一行和每一个行程,观测到的到达间隔的分布几乎是高斯分布,在计划到达间隔附近达到峰值,在路线开始附近(C为南行,D/E为北行)的标准差较小,在接近终点时标准差较大。即使没有统计检验,我们也可以清楚地看到,实际到达时间间隔绝对不是指数分布的,这是等待时间悖论的基本假设。

我们可以利用上述的等待时间模拟函数,求出每条总线的平均等待时间、方向和调度:

grouped = sequenced.groupby(['route', 'direction', 'scheduled_interval'])
sims = grouped['actual'].apply(simulate_wait_times)
sims.apply(lambda times: "{0:.1f} +/- {1:.1f}".format(times.mean(), times.std()))
route  direction   scheduled_interval
C      northbound  10.0                  7.8 +/- 12.5
                   12.0                   7.4 +/- 5.7
                   15.0                   8.8 +/- 6.4
       southbound  10.0                   6.2 +/- 6.3
                   12.0                   6.8 +/- 5.2
                   15.0                   8.4 +/- 7.3
D      northbound  10.0                   6.1 +/- 7.1
                   12.0                   6.5 +/- 4.6
                   15.0                   7.9 +/- 5.3
       southbound  10.0                   6.7 +/- 5.3
                   12.0                   7.5 +/- 5.9
                   15.0                   8.8 +/- 6.5
E      northbound  10.0                   5.5 +/- 3.7
                   12.0                   6.5 +/- 4.3
                   15.0                   7.9 +/- 4.9
       southbound  10.0                   6.8 +/- 5.6
                   12.0                   7.3 +/- 5.2
                   15.0                   8.7 +/- 6.0
Name: actual, dtype: object

平均等待时间可能比预定间隔的一半长一两分钟,但并不像等待时间悖论所暗示的那样等于预定间隔。也就是说,检验悖论得到了证实,但等待时间悖论似乎与现实不符。

#结语

等待时间悖论一直是一个有趣的讨论起点,涉及模拟、概率和统计假设与现实的比较。虽然我们证实了现实中的公交线路确实遵循某种版本的检查悖论,但上述分析相当明确地表明,等待时间悖论背后的核心假设——公交的到达遵循泊松过程的统计数据——并没有充分的依据。

回想起来,这也许并不奇怪:泊松过程是一个无记忆的过程,它假定到达的概率完全独立于上一次到达之后的时间。在现实中,一个运行良好的公交系统会有意地安排时间表来避免这种行为:公交车不会在一天中的任意时间开始它们的路线,而是按照最适合乘坐公共交通工具的时间表开始它们的路线。

这里更重要的教训是,您应该小心为任何数据分析任务带来的假设。泊松过程有时是对到达时间数据的很好的描述。但仅仅因为一种数据听起来像另一种数据,并不意味着对其中一种有效的假设对另一种也一定有效。通常,表面上看起来正确的假设会导致与现实不符的结论。


引用的数据下载链接和源代码获取方式:扫码关注回复“20181209”

AI技术与生活

猜你喜欢

转载自blog.csdn.net/cjl503427235/article/details/84933132