{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 概率與統計簡介\n", "在本筆記本中,我們將操弄一些之前討論過的概念。許多來自概率與統計的概念在 Python 的主要資料處理函式庫中都有良好展現,例如 `numpy` 和 `pandas`。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import random\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 隨機變數與分佈\n", "我們先從 0 到 9 的均勻分佈中抽取 30 個數值的樣本。並且計算其平均值及變異數。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sample = [ random.randint(0,10) for _ in range(30) ]\n", "print(f\"Sample: {sample}\")\n", "print(f\"Mean = {np.mean(sample)}\")\n", "print(f\"Variance = {np.var(sample)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "要視覺上估計樣本中有多少不同的值,我們可以繪製直方圖:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.hist(sample)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 分析實際數據\n", "\n", "均值和方差在分析現實世界數據時非常重要。讓我們載入來自 [SOCR MLB Height/Weight Data](http://wiki.stat.ucla.edu/socr/index.php/SOCR_Data_MLB_HeightsWeights) 的棒球運動員數據\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.read_csv(\"../../data/SOCR_MLB.tsv\",sep='\\t', header=None, names=['Name','Team','Role','Weight','Height','Age'])\n", "df\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> 我們這裡使用一個名為 [**Pandas**](https://pandas.pydata.org/) 的套件來進行數據分析。稍後在本課程中,我們會進一步討論 Pandas 以及如何在 Python 中處理數據。\n", "\n", "讓我們計算年齡、身高和體重的平均值:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df[['Age','Height','Weight']].mean()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "而家嚟集中睇身高,計算標準差同變異數: \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(list(df['Height'])[:20])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mean = df['Height'].mean()\n", "var = df['Height'].var()\n", "std = df['Height'].std()\n", "print(f\"Mean = {mean}\\nVariance = {var}\\nStandard Deviation = {std}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "除了平均值之外,查看中位數和四分位數也是有意義的。它們可以用箱型圖來視覺化: \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(10,2))\n", "plt.boxplot(df['Height'].ffill(), orientation='horizontal', showmeans=True)\n", "plt.grid(color='gray', linestyle='dotted')\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "我哋都可以將數據集嘅子集作箱形圖,例如按球員角色分組。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.boxplot(column='Height', by='Role', figsize=(10,8))\n", "plt.xticks(rotation='vertical')\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> 注意:此圖表顯示,一壘手的平均身高比二壘手的身高高。稍後我們會學習如何更正式地檢驗此假設,以及如何證明我們的數據在統計上具有顯著性。 \n", "\n", "年齡、身高和體重都是連續隨機變數。你認為它們的分佈是如何?一個好的方法是繪製數值的直方圖來找出答案: \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df['Weight'].hist(bins=15, figsize=(10,6))\n", "plt.suptitle('Weight distribution of MLB Players')\n", "plt.xlabel('Weight')\n", "plt.ylabel('Count')\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 正態分佈\n", "\n", "讓我們創建一個符合正態分佈的人工權重樣本,其均值和變異數與我們的真實數據相同:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "generated = np.random.normal(mean, std, 1000)\n", "generated[:20]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(10,6))\n", "plt.hist(generated, bins=15)\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(10,6))\n", "plt.hist(np.random.normal(0,1,50000), bins=300)\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "由於現實生活中的大多數數值通常呈常態分佈,因此我們不應該使用均勻隨機數生成器來生成樣本數據。以下是嘗試使用均勻分佈(由 `np.random.rand` 生成)來生成體重時會發生的情況:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wrong_sample = np.random.rand(1000)*2*std+mean-std\n", "plt.figure(figsize=(10,6))\n", "plt.hist(wrong_sample)\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 信心區間\n", "\n", "而家我哋嚟計算棒球員嘅體重同身高嘅信心區間。 我哋會用[呢個stackoverflow討論](https://stackoverflow.com/questions/15033511/compute-a-confidence-interval-from-sample-data)嘅代碼:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import scipy.stats\n", "\n", "def mean_confidence_interval(data, confidence=0.95):\n", " a = 1.0 * np.array(data)\n", " n = len(a)\n", " m, se = np.mean(a), scipy.stats.sem(a)\n", " h = se * scipy.stats.t.ppf((1 + confidence) / 2., n-1)\n", " return m, h\n", "\n", "for p in [0.85, 0.9, 0.95]:\n", " m, h = mean_confidence_interval(df['Weight'].ffill(),p)\n", " print(f\"p={p:.2f}, mean = {m:.2f} ± {h:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 假設檢定\n", "\n", "讓我們探索棒球選手數據集中不同的角色:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.groupby('Role').agg({ 'Weight' : 'mean', 'Height' : 'mean', 'Age' : 'count'}).rename(columns={ 'Age' : 'Count'})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "讓我們檢驗一下第一壘手是否比二壘手高的假設。最簡單的方法是檢驗信賴區間:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for p in [0.85,0.9,0.95]:\n", " m1, h1 = mean_confidence_interval(df.loc[df['Role']=='First_Baseman',['Height']],p)\n", " m2, h2 = mean_confidence_interval(df.loc[df['Role']=='Second_Baseman',['Height']],p)\n", " print(f'Conf={p:.2f}, 1st basemen height: {m1-h1[0]:.2f}..{m1+h1[0]:.2f}, 2nd basemen height: {m2-h2[0]:.2f}..{m2+h2[0]:.2f}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "我們可以看到區間並沒有重疊。\n", "\n", "一個在統計上更正確證明假設的方法是使用 **Student t檢定**:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scipy.stats import ttest_ind\n", "\n", "tval, pval = ttest_ind(df.loc[df['Role']=='First_Baseman',['Height']], df.loc[df['Role']=='Second_Baseman',['Height']],equal_var=False)\n", "print(f\"T-value = {tval[0]:.2f}\\nP-value: {pval[0]}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`ttest_ind` 函數所返回的兩個值是:\n", "* p 值可被視為兩個分佈具有相同平均值的機率。在我們的例子中,這個值非常低,意味著有強烈的證據支持一壘手較高。\n", "* t 值是標準化平均差的中間值,該值被用於 t 檢驗,並與給定置信值的閾值進行比較。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 使用中心極限定理模擬常態分佈\n", "\n", "Python 中的偽隨機生成器設計是給我們一個均勻分佈。如果我們想要創建一個常態分佈的生成器,我們可以使用中心極限定理。要獲得一個常態分佈的值,我們只需計算一組均勻生成樣本的平均值。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def normal_random(sample_size=100):\n", " sample = [random.uniform(0,1) for _ in range(sample_size) ]\n", " return sum(sample)/sample_size\n", "\n", "sample = [normal_random() for _ in range(100)]\n", "plt.figure(figsize=(10,6))\n", "plt.hist(sample)\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 相關性與邪惡棒球公司\n", "\n", "相關性讓我們能找到資料序列之間的關係。在我們的玩具示例中,假設有一間邪惡的棒球公司,根據球員的身高支付他們的薪水——球員越高,獲得的錢越多。假設有一個基本薪資是$1000,還有一個根據身高介乎$0到$100的額外獎金。我們將取美國職棒大聯盟(MLB)的真實球員資料,計算他們的虛構薪資:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "heights = df['Height'].ffill()\n", "salaries = 1000+(heights-heights.min())/(heights.max()-heights.mean())*100\n", "print(list(zip(heights, salaries))[:10])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "現在讓我們計算這些序列的協方差和相關性。`np.cov` 會給我們所謂的協方差矩陣,這是多變量協方差的擴展。協方差矩陣 $M$ 的元素 $M_{ij}$ 是輸入變量 $X_i$ 和 $X_j$ 之間的相關性,而對角線上的值 $M_{ii}$ 則是 $X_i$ 的變異數。同樣地,`np.corrcoef` 會給我們相關係數矩陣。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(f\"Covariance matrix:\\n{np.cov(heights, salaries)}\")\n", "print(f\"Covariance = {np.cov(heights, salaries)[0,1]}\")\n", "print(f\"Correlation = {np.corrcoef(heights, salaries)[0,1]}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "相關係數等於 1 意味著兩個變量之間存在強烈的 線性關係。我們可以通過將一個值與另一個值繪圖來直觀地看到線性關係:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(10,6))\n", "plt.scatter(heights,salaries)\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "我們來看看如果關係不是線性的結果會如何。假設我們的公司決定隱藏身高與薪水之間明顯的線性依賴關係,並在公式中引入了一些非線性成分,例如 `sin`: \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "salaries = 1000+np.sin((heights-heights.min())/(heights.max()-heights.mean()))*100\n", "print(f\"Correlation = {np.corrcoef(heights, salaries)[0,1]}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "在這種情況下,相關性稍微小一點,但仍然相當高。現在,為了讓關係看起來更不明顯,我們或許想透過加入一些隨機變數到薪水中,增加額外的隨機性。讓我們來看看會發生什麼:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "salaries = 1000+np.sin((heights-heights.min())/(heights.max()-heights.mean()))*100+np.random.random(size=len(heights))*20-10\n", "print(f\"Correlation = {np.corrcoef(heights, salaries)[0,1]}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(10,6))\n", "plt.scatter(heights, salaries)\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> 你能猜出為什麼這些點會像這樣排列成垂直線嗎?\n", "\n", "我們已經觀察到像薪水這類人為設計的概念和觀察變量 身高 之間的相關性。現在讓我們也看看兩個觀察變量,例如身高和體重,是否也有相關性:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.corrcoef(df['Height'].ffill(),df['Weight'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "不幸地,我們沒有取得任何結果——只有一些奇怪的 `nan` 值。這是因為我們的系列中有些值是未定義的,用 `nan` 表示,這導致運算結果也變成未定義。從矩陣中我們可以看到 `Weight` 是有問題的欄位,因為已經計算了 `Height` 值之間的自相關。\n", "\n", "> 這個例子顯示了資料準備清理的重要性。沒有適當的資料,我們無法計算任何東西。\n", "\n", "讓我們使用 `fillna` 方法填充缺失值,並計算相關性:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.corrcoef(df['Height'].ffill(), df['Weight'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "確實存在一個關聯,但並不像我們的人為範例中那麼強烈。事實上,如果我們看一下其中一個值對另一個值的散點圖,這種關係會明顯得少得多:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(10,6))\n", "plt.scatter(df['Weight'],df['Height'])\n", "plt.xlabel('Weight')\n", "plt.ylabel('Height')\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 結論\n", "\n", "在這個筆記本中,我們學會了如何對數據執行基本操作來計算統計函數。我們現在知道如何使用健全的數學和統計工具來驗證一些假設,以及如何根據數據樣本計算任意變量的信賴區間。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n\n\n**免責聲明**:\n本文件使用 AI 翻譯服務 [Co-op Translator](https://github.com/Azure/co-op-translator) 進行翻譯。雖然我們力求準確,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應被視為權威來源。對於重要資訊,建議尋求專業人工翻譯。我們不對因使用本翻譯而引起的任何誤解或曲解承擔責任。\n\n" ] } ], "metadata": { "interpreter": { "hash": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.6" } }, "nbformat": 4, "nbformat_minor": 4 }