統計思維:程序員數學之概率統計(第2版):第1章 探索性數據分析


第1章 探索性數據分析

如果能將數據與實際方法相結合,就可以在存在不確定性時解答問題並指導決策,這就是本書的主題。

舉個例子。我的妻子在懷第一胎時,我聽到了一個問題:第一胎是不是經常晚於預產期出生?下面所給出的案例研究就是由這個問題引出的。

如果用谷歌搜索這個問題,會看到大量的討論。有人認為第一胎的生產日期確實經常晚於預產期,有人認為這是無稽之談,還有人認為恰恰相反,第一胎常常會早產。

在很多此類討論中,人們會提供數據來支持自己的觀點。我發現很多論據是下面這樣的。

“我有兩個朋友最近都剛生了第一個孩子,她們都是超過預產期差不多兩周才出現臨產征兆或進行催產的。”

“我的第一個孩子是過了預產期兩周才出生的,我覺得第二個孩子可能會早產兩周!”

“我認為這種說法不對,因為我姐姐是頭生子,而且是早產兒。我還有好些表兄妹也是這樣。”

這些說法都是基於未公開的數據,通常來自個人經驗,因此稱為軼事證據(anecdotal evidence)。在閑聊時講講軼事當然無可厚非,所以我並不是要批評以上那幾個人。

但是,我們可能需要更具說服力的證據以及更可靠的回答。如果按照這個標准進行衡量,軼事證據通常都靠不住,原因有如下幾點。

  • 觀測值數量較小
    如果第一胎的孕期的確偏長,這個時間差與正常的偏差相比可能很小。在這種情況下,我們可能需要比對大量的孕期數據,才能確定這種時間差確實存在。
  • 選擇數據時存在偏倚
    人們之所以參與這個問題的討論,有可能是因為自己的第一個孩子出生較晚。這樣的話,這個選擇數據的過程就會對結果產生影響。
  • 確認數據時存在偏倚
    贊同這種說法的人也許更可能提供例子進行佐證。持懷疑態度的人則更可能引用反例。
  • 不精確
    軼事通常都是個人經驗,經常會記錯、誤傳或者誤解等。

那我們該如何更好地回答這個問題呢?

1.1 統計學方法

為了解決軼事證據的局限性,我們將使用以下統計學工具。

  • 數據收集
    我們將使用大型的全國性調查數據,這個調查專門設計用於對美國人口進行有效的統計推斷。
  • 描述性統計
    得出統計量,對數據進行簡要的匯總,並評估可視化數據的不同方法。
  • 探索性數據分析
    尋找各種模式、差異,以及其他能夠解決我們感興趣的問題的特征,同時還將檢查數據的不一致性,發現局限性。
  • 估計
    使用樣本數據來估計一般總體的統計特征。
  • 假設檢驗
    如果看到明顯的效應,例如兩個群組之間存在差異,將衡量該效應是否是偶然產生的。

謹慎執行上面的步驟,並避免各種錯誤,我們就可以獲得合理性和准確性更高的結論。

1.2 全國家庭增長調查

從1973年起,美國疾病控制和預防中心(CDC)就開始進行全國家庭增長調查(NSFG,http://cdc.ogv/nchs/nsfg.htm),以收集“與家庭生活、婚姻狀況、妊娠情況、生育情況、避孕情況,以及兩性健康相關的信息。此項調查的結果用於……進行健康服務和健康教育項目的規划,以及對家庭、生育及健康情況進行統計研究”。

我們將使用這項調查收集到的數據研究第一胎是否出生較晚,並解答一些其他問題。為了有效地使用這些數據,我們必須理解這項研究是如何設計的。

全國家庭增長調查是一項橫截面(cross-sectional)研究,也就是說該研究捕獲的是一個群組在某一時刻的快照。在橫截面研究之外,最常見的是縱向(longitudinal)研究,指在一個時間段內重復觀察一個群組。

全國家庭增長調查進行過7次,每一次都稱為一個周期(cycle)。我們將使用第6次的數據,其時間段為2002年1月至2003年3月。

這項調查的目的是對一個總體(population)得出結論。全國家庭增長調查的目標總體是居住在美國、年齡在15~44歲的人。理想情況下,調查要收集這個總體中每個成員的數據,但這是不可能實現的。實際上,我們收集了這個總體的一個子集的數據,這個子集稱為樣本(sample)。參與調查的人稱為調查參與者(respondent)。

通常來說,橫截面研究應該是有代表性(representative)的,也就是說目標總體中每個成員參與調查的機會均等。這種理想條件在實踐中很難實現,但是進行調查的人員會竭盡所能滿足這個條件。

全國家庭增長調查不具有代表性,而是特意進行過度抽樣(oversample)。這項研究的設計者招募了拉美裔美國人、非洲裔美國人和青少年3個群組的參與者,每個群組的招募比例都超過其在美國人口中所占的比例,以確保各群組的參與者數量足夠多,從而進行有效的統計推斷。

當然,過度抽樣也有缺點,那就是不容易從調查的統計數據中得出關於總體的結論。我們稍后會對此進行討論。

在使用這種調查數據時,我們必須熟悉代碼本(codebook),這一點非常重要。代碼本記錄了一項研究的設計、使用的調查問題,以及調查中響應變量的編碼。你可以從美國疾病控制和預防中心的網站(http://www.cdc.gov/nchs/nsfg/nsfg_cycle6.htm)下載全國家庭增長調查數據的代碼本和使用手冊。

1.3 數據導入

本書所用的代碼和數據都可以通過GitHub(https://github.com/AllenDowney/ThinkStats2)獲取。前言中介紹了如何下載和使用這些代碼。

下載代碼后,你會得到一個名為ThinkStats2/code的文件夾,其中包含一個名為nsfg.py的文件。運行nsfg.py會讀取數據文件,執行測試,然后打印出一條消息,例如“All test passed”。

讓我們看看這個文件所執行的工作。第6次全國家庭增長調查的妊娠數據保存在名為2002FemPreg.dat.gz的文件中,這是一個純文本(ASCII碼)形式的gzip壓縮文件,有固定寬度的列。這個文件中的每一行都是一個記錄(record),包含一次妊娠的數據。

2002FemPreg.dct是一個Stata字典文件,記錄了數據文件的格式。Stata是一個統計軟件。Stata“字典”是由變量名、變量類型及標識變量位置的索引值組成的列表。

下面幾行摘自2002FemPreg.dct:

infile dictionary { 
_column
(1) str12 caseid %12s "RESPONDENT ID NUMBER"
_column
(13) byte pregordr %2f "PREGNANCY ORDER (NUMBER)"
}

這個字典描述了兩個變量:caseid是一個長度為12的字符串,代表調查參與者的ID;pregorder是一個單字節整數,說明這條記錄描述的是這位調查參與者的第幾次妊娠。

下載的代碼包含一個thinkstats2.py文件,這是一個Python模塊,包含了本書中用到的很多類和函數,其中有讀取Stats字典和全國家庭增長調查數據文件的函數。這兩個函數在nsfg.py中的用法如下:

def ReadFemPreg(dct_file='2002FemPreg.dct', 
dat_file
='2002FemPreg.dat.gz'):
dct
= thinkstats2.ReadStataDct(dct_file)
df
= dct.ReadFixedWidth(dat_file, compression='gzip')
CleanFemPreg(df)
return df

ReadStataDct的參數是字典文件名,返回值dct是一個FixedWidthVariables對象,其中包含從字典文件中得到的信息。dct對象提供ReadFixdWidth方法進行數據文件的讀取。

1.4 DataFrame

ReadFixedWidth方法返回一個DataFrame對象。DataFrame是pandas提供的基礎數據結構。pandas是一個Python數據和統計包,它的使用會貫穿本書。在DataFrame中,每個記錄為一行(在我們的例子中就是每個妊娠數據為一行),每個變量為一列。

除了數據,DataFrame還包含變量名和變量類型信息,並提供訪問和修改數據的方法。

如果打印df對象,你會看到其中行列的部分數據和DataFrame的大小:13 593行/記錄,244列/變量。

>>> import nsfg
>>> df = nsfg.ReadFemPreg()
>>> df
...
[13593 rows x 244 columns]

dfcolumns屬性將列名返回為一列Unicode字符串。

>>> df.columns
Index([u'caseid', u'pregordr', u'howpreg_n', u'howpreg_p', ... ])

df.columns的結果是一個Index對象,Index也是一個pandas數據結構。我們稍后會詳細介紹Index,現在可以暫時將其視為一個列表。

>>> df.columns[1]
'pregordr'

要訪問DataFrame中的一列,你可以將列名作為鍵值。

>>> pregordr = df['pregordr']
>>> type(pregordr)
<class 'pandas.core.series.Series'>

其結果是一個Series對象,這又是一個pandas數據結構。Series與Python列表類似,還能提供一些附加功能。打印一個Series對象會得到索引和對應的數值。

>>> pregordr
0 1
1 2
2 1
3 2
...
13590 3
13591 4
13592 5
Name: pregordr, Length: 13593, dtype: int64

這個示例中的索引是從0到13 592的整數,但通常索引可以使用任何可排序的數據類型。這個示例中的元素也是整數,但元素可以是任何類型的。

示例中的最后一行列出了變量名、Series長度和數據類型。int64是NumPy提供的類型之一。如果在32位機器上運行這個示例,得到的數據類型可能是int32

你可以使用整數的index和slice值訪問Series中的元素。

>>> pregordr[0]
1
>>> pregordr[2:5]
2 1
3 2
4 3
Name: pregordr, dtype: int64

index操作符的結果是int64,slice的結果還是一個Series。

你也可以使用點標記法來訪問DataFrame中的列。

>>> pregordr = df.pregordr

只有當列名為合法的Python標識符時(即以字母開頭,不包含空格等),才能使用這種寫法。

1.5 變量

我們已經使用了全國家庭增長調查數據集中的兩個變量——caseidpregordr,還看到數據集中共有244個變量。本書的探索性分析用到如下變量。

  • caseid:調查參與者的整數ID。
  • prglength:妊娠周數,是一個整數。
  • outcome:懷孕結果的整數代碼。1代表成功生產。
  • pregordr:妊娠的順序號。例如,一位調查參與者的第一次妊娠為1,第二次為2,以此類推。
  • birthord:成功生產的順序號,一位調查參與者的第一個孩子代碼為1,以此類推。對沒有成功生產的其他妊娠結果,此字段為空。
  • birthwgt_lbbirthwgt_oz:新生兒體重的磅部分數值和盎司部分數值。
  • agepreg:妊娠結束時母親的年齡。
  • finalwgt:調查參與者的統計權重。這是一個浮點數,表示這位調查參與者在全美人口中代表的人數。

如果你仔細閱讀了代碼本,就會發現這些變量中很多都是重編碼(recode),也就是說這些不是調查收集的原始數據(raw data),而是使用原始數據計算得到的。

例如,如果成功生產,prglngth的值就與原始變量wksgest(妊娠周數)相等;否則,prglngth的值估算為mosgest * 4.33(妊娠月數乘以一個月的平均周數)。

重編碼通常都基於一定的邏輯,這種邏輯用於檢查數據的一致性和准確性。一般情況下,如果數據中存在重編碼,我們就直接使用,除非有特殊的原因需要自己處理原始數據。

1.6 數據變換

導入調查數據時,經常需要檢查數據中是否存在錯誤,處理特殊值,將數據轉換為不同的格式並進行計算。這些操作都稱為數據清洗(data cleaning)。

nsfg.py包含一個CleanFemPreg函數,用於清洗計划使用的變量。

def CleanFemPreg(df): 
df
.agepreg /= 100.0

na_vals
= [97, 98, 99]
df
.birthwgt_lb.replace(na_vals, np.nan, inplace=True)
df
.birthwgt_oz.replace(na_vals, np.nan, inplace=True)

df
['totalwgt_lb'] = df.birthwgt_lb + df.birthwgt_oz / 16.0

agepreg包含母親在妊娠結束時的年齡。在數據文件中,agepreg是以百分之一年為單位的整數值。因此CleanFemPreg的第一行將每個agepreg除以100,從而獲得以年為單位的浮點數值。

birthwgt_lbbirthwgt_oz包含成功生產時的新生兒體重,分別是磅和盎司的部分。這兩個變量還使用幾個特殊的代碼。

97 NOT ASCERTAINED 
98 REFUSED
99 DON'T KNOW

用數字編碼特殊值是一種危險的做法,因為如果沒有進行正確的處理,這些數字可能產生虛假結果,例如,99磅重的新生兒。replace方法可以將這些值替換為np.nan,這是一個特殊的浮點數值,表示“不是數字”。replace方法使用inplace標識,說明直接修改現有的Series對象,而不是創建新對象。

IEEE浮點數表示法標准中規定,在任何算術運算中,如果有參數為nan,結果都返回nan

>>> import numpy as np
>>> np.nan / 100.0
nan

因此使用nan進行計算會得到正確的結果,而且大部分的pandas函數都能恰當地處理nan。但我們經常需要處理數據缺失的問題。

CleanFemPreg函數的最后一行生成一個新列totalwgt_lb,將磅和盎司值結合在一起,得到一個以磅為單位的值。

需要注意的是,向DataFrame添加新列時,必須使用如下字典語法:

# 正確 
df
['totalwgt_lb'] = df.birthwgt_lb + df.birthwgt_oz / 16.0

而不是使用點標記:

# 錯誤!
df
.totalwgt_lb = df.birthwgt_lb + df.birthwgt_oz / 16.0

使用點標記的寫法會給DataFrame對象添加一個新屬性,而不是創建一個新列。

1.7 數據驗證

當數據從一個軟件環境導出,再導入另一個環境時,可能會產生錯誤。如果不熟悉新數據集,可能會對數據進行不正確的解釋,或者引入其他的誤解。如果能抽出一些時間進行數據驗證,就可以節省后續可能花費的時間,避免可能出現的錯誤。

驗證數據的一種方法是計算基本的統計量,並與已發布的結果進行比較。例如,全國家庭增長調查的代碼本為每個變量提供了概要表。outcome變量對每個妊娠結果進行了編碼,其概要表如下:

value label Total 
1 LIVE BIRTH 9148
2 INDUCED ABORTION 1862
3 STILLBIRTH 120
4 MISCARRIAGE 1921
5 ECTOPIC PREGNANCY 190
6 CURRENT PREGNANCY 352

Series類提供了一個value_counts方法,可用於計算每個值出現的次數。如果得到DataFrame中的outcome Series,我們可以使用value_counts方法,將結果與已發布的數據進行比較。

>>> df.outcome.value_counts().sort_index()
1 9148
2 1862
3 120
4 1921
5 190
6 352

value_counts返回的結果是一個Series對象。sort_index方法將Series對象按索引排序,使結果按序顯示。

我們將得到的結果與官方發布的表格進行對比,outcome變量的值似乎沒有問題。類似地,已發布的關於birthwgt_lb的概要表如下:

value label Total 
. INAPPLICABLE 4449
0-5 UNDER 6 POUNDS 1125
6 6 POUNDS 2223
7 7 POUNDS 3049
8 8 POUNDS 1889
9-95 9 POUNDS OR MORE 799

birthwgt_lbvalue_counts結果如下:

>>> df.birthwgt_lb.value_counts(sort=False) 
0 8
1 40
2 53
3 98
4 229
5 697
6 2223
7 3049
8 1889
9 623
10 132
11 26
12 10
13 3
14 3
15 1
51 1

數值6、7、8的出現次數是正確的。如果計算出0~5和9~95的次數,結果也是正確的。但是,如果再看仔細些,你會發現有一個數值肯定是錯的——一個51磅的新生兒!

為了處理這個錯誤,可以在CleanFemPreg中加入一行代碼。

df.birthwgt_lb[df.birthwgt_lb > 20] = np.nan

這行代碼將非法值替換為np.nan。方括號中的表達式產生一個bool類型的Series對象,值為True表示滿足該條件。當一個布爾Series用作索引時,它只選擇滿足該條件的元素。

1.8 解釋數據

要想有效使用數據,就必須同時在兩個層面上思考問題:統計學層面和上下文層面。

例如,讓我們看一看幾位調查參與者的outcome序列。由於數據文件的組織方式,我們必須進行一些處理才能得到每位調查參與者的妊娠數據。以下函數實現了我們需要的處理:

def MakePregMap(df): 
d
= defaultdict(list)
for index, caseid in df.caseid.iteritems():
d
[caseid].append(index)
return d

df是包含妊娠數據的DataFrame對象。iteritems方法遍歷所有妊娠記錄的索引(行號)和caseid

d是將每個caseID映射到一列索引的字典。如果你不熟悉defaultdict,可以到Python的collections模塊中查看其定義。使用d,我們可以查找一位調查參與者,獲得其妊娠數據的索引。

下面的示例就查找了一位調查參與者,並打印出其妊娠結果列表:

>>> caseid = 10229
>>> indices = preg_map[caseid]
>>> df.outcome[indices].values
[4 4 4 4 4 4 1]

indices是調查參與者10229的妊娠記錄索引列表。

以這個列表為索引可以訪問df.outcome中指定的行,獲得一個Series。上面的示例沒有打印整個Series對象,而是選擇輸出values屬性,這個屬性是一個NumPy數組。

輸出結果中的代碼1表示成功分娩。代碼4表示流產,即自發終止的妊娠,終止原因通常未知。

從統計學上看,這位調查參與者並無異常。流產並不少見,其他一些調查參與者的流產次數相同或者更多。

但是考慮到上下文,這個數據說明一位婦女懷孕6次,每次都以流產告終。她第7次也是最近一次懷孕成功產下了孩子。如果我們抱着同情心看待這些數據,就很容易被數據背后的故事感動。

全國家庭增長調查數據集中的每一條記錄都代表一位參與者,這些參與者誠實地回答了很多非常私密而且難以回答的問題。我們可以使用這些數據解答與家庭生活、生育和健康相關的統計學問題。同時,我們有義務思及這些數據所代表的參與者,對他們心存敬意和感謝。

1.9 練習

  • 練習1.1

    你下載的代碼中應該有一個名為chap01ex.ipynb的文件,這是一個IPython記事本。你可以用如下命令從命令行啟動IPython記事本:

    $ ipython notebook &

    如果系統安裝了IPython,會啟動一個在后台運行的服務器,並打開一個瀏覽器查看記事本。如果你不熟悉IPython,我建議你從IPython網站(http://ipython.org/ipython-doc/stable/notebook/notebook.html)開始學習。

    你可以添加一個命令行選項,使圖片在“行內”(即在記事本中)顯示,而非彈出窗口:

    $ ipython notebook --pylab=inline &

    打開chap01ex.ipynb。記事本中一些單元已經填好了代碼,可以直接執行。其他單元列出了你應該嘗試的練習。

    本練習的參考答案在chap01soln.ipynb中。

  • 練習1.2

    創建一個名為chp01ex.py的文件,編寫代碼,讀取參與者文件2001FemResp.dat.gz。你可以復制nsfg.py文件並對其進行修改。

    變量pregnum是一個重編碼,用於說明每位調查參與者有過多少次妊娠經歷。打印這個變量中不同值的出現次數,將結果與全國家庭增長調查代碼本中發布的結果進行比較。

    你也可以將每位調查參與者的pregnum值與妊娠文件中的記錄數進行比較,對調查參與者文件和妊娠文件進行交叉驗證。

    你可以使用nsfg.MakePregMap生成一個字典,將每個caseid映射到妊娠DataFrame的索引列表。

    本練習的參考答案在chp01soln.py中。

  • 練習1.3

    學習統計學的最好方法是使用一個你感興趣的項目。你想研究“第一胎是否都會晚出生”這樣的問題嗎?

    請思考一些你個人感興趣的問題,可以是傳統觀點、爭議話題或影響政局的問題,看是否可以構想出一個能以統計調查進行驗證的問題。

    尋找能幫助你回答這個問題的數據。公共研究的數據經常可以免費獲取,因此政府網站是很好的數據來源,如http://www.data.gov/http://www.science.gov/。如果想獲得英國的數據,可以訪問http://data.gov.uk/

    我個人最喜愛的兩個數據集是General Social Survey(http://www3.norc.org/gss+website/)和European Social Survey(http://www.europeansocialsurvey.org)。

    如果有人看似已經解答了你的問題,那么仔細檢查該回答是否合理。數據和分析中可能存在的缺陷都會使結論不可靠。如果發現別人的解答存在問題,你可以對同樣的數據進行不同的分析,或者尋找更好的數據來源。

    如果有一篇論文解答了你的問題,那么你應該能夠獲得論文使用的原始數據。很多論文作者會把數據放在網上供大家使用,但如果涉及敏感信息,你可能需要向作者寫信索要,提供你計划如何使用這些數據的信息,或者同意某些使用條款。堅持就是勝利!

1.10 術語

  • 軼事證據(anecdotal evidence)

    隨意收集,而非通過精心設計的研究獲得的證據,通常是個人證據。

  • 總體(population)

    在研究中,我們感興趣的群組。“總體”經常指一組人,但這個詞也可以用於其他對象。

  • 橫截面研究(cross-sectional study)

    收集一個總體在某個特定時間點的數據的研究。

  • 周期(cycle)

    在重復進行的橫截面研究中,每次研究稱為一個周期。

  • 縱向研究(longtitudinal study)

    在一段時間內跟蹤一個總體的研究,從同一個群體重復收集數據。

  • 記錄(record)

    在數據集中,關於單個人或其他對象的信息集合。

  • 調查參與者(respondent)

    參與調查的人。

  • 樣本(sample)

    總體中用於數據收集的一個子集。

  • 有代表性(representative)

    如果總體中的每個成員被選入樣本的機會都均等,那么這個樣本就是有代表性的。

  • 過度抽樣(oversampling)

    一種通過增加一個子總體的樣本數來避免因樣本規模過小產生錯誤的技術。

  • 原始數據(raw data)

    沒有經過或只經過少許檢查、計算或解釋,直接收集和記錄的值。

  • 重編碼(recode)

    通過計算和應用於原始數據的其他邏輯生成的值。

  • 數據清洗(data cleaning)

    數據處理過程,包括數據驗證、錯誤檢查,以及數據類型和表示的轉換等。

from: http://www.ituring.com.cn/tupubarticle/3914

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com