Python 提供了強大的統計分析工具,可以計算平均值、中位數、眾數、標準差、標準化分數和相關係數等關鍵指標。標準差能衡量資料分散程度,標準化分數則用於比較不同資料集的相對位置,而相關係數則揭示變數之間的線性關係。在實際應用中,生成器表示式提供了一種高效的資料處理方式,但需要注意其限制,例如與 len() 函式的相容性問題,以及只能使用一次的特性。為瞭解決這些問題,可以將生成器表示式轉換為列表,或使用 itertools 函式庫中的 tee 函式建立副本。此外,理解 Python 模組的概念對於程式碼的組織和重用至關重要。可以建立包含函式和類別定義的函式庫模組,以及執行實際工作的應用程式模組。透過將程式碼拆分成不同的模組,可以提高程式碼的可讀性、可維護性和可重用性。
更敏感的分析:間諜頭目的統計洞察
在間諜的世界裡,蒐集原始資料只是基礎,提供有價值的摘要才是真正的技術所在。本文將探討如何使用Python進行統計分析,包括標準差、標準化分數和相關係數的計算。
建立統計摘要
統計摘要的一種基本形式是集中趨勢的測量,包括平均值(mean)、中位數(median)和眾數(mode)。
- 平均值:將所有資料相加後除以資料總數,適合用於描述隨機資料。
- 中位數:資料排序後的中間值,對於非隨機資料的描述更為準確。
- 眾數:資料中最常見的值,但在連續資料中可能沒有意義。
蒐集資料
在之前的章節中,我們蒐集了美國的芝士消費資料,儲存在year_cheese列表中:
year_cheese = [(2000, 29.87), (2001, 30.12), (2002, 30.6), (2003, 30.66),
(2004, 31.33), (2005, 32.62), (2006, 32.73), (2007, 33.5),
(2008, 32.84), (2009, 33.02), (2010, 32.92), (2011, 33.27),
(2012, 33.51)]
解析原始資料檔案
我們還需要解析CDC網站上的死亡原因資料。原始檔案以tab分隔,類別似CSV格式。可以使用Python的csv模組讀取:
import csv
def deaths():
with open("Cause of Death by Year.txt") as source:
rdr = csv.DictReader(source, delimiter="\t")
for row in rdr:
if row['Notes'] == "Total":
break
yield int(row['Year']), int(row['Deaths'])
year_deaths = list(deaths())
計算平均值
平均值的計算公式看似複雜,但其實是Python內建函式的簡單應用:
# 平均值 = 總和 / 數量
data = [x[1] for x in year_deaths]
mean = sum(data) / len(data)
內容解密:
data = [x[1] for x in year_deaths]:從year_deaths列表中提取死亡人數,組成新的列表data。mean = sum(data) / len(data):計算data列表中所有死亡人數的平均值。sum(data):計算列表中所有元素的總和。len(data):取得列表中的元素數量。- 將總和除以數量,得到平均值。
更深入的統計分析與實踐
計算標準差
標準差是衡量資料分散程度的重要指標。其公式為: [ \sigma = \sqrt{\frac{\sum_{i=1}^{n} (x_i - \mu)^2}{n}} ] 其中,(\mu) 是平均值,(x_i) 是每個資料點,(n) 是資料總數。
import math
def calculate_std_dev(data, mean):
variance = sum((x - mean) ** 2 for x in data) / len(data)
return math.sqrt(variance)
data = [x[1] for x in year_deaths]
mean = sum(data) / len(data)
std_dev = calculate_std_dev(data, mean)
內容解密:
variance = sum((x - mean) ** 2 for x in data) / len(data):計算資料的變異數。- 對每個資料點 (x),計算其與平均值 (mean) 的差值,並平方。
- 將所有平方差值相加後除以資料總數,得到變異數。
math.sqrt(variance):對變異數取平方根,得到標準差。
標準化分數
標準化分數(Z-score)用於衡量某個資料點相對於平均值的偏離程度。其公式為: [ Z = \frac{x - \mu}{\sigma} ] 其中,(x) 是資料點,(\mu) 是平均值,(\sigma) 是標準差。
def calculate_z_score(data_point, mean, std_dev):
return (data_point - mean) / std_dev
# 示例:計算第一個死亡人數的Z-score
first_data_point = data[0]
z_score = calculate_z_score(first_data_point, mean, std_dev)
內容解密:
(data_point - mean):計算某個資料點與平均值的差值。/ std_dev:將差值除以標準差,得到標準化分數。
相關係數
相關係數用於衡量兩個變數之間的線性關係。其公式為: [ r = \frac{\sum_{i=1}^{n} (x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum_{i=1}^{n} (x_i - \bar{x})^2} \cdot \sqrt{\sum_{i=1}^{n} (y_i - \bar{y})^2}} ] 其中,(x_i) 和 (y_i) 是配對的資料點,(\bar{x}) 和 (\bar{y}) 分別是 (x) 和 (y) 的平均值。
def calculate_correlation(x_data, y_data):
x_mean = sum(x_data) / len(x_data)
y_mean = sum(y_data) / len(y_data)
numerator = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_data, y_data))
denominator = math.sqrt(sum((x - x_mean) ** 2 for x in x_data)) * math.sqrt(sum((y - y_mean) ** 2 for y in y_data))
return numerator / denominator
# 示例:計算年份與死亡人數之間的相關係數
years = [x[0] for x in year_deaths]
correlation = calculate_correlation(years, data)
內容解密:
numerator = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_data, y_data)):計算分子部分,即 (x) 和 (y) 的協方差。denominator:計算分母部分,即 (x) 和 (y) 的標準差的乘積。numerator / denominator:得到相關係數。
瞭解生成器表示式及其限制
在Python中,生成器表示式是一種強大的工具,用於處理資料。然而,它們有一些限制,需要開發者瞭解。
生成器表示式的三個重要規則
- 並非所有函式都能與生成器物件一起工作:有些函式需要序列物件,而生成器表示式不提供序列。
- 生成器表示式是懶惰的:只有在絕對必要時,才會建立物件。這與列表不同,列表實際上包含物件。
- 生成器函式只能使用一次:與列表不同,列表可以無限次重複使用。
len() 函式的問題
len() 函式不適用於生成器表示式,因為它需要一個序列物件。生成器表示式的大小在建立之前是未知的,因此 len() 無法傳回正確的大小。
自定義 count() 函式
為瞭解決 len() 的問題,可以定義一個 count() 函式,用於計算生成器表示式產生的專案數量。
def count(values):
"""計算生成器表示式產生的專案數量"""
return sum(1 for x in values)
mean() 函式的重寫
最初的 mean() 函式不適用於生成器表示式。嘗試使用 count() 函式重寫 mean() 函式,但由於生成器表示式只能使用一次,因此失敗了。
def mean2(values):
"""嘗試重寫 mean() 函式以支援生成器表示式"""
return sum(values) / count(values)
解決方案
有三種解決方案:
- 編寫更複雜的
sum()函式:可以計算總和和計數,但這太複雜了。 - 使用
itertools函式庫:可以使用tee函式建立生成器表示式的副本,但這太高階了。 - 建立列表物件:可以使用
list()函式或將生成器表示式包裝在[]中。
最簡單的解決方案是建立列表物件:
>>> mean([cheese for year, cheese in year_cheese])
32.076153846153844
中位數的計算
中位數是排序後的資料的中間值。可以使用以下函式計算中位數:
def median(values):
"""計算序列的中位數"""
s = sorted(values)
if len(s) % 2 == 1: # 奇數
return s[len(s) // 2]
else:
mid = len(s) // 2
return (s[mid - 1] + s[mid]) / 2
內容解密:
sorted(values):首先對輸入的資料進行排序,傳回一個新的已排序的列表。len(s) % 2 == 1:檢查排序後列表的長度是否為奇數。如果是奇數,直接傳回中間的值。len(s) // 2:計算中間位置的索引。在Python中,//表示整數除法。mid = len(s) // 2:如果是偶數個元素,則計算中間兩個元素的平均值。- 最佳化建議:可以使用
divmod(len(s), 2)同時取得商和餘數,以簡化程式碼。
最終建議
在編寫函式時,應考慮到生成器表示式的限制,並在檔案字串中註明函式的適用範圍。例如,可以在 mean() 函式中新增檔案字串,以提醒使用者該函式不適用於生成器表示式。
def mean(values):
"""計算序列的平均值(不適用於可迭代物件)"""
return sum(values) / len(values)
建立 Python 模組與應用程式
在前面的章節中,我們大量使用了 Python 函式庫中的模組。此外,我們還增加了幾個套件,包括 Pillow 和 BeautifulSoup。現在的問題是,我們能否建立自己的模組?
建立 Python 模組的基礎
答案是肯定的。一個 Python 模組其實就是一個檔案。事實上,我們之前寫的每個範例指令碼都可以視為一個模組。讓我們更深入地探討如何建立可重用的 Python 程式。
在 Python 程式中,我們通常會遇到三種檔案:
- 純粹定義的函式庫模組
- 執行實際工作的應用程式模組
- 既是應用程式又是函式庫的混合模組
建立 Python 模組的關鍵在於將頂層指令碼的實際工作與支援這些工作的各種定義分開。在我們的範例中,這些定義主要是使用 def 陳述式建立的函式。當然,還有其他型別的定義,例如類別定義,我們將在後面的章節中討論。
建立和使用模組
要建立一個只包含定義的模組,我們只需將所有的函式和類別定義放在一個檔案中。這個檔案的名稱必須是一個合法的 Python 變數名稱,也就是說,它只能包含字母、數字和底線(_)。雖然某些作業系統允許在檔案名稱中使用 Python 的運算元符號(例如 +、-、/ 等),但這些符號不能用於命名模組檔案。
檔案名稱必須以 .py 結尾。這不是模組名稱的一部分,而是為了作業系統的方便而設定的。
假設我們將統計函式收集到一個名為 stats.py 的檔案中。這個檔案定義了一個名為 stats 的模組。
使用模組
我們可以匯入整個 stats 模組,或者只匯入其中的某些函式。有以下幾種方式可以做到這一點:
>>> from stats import *
這樣做會匯入 stats 模組中定義的所有函式和類別。我們可以直接使用這些名稱,例如 mean(some_list)。
或者,我們可以這樣做:
>>> from stats import mean, median
這樣只會匯入 stats 模組中的 mean 和 median 兩個函式,而忽略該模組中的其他定義。
我們也可以這樣做:
>>> import stats
這樣會匯入整個 stats 模組,但不會將其中的名稱放入我們通常使用的全網域名稱空間中。要使用 stats 模組中的名稱,需要使用合格的名稱,例如 stats.mean(some_list)。在複雜的指令碼中,使用合格名稱有助於闡明某個特定的函式或類別是在哪裡定義的。
建立應用程式模組
建立具有命令列介面(CLI)的應用程式最簡單的方法是寫一個檔案,然後從命令列執行它。例如:
python3 basic_stats.py
在終端機視窗或命令視窗中輸入上述命令,就可以使用作業系統的 python3 命令來執行指定的檔案。在 Windows 中,Python 3 的執行檔名有時是 python.exe,因此命令可能是 python basic_stats.py。
分離定義和實際工作
如前所述,我們希望將定義放在一個檔案中,而將實際工作放在另一個檔案中。檢視 basic_stats.py 的內容,可能會看到以下程式碼:
"""第 5 章範例 2。
從 ch_5_ex_1 模組匯入統計函式庫函式。
從 ch_5_ex_1 模組匯入資料擷取函式。
計算一些簡單的描述性統計資料。
"""
from ch_5_ex_1 import mean, mode, median
from ch_5_ex_1 import get_deaths, get_cheese
year_deaths = list(get_deaths())
years = list(year for year, death in year_deaths)
deaths = list(death for year, death in year_deaths)
print("年份範圍", min(years), "到", max(years))
print("平均死亡人數 {:.2f}".format(mean(deaths)))
程式碼解析
from ch_5_ex_1 import mean, mode, median
from ch_5_ex_1 import get_deaths, get_cheese
這段程式碼從 ch_5_ex_1 模組中匯入了所需的函式,包括統計函式和資料擷取函式。
year_deaths = list(get_deaths())
years = list(year for year, death in year_deaths)
deaths = list(death for year, death in year_deaths)
這段程式碼使用匯入的函式來擷取資料,並將其轉換為所需的格式。
print("年份範圍", min(years), "到", max(years))
print("平均死亡人數 {:.2f}".format(mean(deaths)))
這段程式碼計算並輸出所需的統計資料,包括年份範圍和平均死亡人數。
重構程式碼
為了使程式碼更具可讀性和可維護性,我們可以將其重構為更模組化的結構。例如,我們可以將資料擷取和統計計算分成不同的函式。
def calculate_statistics(years, deaths):
print("年份範圍", min(years), "到", max(years))
print("平均死亡人數 {:.2f}".format(mean(deaths)))
def main():
year_deaths = list(get_deaths())
years = list(year for year, death in year_deaths)
deaths = list(death for year, death in year_deaths)
calculate_statistics(years, deaths)
if __name__ == "__main__":
main()
程式流程
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Python統計分析技術與模組應用
package "統計分析流程" {
package "資料收集" {
component [樣本資料] as sample
component [母體資料] as population
}
package "描述統計" {
component [平均數/中位數] as central
component [標準差/變異數] as dispersion
component [分佈形狀] as shape
}
package "推論統計" {
component [假設檢定] as hypothesis
component [信賴區間] as confidence
component [迴歸分析] as regression
}
}
sample --> central : 計算
sample --> dispersion : 計算
central --> hypothesis : 檢驗
dispersion --> confidence : 估計
hypothesis --> regression : 建模
note right of hypothesis
H0: 虛無假設
H1: 對立假設
α: 顯著水準
end note
@enduml
此圖示展示了程式的主要流程,從匯入必要的模組開始,到輸出結果結束。