在大數據和人工智慧的時代,資料科學、資料探勘和機器學習皆已在許多科學技術領域扮演越來越關鍵的角色,Python程式語言的簡單易於使用,加上廣大的社群支援以及豐富的資料科學與機器學習的程式庫,使得它成為最受歡迎的處理數據與資料科學的程式語言平台。
任何領域對於蒐集取回的資料,最基本或初步的分析需求是要能夠直觀描述,匯總與表示數據資料的基本特徵,本文將說明何謂描述性統計以及如何利用Python語言來計算或取得資料集的各種描述統計量。
何謂描述性統計(What is Descriptive Statistics)
描述性統計是描述一組資料基本特徵的概括訊息,描述性統計主要分為集中趨勢(central tendency)的測量和變動 (Variability)或分散程式(Dispersion)之測量,而集中趨勢的測量包括了平均數(mean)、中位數(median)和眾數(mode),至於變異程度的測量則包括了標準差(standard deviation)與變異數(variance),四分位數(Quartile)、四分位距(Interquartile range)以及峰度(kurtosis)和偏度(skewness)之分佈形狀之測量。下圖說明了描述統計涵蓋的內容與類型。
◎ 集中趨勢(central tendency)
- 算術平均數(Arithmetic mean):平均數是最常使用的集中趨勢量測值,因為樣本平均數為母體期望值的最佳線性不偏估計量 (BLUE)。為所有觀測值的總和除以觀測值的個數,即 \(\frac{x_1+x_2+x_3.....+x_n}{n}\) ,
- 中位數(Median):將所有觀測值依大小排序之後,在順序上取其居中的數值即是。若資料集個數為偶數,中間數則是位於中間二項的平均。
- 眾數(Mode):所有觀測值中出現次數最多的觀測值,也是畫成直方圖中的最高條形的那個值。
平均數,中位數和眾數皆是集中趨勢的有效度量,但是在不同條件下,某些集中趨勢的度量會比其他度量更適合使用,因此並沒有所謂的“最佳”的集中趨勢度量,因為這將取決於您擁有的資料類型(名目或連續資料),分佈型態、偏斜程度;變異大小以及研究目的,舉例而言,有一家公司的部門員工薪資資料集為 {33k,38k,31k,28k,41k,46k,38k,29k,94k},則計算其平均數為\(\frac{33+38+31+28+41+46+38+29+94}{9}=42\),中位數則需將觀測值先由小到大排列為{28,29,31,33,38,38,41,46,94},然後取正中間第5順位的那個值為38,眾數則應是出現次數達2次的38。
平均數一個主要的的問題是容易受到極端或離群值的影響,在上面薪資例中,有一極端值94k,讓平均值明顯高於大部份員工的薪資,而喪失了代表性,其次,一旦資料的分佈偏斜時,平均值也將失去為資料提供最佳中心位置的能力,特別是當分佈越偏斜時,中位數和均值之間的差異就越大,可能應該更多地強調使用中位數而不是平均值。因此當明顯存在極端值或是分佈明顯偏斜時,由於中位數受極端值和偏斜數據的影響較小,可能是集中趨勢點的較佳選擇,此外,眾數適用於一組分類或名目資料,讓我們了解哪個是最常見的類別,眾數如果用在連續資料時就可能發生找不到任一個數值出現較高的頻率之窘境,眾數另一個問題是當最常見的資料項目遠離資料集的其餘數據時,它也將無法為我們提供良好的集中趨勢度量。
◎ 離散程度(Dispersion)
離散程度是指一個分布或隨機變量的壓縮和拉伸的程度,亦即樣本偏離平均值有多大的程度,用以描述離散程度的統計量主要有變異數(Variance)、標準差(Standard deviation)、四分位數(Quartile)、四分位距(Interquartile range)等,因此離散程度的概念與集中趨勢是相對。
- 變異數(Variance):變異數是用來測量一組資料彼此間差異的程度,也可用來觀察一組資料的同質性、一致性或分散程度,基本算法是測量所有資料到平均數的平均距離,母體變異數公式為 \(\sigma^2=\frac{1}{N}\sum\limits_{i = 1}^N {\left( {x_i - \mu} \right)^2 }\),而樣本變異數由於要求不偏估計與自由度的關係,分母需減一,故樣本變異數改為\(S^2=\frac{1}{N-1}\sum\limits_{i = 1}^N {\left( {x_i - \bar x} \right)^2 }\)
- 標準差(Standard deviation):變異量因為用了距離的平方,所以變異數的單位是原資料單位的平方,故取開根號的值做為標準差,可與原資料擁有相同的單位,回到原來的距離單位,因此計算公式就成為 \(\sqrt {\frac{1}{N-1}\sum\limits_{i = 1}^N {\left( {x_i - \bar x} \right)^2 } }\)
- 百分位數(Percentile): 將一組數據由小排序到大,並計算其累計百分位,則某一百分位所對應數據的值就稱為這一百分位的百分位數,利用\(P_k\)表示至少有k%的資料小於或等於這個數,同時也有(100-k)%的資料大於或等於這個數,而這個數值就稱為這群資料的第k百分位數。例如某班的數學成績第60百分位數是71分,代表該班至少有60%的數學成績少於等於71分,也至少有40%的數學成績高於71分。
- 四分位數(Quartile):
- \(Q_1\)=第1四分位數,即第25百分位數\(P_{25}\)
- \(Q_2\)=第2四分位數,即第50百分位數\(P_{50}\);即中位數
- \(Q_3\)=第3四分位數,即第75百分位數\(P_{75}\)
- 四分位距(Interquartile range;IQR):第三個四分位數和第一個四分位數的差值(即\(Q_3\)與\(Q_1\)的差距),與上述的變異數、標準差一樣,表示統計資料中的分散情形。
◎ 分佈形狀(shape statistics)
兩個被稱為“形狀”統計量而被用以描述料分佈的形狀者分別為『偏度(skewness)』和『峰度(kurtosis)』。- 偏斜度(skewness):偏斜度是測量資料分佈中的對稱性程度,或說偏離對稱性的測量,亦即資料分布偏斜的方向與程度,一個對稱資料集的偏度等於0,所以一個常態分配具有趨近於零的偏度,若是右偏分布,又稱為正偏分布,偏度>0,表示有少數幾筆資料值很大,故平均數>中位數。相反的的是左偏分布,也稱之為負偏分布,其偏度<0,代表有少數幾筆資料值很小,故平均數<中位數。偏度有好幾種數學定義,一個常被統計套裝軟體或Excel採用的adjusted Fisher-Pearson standardized moment coefficient簡化樣本公式,其計算式為: $$ \frac{n}{(n-1)(n-2)}\sum_{i=1}^n(\frac{x_i-\bar x}{s})^3 $$ 偏度會影響到集中趨勢統計量-平均數、中位數和眾數的排列位置,
- 峰度(kurtosis):峰度是衡量某個分布與標準常態分布而言,其峰值高低的程度。其中尖峰態(Leptokurtic)就是比常態分布更尖細的一種分布,低峰態(Platykurtic)則是指比常態分布平坦的一種分布,對於峰度而言,常態分布的峰度正好等於3,尖峰態的峰度大於3,低峰態的峰度小於3,使用 Python SciPy.stats時Fisher參數設定若採用了fisher=True的定義(預設),則常態分配的峰度為0,所以大於0的正數就是尖峰態,小於0的負數就是低峰態,如果使用了Pearson的定義(fisher=False),則常態分配的峰度為3,所以大於3的正數就是尖峰態,小於3的負數就是低峰態。 $$ \frac{n(+1)}{(n-1)(n-2)(-3)}\sum_{i=1}^n(\frac{x_i-\bar x}{s})^4-\frac{3(n-1)^2}{(n-2)(n-3)} $$
Python統計程式庫
Python語言除了有內建的統計程式庫外,還有多個常用的第三方數值與統計相關的程式庫,本文程式中用到的程式庫與模組包括了:- statistics:這是內建的Python程式庫,支援基本的描述性統計。如果您的資料集不大也無特殊的統計量需求,或者並不想額外安裝與使用其它第三方的程式庫,則可以直接在Python程式中使用它。
- Numpy:這是專為數值運算目的所提供的第三方程式庫,主要是支援高階大量維度陣列與矩陣運算,該程式庫包含許多用於統計分析的函式。
- SciPy:基於Numpy的第三方科學運算用途程式庫,它提供了許多數學功能,模組包括了最佳化、線性代數、積分、插值、特殊函數、快速傅立葉變換、訊號處理和圖像處理、常微分方程式求解和其他科學與工程中常用的計算,當然也包含了用於統計分析的scipy.stats。
- Pandas :另一個基於Numpy的第三方數值運算用途的程式庫,可使用series物件處理一維資料,使用DataFrame物件處理多維資料,如關聯性資料庫與 CSV檔案。
- Matplotlib :第三方的資料作圖程式庫,可提供Numpy、Scipy和Pandas資料處理上的視覺化和各種統計圖能力。
撰寫Python 語言計算描述統計量
舉凡資料科學與探勘皆需先蒐集研究資料,接下來利用適當的分析工具進行資料預處理,然後分析之。
◎ 開啟資料檔案(central tendency)
以Python語言平台分析資料,需先開啟資料集檔案,多變數的資料檔案格式常見有逗號隔開的CSV檔或TAB/空白隔開的文字檔,使用Python 的csv模組可以輕易的開啟和讀寫各種符號隔開變數的文字檔,您可以先以萬國碼的方式開啟檔案 cvsfile=open(filename, newline='',encoding="utf-8")
,然後再利用csv模組的reader讀取CSV檔案, reader = csv.Reader(cvsfile)
,若CSV檔第一列為欄位名稱,則可改以DictReader函式以dictionary(字典)方式讀取,如此就會自動把第一列(row)視為欄位的名稱,將第二列以後的每一列轉為字典內容,以後便可以直接使用欄位名稱來存取資料,若檔案內的欄位區隔符號並非標準的逗號,則加上參數 delimiter來定義不同的分隔符號,若欄位間有多個空白存在,也可利用skipinitialspace=True參數來讓分隔符後的空白立即被忽略,程式碼可為:reader = csv.DictReader(cvefile, delimiter=' ', skipinitialspace=True)
。
df = pd.read_csv(filename)
,況且pandas可支援提供的資料結構包含以Series物件處理時間序列的一維資料,以DataFrame處理有列索引與欄標籤的二維資料集,以及Panel物件處理有資料、索引與欄標籤的三維資料集,相當有彈性。 ◎ 以基本數學函式計算描述統計量
由於描述統計大多是簡單的算式,時間複雜度也多是O(n)或O(n log n),所以不需要依賴特別的程式庫函式,直接使用Python的基本數學運算函式即可,底下範例為一間公司的員工的姓別、身高、體重與年齡的CSV檔案的資料集,只要利用基本的加減乘除、平方、排序等數學函式即可算出平均數、中位數、眾數、變異數與標準差等最基本的描述統計量,完全無須使用到任何額外的數值或統計科學程式庫。
姓別,身高,年齡,體重 女,155,21,38 男,165,29,58 女,159,31,45 男,158,45,68 女,165,50,54 男,175,55,78 女,169,35,58 男,170,33,62 女,165,65,62 男,185,28,90 女,165,45,58 男,182,28,79 女,164,44,57 男,188,38,94 女,155,44,51 男,160,55,52 女,165,60,45 男,175,33,78 女,172,45,68 男,177,21,64 |
import csv weight = [] with cvsfile: # 中位數需先判斷奇偶數的筆數 #眾數以count和max函式找出最高出現次數者 #利用先前計算的mean來計算變異數,變異數再開根號得到標準差 print("身高、體重與年齡的變異數分別為 %.3f、%.3f、%.3f" %(var1,var2,var3)) |
請輸入要計算的數據檔案:c:\weight.csv |
◎ 呼叫適當的程式庫來計算描述統計量
雖然您可以如上例的方法,依據公式來撰寫計算各種描述統計量的程式碼,但大部份的情況下,直接呼叫適當程式庫的函式較為方便而直接,在Python語言中,除了內建無需安裝的statistics程式庫外,還有Numpy、Scipy與Pandas皆有支援描述統計之相關函式或方法,底下程式碼範例則是利用多種不同程式庫的函式所計算「體重」之描述統計量,這個例子中的資料集格式改為空白隔開的文字檔 ,讀取檔案時可利用 skipinitialspace=True 參數來避免欄位間存在多個空白所導致的錯誤。而呼叫Scipy.stats程式庫中的skew()與kurtosis()函式可計算資料分佈的偏度與峰度,第二個參數bias=False代表要計算的為樣本偏度與峰度,並非母體。
import csv |
請輸入要計算的數據檔案:c:\weight1.csv |
從結果得知平均數大於中位數以及偏度大於零來看,可知道這個樣本分配為右偏分佈,而Fisher峰態小於零也表示為較平坦的低峰態。
◎ 統計圖
除了計算各種描述統計量的數字之外,您還可以使用圖形的方法來呈現,描述和匯總數據的概觀,而以圖形直觀地觀察資料集有時候反倒會比數字的顯示更能提供有價值的資訊與判斷。Python語言常用的資料視覺化套件包括了Matplotlib、Seaborn、Plotly .....等。
- 箱形圖(Box plots):箱形圖基本上是由五個數值點組成:
- 最小值(min):最低資料點,但不能包括任何離群值或極端值
- 第一四分位數(Q1;涵蓋25%之資料)
- 中位數(median)(Q2;涵蓋50%之資料)
- 第三四分位數(Q3;涵蓋75%之資料)
- 最大值(max):最高資料點,但不能包括任何離群值或極端值
Python語言中可以利用matplotlib程式庫axes物件的boxplot()方法來繪出箱形圖,除此之外,利用pandas程式庫中的Series.plot()、DataFrame.plot()或DataFrame.boxplot()方法也可以繪出箱形圖,底下範例仍是將本文的資料集以matplotlib程式庫繪出箱形圖,使用時須先匯入pyplot子模組並設別名為plt:
import matplotlib.pyplot as plt
,而程式碼主要是透過.boxplot()和傳遞的參數來執行繪製箱形圖的工作,而boxplot參數包括了:- 第一個參數是資料,例子中繪製資料集中的身高、體重和年齡三個變量的箱形圖
- vert:將箱形圖的方向設置為水平或垂直,預設方向為垂直方向
- showmeans:設定是否顯示平均數
- meanline:平均數是以線或點來表示,建議以線來表達,不僅較清楚,也易於與中位數比較
- labels:是否顯示資料的標籤名稱
- patch_artist:箱子是否塗色
- medianprops:中位線的顏色與線寬度
- meanprops:平均線的顏色與線寬度
import csv import os.path import matplotlib.pyplot as plt weight = [] height = [] age = [ ] # 輸入路徑以開啟 CSV 檔案 while True: filename = input('請輸入要計算的數據檔案:') if os.path.exists(filename): cvsfile=open(filename, newline='',encoding="utf-8") break else: print('您輸入的檔案不存在,請重新輸入') continue with cvsfile: |
依此程式碼所繪出的箱形圖如下所示:
- 直方圖(Histogram):直方圖是一種表達一組資料的近似分布情況的二維統計圖,它的兩個坐標分別是統計樣本值和其出現頻率或次數,適合用來表現不同的數據值在數量上的上下變動情況,直方圖用於連續性,習慣上將已排序的資料集值劃分為數個範圍來間隔,這個數據範圍常稱之為bins ,而每個 bin的上下限值稱為邊緣 (edge), Python畫直方圖 時可先使用 numpy.histogram(資料集,分組的bin數量) 來定義資料集所有劃分的分組數(bin數量)(預設是分成十組範圍),則函式會傳回每組的次數和邊緣值陣列,然後再利用Matplotlib.pyplot.hist()函式傳入資料集與histogram計算出來的邊緣值以圖形方式顯示。
import csv
import os.path
import matplotlib.pyplot as plt
import numpy
weight = []
height = []
age = [ ]
# 輸入路徑以開啟 CSV 檔案
while True:
filename = input('請輸入要計算的數據檔案:')
if os.path.exists(filename):
cvsfile=open(filename, newline='',encoding="utf-8")
break
else:
print('您輸入的檔案不存在,請重新輸入')
continuewith cvsfile:
#以字典方式讀取資料將分析所需的三個欄位存放三個串列
reader = csv.DictReader(cvsfile)
for row in reader:
weight.append(int(row['體重']))
height.append(int(row['身高']))
age.append(int(row['年齡']))
#設定三組資料分成15組等寬距離的間隔,預設為10組
#傳回15組直方圖的值(次數)之陣列在hist以及15組邊緣在bin_edge
hist1, bin_edge1 = numpy.histogram(height, bins=15)
hist2, bin_edge2 = numpy.histogram(weight, bins=15)
hist3, bin_edge3 = numpy.histogram(age, bins=15)
#在構圖上建立三個平行排列(1x3)的子圖,並指定圖的尺吋為12英吋寬度和6吋高度
fig, ax = plt.subplots(1,3,figsize=(12,6))
#依序以不同顏色繪出身高、體重與年齡的直方圖
ax[0].hist(height, bin_edge1, cumulative=False,color="red")
ax[0].set_xlabel('height')
ax[0].set_ylabel('Frequency')
ax[1].hist(weight, bin_edge2, cumulative=False,color="green")
ax[1].set_xlabel('weight')
ax[1].set_ylabel('Frequency')
ax[2].hist(age, bin_edge3, cumulative=False,color="blue")
ax[2].set_xlabel('age')
ax[2].set_ylabel('Frequency')
fig.tight_layout()
plt.show()
三個平行排列的直方圖繪出結果如下:
結語
在大數據和人工智慧時代,您必須知道蒐集到的資料集如何計算其描述性統計量,以描述性統計來對資料集做一初步的了解,了解資料集變數的集中趨勢,分散的程度以及分佈的形狀,Python的許多程式庫支援了各種不同的數值分析和統計機率函式,基本的描述性統計量大多涵蓋其內,因此了解如何正確使用與呼叫這些統計函式以產生正確的統計結果為最基礎的課題,更重要的卻是如何看懂並分析這些統計結果以判別如何更進一步的採用適合資料集的演算法或推論統計。