實例講解統計學基礎知識(2):描述性統計分析

作者:xxw9485
時間:2018/3/20
來源:https://www.jianshu.com/p/8982ad63eb85


描述性統計分析

下面將從統計量和統計圖兩種視角來觀察數據,查看了數據的中心趨勢、相對位置、離散程度和相關性。統計量包括:衡量中心趨勢的均值、中位數、衆數,衡量相對位置的分位數,衡量離散程度的方差和標準差,以及衡量相關性的Pearson相關係數。統計圖則包括直方圖、ECDF圖、箱圖和散點圖。

數據導入

新建py文件,導入相應的Python模塊和BRFSS數據。代碼如下:

import pandas as pd  # 導入各類模塊
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import brfss

df = brfss.ReadBrfss()  # 導入 BRFSS 數據

數據以DataFrame的格式存儲,每一行是一個受訪者的調查數據,每一列是一項屬性,我們先選取bmi和income兩列。其中bmi列代表BMI指數,用來衡量人的胖瘦程度,BMI指數越高表示人越胖。income列代表收入水平,這裏分了8級,分別用數字1到8代表,8級是年家庭收入超過7.5萬美元的人羣,在這裏我們將8級的人羣定義爲富人,其他1-7級的人羣定義爲普通人。

# 輸入
bmi_income = df[['bmi','income']].dropna()  # 選取bmi和income兩列數據,並捨棄缺失的數據。
print(bmi_income.head())  # 顯示前5行數據
# 輸出
     bmi  income
0  40.18     3.0
1  25.09     1.0
3  28.19     8.0
5  26.52     6.0
6  23.89     4.0

數據概況

使用info()方法,查看數據的全貌,可以得到該數據一共有343092行,每一列都是浮點類型數據,且沒有缺失值。

# 輸入
bmi_income.info()
# 輸出
<class 'pandas.core.frame.DataFrame'>
Int64Index: 343092 entries, 0 to 441455
Data columns (total 2 columns):
bmi       343092 non-null float64
income    343092 non-null float64
dtypes: float64(2)
memory usage: 7.9 MB

接着提取收入水平爲8級的富人們的bmi數據,存入變量bmi_rich中,相應的其他普通人的bmi數據存入變量bmi_ord中。用describe()方法查看這兩類人羣的bmi數據在統計方面的信息,包括樣本量(count)、均值(mean)、標準差(std)、最大(max)和最小(min)值,以及分位數。

# 輸入
bmi_rich = bmi_income[bmi_income.income == 8]['bmi']
bmi_ord = bmi_income[bmi_income.income != 8]['bmi']
print(bmi_rich.describe(),'\n')
print(bmi_ord.describe())
# 輸出
count    110259.000000
mean         27.450733
std           5.900353
min          12.050000
25%          23.690000
50%          26.570000
75%          30.040000
max          97.650000
Name: bmi, dtype: float64 

count    232833.000000
mean         28.537320
std           6.971436
min          12.020000
25%          24.030000
50%          27.370000
75%          31.620000
max          97.650000
Name: bmi, dtype: float64

中心趨勢

除了均值,還有中位數和衆數,都可以用來代表一組數據的中心趨勢。

均值

由於存儲兩類人羣的bmi數據bmi_rich和bmi_ord都是Pandas中的Series數據類型,所以我們使用mean()方法來求算數平均值。經計算,富人們的BMI指數均值爲27.45,普通人的則是28.54,從均值上看,似乎富人們更瘦一些。

# 輸入
mean_rich = bmi_rich.mean()  # 計算均值
mean_ord = bmi_ord.mean()
print('BMI mean of rich people: %.2f' % mean_rich)
print('BMI mean of ordinary people: %.2f' % mean_ord)
# 輸出
BMI mean of rich people: 27.45
BMI mean of ordinary people: 28.54
中位數

如果將數據從小到大按順序排列,那麼處於中間的那個數就是中位數。如果樣本總量是偶數,中間就存在兩個數,那麼中位數就是這二者的平均值。當數據中出現異常偏離中心的值時,中位數就比均值更具代表性。使用median()方法可計算中位數。

# 輸入
median_rich = bmi_rich.median()  # 計算中位數
median_ord = bmi_ord.median()
print('BMI median of rich people: %.2f' % median_rich)
print('BMI median of ordinary people: %.2f' % median_ord)
# 輸出
BMI median of rich people: 26.57
BMI median of ordinary people: 27.37
衆數

正如其名,衆數就是數量最多的那一個數,比如選舉中最多的那個票數,商家最暢銷產品的銷售量。衆數一般是用在不連續的分類數據中,但如果用在連續數據中,一般是將連續數據劃分成多個區間,統計每個區間的數據量,從而得出數量最多的那個區間。
在這裏,BMI指數本是連續數值,但因爲只精確到小數點後兩位,所以也可以將之看成是離散不連續的,又因爲我們數據的樣本量非常之大,所以這裏也可以用mode()得到bmi的衆數。

# 輸入
# 計算富人的衆數
mode_rich = bmi_rich.mode().iloc[0]
mode_count_rich = np.sum(bmi_rich == mode_rich)
print('BMI mode of rich people: %.2f (counts %d)' % (mode_rich, mode_count_rich))
# 計算普通人的衆數
mode_ord = bmi_ord.mode().iloc[0]
mode_count_ord = np.sum(bmi_ord == mode_ord)
print('BMI mode of ordinary people: %.2f (counts %d)' % (mode_ord, mode_count_ord))
# 輸出
BMI mode of rich people: 26.63 (counts 1246)
BMI mode of ordinary people: 26.63 (counts 2766)
直方圖

如果將BMI數據等分成若干個區間,統計落入每個區間的數據的數量,就可以得到下面的直方圖,橫軸代表BMI指數的值,縱軸是每個區間內數據量。直方圖可以反映數據的總體分佈情況,從圖中可以看出人們的BMI指數大致集中在20到40之間,當然也有異常接近100的人,只是數量非常少。同時也能非常直觀地找到衆數,就是最高的那個豎條所在的區間。值得注意的是,直方圖中區間劃分的不同,也會影響圖形的樣子和衆數,特別是在數據量較少的情況下。

fig = plt.figure(figsize=(14,4))
# 繪製富人bmi數據的直方圖
p1 = fig.add_subplot(121)
plt.hist(bmi_rich, bins=50, rwidth=0.9)
plt.xlabel('BMI')
plt.ylabel('Counts')
plt.title('BMI histogram of rich people')
# 繪製普通人bmi數據的直方圖
p2 = fig.add_subplot(122)
plt.hist(bmi_ord, bins=50, rwidth=0.9)
plt.xlabel('BMI')
plt.ylabel('Counts')
plt.title('BMI histogram of ordinary people')
# 展示圖形
plt.show()

image
爲了更清晰地比較兩類人羣的數據分佈,我們將上面兩個直方圖合在一起,同時截取了BMI取值在10到60之間的數據。用紫色代表的普通人羣的分佈總體上比用紅色代表的富人的分佈更向BMI值大的方向偏離,這讓我們似乎更確信富人更瘦一些,因爲現在讓我們得出結論的不是單單一個數值,而是許多數據組成的圖。

plt.hist(bmi_rich, bins=50, range=(10,60), normed=True, label='rich', alpha=0.4, color='red')
plt.hist(bmi_ord, bins=50, range=(10,60), normed=True, label='ordinary', alpha=0.4, color='blue')
plt.legend()
plt.xlabel('BMI')
plt.ylabel('probability density')
plt.title('BMI histogram')
plt.show()

image

偏度

仔細觀察BMI分佈的直方圖,雖然數值集中在20到40之間,但是在其右邊有一條細細長長的尾巴,我們稱這樣的分佈是右偏的,計算其偏度也是一個正數。在右偏分佈中,度量數據中心趨勢的三個量關係如下:衆數 < 中位數 < 均值。

# 計算衆數區間
bin_edge = np.arange(10,60,1)
counts, bins = np.histogram(bmi_rich, bin_edge)
mode_left = bins[np.argmax(counts)]
mode_right = bins[np.argmax(counts)+1]
mode_middle = (mode_left + mode_right) / 2
print('mode range: (%.2f, %.2f)' % (mode_left, mode_right))
print('median: %.2f' % median_rich)
print('mean: %.2f' % mean_rich)
# 計算偏度
print('skewness: %.2f' %bmi_rich.skew())
# 做圖
plt.axvline(x=mean_rich, linewidth=1, color='red', label='mean')
plt.axvline(x=median_rich, linewidth=1, color='green', label='median')
plt.axvline(x=mode_middle, linewidth=1, color='blue', label='mode')
plt.legend()
plt.hist(bmi_rich, bins=bin_edge, range=(10,60), rwidth=0.9, alpha=0.5)
plt.xlabel('BMI')
plt.ylabel('Counts')
plt.title('BMI distribution of rich people')
plt.show()
# 輸出
mode range: (25.00, 26.00)
median: 26.57
mean: 27.45
skewness: 2.58

image
既然有右偏,那自然也有左偏,其偏度爲負值,性質也與右偏相反。下面給出了我們研究的樣本人羣收入水平的分佈,是一個左偏的分佈。

# 計算偏度
print('skewness: %.2f' %bmi_income.income.skew())
# 做圖
bins = np.arange(1,10)
plt.hist(bmi_income.income, align='left', bins=bins, rwidth=0.9)
plt.title('income distribution')
plt.xlabel('income level')
plt.ylabel('counts')
plt.show()
# 輸出
skewness: -0.74

image

相對位置

ECDF圖

在比較兩類人羣的bmi數據時,我們先後使用了均值和直方圖,這兩者其實都是對數據信息的壓縮。均值將信息壓縮到一個數值,而丟棄了大部分信息量;相比之下直方圖則保留了更多的信息量,只是將數據壓縮到一個個連續的區間中。顯示所有的數據點則需要用到經驗累積分佈函數圖:ECDF(Empirical Cumulative Distribution Function)。
將BMI數據從小到大排列,並用排名除以總數計算每個數據點在所有數據中的位置佔比。比如總共100個數據中排第20位的數據,其位置佔比爲20/100=0.2。將所有的數據以BMI值爲橫座標,位置佔比數值爲縱座標描畫於圖中,就得到了ECDF圖。

# 計算數據的ECDF值
def ecdf(data): 
    x = np.sort(data)
    y = np.arange(1, len(x)+1) / len(x)
    return (x,y)
# 繪製ECDF圖
def plot_ecdf(data, xlabel=None , ylabel='ECDF', label=None):     
    x, y = ecdf(data)
    _ = plt.plot(x, y, marker='.', markersize=3, linestyle='none', label=label)
    _ = plt.legend(markerscale=4)
    _ = plt.xlabel(xlabel)
    _ = plt.ylabel(ylabel)
    plt.margins(0.02)

plot_ecdf(bmi_rich,label='rich')
plot_ecdf(bmi_ord, xlabel='BMI',label='ordinary')
plt.show()

image
ECDF圖中顯示了所有的數據點及其在樣本中所處的位置,從上圖中可以清晰地看到普通人羣(綠色點)比富人(藍色點)的分佈更靠右,即向BMI變大的方向偏移。

分位數

在ECDF圖中我們可以得到許多信息,比如最大和最小值,也可以得到任意比例所對應的分位數。比如中位數,就是佔比爲50%的分位數。另外時常用到的還有25%和75%所對應的四分位數,而這兩者的差值,稱爲IQR(Interquartile range),它可以看做樣本變異性的度量。

# 輸入
q1 = bmi_rich.quantile(0.25)
q2 = bmi_rich.quantile(0.5)
q3 = bmi_rich.quantile(0.75)
IQR = q3 - q1
print('min:  ', bmi_rich.min())
print('max:  ', bmi_rich.max())
print('25%:  ', q1)
print('50%:  ', q2)
print('75%:  ', q3)
print('IQR:   %.2f' % IQR)
# 輸出
min:   12.05
max:   97.65
25%:   23.69
50%:   26.57
75%:   30.04
IQR:   6.35
箱圖(box plot)

更直觀反映分位數的是箱圖,圖中直接畫出了中位數、四分位數和IQR,並且從中還能發現離羣值,它們是數據中異常大或異常小的數值。在箱圖的上下兩側分別有兩道籬笆,它們的數值分別是Q1-1.5IQR和Q3+1.5IQR,其中Q1,Q3是四分位數。而處於這兩道籬笆之外的數值可以看做異常值。

# 繪製箱圖
bmi_income['income_level'] = bmi_income.income.apply(lambda x: 'rich' if x==8 else 'ordinary')
sns.boxplot(x='income_level', y='bmi', data=bmi_income, palette="Set3")
plt.show()

image

離散度

在比較富人和普通人BMI的均值後,讓我們不敢妄下結論的還有一點,就是我們擔心這樣的差值是不是足夠大,大到足以超越每組人羣本身的波動性呢?

方差和標準差

數據圍繞均值的上下波動,也可以看做是數據的離散程度,我們使用方差和標準差來衡量。標準差是方差的平方根,代表數據中所有點距離均值的平均距離,其公式定義如下:
image
這裏分母中使用N-1而非N,是因爲當使用樣本數據推測總體的標準差時,需進行Bessel修正。另外可以使用var()和std()方法計算方差和標準差。

# 輸入
var_rich = bmi_rich.var()
std_rich = bmi_rich.std()
print(' For rich people: Variance = %.2f, Standard deviation = %.2f' % (var_rich, std_rich))
var_ord = bmi_ord.var()
std_ord = bmi_ord.std()
print(' For ordinary people: Variance = %.2f, Standard deviation = %.2f' % (var_ord, std_ord))
# 輸出
 For rich people: Variance = 34.81, Standard deviation = 5.90
 For ordinary people: Variance = 48.60, Standard deviation = 6.97
Cohen’s d

當考慮了樣本數據的離散度後,就能夠更精準的衡量兩類人羣BMI值的差異,即使用一個新的量:Cohen’s d,它可以簡單看做是均值的差值除以兩個樣本綜合的標準差。其公式定義如下:
image
根據上面的公式,我們定義函數cohen_d()來計算BMI數據的Cohen‘s數值。

# 輸入
def cohen_d(data1, data2):
    n1 = len(data1)
    n2 = len(data2)
    x1 = np.mean(data1)
    x2 = np.mean(data2)
    var1 = np.var(data1, ddof=1)
    var2 = np.var(data2, ddof=1)
    sp = np.sqrt(((n1-1)*var1+(n2-1)*var2)/(n1+n2-2))
    return (x1-x2)/sp

print("Cohen's d: %.3f" %cohen_d(bmi_rich, bmi_ord))
# 輸出
Cohen's d: -0.163

這裏得到的Cohen’s d的絕對值是0.163, 這個值是大是小呢?首先我們需要對Cohen’s d有一個大概的數值範圍概念,當它的值爲0.8代表有較大的差異,0.5位列中等,0.2較小,0.01則非常之小。所以這裏計算出的0.163代表兩類人羣的BMI值有差異,但是差異較小。

相關性

之前我們觀察的都是單個變量(主要是BMI指數)的統計學性質,接下來我們考察下兩個變量之間的關係。

協方差

協方差(Covariance)是衡量兩個變量的總體誤差,方差可以看做是兩個變量相同時的特殊情況。其公式如下:

image
下面使用 numpy 中的cov()函數來計算樣本人羣中身高和體重的協方差。

# 輸入
df2 = df[['height','weight', 'bmi' ]].dropna()  #從datafrme中取身高、體重和bmi三列數據
height = df2.height
weight = df2.weight
bmi = df2.bmi
print(np.cov(height,weight)) # 計算協方差
# 輸出
[[1.12563400e-02 1.08190764e+00]
 [1.08190764e+00 4.67153513e+02]]

得到的結果是一個2乘2的對稱矩陣,對角線上的數值分別代表兩個變量各自的方差,而處於第一行第二列的數值正是這兩個變量的協方差。

相關係數

瞭解了協方差的概念後,就可以使用Pearson相關係數來衡量兩個變量的相關性,它的定義是協方差除以兩個變量各自的標準差,公式如下:image
Pearson相關係數的取值範圍在-1到1之間,0代表無相關性,正數代表正相關,負數代表負相關,絕對值越大,相關性越高。
可以使用 numpy 中的corrcoef()函數計算身高和體重的相關係數。

# 輸入
df2 = df[['height','weight', 'bmi' ]].dropna()  #從datafrme中取身高、體重和bmi三列數據
height = df2.height
weight = df2.weight
a = np.corrcoef(height, weight)[0,1]  # 計算Pearson相關係數
print(a)
# 輸出
0.4718041740847724

得到0.47的相關係數,可見身高和體重之間是存在一些相關性的。我們將身高和體重分別做爲橫座標和縱座標,數據作於下方的散點圖中,可以看出隨着身高的增長,體重的總體趨勢有上升,但關係不是特別明顯。

# 繪製身高、體重的散點圖
plt.plot(height,weight, marker='.', linestyle='none', alpha = 0.05)
plt.xlabel('height (m)')
plt.ylabel('weight (kg)')
plt.title('correlation of weight and height')
plt.show()

image
同樣,我們計算得到BMI值和體重的相關係數爲0.87,有非常強烈的正相關性,從它們的散點圖中也能看出來。

# 輸入
# 計算BMI值和體重的相關係數
corr = np.corrcoef(weight, bmi)
print('Pearson correlation coefficient: %.2f' % corr[0,1] )
# 繪製BMI值和體重的散點圖
plt.plot(weight,bmi, marker='.', linestyle='none', alpha = 0.05)
plt.xlabel('weight (kg)')
plt.ylabel('BMI')
plt.title('correlation of weight and BMI')
plt.show()
# 輸出
Pearson correlation coefficient: 0.87

image
另外,下面求得BMI和身高的相關係數只有-0.006,微弱到可以忽略的程度。其實從常識中也可以判斷,BMI既然是胖瘦的衡量,高的人並不一定胖啊。

# 輸入
# 計算BMI值和身高的相關係數
corr = np.corrcoef(height, bmi)
print('Pearson correlation coefficient: %.4f' % corr[0,1] )
# 繪製BMI值和身高的散點圖
plt.plot(height,bmi, marker='.', linestyle='none', alpha = 0.05)
plt.xlabel('height (m)')
plt.ylabel('BMI')
plt.title('correlation of BMI and height')
plt.show()
# 輸出
Pearson correlation coefficient: -0.0060

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章