八、隨機性
原文:Randomness
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
在前面的章節(jié)中,我們開發(fā)了深入描述數(shù)據所需的技能。 數(shù)據科學家也必須能夠理解隨機性。 例如,他們必須能夠隨機將個體分配到實驗組和對照組,然后試圖說明,觀察到的兩組結果之間的差異是否僅僅是由于隨機分配,或真正由于實驗所致。
在這一章中,我們開始分析隨機性。 首先,我們將使用 Python 進行隨機選擇。 在numpy中有一個叫做random的子模塊,它包含許多涉及隨機選擇的函數(shù)。 其中一個函數(shù)稱為choice。 它從一個數(shù)組中隨機選取一個項目,選擇任何項目都是等可能的。 函數(shù)調用是np.random.choice(array_name),其中array_name是要從中進行選擇的數(shù)組的名稱。
因此,下面的代碼以 50% 的幾率求值為treatment,50% 的機率為control。
two_groups = make_array('treatment', 'control')
np.random.choice(two_groups)
'treatment'
上面的代碼和我們迄今運行的所有其他代碼之間的巨大差異在于,上面的代碼并不總是返回相同的值。 它可以返回treatment或control,我們不會提前知道會選擇哪一個。 我們可以通過提供第二個參數(shù)來重復這個過程,它是重復這個過程的次數(shù)。
np.random.choice(two_groups, 10)
array(['treatment', 'control', 'treatment', 'control', 'control',
'treatment', 'treatment', 'control', 'control', 'control'],
dtype='<U9')
隨機事件的根本問題是它們是否發(fā)生。 例如:
- 個體是否被分配到實驗組?
- 賭徒是否會贏錢?
- 一個民意調查是否做出了準確的預測?
一旦事件發(fā)生,你可以對所有這些問題回答“是”或“否”。 在編程中,通常通過將語句標記為True或False來執(zhí)行此操作。 例如,如果個體被分配到實驗組,那么“個體被分配到實驗組”的陳述將是真的。 如果不是,那將是假的。
布爾值和比較
在 Python 中,布爾值(以邏輯學家 George Boole 命名)表示真值,并只有兩個可能的值:True和False。 無論問題是否涉及隨機性,布爾值通常都由比較運算符產生。 Python 包含了各種比較值的運算符。 例如,3大于1 + 1。
3 > 1 + 1
True
True表示比較是有效的;Python 已經證實了3和1 + 1的關系的這個簡單事實。 下面列出了一整套通用的比較運算符。
| 比較 | 運算符 | True 示例 | False 示例 |
|---|---|---|---|
| 小于 | < |
2 < 3 |
2 < 2 |
| 大于 | > |
3 > 2 |
3 > 3 |
| 小于等于 | <= |
2 <= 2 |
3 <= 2 |
| 大于等于 | >= |
3 >= 3 |
2 >= 3 |
| 等于 | == |
3 == 3 |
3 == 2 |
| 不等于 | != |
3 != 2 |
2 != 2 |
注意比較中的兩個等號==用于確定相等性。 這是必要的,因為 Python 已經使用=來表示名稱的賦值,我們之前看到過。 它不能將相同的符號用于不同的目的。 因此,如果你想檢查5是否等于10/2,那么你必須小心:5 = 10/2返回一個錯誤信息,因為 Python 假設你正試圖將表達式10/2的值賦給一個名稱,它是數(shù)字5。相反,你必須使用5 == 10/2,其計算結果為True。
5 = 10/2
File "<ipython-input-4-5c7d3e808777>", line 1
5 = 10/2
^
SyntaxError: can't assign to literal
5 == 10/2
True
一個表達式可以包含多個比較,并且它們都必須滿足,為了整個表達式為真。 例如,我們可以用下面的表達式表示1 + 1在1和3之間。
1 < 1 + 1 < 3
True
兩個數(shù)字的平均值總是在較小的數(shù)字和較大的數(shù)字之間。 我們用下面的數(shù)字x和y來表示這種關系。 你可以嘗試不同的x和y值來確認這種關系。
x = 12
y = 5
min(x, y) <= (x+y)/2 <= max(x, y)
True
字符串比較
字符串也可以比較,他們的順序是字典序。 較短的字符串小于以較短的字符串開頭的較長的字符串。
'Dog' > 'Catastrophe' > 'Cat'
我們回到隨機選擇。 回想一下由兩個元素組成的數(shù)組two_groups,treatment和control。 為了看一個隨機分配的個體是否去了實驗組,你可以使用比較:
np.random.choice(two_groups) == 'treatment'
False
和以前一樣,隨機選擇并不總是一樣的,所以比較的結果也不總是一樣的。 這取決于是選擇treatment還是control。 對于任何涉及隨機選擇的單元格,多次運行單元格來獲得結果的變化是一個好主意。
比較數(shù)組和值
回想一下,我們可以對數(shù)組中的很多數(shù)字執(zhí)行算術運算。 例如,make_array(0, 5, 2)*2等同于make_array(0, 10, 4)。 以類似的方式,如果我們比較一個數(shù)組和一個值,則數(shù)組的每個元素都與該值進行比較,并將比較結果求值為布爾值數(shù)組。
tosses = make_array('Tails', 'Heads', 'Tails', 'Heads', 'Heads')
tosses == 'Heads'
array([False, True, False, True, True], dtype=bool)
numpy方法count_nonzero計算數(shù)組的非零(即True)元素的數(shù)量。
np.count_nonzero(tosses == 'Heads')
3
條件語句
在許多情況下,行動和結果取決于所滿足的一組特定條件。例如,隨機對照試驗的個體如果被分配給實驗組,則接受實驗。賭徒如果贏了賭注就賺錢。
在本節(jié)中,我們將學習如何使用代碼來描述這種情況。條件語句是一個多行語句,它允許 Python 根據表達式的真值選擇不同的選項。雖然條件語句可以出現(xiàn)在任何地方,但它們通常出現(xiàn)在函數(shù)體內,以便根據參數(shù)值執(zhí)行可變的行為。
條件語句總是以if開頭,這是一行,后面跟著一個縮進的主體。只有當if后面的表達式(稱為if表達式)求值為真時,才會執(zhí)行主體。如果if表達式的計算結果為False,則跳過if的主體。
讓我們開始定義一個返回數(shù)字符號的函數(shù)。
def sign(x):
if x > 0:
return 'Positive'
sign(3)
'Positive'
如果輸入是正數(shù),則此函數(shù)返回正確的符號。 但是,如果輸入不是正數(shù),那么if表達式的計算結果為false,所以return語句被跳過,函數(shù)調用沒有值(為None)。
sign(-3)
所以,讓我們改進我們的函數(shù)來返回負數(shù),如果輸入是負數(shù)。 我們可以通過添加一個elif子句來實現(xiàn),其中elif是 Python 的else, if的縮寫。
def sign(x):
if x > 0:
return 'Positive'
elif x < 0:
return 'Negative'
現(xiàn)在當輸入為-3時,sign返回正確答案。
sign(-3)
'Negative'
那么如果輸入是0呢?為了處理這個情況,我們可以添加elif子句:
def sign(x):
if x > 0:
return 'Positive'
elif x < 0:
return 'Negative'
elif x == 0:
return 'Neither positive nor negative'
sign(0)
'Neither positive nor negative'
與之等價,我們可以用else子句替換最后的elif子句,只有前面的所有比較都是false,才會執(zhí)行它的正文。 也就是說,輸入值等于0的時候。
def sign(x):
if x > 0:
return 'Positive'
elif x < 0:
return 'Negative'
else:
return 'Neither positive nor negative'
sign(0)
'Neither positive nor negative'
一般形式
條件語句也可以有多個具有多個主體的子句,只有其中一個主體可以被執(zhí)行。 多子句的條件語句的一般格式如下所示。
if <if expression>:
<if body>
elif <elif expression 0>:
<elif body 0>
elif <elif expression 1>:
<elif body 1>
...
else:
<else body>
總是只有一個if子句,但是可以有任意數(shù)量的elif子句。 Python 將依次求解頭部的if和elif表達式,直到找到一個真值,然后執(zhí)行相應的主體。 else子句是可選的。 當提供else頭部時,只有在前面的子句的頭部表達式都不為真時才執(zhí)行else頭部。 else子句必須總是在最后(或根本沒有)。
示例:"另一個"
現(xiàn)在我們將使用條件語句來定義一個看似相當虛假和對立的函數(shù),但是在本章后面的章節(jié)中會變得方便。 它需要一個數(shù)組,包含兩個元素(例如,red和blue),以及另一個用于比較的元素。 如果該元素為red,則該函數(shù)返回blue。 如果元素是(例如)blue,則函數(shù)返回red。 這就是為什么我們要將函數(shù)稱為other_one。
def other_one(x, a_b):
"""Compare x with the two elements of a_b;
if it is equal to one of them, return the other one;
if it is not equal to either of them, return an error message.
"""
if x == a_b.item(0):
return a_b.item(1)
elif x == a_b.item(1):
return a_b.item(0)
else:
return 'The input is not valid.'
colors = make_array('red', 'blue')
other_one('red', colors)
'blue'
other_one('blue', colors)
'red'
other_one('potato', colors)
'The input is not valid.'
迭代
編程中經常出現(xiàn)這樣的情況,特別是在處理隨機性時,我們希望多次重復一個過程。 例如,要檢查np.random.choice是否實際上是隨機選取的,我們可能需要多次運行下面的單元格,以查看Heads是否以大約 50% 的幾率出現(xiàn)。
np.random.choice(make_array('Heads', 'Tails'))
'Heads'
我們可能希望重新運行代碼,帶有稍微不同的輸入或其他稍微不同的行為。 我們可以多次復制粘貼代碼,但是這很枯燥,容易出現(xiàn)拼寫錯誤,如果我們想要這樣做一千次或一百萬次,忘記它吧。
更自動化的解決方案是使用for語句遍歷序列的內容。 這被稱為迭代。 for語句以單詞for開頭,后面跟著一個名字,我們要把這個序列中的每個項目賦給它,后面跟著單詞in,最后以一個表達式結束,它求值為一個序列。 對于序列中的每個項目,for語句的縮進主體執(zhí)行一次。
for i in np.arange(3):
print(i)
0
1
2
想象一下,沒有for語句的情況下,完全實現(xiàn)for語句功能的代碼,這樣很有幫助。 (這被稱為循環(huán)展開。)for語句簡單地復制了內部的代碼,但是在每次迭代之前,它從給定的序列中將我們選擇的名稱賦為一個新的值。 例如,以下是上面循環(huán)的展開版本:
i = np.arange(3).item(0)
print(i)
i = np.arange(3).item(1)
print(i)
i = np.arange(3).item(2)
print(i)
0
1
2
譯者注:實際的實現(xiàn)方式不是這樣,但是效果一樣。這里不做深究。
請注意,我的名字是任意的,就像我們用=賦值的名字一樣。
在這里我們用一個更為現(xiàn)實的方式使用for語句:我們從數(shù)組中打印5個隨機選項。
coin = make_array('Heads', 'Tails')
for i in np.arange(5):
print(np.random.choice(make_array('Heads', 'Tails')))
Heads
Heads
Tails
Heads
Heads
在這種情況下,我們只執(zhí)行了幾次完全相同的(隨機)操作,所以我們for語句中的代碼實際上并不涉及到i。
擴展數(shù)組
雖然上面的for語句確實模擬了五次硬幣投擲的結果,但結果只是簡單地打印出來,并不是我們可以用來計算的形式。 因此,for語句的典型用法是創(chuàng)建一個結果數(shù)組,每次都擴展它。
numpy中的append方法可以幫助我們實現(xiàn)它。 調用np.append(array_name,value)將求出一個新的數(shù)組,它是由value擴展的array_name。在使用append時請記住,數(shù)組的所有條目必須具有相同的類型。
pets = make_array('Cat', 'Dog')
np.append(pets, 'Another Pet')
array(['Cat', 'Dog', 'Another Pet'],
dtype='<U11')
這會使pets數(shù)組保持不變。
pets
array(['Cat', 'Dog'],
dtype='<U3')
但是在擴展數(shù)組的時候,通常使用for循環(huán)來修改它很方便。 這通過將擴展后的數(shù)組賦給原始數(shù)組的相同名稱來實現(xiàn)。
pets = np.append(pets, 'Another Pet')
pets
array(['Cat', 'Dog', 'Another Pet'],
dtype='<U11')
示例:計算正面的數(shù)量
現(xiàn)在我們可以模擬一個硬幣的五次投擲,并把結果放入一個數(shù)組中。 我們將從創(chuàng)建一個空數(shù)組開始,然后附加每次投擲的結果。
coin = make_array('Heads', 'Tails')
tosses = make_array()
for i in np.arange(5):
tosses = np.append(tosses, np.random.choice(coin))
tosses
array(['Tails', 'Heads', 'Tails', 'Heads', 'Tails'],
dtype='<U32')
讓我們將for語句展開,重寫單元格。
coin = make_array('Heads', 'Tails')
tosses = make_array()
i = np.arange(5).item(0)
tosses = np.append(tosses, np.random.choice(coin))
i = np.arange(5).item(1)
tosses = np.append(tosses, np.random.choice(coin))
i = np.arange(5).item(2)
tosses = np.append(tosses, np.random.choice(coin))
i = np.arange(5).item(3)
tosses = np.append(tosses, np.random.choice(coin))
i = np.arange(5).item(4)
tosses = np.append(tosses, np.random.choice(coin))
tosses
array(['Heads', 'Heads', 'Tails', 'Tails', 'Heads'],
dtype='<U32')
通過將結果捕獲到數(shù)組中,我們自己有能力使用數(shù)組方法進行計算。 例如,我們可以使用np.count_nonzero來計算五次投擲中的正面數(shù)量。
np.count_nonzero(tosses == 'Heads')
2
迭代是一個強大的技術。 例如,通過為 1000 次投擲運行完全相同的代碼,而不是5次,我們可以計算1000次投擲的正面數(shù)量。
tosses = make_array()
for i in np.arange(1000):
tosses = np.append(tosses, np.random.choice(coin))
np.count_nonzero(tosses == 'Heads')
481
示例:100 次投擲中的正面數(shù)量
預測 100 次硬幣投擲中有 50 個正面是很自然的,或多或少。
但多少是“或多或少”呢? 獲得正好 50 個正面的幾率是多少? 像數(shù)據科學這樣的問題,不僅因為它們涉及隨機性的有趣方面,而且因為它們可以用于分析試驗,其中實驗和控制組的分配由硬幣的投擲決定。
在這個例子中,我們將模擬以下實驗的 10,000 次重復:
- 擲硬幣 100 次,記錄正面數(shù)量。
我們的結果的直方圖會讓我們了解有多少個正面。
作為一個預熱,請注意,np.random.choice接受可選的第二個參數(shù)來指定選擇的數(shù)量。 默認情況下,選擇使用替換來進行。 這里是一個硬幣 10 次投擲的模擬:
np.random.choice(coin, 10)
array(['Tails', 'Heads', 'Heads', 'Tails', 'Tails', 'Heads', 'Tails',
'Tails', 'Heads', 'Tails'],
dtype='<U5')
現(xiàn)在我們來研究 100 次投擲。 我們將首先創(chuàng)建一個名為heads的空數(shù)組。 然后,在每個一萬次重復中,我們會拋硬幣 100 次,計算正面的數(shù)量,并將其附加到heads上。
N = 10000
heads = make_array()
for i in np.arange(N):
tosses = np.random.choice(coin, 100)
heads = np.append(heads, np.count_nonzero(tosses == 'Heads'))
heads
array([ 46., 64., 59., ..., 56., 54., 56.])
讓我們將結果收集到表格中,并繪制直方圖:
results = Table().with_columns(
'Repetition', np.arange(1, N+1),
'Number of Heads', heads
)
results
| Repetition | Number of Heads |
|---|---|
| 1 | 46 |
| 2 | 64 |
| 3 | 59 |
| 4 | 57 |
| 5 | 54 |
| 6 | 47 |
| 7 | 45 |
| 8 | 50 |
| 9 | 44 |
| 10 | 57 |
(省略了 9990 行)
這里是數(shù)據的直方圖,桶的寬度為 1,中心為每個正面數(shù)量的值。
results.select('Number of Heads').hist(bins=np.arange(30.5, 69.6, 1))
https://gitee.com/wizardforcel/data8-textbook-zh/raw/master/img/8-1.png
毫不奇怪,直方圖看起來大約關于 50 個正面左右對稱。 50 處的條形的高度大約是每單位 8%。 由于每個條形的寬度都是 1 個單位,這就是說,8% 的重復正好產生了 50 個正面。 這不是一個很大的百分比,但是與其他數(shù)量的正面相比,這是最大的。
直方圖還顯示,在幾乎所有的重復中,100 次投擲的正面數(shù)量在 35 到 65 之間。事實上,大部分的重復產生 45 到 55 個正面數(shù)量。
理論上,正面數(shù)量可能在 0 到 100 之間,但模擬顯示可能值的范圍要小得多。
這是一個更普遍現(xiàn)象的例子,關于擲硬幣中的變化,我們將在后面看到。
Monty Hall 問題
多年來這個問題已經使許多人感到困惑,包括數(shù)學家在內。 讓我們看看我們是否可以解決。
這個設定來源于一個名為“讓我們做個交易”(Let's Make a Deal)的電視游戲節(jié)目。Monty Hall 在二十世紀六十年代主持了這個節(jié)目,從此產生了一些副產品。 這個節(jié)目令人興奮的一部分是,雖然參賽者有機會贏得大獎,但他們可能最終會選擇不那么理想的“zonks”。 這就是現(xiàn)在所謂的 Monty Hall 問題的基礎。
這個設定是一個游戲節(jié)目,參賽者面對三個閉著的門。 在其中一扇門的后面是一輛奇特的汽車,另外兩扇門后面有一只山羊。 參賽者不知道汽車的位置,必須按照以下規(guī)則進行嘗試。
- 參賽者進行初步選擇,但不打開那個門。
- 其他兩個門中至少有一個門的后面必須有一只山羊。Monty 打開這些門之一來展示山羊,維基百科中顯示了他所有的榮耀。
https://gitee.com/wizardforcel/data8-textbook-zh/raw/master/img/8-2.png
- 還剩下兩個門,其中一個是參賽者的原始選擇。 其中一扇門后面有車,另一扇有一只山羊。 參賽者現(xiàn)在可以選擇打開兩扇門中的哪一扇。
參賽者需要作出決定。 如果她想要這輛車,她應該選擇打開哪扇門? 她應該堅持最初的選擇,還是轉向另一個門? 這是 Monty Hall 問題。
解法
在涉及幾率的任何問題中,重要的隨機性的假設。 假設有三分之一的幾率,參賽者的最初選擇是后面有車的門,這是合理的。
在這個假設下,解決這個問題的方法非常簡單,盡管簡單的解決方案并不能說服每個人。 無論如何就是這樣。
- 汽車在原來選擇的門后面的幾率是 1/3。
- 汽車在原來選擇的門后面或者剩余的門后面。 它不能在其他地方。
- 因此,汽車在剩余的門后的幾率是 2/3。
- 因此,選手應該更改選擇。
- 就是這樣,故事結束了。
不相信? 那么讓我們模擬游戲,看看結果如何。
模擬
我們開始建立兩個實用的數(shù)組,doors和goats,這會讓我們區(qū)分三個門和兩只山羊。
doors = make_array('Car', 'Goat 1', 'Goat 2')
goats = make_array('Goat 1', 'Goat 2')
現(xiàn)在我們定義一個函數(shù)monty_hall來模擬游戲,并按照這個順序返回含有三個字符串的數(shù)組:
- 參賽選手的原始選擇的什么
- Monty 排除了什么
- 剩下的門是什么
如果選手的原始選擇是帶山羊的門,蒙蒂必須扔掉另一只山羊,剩下的就是這輛車。 如果最初的選擇是帶車的門,蒙蒂必須扔掉兩只山羊中的一只,剩下的就是另一只羊。
因此很顯然,在前一節(jié)中定義的函數(shù)將是有用的。 它需要一個字符串和一個兩個元素的數(shù)組; 如果字符串等于其中一個元素,則返回另一個元素。
def other_one(x, a_b):
if x == a_b.item(0):
return a_b.item(1)
elif x == a_b.item(1):
return a_b.item(0)
else:
return 'Input Not Valid'
如果選手的原始選擇是山羊,游戲的結果是這二者之一:
original = 'Goat 1'
make_array(original, other_one(original, goats), 'Car')
array(['Goat 1', 'Goat 2', 'Car'],
dtype='<U6')
original = 'Goat 2'
make_array(original, other_one(original, goats), 'Car')
array(['Goat 2', 'Goat 1', 'Car'],
dtype='<U6')
現(xiàn)在我們可以把所有這些代碼放到monty_hall函數(shù)中,來模擬一次游戲的結果。 該函數(shù)不帶任何參數(shù)。
參賽者的原始選擇將是三門之中隨機選擇的門。
為了檢查原始選擇是否是山羊,我們首先寫一個名為is_goat的小函數(shù):
def is_goat(door_name):
""" Check whether the name of a door (a string) is a Goat.
Examples:
=========
>>> is_goat('Goat 1')
True
>>> is_goat('Goat 2')
True
>>> is_goat('Car')
False
"""
if door_name == "Goat 1":
return True
elif door_name == "Goat 2":
return True
else:
return False
def monty_hall():
""" Play the Monty Hall game once
and return an array of three strings:
original choice, what Monty throws out, what remains
"""
original = np.random.choice(doors)
if is_goat(original):
return make_array(original, other_one(original, goats), 'Car')
else:
throw_out = np.random.choice(goats)
return make_array(original, throw_out, other_one(throw_out, goats))
讓我們玩幾次這個游戲。這里是一個結果。你應該運行幾次單元格來觀察結果如何變化。
monty_hall()
array(['Car', 'Goat 2', 'Goat 1'],
dtype='<U6')
為了衡量不同結果發(fā)生的頻率,我們必須玩多次游戲并收集結果。 為此,我們將使用for循環(huán)。
我們將首先定義三個空數(shù)組,每個數(shù)組對應原始選擇,Monty 排除了什么,剩下的是什么。然后我們將玩這個游戲 N 次并收集結果。我們已經將 N 設為 10,000,但是你可以改變它。
# Number of times we'll play the game
N = 10000
original = make_array() # original choice
throw_out = make_array() # what Monty throws out
remains = make_array() # what remains
for i in np.arange(N):
result = monty_hall() # the result of one game
# Collect the results in the appropriate arrays
original = np.append(original, result.item(0))
throw_out = np.append(throw_out, result.item(1))
remains = np.append(remains, result.item(2))
# The for-loop is done! Now put all the arrays together in a table.
results = Table().with_columns(
'Original Door Choice', original,
'Monty Throws Out', throw_out,
'Remaining Door', remains
)
results
| Original Door Choice | Monty Throws Out | Remaining Door |
|---|---|---|
| Car | Goat 1 | Goat 2 |
| Goat 1 | Goat 2 | Car |
| Goat 2 | Goat 1 | Car |
| Car | Goat 2 | Goat 1 |
| Car | Goat 2 | Goat 1 |
| Goat 1 | Goat 2 | Car |
| Goat 1 | Goat 2 | Car |
| Goat 1 | Goat 2 | Car |
| Goat 2 | Goat 1 | Car |
| Goat 1 | Goat 2 | Car |
(省略了 9990 行)
為了看看選手是否應該堅持原來的選擇或更改,讓我們看看她的兩個選項后面的車的頻率。
results.group('Original Door Choice')
| Original Door Choice | count |
|---|---|
| Car | 3312 |
| Goat 1 | 3382 |
| Goat 2 | 3306 |
results.group('Remaining Door')
| Remaining Door | count |
|---|---|
| Car | 6688 |
| Goat 1 | 1640 |
| Goat 2 | 1672 |
我們的解決方案說明了,這輛車有三分之二的幾率在剩下的門后面,這是相當不錯的近似值。 如果參賽者更改了她的選擇,她有兩倍的可能性會得到車。
為了使結果可視化,我們可以將上面的兩個表格連接在一起并繪制疊加的條形圖。
results_o = results.group('Original Door Choice')
results_r = results.group('Remaining Door')
joined = results_o.join('Original Door Choice', results_r, 'Remaining Door')
combined = joined.relabeled(0, 'Item').relabeled(1, 'Original Door').relabeled(2, 'Remaining Door')
combined
| Item | Original Door | Remaining Door |
|---|---|---|
| Car | 3312 | 6688 |
| Goat 1 | 3382 | 1640 |
| Goat 2 | 3306 | 1672 |
combined.barh(0)
https://gitee.com/wizardforcel/data8-textbook-zh/raw/master/img/8-3.png
注意三條藍色條形幾乎相等 - 原始選擇有同等可能是三個可用條目中的任何一條。 但是,汽車對應的金色條形是藍色條形的兩倍。
模擬證實了,如果參賽者改變選擇,她有兩倍的可能性獲勝。
發(fā)現(xiàn)概率
幾個世紀以來,對于什么是概率存在哲學爭論。有些人認為概率是相對頻率;其他人認為他們是長期的相對頻率較長;還有一些人認為概率是個人不確定性程度的主觀測量。
在這個課程中,大多數(shù)概率將是相對頻率,盡管許多人會有主觀的解釋。無論如何,在不同的解釋中,概率計算和組合的方式是一致的。
按照慣例,概率是介于 0 和 1 之間的數(shù)字,或者 0% 和 100% 之間。不可能的事件概率為 0。確定的事件概率為 1。
數(shù)學是準確發(fā)現(xiàn)概率的主要工具,盡管計算機也可用于此目的。模擬可以提供出色的近似,具有很高的概率。在本節(jié)中,我們將以非正式方式制定一些簡單的規(guī)則來管理概率的計算。在隨后的章節(jié)中,我們將回到模擬來近似復雜事件的概率。
我們將使用標準符號 https://www.zhihu.com/equation?tex=P%28%5Cmbox%7Bevent%7D%29 來表示“事件”發(fā)生的概率,我們將交替使用“幾率”和“概率”兩個字。
事件不會發(fā)生的時候
如果事件發(fā)生的概率是 40%,不發(fā)生的幾率就是 60%。這個自然的計算可以這樣秒速:
所有結果等可能的時候
如果你投擲一個普通的骰子,一個自然的假設是,所有六個面都是等可能的。 那么一個面出現(xiàn)的概率可以很容易地計算出來。 例如,骰子顯示偶數(shù)的幾率是:
與之相似:
通常:
前提是所有的結果都是等可能的。
并非所有的隨機現(xiàn)象都像骰子一樣簡單。 下面的兩個主要的概率規(guī)則甚至允許數(shù)學家在復雜的情況下找到概率。
兩個事件必須同時發(fā)生時
假設你有一個盒子,包含三張紙條:一張紅色,一張藍色和一張綠色。 假設你隨機抽兩張紙條而不放回;也就是你把三張紙條打亂,抽一張,打亂其余兩張,再從這兩張中抽出一張。 你先得到綠色紙條,然后是紅色紙條的幾率是多少?
有六種可能的顏色對:RB,BR,RG,GR,BG,GB(我們已經縮寫了每種顏色的名字,就是它的第一個字母)。 所有這些都是抽樣方案是等可能的,只有其中一個(GR)使事件發(fā)生。所以:
但是還有另外一種方法來得到答案,可以用兩個階段來思考這個事件。 必須首先抽取綠色紙條。幾率是 1/3,也就是說在所有實驗的大約 1/3 的重復中,先抽取了綠色紙條,但事件還沒完成。在這 1/3 的重復中,必須再次抽取紅色紙條。這個發(fā)生在大約 1/2 的重復中,所以:
這個計算通常按照事件順序,像這樣:
因數(shù) 1/2 叫做“假設第一次出現(xiàn)了綠色紙條,第二次出現(xiàn)紅色紙條的條件幾率”。
通常,我們擁有乘法規(guī)則:
兩個事件同時發(fā)生的概率,等于第一個事件發(fā)生的概率,乘上第一個事件發(fā)生的情況下第二個事件發(fā)生的概率。
因此,這里有兩個條件 - 一個事件必須發(fā)生,另一個也是 - 幾率是分數(shù)的分數(shù),這比兩個因數(shù)的任何一個都要小。 滿足的條件越多,滿足的可能性就越小。
事件以兩種不同的方式發(fā)生
相反,假設我們希望兩張紙條中的一張是綠色的,另一張是紅色的。 此事件不指定顏色必須出現(xiàn)的順序。所以他們可以以任何順序出現(xiàn)。
解決這樣的問題的一個好方法就是對事件進行劃分,以便它正好能夠以幾種不同的方式之一發(fā)生。 “一綠一紅”的自然劃分是:GR,RG。
根據上面的計算,GR 和 RG 每個的幾率都是 1/6。所以你可以通過把它們相加來計算一綠一紅的概率。
通常,我們擁有加法規(guī)則:
事件發(fā)生的概率,等于以第一種方式發(fā)生的概率,加上以第二種方式發(fā)生的概率。
只要事件正好以兩種方式之一發(fā)生。
因此,當事件以兩種不同的方式之一發(fā)生時,發(fā)生的幾率是一些幾率的總和,因此比任何一種方式的幾率都大。
乘法規(guī)則可以自然擴展到兩個以上的事件,我們將在下面看到。 所以這個加法規(guī)則也有自然的擴展,事件可以以幾種不同的方式之一發(fā)生。
我們將所有這些規(guī)則組合成示例,并用示例來結束該部分。
至少有一個成功
數(shù)據科學家經常使用來自總體的隨機樣本。 有時候問題就來了,就是總體中的一個特定個體選進樣本的可能性。為了找出幾率,這個個體被稱為“成功”,問題是要找到樣本包含成功的幾率。
要看看如何計算這樣的幾率,我們從一個更簡單的設定開始:投擲硬幣兩次。
如果你投擲硬幣兩次,有四個等可能的結果:HH,HT,TH 和 TT。 我們把正面縮寫為 H ,反面縮寫為 T。至少有一個正面的幾率是 3/4。
得出這個答案的另一種方法是,弄清楚如果你不能得到至少一個正面,會發(fā)生什么事情:這兩次投擲都必須是反面。所以:
要注意根據乘法規(guī)則:
這兩個觀察使我們能夠在任何給定數(shù)量的投擲中找到至少一個正面的幾率。 例如:
而現(xiàn)在我們有能力找到在骰子的投擲中,六點至少出現(xiàn)一次的幾率:
下表展示了,這些概率隨著投擲數(shù)量從 1 增加到 50 而增加。
rolls = np.arange(1, 51, 1)
results = Table().with_columns(
'Rolls', rolls,
'Chance of at least one 6', 1 - (5/6)**rolls
)
results
| Rolls | Chance of at least one 6 |
|---|---|
| 1 | 0.166667 |
| 2 | 0.305556 |
| 3 | 0.421296 |
| 4 | 0.517747 |
| 5 | 0.598122 |
| 6 | 0.665102 |
| 7 | 0.720918 |
| 8 | 0.767432 |
| 9 | 0.806193 |
| 10 | 0.838494 |
(省略了 40 行)
隨著投擲數(shù)量的增加,六點至少出現(xiàn)一次的幾率迅速增加。
results.scatter('Rolls')
https://gitee.com/wizardforcel/data8-textbook-zh/raw/master/img/8-4.png
在 50 次投擲中,你幾乎肯定能得到至少一個六。
results.where('Rolls', are.equal_to(50))
| Rolls | Chance of at least one 6 |
|---|---|
| 50 | 0.99989 |
像這樣的計算可以用來找到,隨機樣本中選擇特定個體的幾率。 準確的計算將取決于抽樣方案。 但是我們上面的觀察的通??梢员煌茝V:增加隨機樣本的大小增加了選擇個體的幾率。
抽樣
現(xiàn)在我們來仔細看看抽樣,例子基于top_movies.csv數(shù)據集。
top1 = Table.read_table('top_movies.csv')
top2 = top1.with_column('Row Index', np.arange(top1.num_rows))
top = top2.move_to_start('Row Index')
top.set_format(make_array(3, 4), NumberFormatter)
| Row Index | Title | Studio | Gross | Gross (Adjusted) | Year |
|---|---|---|---|---|---|
| 0 | Star Wars: The Force Awakens | Buena Vista (Disney) | 906,723,418 | 906,723,400 | 2015 |
| 1 | Avatar | Fox | 760,507,625 | 846,120,800 | 2009 |
| 2 | Titanic | Paramount | 658,672,302 | 1,178,627,900 | 1997 |
| 3 | Jurassic World | Universal | 652,270,625 | 687,728,000 | 2015 |
| 4 | Marvel's The Avengers | Buena Vista (Disney) | 623,357,910 | 668,866,600 | 2012 |
| 5 | The Dark Knight | Warner Bros. | 534,858,444 | 647,761,600 | 2008 |
| 6 | Star Wars: Episode I - The Phantom Menace | Fox | 474,544,677 | 785,715,000 | 1999 |
| 7 | Star Wars | Fox | 460,998,007 | 1,549,640,500 | 1977 |
| 8 | Avengers: Age of Ultron | Buena Vista (Disney) | 459,005,868 | 465,684,200 | 2015 |
| 9 | The Dark Knight Rises | Warner Bros. | 448,139,099 | 500,961,700 | 2012 |
(省略了 190 行)
對表格的行進行抽樣
數(shù)據表的每一行代表一個個體;最重要的是,每個個體都是一部電影。 因此可以通過表格的行的抽樣來實現(xiàn)對個體的抽樣。
一行的內容是在同一個個體上測量的不同變量的值。 因此,行的內容的抽樣形成了每個變量值的樣本。
確定性樣本
當你只是簡單地指定,你要選擇的集合中的哪些元素時,就不會涉及任何幾率,可以創(chuàng)建確定性樣本。
你已經做了很多次了,例如使用take:
top.take(make_array(3, 18, 100))
| Row Index | Title | Studio | Gross | Gross (Adjusted) | Year |
|---|---|---|---|---|---|
| 3 | Jurassic World | Universal | 652,270,625 | 687,728,000 | 2015 |
| 18 | Spider-Man | Sony | 403,706,375 | 604,517,300 | 2002 |
| 100 | Gone with the Wind | MGM | 198,676,459 | 1,757,788,200 | 1939 |
你也使用了where:
top.where('Title', are.containing('Harry Potter'))
| Row Index | Title | Studio | Gross | Gross (Adjusted) | Year |
|---|---|---|---|---|---|
| 22 | Harry Potter and the Deathly Hallows Part 2 | Warner Bros. | 381,011,219 | 417,512,200 | 2011 |
| 43 | Harry Potter and the Sorcerer's Stone | Warner Bros. | 317,575,550 | 486,442,900 | 2001 |
| 54 | Harry Potter and the Half-Blood Prince | Warner Bros. | 301,959,197 | 352,098,800 | 2009 |
| 59 | Harry Potter and the Order of the Phoenix | Warner Bros. | 292,004,738 | 369,250,200 | 2007 |
| 62 | Harry Potter and the Goblet of Fire | Warner Bros. | 290,013,036 | 393,024,800 | 2005 |
| 69 | Harry Potter and the Chamber of Secrets | Warner Bros. | 261,988,482 | 390,768,100 | 2002 |
| 76 | Harry Potter and the Prisoner of Azkaban | Warner Bros. | 249,541,069 | 349,598,600 | 2004 |
雖然這些是電影的樣本,它們并不涉及幾率。
概率抽樣
很多數(shù)據科學都根據隨機樣本中的數(shù)據得到結論。 根據隨機樣本的正確解釋分析,需要數(shù)據科學家準確地檢查隨機樣本。
總體是從中抽取樣本的所有元素的集合。
概率樣本是一種樣本,在抽取樣本之前,可以計算出的元素的任何子集將進入樣本的幾率。
在概率樣本中,所有的元素不需要有相同的選中幾率。
隨機抽樣方案
例如,假設根據以下方案,從三個個體 A,B 和 C 組成的總體中選擇兩個個體:
- 個體 A 選中概率為 1。
- 個體 B 或 C 根據擲硬幣來選擇:如果硬幣為正面,選擇 B,否則,選擇 C。
這是一個大小為 2 的概率樣本。下面是所有非空子集的選中幾率:
A: 1
B: 1/2
C: 1/2
AB: 1/2
AC: 1/2
BC: 0
ABC: 0
個體 A 比 B 或 C 有更高的選中幾率;的確,個體 A 肯定會被選中。由于這些差異是已知的和量化的,所以在處理樣本時可以考慮這些差異。
系統(tǒng)樣本
想象一下,總體的所有元素都列出在序列中。 抽樣的一種方法是,先從列表中選擇一個隨機的位置,然后是它后面的等間隔的位置。樣本由這些位置上的元素組成。這樣的樣本被稱為系統(tǒng)樣本。
在這里,我們將選擇頂部一些行的系統(tǒng)樣本。我們最開始隨機選取前 10 行中的一行,然后我們將選取它后面的每個第 10 行。
"""Choose a random start among rows 0 through 9;
then take every 10th row."""
start = np.random.choice(np.arange(10))
top.take(np.arange(start, top.num_rows, 10))
| Row Index | Title | Studio | Gross | Gross (Adjusted) | Year |
|---|---|---|---|---|---|
| 6 | Star Wars: Episode I - The Phantom Menace | Fox | 474,544,677 | 785,715,000 | 1999 |
| 16 | Iron Man 3 | Buena Vista (Disney) | 409,013,994 | 424,632,700 | 2013 |
| 26 | Spider-Man 2 | Sony | 373,585,825 | 523,381,100 | 2004 |
| 36 | Minions | Universal | 336,045,770 | 354,213,900 | 2015 |
| 46 | Iron Man 2 | Paramount | 312,433,331 | 341,908,200 | 2010 |
| 56 | The Twilight Saga: New Moon | Sum. | 296,623,634 | 338,517,700 | 2009 |
| 66 | Meet the Fockers | Universal | 279,261,160 | 384,305,300 | 2004 |
| 76 | Harry Potter and the Prisoner of Azkaban | Warner Bros. | 249,541,069 | 349,598,600 | 2004 |
| 86 | The Exorcist | Warner Bros. | 232,906,145 | 962,212,800 | 1973 |
| 96 | Back to the Future | Universal | 210,609,762 | 513,740,700 | 1985 |
(省略了 10 行)
運行單元個幾次,看看輸出如何變化。
這個系統(tǒng)樣本是一個概率樣本。 在這個方案中,所有的行都有機會被選中。 例如,當且僅當?shù)?3 行被選中時,第 23 行才被選中,并且其幾率是 1/10。
但并不是所有的子集都有相同的選中幾率。 由于選中的行是等間隔的,大多數(shù)行的子集都沒有機會被選中。 唯一可能的子集是由所有間隔為 10 的行構成的子集。任何這些子集都以 1/10 的幾率被選中。 其他子集,如包含表格前 11 行的子集,選中幾率都是 0。
放回或不放回的隨機抽樣
在這個課程中,我們將主要處理兩個最直接的抽樣方法。
首先是帶放回的隨機抽樣,它(如我們前面所見)是np.random.choice從數(shù)組中抽樣時的默認行為。
另一個稱為“簡單隨機樣本”,是隨機抽取的樣本,不帶放回。在下一個個體被抽中之前,抽中的個體不會放回總體。例如,當你發(fā)牌時,就會發(fā)生這種抽樣。
在下一章中,我們將使用模擬來研究帶放回和不放回的大樣本隨機抽取。
繪制隨機樣本需要謹慎和精確。這不是隨便的,即使這是“隨機”一詞的口語意義。如果你站在街頭,選取前十名經過的人作為樣本,你可能會認為你在隨機抽樣,因為你沒有選擇誰走過。但它不是一個隨機樣本 - 這是一個方便的例子。你沒有提前知道每個人進入樣本的概率,也許甚至你沒有具體指定誰在總體中。