做者|GUEST BLOG
編譯|VK
來源|Analytics Vidhyapython
「事實是每一個人都相信的簡單陳述。也就是事實是沒有錯的,除非它被人發現了錯誤。假設有一個沒人願意相信的建議,那麼它要直到被發現有效的時候才能成爲事實。」 –愛德華·泰勒
咱們正在應對一場空前規模的流行病。全世界的研究人員都在瘋狂地試圖開發一種疫苗或COVID-19的治療方法,而醫生們正試圖阻止這種流行病席捲整個世界。git
我最近有了一個想法,把個人統計知識應用到這些大量COVID數據中。github
考慮這樣一個場景:醫生有四種醫療方法來治療病人。一旦咱們有了測試結果,用最少時間治癒病人的治療會是最好的方法。api
但若是這些病人中的一些已經部分治癒,或者其餘藥物已經在治療他們呢?app
爲了做出一個有信心和可靠的決定,咱們須要證據來支持咱們的作法。這就是方差分析的概念發揮做用的地方。dom
在本文中,我將向你介紹方差分析測試及其用於作出更好決策的不一樣類型。我將在Python中演示每種類型的ANOVA(方差分析)測試,以可視化它們並處理COVID-19數據。機器學習
注意:你必須瞭解統計學的基本知識才能理解這個主題。最好了解t檢驗和假設檢驗。函數
方差分析,或稱方差分析,能夠看做是兩組以上的t檢驗的推廣。獨立t檢驗用於比較兩組之間的條件平均值。當咱們想比較兩組以上患者的病情平均值時,使用方差分析。學習
方差分析測試模型中某個地方的平均值是否存在差別(測試是否存在總體效應),但它不能告訴咱們差別在哪裏(若是存在)。爲了找出兩組之間的區別,咱們必須進行過後檢驗。測試
要執行任何測試,咱們首先須要定義原假設和替代假設:
基本上,方差分析是經過比較兩種類型的變化來完成的,即樣本均值之間的變化,以及每一個樣本內部的變化。如下公式表示單向Anova測試統計數據。
ANOVA公式的結果,即F統計量(也稱爲F比率),容許對多組數據進行分析,以肯定樣本之間和樣本內部的可變性。
單向ANOVA的公式能夠這樣寫:
當咱們繪製ANOVA表時,上面的全部組成部分均可以以下所示:
通常來講,若是與F相關聯的p值小於0.05,則將拒絕原假設並支持替代假設。若是原假設被拒絕,咱們能夠得出結論,全部組的均值不相等。
注:若是被測組之間不存在真正的差別,也就是所謂的零假設,那麼方差分析的F比統計結果將接近1。
在進行方差分析以前,咱們須要作一些假設:
方差同質性的假設能夠用Levene檢驗或Brown-Forsythe檢驗來檢驗。分數分佈的正態性能夠用直方圖、偏度和峯度值來檢驗,也能夠用Shapiro-Wilk或Kolmogorov-Smirnov或Q-Q圖來檢驗。獨立性的假設能夠根據研究設計來肯定。
值得注意的是,方差分析對於假設獨立性的違規行爲並不強大。這就是說,即便你違反了同質性或正態性的假設,你也能夠進行測試並基本相信結果。
可是,若是違反了獨立性假設,方差分析的結果是無效的。通常來講,在違反同質性的狀況下,若是具備相同大小的組,則分析被認爲是可靠的。對於違反正態性的狀況,若是樣本量較大,繼續進行方差分析一般是能夠的。
單向方差分析:單向方差分析只有一個自變量
雙向方差分析:雙向方差分析(也稱爲因子方差分析)是指使用兩個獨立變量的方差分析
N向方差分析:一個研究者也可使用兩個以上的自變量,這是一個N向方差分析(N是你擁有的自變量的數量),也就是MANOVA檢驗。
你可能常常聽到關於方差分析的複製和不復制。讓咱們瞭解這些是什麼:
具備複製功能的雙向ANOVA:兩個小組和這些小組的成員所作的不僅是一件事情
雙向ANOVA(無複製):只有一個組而且對同一組進行雙重測試時使用
當咱們進行方差分析時,咱們試圖肯定各組之間是否存在統計學上的顯著差別。若是咱們發現存在差別,則須要檢查組差別的位置。
基本上,過後檢驗告訴研究者哪些組彼此不一樣。
此時,你能夠運行過後檢驗,這是t檢驗,用於檢驗組之間的均值差別。能夠進行多個比較測試來控制I型錯誤率,包括Bonferroni、Scheffe、Dunnet和Tukey測試。
如今,讓咱們用一些真實的數據來理解每種類型的方差分析測試,並使用Python。
我從一個正在進行的Kaggle競賽中下載了這些數據:https://www.kaggle.com/sudala...
在此測試中,咱們將嘗試分析區域或狀態的密度與日冕例數之間的關係。所以,咱們將根據每一個州的人口密度來映射每一個州。
首先導入全部必需的庫和數據:
import pandas as pd import numpy as np import scipy.stats as stats import os import random import statsmodels.api as sm import statsmodels.stats.multicomp from statsmodels.formula.api import ols from statsmodels.stats.anova import anova_lm import matplotlib.pyplot as plt from scipy import stats import seaborn as sns
從目錄加載數據:
StatewiseTestingDetails=pd.read_csv('./StatewiseTestingDetails.csv') population_india_census2011=pd.read_csv('./population_india_census2011.csv')
StatewiseTestingDetails包含有關每一個州一天中陽性和陰性病例總數的信息。而human_india_census2011包含有關每一個州的密度的信息以及有關人口的其餘相關信息。
population_india_census2011.head() StatewiseTestingDetails.head() #瞭解數據 StatewiseTestingDetails['Positive'].sort_values().head() #排序
從上面的代碼片斷中,咱們能夠看到有幾個州在一天內有0個日冕案例或沒有日冕案例。因此讓咱們看看這樣的州:
StatewiseTestingDetails['State'][StatewiseTestingDetails['Positive']==1].unique()
咱們看到,Nagaland和Sikkim在一天內也沒有日冕病例。另外一方面,Arunachal和Mizoram一天只有一個日冕病例。
估算缺失值:咱們注意到「Positive」列中有許多缺失值。所以,讓咱們用每一個州的Positive中值來估算這些缺失的值:
stateMedianData=StatewiseTestingDetails.groupby('State')[['Positive']].median().reset_index().rename(columns={'Positive':'Median'}) stateMedianData.head() for index,row in StatewiseTestingDetails.iterrows(): if pd.isnull(row['Positive']): StatewiseTestingDetails['Positive'][index]=int(stateMedianData['Median'][stateMedianData['State']==row['State']]) #合併StatewiseTestingDetails & population_india_census2011 data=pd.merge(StatewiseTestingDetails,population_india_census2011,on='State')
如今咱們能夠編寫一個函數,根據每一個州的密度建立一個密度組bucket,其中Dense1<Dense2<Dense3<Dense4:
def densityCheck(data): data['density_Group']=0 for index,row in data.iterrows(): status=None i=row['Density'].split('/')[0] try: if (',' in i): i=int(i.split(',')[0]+i.split(',')[1]) elif ('.' in i): i=round(float(i)) else: i=int(i) except ValueError as err: pass try: if (0<i<=300): status='Dense1' elif (300<i<=600): status='Dense2' elif (600<i<=900): status='Dense3' else: status='Dense4' except ValueError as err: pass data['density_Group'].iloc[index]=status return data
如今,用密度組映射每一個州。咱們能夠導出這些數據,以便之後在雙向方差分析測試中使用:
data=densityCheck(data) #導出 stateDensity=data[['State','density_Group']].drop_duplicates().sort_values(by='State')
讓咱們對能夠用於方差分析測試的數據集進行從新排列:
df=pd.DataFrame({'Dense1':data[data['density_Group']=='Dense1']['Positive'], 'Dense2':data[data['density_Group']=='Dense2']['Positive'], 'Dense3':data[data['density_Group']=='Dense3']['Positive'], 'Dense4':data[data['density_Group']=='Dense4']['Positive']})
咱們的ANOVA檢驗假設之一是應隨機選擇樣本,而且樣本應接近高斯分佈。所以,讓咱們從每一個因子或水平中選擇10個隨機樣本:
np.random.seed(1234) dataNew=pd.DataFrame({'Dense1':random.sample(list(data['Positive'][data['density_Group']=='Dense1']), 10), 'Dense2':random.sample(list(data['Positive'][data['density_Group']=='Dense1']), 10), 'Dense3':random.sample(list(data['Positive'][data['density_Group']=='Dense1']), 10), 'Dense4':random.sample(list(data['Positive'][data['density_Group']=='Dense1']), 10)})
讓咱們繪製日冕案例數量的密度分佈圖,以檢查它們在不一樣密度組中的分佈:
咱們能夠清楚地看到數據不遵循高斯分佈。
有不一樣的數據轉換方法可使數據接近高斯分佈。咱們進行Box-Cox變換:
dataNew['Dense1'],fitted_lambda = stats.boxcox(dataNew['Dense1']) dataNew['Dense2'],fitted_lambda = stats.boxcox(dataNew['Dense2']) dataNew['Dense3'],fitted_lambda = stats.boxcox(dataNew['Dense3']) dataNew['Dense4'],fitted_lambda = stats.boxcox(dataNew['Dense4'])
如今讓咱們再次繪製它們的分佈圖來檢查:
Python中有兩種方法能夠執行ANOVA測試。一個是stats.f_oneway()方法:
F, p = stats.f_oneway(dataNew['Dense1'],dataNew['Dense2'],dataNew['Dense3'],dataNew['Dense4']) ## 看看總體模型是否重要 print('F-Statistic=%.3f, p=%.3f' % (F, p))
咱們發現p值<0.05。所以,咱們能夠拒絕零假設——不一樣密度組之間沒有差別。
正如咱們在迴歸中所知道的,咱們能夠對每一個輸入變量進行迴歸,並檢查其對目標變量的影響。因此,咱們將遵循一樣的方法,咱們在線性迴歸中遵循的方法。
model = ols('Count ~ C(density_Group)', newDf).fit() model.summary()
## 看看總體模型是否重要 print(f"Overall model F({model.df_model: .0f},{model.df_resid: .0f}) = {model.fvalue: .3f}, p = {model.f_pvalue: .4f}") #建立方差分析表 res = sm.stats.anova_lm(model, typ= 2) res
從以上輸出結果能夠看出,p值小於0.05。所以,咱們能夠拒毫不同密度組之間沒有差別的零假設。
F-statistic= 5.817,p-value= 0.002,這代表density_Group對日冕陽性病例有整體顯着影響。可是,咱們尚不知道desnity_groups之間的區別在哪裏。所以,基於p值,咱們能夠拒絕H0;就面積密度和日冕例數而言,沒有顯着差別。
當咱們進行方差分析時,咱們試圖肯定各組之間是否存在統計學上的顯着差別。那麼,若是咱們發現統計學意義呢?
若是發現存在差別,則須要檢查組差別的位置。所以,咱們將使用Tukey HSD測試來肯定差別所在:
mc = statsmodels.stats.multicomp.MultiComparison(newDf['Count'],newDf['density_Group']) mc_results = mc.tukeyhsd() print(mc_results)
Tuckey HSD測試清楚地代表,Group1 – Group3,Group1 – Group4,Group2 – Group3和Group3 – Group4之間存在顯着差別。
這代表,除上述兩組外,全部其餘日冕病例數的成對比較均拒絕零假設,且無統計學顯著性差別。
當使用線性迴歸和方差分析模型時,假設與殘差有關,而不是變量自己。
### 正態性假設檢查 w, pvalue = stats.shapiro(model.resid) print(w, pvalue)
從上面的代碼片斷中,咱們看到全部密度組的p值都大於0.05。所以,咱們能夠得出結論,它們遵循高斯分佈。
咱們可使用Q-Q圖來檢驗這個假設:
res = model.resid fig = sm.qqplot(res, line='s') plt.show()
從上圖中,咱們看到全部數據點都靠近45度線,所以咱們能夠得出結論,它遵循正態分佈。
應針對分類變量的每一個級別檢查方差假設的同質性。咱們可使用Levene檢驗來檢驗組之間的均等方差。
w, pvalue = stats.bartlett(newDf['Count'][newDf['density_Group']=='Dense1'], newDf['Count'][newDf['density_Group']=='Dense2'] , newDf['Count'][newDf['density_Group']=='Dense3'], newDf['Count'][newDf['density_Group']=='Dense4']) print(w, pvalue) ## Levene 方差測試 stats.levene(dataNew['Dense1'],dataNew['Dense2'],dataNew['Dense3'],dataNew['Dense4'])
咱們發現全部密度組的p值都大於0.05。所以,咱們能夠得出結論,各組具備相等的方差。
一樣,使用相同的數據集,咱們將試圖瞭解一個地區或州的密度、人口年齡和日冕病例數量之間是否存在顯著關係。所以,咱們將根據居住在其中的人口密度繪製每一個州的地圖。
讓咱們導入數據並檢查是否存在任何數據歧義:
individualDetails=pd.read_csv('./individualDetails.csv',parse_dates=[2]) stateDensity=pd.read_csv('./stateDensity.csv')
從上面的代碼片斷中,咱們能夠看到沒有感染嬰兒的記錄。接下來,檢查數據中是否缺乏值:
individualDetails.isna().sum() print('Percentage of missing values in age & gender columns respectively :', \ (individualDetails['age'].isna().sum()/individualDetails.shape[0])*100,'%',\ (individualDetails['gender'].isna().sum()/individualDetails.shape[0])*100,'%')
咱們發如今年齡和性別欄中分別有超過91%和80%的條目丟失。因此咱們須要設計一種方法來估算它們。
在這裏,我將以各州的性別中位數和各州的性別中位數估算年齡。所以,我將計算中位數和衆數:
ageMedianPerState=individualDetails[~individualDetails['age'].isna()] ageMedianPerState['age']=ageMedianPerState['age'].astype(str).astype(int) ageMedianPerState=ageMedianPerState.groupby('State')[['age']].median().reset_index() ageMedianPerState['age']=ageMedianPerState['age'].apply(lambda x:math.ceil(x)) #經過COVID-19查找每一個州的最常感染的性別 genderModePerState=individualDetails.groupby(['State'])['gender'].agg(pd.Series.mode).to_frame().reset_index() #這沒有得到有關整體性別的信息性別 genderModePerState=genderModePerState[genderModePerState['State']!='Arunachal Pradesh']
#如今在年齡和性別欄填充丟失的值 for index,row in individualDetails.iterrows(): if row['State']=='Arunachal Pradesh': individualDetails.drop([index],inplace=True) continue if pd.isnull(row['age']): individualDetails['age'][index]=list(ageMedianPerState['age'][ageMedianPerState['State']=='West Bengal'].values)[0] if pd.isnull(row['gender']): if len(genderModePerState['gender'][genderModePerState['State']==row['State']].values)>0: individualDetails['gender'][index]=(genderModePerState['gender'][genderModePerState['State']==row['State']].values[0])
如今,讓咱們合併individualDetails和stateDensity數據幀,爲咱們建立一個總體數據集:
data = pd.merge(individualDetails,stateDensity,on='State',how='left').reset_index(drop=True)
如今咱們能夠建立年齡組桶:
data.dropna(subset=['density_Group'],inplace=True) data.reset_index(drop=True,inplace=True)
合併數據以得到一個數據集,其中每一個人都映射了他們的年齡組和各自的州密度組:
patient_Count=data.groupby(['diagnosed_date','density_Group'])[['diagnosed_date']].count().\ rename(columns={'diagnosed_date':'Count'}).reset_index() data=pd.merge(data,patient_Count,on=['diagnosed_date','density_Group'],how='inner') newData=data.groupby(['density_Group','age_Group'])['Count'].apply(list).reset_index() newData.head() np.random.seed(1234) AnovaData=pd.DataFrame(columns=['density_Group','age_Group','Count']) for index,row in newData.iterrows(): count=17 tempDf=pd.DataFrame(index=range(0,count),columns=['density_Group','age_Group','Count']) tempDf['age_Group']=newData['age_Group'][index] tempDf['density_Group']=newData['density_Group'][index] tempDf['Count']=random.sample(list(newData['Count'][index]),count) AnovaData=pd.concat([AnovaData,tempDf],axis=0)
檢查數據中Count列的分佈,並使用箱線圖方法檢查數據中是否存在異常值:
plt.hist(AnovaData['Count']) plt.show() sns.kdeplot(AnovaData['Count'],cumulative=False,bw=2)
咱們發如今咱們的數據中有許多異常值。甚至計數變量的分佈也不是高斯分佈。所以,咱們將使用Box-Cox變換方法來處理這種狀況:
sns.boxplot(x='age_Group', y='Count', data=AnovaData, palette="colorblind")
AnovaData['Count']=AnovaData['Count'].astype(int) ## 數據轉換 AnovaData['newCount'],fitted_lambda = stats.boxcox(AnovaData['Count']) import matplotlib.pyplot as plt sns.kdeplot(AnovaData['newCount'],cumulative=False,bw=2)
如今讓咱們使用OLS模型來檢驗咱們的假設:
## 擬合OlS模型-方法1 model2 = ols('newCount ~ C(age_Group)+ C(density_Group)', AnovaData).fit() print(f"Overall model F({model2.df_model: },{model2.df_resid: }) = {model2.fvalue: }, p = {model2.f_pvalue: }") model2.summary()
## 建立方差分析表 res2 = sm.stats.anova_lm(model2, typ=2) res2 #檢驗殘差的正態分佈 res = model2.resid fig = sm.qqplot(res, line='s') plt.show()
從上面的Q-Q圖,咱們能夠看到殘差幾乎是正態分佈的(儘管在最末端的點能夠被貼現)。所以,咱們能夠得出結論,它知足方差分析檢驗的正態性假設。
#方法2-檢查組之間的交互 formula = 'newCount ~ C(age_Group) *C(density_Group)' model = ols(formula, AnovaData).fit() model.summary()
aov_table = anova_lm(model, typ=2) print(aov_table.round(4))
經過ANOVA分析得到的日冕病例數,年齡組和密度組以及相互做用的P值具備統計學意義(P <0.05)。咱們得出結論,density_Group的類型顯着影響日冕的結果。
age_Group顯着影響日冕病例的結果,age_Group和density_Group的相互做用也顯着影響日冕病例的結果。
最後,讓咱們肯定哪些組在統計上是不一樣的。咱們將使用Tuckey HSD方法:
mc = statsmodels.stats.multicomp.MultiComparison(AnovaData['newCount'],AnovaData['density_Group']) mc_results = mc.tukeyhsd() print(mc_results)
mc = statsmodels.stats.multicomp.MultiComparison(AnovaData['newCount'],AnovaData['age_Group']) mc_results = mc.tukeyhsd() print(mc_results)
從上面的Tuckey HSD測試結果中,咱們能夠清楚地看到,密度組中的Group1 – Group3,Group1 – Group4與年齡組中的Young – Adult&Young –old組之間也存在顯着差別。
所以,Tukey HSD的上述結果代表,除上述組外,日冕病例數的全部其餘成對比較均拒絕了原假設,而且代表沒有統計學上的顯着差別。
在病毒大流行時期,我試着用一個相關的案例來解釋方差分析。你能夠克隆個人Github存儲庫來下載所有代碼和數據:https://github.com/Praveen76/...
原文連接:https://www.analyticsvidhya.c...
歡迎關注磐創AI博客站:
http://panchuang.net/
sklearn機器學習中文官方文檔:
http://sklearn123.com/
歡迎關注磐創博客資源彙總站:
http://docs.panchuang.net/