李雷、小蒋、钱天培编译

公交车总迟到?你大概掉进了“等待时间悖论”

如果你经常坐公交车,相信下面这一场景对你绝不陌生。

你到了车站,准备搭乘声称每10分钟一班的公交车。你盯着你的手表留意着时间,结果公交车终于在11分钟后到来。

这时你不由得感叹:为什么今天运气这么差!

想想也是。如果公交车每10分钟一班,而你到达的时间是随机的,那么你的平均等待时间难道不是5分钟嘛?

但实际上,等待公交车的时间似乎永远要比你预估的久。

究竟是你错了?还是公交运营系统出了问题?

事实证明,在一些合理的假设下,你可以得出一个惊人的结论:

在等待平均10分钟一班的公交车时,你的平均等待时间将为10分钟。

这就叫等待时间悖论。

等待时间悖论

如果公交车精确每10分钟来一辆,那么你的平均等待时间就是这个间隔的一半:5分钟。

可是,如果我们给这个10分钟加上一点随机成分呢?

这时,等待时间悖论就出现了。

等待时间悖是检验悖论的一种。那么,什么是检验悖论呢?

简言之,只要观察量的概率与观察量有关,就会出现检验悖论。比如说,我们做了一个调查大学生班级平均人数的调查。虽然学校确实保证每班平均有30名学生,但实际调查下来的平均班级规模通常会大得多。

原因是,较大的班级中就有更多的学生,因此在计算学生的平均体验时,你会对大班进行过度地抽样。极端得讲,如果有一个班一个学生也没有,那你压根不会抽样到这个班级的学生。

对于通常10分钟一班的公交线路,有时两班车的间隔会超过10分钟,有时则短点。如果你在随机时间到达,那你会有更多机会遇到更长的等待间隔,而不是较短的。

因此,乘客所经历的平均等待时间间隔将比公交车之间的平均到达时间间隔更长,因为较长的间隔是被过度采样了的。

但等待时间悖论提出了一个比这更震撼的主张。

当两班车的平均间隔是N分钟时,搭乘者所经历的平均等待时间也是N分钟,而非N/2分钟。

这是真的吗?

模拟等待时间

为了证明等待时间悖论的合理性,让我们首先模拟平均每10分钟到达一班的公交车流。

我们将模拟大量的公交车到达的情况:100万辆(或大约19年中全天不间断的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()

输出:

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()

输出:

10.001584206227317

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

深入挖掘:概率和泊松过程

我们如何理解这一现象呢?

从本质上说,这是检验悖论的一个例子,其中观察值的概率与观察值本身有关。 让我们用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分钟。这看起来非常像指数分布,而且并非偶然:我们将公交车的到达时间模拟为均匀随机数,这非常接近于泊松过程,对于这样的过程,可以证明到达之间的间隔分布是呈指数分布的。

注:实际上,在区间Nτ内均匀采样N个点,点之间的间隔T遵循β分布:T /(Nτ)〜Bet [1,N],当N很大的时候这个极限趋于T~Exp [1 /τ]。

区间的指数分布意味着到达时间遵循泊松过程

通过再次检查这个推断,我们可以确认它与泊松过程的另一个属性的相匹配:在固定时间范围内到达公交的数量将是泊松分布的。让我们将模拟到达的时间按小时分桶检查一下:

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分钟:对泊松过程来说,你花费在等待的时间没用。

实际的等待时间

如果通过泊松过程确实描述了真实世界的公交到达时间,上述分析是正确的,但事实真的如此吗?

为了确定等待时间悖论是否描述了现实情况,我们深入研究了一些可供下载的数据:arrival_times.csv(3MB的CSV文件)

https://gist.githubusercontent.com/jakevdp/82409002fcc5142a2add0168c274a869/raw/1bbabf78333306dbc45b9f33662500957b2b6dc3/arrival_times.csv

该数据集包含2016年第二季度记录的西雅图市中心3rd & Pike公交站的西雅图Rapid Ride  C、D、E线的预定和实际到达时间。

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

我特意选择Rapid Ride路线的数据是因为,在一天的大部分时间里,公交车的间隔很规律,通常在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()

公交车晚了多少?

该表中主要有六个不同的数据集: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');

你可能会认为公交车每次在行程开始时与其时间表更接近,并且在快结束时有更多的差异,这在数据中得到了证实:南行(southbound)C线和北行(northbound) D线、E线都在各自路线的起点接近时间表,而其反方向在终点时更接近。

预定和观察到的到达时间间隔

接下来让我们来看看这六条路线观察和预计的到达时间间隔。我们首先使用Pandas 的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线的预定间隔为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()

使用这些清理过的数据,我们可以绘制不同路线、方向和到达频率的“实际”到达间隔的分布:

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的南行(southbound),D / E的北行(northbound)),以及在路线结束附近有更大的标准差。

即使不经过统计测试,我们也可以清楚地看到,实际的到达时间间隔肯定不是指数分布的,因而等待时间悖论所依赖的基本假设并不成立。

我们可以利用上面使用的等待时间模拟功能来找到每条公交路线、方向和时间表的平均等待时间:

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()))

输出:

平均等待时间可能比预定时间间隔的一半长上一两分钟,但不等于等待时间悖论所暗示的预定时间间隔。换句话说,检验悖论得到了证实,但等待时间悖论似乎与现实不符。

结论

等待时间悖论是个非常有趣的现象。它涵盖了模拟、概率以及统计假设与现实的比较。

虽然我们确认了,现实世界的公交线路确实遵循了一些版本的检验悖论,但上面的分析非常明确地显示,等待时间悖论背后的核心假设(公交车的到达时间遵循泊松过程)并不是很有根据。

回想起来,这也并不令人惊讶:泊松过程是一个无记忆过程,它假设到达的概率完全独立于自上次到达的时间。实际上,一个运行良好的公交系统将有一个有意安排的时间表,用以避免这种行为:公交车不会在一天中的随机时间开始他们的路线,而是按照选择能够最佳服务公众的时间表开始他们的路线。

这里更大的教训是,你应该谨慎对待任何数据分析工作的假设。泊松过程可以良好地描述到达时间的数据 – 但只是在某些特定情况下。

仅仅因为一种类型的数据看起来像另一种类型的数据,并不能推导出对一种数据有效的假设必然对另一种有效。

通常那些看似正确的假设可能会导致与现实不符的结论。

最后,你可以在这里下载本文全部代码👇http://jakevdp.github.io/downloads/notebooks/WaitingTimeParadox.ipynb

相关报道:http://jakevdp.github.io/blog/2018/09/13/waiting-time-paradox/?utm_source=mybridge&utm_medium=blog&utm_campaign=read_more

大数据文摘
大数据文摘

秉承“普及数据思维,传播数据文化,助⼒产业发展”的企业⽂化,我们专注于数据领域的资讯、案例、技术,形成了“媒体+教育+⼈才服务”的良性⽣态,致⼒于打造精准数据科学社区。

理论
31
相关数据
数据分析技术

数据分析是一类统计方法,其主要特点是多维性和描述性。有些几何方法有助于揭示不同的数据之间存在的关系,并绘制出统计信息图,以更简洁的解释这些数据中包含的主要信息。其他一些用于收集数据,以便弄清哪些是同质的,从而更好地了解数据。 数据分析可以处理大量数据,并确定这些数据最有用的部分。

高斯分布技术

正态分布是一个非常常见的连续概率分布。由于中心极限定理(Central Limit Theorem)的广泛应用,正态分布在统计学上非常重要。中心极限定理表明,由一组独立同分布,并且具有有限的数学期望和方差的随机变量X1,X2,X3,...Xn构成的平均随机变量Y近似的服从正态分布当n趋近于无穷。另外众多物理计量是由许多独立随机过程的和构成,因而往往也具有正态分布。

概率分布技术

概率分布(probability distribution)或简称分布,是概率论的一个概念。广义地,它指称随机变量的概率性质--当我们说概率空间中的两个随机变量具有同样的分布(或同分布)时,我们是无法用概率来区别它们的。

分桶技术

将一个特征(通常是连续特征)转换成多个二元特征(称为桶或箱),通常是根据值区间进行转换。例如,您可以将温度区间分割为离散分箱,而不是将温度表示成单个连续的浮点特征。假设温度数据可精确到小数点后一位,则可以将介于 0.0 到 15.0 度之间的所有温度都归入一个分箱,将介于 15.1 到 30.0 度之间的所有温度归入第二个分箱,并将介于 30.1 到 50.0 度之间的所有温度归入第三个分箱。

泊松过程技术

Poisson过程是以法国数学家泊松(1781 - 1840)的名字命名的。泊松过程是随机过程的一种,是以事件的发生时间来定义的。

堆叠技术

堆叠泛化是一种用于最小化一个或多个泛化器的泛化误差率的方法。它通过推导泛化器相对于所提供的学习集的偏差来发挥其作用。这个推导的过程包括:在第二层中将第一层的原始泛化器对部分学习集的猜测进行泛化,以及尝试对学习集的剩余部分进行猜测,并且输出正确的结果。当与多个泛化器一起使用时,堆叠泛化可以被看作是一个交叉验证的复杂版本,利用比交叉验证更为复杂的策略来组合各个泛化器。当与单个泛化器一起使用时,堆叠泛化是一种用于估计(然后纠正)泛化器的错误的方法,该泛化器已经在特定学习集上进行了训练并被询问了特定问题。

保密・CV工程师
还是有意思啊