編者按:在數據科學領域,值得閱讀的好書有很多,尤其是O‘Reilly出版的一系列動物封面英文書。其中一本“蜥蜴書”叫《Python數據科學指南》(Python Data Science Handbook),書中詳細介紹了Jupyter、Numpy、Pandas、數據可視化和scikit-learn模塊的具體使用方式,是新手入門機器學習的一條捷徑。近日,這本書的作者Jake VanderPlas寫了一篇博文,深入淺出地介紹了一個有趣的概念:等待時間悖論。
圖像來源:維基百科 許可:CC-BY-SA 3.0
經常乘坐公共交通工具的人可能都遇到過這種情況:
你到公交站臺等車,標牌顯示公交車的發車間隔是10分鐘一輛。你瞥了眼手表,記下時間……11分鐘后,公交車終于來了,你不由開始懊惱:為什么我老是這么倒霉!
已知公交車每隔10分鐘發一班,你到站臺是在某個隨機時間點(不借助實時公交APP),面對這種情況,很多人會想當然地覺得自己的平均等待時間應該是5分鐘。但實際上,5分鐘后,公交車沒來,這時你只能繼續等;10分鐘后,車子可能還是沒有來……在一些合理的數學假設下,你可以得出一個驚人結論:
當公交車的平均發車間隔是10分鐘時,乘客平均等公交車的時間也會是10分鐘。
這就是等待時間悖論。
那么這個悖論真實存在嗎?這些“合理的假設”究竟是只流于理論,還是同樣適用于現實?本文會以美國西雅圖市的真實公交車到達時間數據為例,從模擬和概率論證的角度探討這個問題。
檢驗悖論(Inspection Paradox)
如果每隔10分鐘一定有一班車到這個站臺,那我們的平均等待時間確實是這個間隔的一半:5分鐘。但是,如果10分鐘只是個平均值,乘客的平均等待時間其實會比5分鐘更長一些,這點不難理解。
等待時間悖論其實是檢驗悖論的一個特殊例子,后者在日常生活中更普遍。舉一個簡單例子,假設你正在調查某大學的班級規模,調查方法是隨機選一些學生問“你所在的班級有多少人”再計算平均值,最后你統計出的結果是平均56人。但是,全校的班級平均人數實際上只有36(以上數據來自普渡大學調查)。這不是說有人撒了謊,而是10人班級和100人班級被抽樣的概率不一樣,隨機抽樣會導致對人數較多的班級過度抽樣,使結果向人多的一方傾斜。
同理,在平均每隔10分鐘就有一班車到站臺的情況下,有時前后兩輛公交車的到達間隔會超過10分鐘,有時候不到10分鐘,如果你到站臺是個隨機時間點,你就有更大概率會遇到超過10分鐘的情況。所以乘客的平均等待時間更長是有道理的,因為較長間隔被過度采樣了。
但等待時間悖論提出了一個更令人“匪夷所思”的結論:當前后兩輛車的平均到站間隔是N分鐘時,乘客體驗到的公交車平均到站間隔是2N分鐘。這會是真的嗎?
模擬等待時間
為了證明等待時間悖論的結論是正確的,首先我們可以模擬一些公交車,它們的平均到站時間是10分鐘。已知樣本數量越大,結果越準確,我們設一共有100萬輛公交車。:
import numpy as np
N = 1000000# 公交車數量
tau = 10# 平均到站間隔
rand = np.random.RandomState(42) # 隨機種子
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的隨機種子
n_passengers=1000000):
rand = np.random.RandomState(rseed)
arrival_times = np.asarray(arrival_times)
passenger_times = arrival_times.max() * rand.rand(n_passengers)
# 為每個模擬乘客找到下一輛公交車
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)的分布:
%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分鐘。可以發現,上圖分布形狀很像指數分布,這并不是偶然:我們把公交車到站間隔模擬成均勻隨機數的做法十分類似泊松過程,而如果構成泊松過程,到站間隔的分布一定符合指數分布。
注:在我們的例子里,到站間隔分布只是近似指數分布。
如果到站間隔符合指數分布,它就遵循泊松過程——為了驗證這個推理,我們可以用另一個泊松過程的屬性來檢查:在固定時間范圍內,公交車到站次數的分布滿足泊松分布。我們可以在之前的模擬中查看每小時公交車的到站次數:
from scipy.stats import poisson
# 計算1小時內公交車到站次數
binsize = 60
binned_arrivals = np.bincount((bus_arrival_times // binsize).astype(int))
x = np.arange(20)
# 繪制結果
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();
如上圖所示,模擬次數分布(方柱)和泊松分布(黑點)幾乎一致?,F在理論、模擬實踐都支持這么一個事實:對于一個足夠大的N,公交車的到站間隔可以用泊松過程描述,到站間隔分布滿足指數分布。
這意味著我們可以把概率分布寫成:
把上式帶入之前的等式,可得每名乘客的平均等待時間是:
因此,如果公交車的到站間隔符合泊松過程,乘客的平均期望等待時間和公交車的平均到站間隔相同。
推斷這個結論的另一種補充方法是:泊松過程是一個無記憶過程,這意味事歷史事件與下一事件發生的預期時間無關。所以當你到達公交站時,你對下一班車的平均等待時間始終是一樣的:不管前一班車是什么時候來的,你平均都得等10分鐘。同理,無論你之前已經等了多久,乘客對下一班車的預期等待時間還是10分鐘。
現實中的等待時間
那么泊松過程能描述現實生活中的公交車到站時間嗎?
為了探討等待時間悖論和現實情況是否存在矛盾,我們可以用一些數據進行更深入的研究(arrival_times.csv,3MB CSV文件)。這個數據集包含2016年第二季度美國西雅圖3rd & Pike公交站的記錄,它一共有3條快速線:C、D和E,給出了每輛公交車的預定和實際到達時間。
import pandas as pd
df = pd.read_csv('arrival_times.csv')
df = df.dropna(axis=0, how='any')
df.head()
之所以選擇快速線,是因為在一天的大部分時間里,這幾路公交車的到站間隔都穩定在10-15分鐘之間。
數據清理
首先,讓我們簡單做一些數據清理,把數據集里的表格轉成更易于使用的形式:
# 把日期和時間組合成單個時間戳
df['scheduled'] = pd.to_datetime(df['OPD_DATE'] + ' ' + df['SCH_STOP_TM'])
df['actual'] = pd.to_datetime(df['OPD_DATE'] + ' ' + df['ACT_STOP_TM'])
# 如果公交車的預計到點和實際到點過了半夜,需要調整日期
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
# 內外部路徑映射
df['route'] = df['RTE'].replace({673: 'C', 674: 'D', 675: 'E'}).astype('category')
df['direction'] = df['DIR'].replace({'N': 'northbound', 'S': 'southbound'}).astype('category')
# 抓取有用的列
df = df[['route', 'direction', 'scheduled', 'actual', 'minutes_late']].copy()
df.head()
公交車晚點情況
數據集中有6組不同數據:南向行駛和北向行駛的3路公交車。為了感受它們的早到/遲到特點,我們可以用實際到達時間減去預期到達時間,繪制6幅公交車“晚點”情況圖:
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路公交車(圖四)、北向行駛的D路公交車(圖二)和北向行駛的E路公交車在剛開出時還很準時,到最后卻出現了晚點超過十幾分鐘的情況。
預計到點和實際到點
接著,我們來看看這6條線路的實際到站間隔,這可以用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)
如上圖所示,這三路公交車的到站間隔分布近似高斯分布:在預期到站間隔附近達到峰值,一開始標準偏差較小,越往后越大。所以很顯然,這和等待時間悖論的基石——指數分布相違背。
我們再用上面的數據計算每個公交路線、行駛方向和到站頻率的公交車的乘客平均等待時間:
grouped = sequenced.groupby(['route', 'direction', 'scheduled_interval'])
sims = grouped['actual'].apply(simulate_wait_times)
輸出:
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
平均等待時間可能比預期到站間隔的一半長一兩分鐘,但不是等待時間悖論所暗示的結果。換句話說,這個結果證實了檢驗悖論,而等待時間悖論似乎與現實不符。
最后的想法
等待時間悖論一直是一個有趣的論題,它涵蓋模擬、概率統計假設與現實的比較。雖然我們現在已經確認現實世界的公交線路確實遵循了一些檢驗悖論,但上述分析也非常明確地表明等待時間悖論背后的核心假設——公交車到站間隔遵循泊松過程有很大問題。
回想起來,這可能并不令人驚訝:泊松過程是一個無記憶過程,它假設公交車到站概率完全獨立于自上次到站以來的時間。但在現實中,一個運行良好的公交系統會設計合理的行車時間表,每輛公交車的出發時間都不是隨機的,它們要考慮乘客多少。
而由這個問題引出的更大教訓,是我們應該謹慎對待任何數據分析任務的假設。雖然泊松過程有時是對到站時間數據的良好描述,但我們不能僅僅因為一種類型的數據看起來和另一種類型的數據很像,就直接想當然地認為對這種數據有效的假設必然對另一種同樣有效??此普_的假設可能導致與現實不符的結論。
-
數據集
+關注
關注
4文章
1208瀏覽量
24701 -
數據可視化
+關注
關注
0文章
466瀏覽量
10259
原文標題:等待時間悖論:為什么我的公交車總是遲到?
文章出處:【微信號:jqr_AI,微信公眾號:論智】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論