三、Python 編程
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
編程可以極大地提高我們收集和分析世界信息的能力,而這些信息又可以通過上一節(jié)所述的謹(jǐn)慎推理來發(fā)現(xiàn)。 在數(shù)據(jù)科學(xué)中,編寫程序的目的是,指示計(jì)算機(jī)執(zhí)行分析步驟。 電腦無法自行研究世界。 人們必須準(zhǔn)確描述計(jì)算機(jī)應(yīng)該執(zhí)行什么步驟來收集和分析數(shù)據(jù),這些步驟是通過程序來表達(dá)的。
表達(dá)式
編程語言比人類語言簡單得多。 盡管如此,在任何語言中,還是有一些語法規(guī)則需要學(xué)習(xí),這里就是我們開始的地方。 在本文中,我們將使用 Python 編程語言。 學(xué)習(xí)語法規(guī)則是必不可少的,最基本的程序中使用的規(guī)則也是更復(fù)雜程序的核心。
程序由表達(dá)式組成,向計(jì)算機(jī)描述了如何組合數(shù)據(jù)片段。 例如,乘法表達(dá)式由兩個(gè)數(shù)字表達(dá)式之間的*符號(hào)組成。表達(dá)式,例如3*4,由計(jì)算機(jī)求值。在這種情況下,(IPython 中的)每個(gè)單元格中的最后一個(gè)表達(dá)式的值(求值結(jié)果)將顯示在單元格下方,這里是 12。
3 * 4
12
編程語言的語法規(guī)則是僵化的。 在 Python 中,*符號(hào)不能連續(xù)出現(xiàn)兩次。 計(jì)算機(jī)不會(huì)試圖解釋一個(gè)與規(guī)定的表達(dá)式結(jié)構(gòu)不同的表達(dá)式。 相反,它會(huì)顯示SyntaxError錯(cuò)誤。 語言的語法是其語法規(guī)則的集合,SyntaxError表示表達(dá)式結(jié)構(gòu)不匹配任何語法規(guī)則。
3 * * 4
File "<ipython-input-4-d90564f70db7>", line 1
3 * * 4
^
SyntaxError: invalid syntax
表達(dá)式的小改動(dòng)可以完全改變它的含義。 下面,*之間的空格已被刪除。 因?yàn)?code>**出現(xiàn)在兩個(gè)數(shù)字表達(dá)式之間,所以表達(dá)式是一個(gè)格式良好的指數(shù)表達(dá)式(第一個(gè)數(shù)字的第二個(gè)數(shù)字次方,3*3*3*3)。 符號(hào)*和**稱為運(yùn)算符,它們組合的值稱為操作數(shù)。
3 ** 4
81
常用操作符。 數(shù)據(jù)科學(xué)通常涉及數(shù)值的組合,而編程語言中的一組操作符,是為了使得表達(dá)式可以用于表示任何類型的算術(shù)。 在Python中,以下操作符是必不可少的。
| 表達(dá)式類型 | 運(yùn)算符 | 示例 | 值 |
|---|---|---|---|
| 加法 | + |
2 + 3 |
5 |
| 減法 | - |
2 - 3 |
-1 |
| 乘法 | * |
2 * 3 |
6 |
| 除法 | / |
7 / 3 |
2.66667 |
| 取余 | % |
7 % 3 |
1 |
| 指數(shù) | ** |
2 ** 0.5 |
1.41421 |
Python 表達(dá)式遵循熟悉的優(yōu)先級(jí)規(guī)則,與代數(shù)中相同:乘法和除法在加法和減法之前計(jì)算。 圓括號(hào)可以用來在較大的表達(dá)式中,將較小的表達(dá)式組合在一起。
1 + 2 * 3 * 4 * 5 / 6 ** 3 + 7 + 8 - 9 + 10
17.555555555555557
1 + 2 * (3 * 4 * 5 / 6) ** 3 + 7 + 8 - 9 + 10
2017.0
示例
這里是一個(gè)圖表,來自 20 世紀(jì) 80 年代初期的“華盛頓郵報(bào)”(The Washington Post),試圖比較幾十年來醫(yī)生的收入與其他專業(yè)人員的收入。 我們是否真的需要在每個(gè)條形上看到兩個(gè)頭(一個(gè)帶有聽診器)? 耶魯大學(xué)教授愛德華·圖夫特(Edward Tufte)是世界上量化信息可視化的專家之一,他為這種不必要的修飾創(chuàng)造了“垃圾圖表”(chartjunk)一詞。 這張圖也是 Tufte 痛恨的“數(shù)據(jù)與油墨比例過低”的一個(gè)例子。

華盛頓郵報(bào)圖片
最重要的是,圖的橫軸不是按比例繪制的。 這對(duì)條形圖的形狀有顯著的影響。 當(dāng)按規(guī)模繪制并把裝飾修剪掉時(shí),圖表顯示的趨勢非常不同于原來明顯的線性增長。 下面的優(yōu)雅圖表由統(tǒng)計(jì)系統(tǒng) R 的創(chuàng)始人之一 Ross Ihaka 提供。

Ross Ihaka 的圖片版本
在 1939 年到 1963 年間,醫(yī)生的收入從 3,262 美元增加到 25,050 美元。 所以在這個(gè)時(shí)期,每年的平均收入增加了大約 900 美元。
(25050 - 3262)/(1963 - 1939)
907.8333333333334
在 Ross Ihaka 的圖表中可以看到,在這個(gè)時(shí)期,醫(yī)生的收入大致呈線性上升,并且保持在一個(gè)相對(duì)穩(wěn)定的水平。 正如我們剛剛計(jì)算的那樣,這個(gè)比率大約是 900 美元。
但是從 1963 年到 1976 年,這個(gè)比例是三倍多:
(62799 - 25050)/(1976 - 1963)
2903.769230769231
這就是 1963 年之后,這個(gè)圖形急劇上升的原因。
本章介紹了許多類型的表達(dá)式。 學(xué)習(xí)編程需要結(jié)合學(xué)到所有的東西,調(diào)查計(jì)算機(jī)的行為。 如果你連續(xù)除兩次會(huì)發(fā)生什么? 你并不需要總是問專家(或互聯(lián)網(wǎng));許多這些細(xì)節(jié)可以通過自己嘗試發(fā)現(xiàn)。
數(shù)值
整數(shù)值
計(jì)算機(jī)為執(zhí)行數(shù)值計(jì)算而設(shè)計(jì),但是關(guān)于處理數(shù)字有一些重要的細(xì)節(jié),每個(gè)處理定量數(shù)據(jù)的程序員都應(yīng)該知道它。 Python(和大多數(shù)其他編程語言)區(qū)分兩種不同類型的數(shù)字:
- 整數(shù)在 Python 語言中稱為
int值。 它們只能表示沒有小數(shù)部分的整數(shù)(負(fù)數(shù),零或正數(shù)) - 實(shí)數(shù)在 Python 語言中被稱為
float值(或浮點(diǎn)值)。 他們可以表示全部或部分?jǐn)?shù)字,但有一些限制。
數(shù)值的類型在展示方式上是明顯的:int值沒有小數(shù)點(diǎn),float值總是有一個(gè)小數(shù)點(diǎn)。
# Some int values
2
2
1 + 3
4
-1234567890000000000
-1234567890000000000
# Some float values
1.2
1.2
1.5 + 2
3.5
3 / 1
3.0
-12345678900000000000.0
-1.23456789e+19
當(dāng)一個(gè)float值和一個(gè)int值,通過算術(shù)運(yùn)算符組合在一起時(shí),結(jié)果總是一個(gè)float值。 在大多數(shù)情況下,兩個(gè)整數(shù)的組合形成另一個(gè)整數(shù),但任何數(shù)字(int或float)除以另一個(gè)將是一個(gè)float值。 非常大或非常小的float值可以使用科學(xué)記數(shù)法表示。
浮點(diǎn)值
浮點(diǎn)值非常靈活,但他們有限制。
float可以表示非常大和非常小的數(shù)字。存在限制,但你很少遇到他們。
浮點(diǎn)數(shù)只能表示任何數(shù)字的 15 或 16 位有效數(shù)字;剩下的精度就會(huì)丟失。 這個(gè)有限的精度對(duì)于絕大多數(shù)應(yīng)用來說已經(jīng)足夠了。
將浮點(diǎn)值與算術(shù)運(yùn)算結(jié)合后,最后的幾位數(shù)字可能不正確。 第一次遇到時(shí),微小的舍入錯(cuò)誤往往令人困惑。
第一個(gè)限制可以通過兩種方式來觀察。 如果一個(gè)計(jì)算的結(jié)果是一個(gè)非常大的數(shù)字,那么它被表示為無限大。 如果結(jié)果是非常小的數(shù)字,則表示為零。
2e306 * 10
2e+307
2e306 * 100
inf
2e-322 / 10
2e-323
2e-322 / 100
0.0
第二個(gè)限制可以通過涉及超過 15 位有效數(shù)字的表達(dá)式來觀察。 在進(jìn)行任何算術(shù)運(yùn)算之前,這些額外的數(shù)字被丟棄。
0.6666666666666666 - 0.6666666666666666123456789
0.0
當(dāng)兩個(gè)表達(dá)式應(yīng)該相等時(shí),可以觀察到第三個(gè)限制。 例如,表達(dá)式2 ** 0.5計(jì)算 2 的平方根,但是該值的平方不會(huì)完全恢復(fù)成 2。
2 ** 0.5
1.4142135623730951
(2 ** 0.5) * (2 ** 0.5)
2.0000000000000004
(2 ** 0.5) * (2 ** 0.5) - 2
4.440892098500626e-16
上面的最終結(jié)果是0.0000000000000004440892098500626,這個(gè)數(shù)字非常接近零。 這個(gè)算術(shù)表達(dá)式的正確答案是 0,但是最后的有效數(shù)字中的一個(gè)小錯(cuò)誤,在科學(xué)記數(shù)法中顯得非常不同。 這種行為幾乎出現(xiàn)在所有的編程語言中,因?yàn)樗窃谟?jì)算機(jī)上進(jìn)行算術(shù)運(yùn)算的標(biāo)準(zhǔn)方式的結(jié)果。
盡管float并不總是精確的,但它們當(dāng)然是可靠的,并且在所有不同種類的計(jì)算機(jī)和編程語言中,以相同的方式工作。
名稱
名稱通過賦值語句在 Python 中得到一個(gè)值。 在賦值中,名稱后面是=,再后面是任何表達(dá)式。 =右邊的表達(dá)式的值被賦給名稱。 一旦名稱有了賦給它的值,在將來的表達(dá)式中,值會(huì)替換為這個(gè)名稱。
a = 10
b = 20
a + b
30
之前賦值的名稱可以在=右邊的表達(dá)式中使用。
quarter = 1/4
half = 2 * quarter
half
0.5
但是,僅僅是表達(dá)式的當(dāng)前值賦給了名稱。 如果該值稍后改變,則由該值定義的名稱將不會(huì)自動(dòng)更改。
quarter = 4
half
0.5
名稱必須以字母開頭,但可以包含字母和數(shù)字。 名稱不能包含空格;相反,通常使用下劃線字符_來替換每個(gè)空格。名稱只在你編寫的時(shí)候是有用的;程序員可以選擇易于理解的名稱。 通常,比起a和b,你可以創(chuàng)造更有意義的名字。 例如,為了描述加利福尼亞州伯克利 5 美元商品的銷售稅,以下名稱闡明了各種相關(guān)數(shù)量的含義。
purchase_price = 5
state_tax_rate = 0.075
county_tax_rate = 0.02
city_tax_rate = 0
sales_tax_rate = state_tax_rate + county_tax_rate + city_tax_rate
sales_tax = purchase_price * sales_tax_rate
sales_tax
0.475
示例:增長率
相同數(shù)量在不同時(shí)間取得的兩次測量值之間的關(guān)系通常表示為增長率。 例如,美國聯(lián)邦政府在 2002 年雇用了 276.6 萬人,在 2012 年雇用了 281.4 萬人。為了計(jì)算增長率,我們必須首先決定將哪個(gè)值作為初始值。 對(duì)于隨著時(shí)間變化的數(shù)值,較早的值是一個(gè)自然的選擇。 然后,我們將變動(dòng)值和初始值之間的差除以初始值。
initial = 2766000
changed = 2814000
(changed - initial) / initial
0.01735357917570499
通常從兩個(gè)測量值的比例中減去 1,這產(chǎn)生相同的值。
(changed/initial) - 1
0.017353579175704903
這個(gè)值是 10 年間的增長率。 增長率的一個(gè)實(shí)用屬性是,即使值以不同的單位表示,它們也不會(huì)改變。 所以,例如,我們可以以千人為單位,在 2002 年和 2012 年之間表達(dá)同樣的關(guān)系。
initial = 2766
changed = 2814
(changed/initial) - 1
0.017353579175704903
10 年以來,美國聯(lián)邦政府的雇員人數(shù)僅增長了 1.74%。 那個(gè)時(shí)候,美國聯(lián)邦政府的總支出從 2.37 萬億美元增加到 2012 年的 3.38 萬億美元。
initial = 2.37
changed = 3.38
(changed/initial) - 1
0.4261603375527425
聯(lián)邦預(yù)算增長 42.6% 遠(yuǎn)高于聯(lián)邦雇員增長 1.74%。 實(shí)際上,聯(lián)邦雇員的數(shù)量增長速度遠(yuǎn)遠(yuǎn)低于美國人口。美國人口同期增長 9.21%,從 2002 年的 2.8760 億人增加到 2012 年的 3.41 億。
initial = 287.6
changed = 314.1
(changed/initial) - 1
0.09214186369958277
增長率可能是負(fù)值,表示某種值的下降。 例如,美國的制造業(yè)就業(yè)崗位從 2002 年 的 1530 萬減少到 2012 年的 1190 萬,增長率為 -22.2%。
initial = 15.3
changed = 11.9
(changed/initial) - 1
-0.2222222222222222
年增長率是一年之內(nèi)的某個(gè)數(shù)量的增長率。 年增長率為 0.035,累計(jì)十年,十年增長率為 0.41(即 41%)。
1.035 * 1.035 * 1.035 * 1.035 * 1.035 * 1.035 * 1.035 * 1.035 * 1.035 * 1.035 - 1
0.410598760621121
相同的計(jì)算可以使用名稱和指數(shù)表達(dá)。
annual_growth_rate = 0.035
ten_year_growth_rate = (1 + annual_growth_rate) ** 10 - 1
ten_year_growth_rate
0.410598760621121
同樣,十年的增長率可以用來計(jì)算等價(jià)的年增長率。 下面,t是兩次測量值之間經(jīng)過的年數(shù)。 下面計(jì)算過去 10 年聯(lián)邦支出的年增長率。
initial = 2.37
changed = 3.38
t = 10
(changed/initial) ** (1/t) - 1
0.03613617208346853
十年來的總增長率相當(dāng)于每年增長 3.6%。
總之,增長率g用來描述initial(初始值)和經(jīng)過一段時(shí)間t之后的changed(變化值)的相對(duì)大小。 為了計(jì)算changed,使用指數(shù)來重復(fù)應(yīng)用增長率g t次。
initial * (1 + g) ** t
為了計(jì)算g,計(jì)算總增長率的1/t次方并減一。
(changed/initial) ** (1/t) - 1
調(diào)用表達(dá)式
調(diào)用表達(dá)式調(diào)用函數(shù),這些函數(shù)是具名操作。 函數(shù)名稱首先出現(xiàn),然后是括號(hào)中的表達(dá)式。
abs(-12)
12
round(5 - 1.3)
4
max(2, 2 + 3, 4)
5
在這最后一個(gè)例子中,max函數(shù)在三個(gè)參數(shù):2, 5和4上調(diào)用。圓括號(hào)內(nèi)每個(gè)表達(dá)式的值被傳遞給函數(shù),函數(shù)返回整個(gè)調(diào)用表達(dá)式的最終值。 max函數(shù)可以接受任意數(shù)量的參數(shù)并返回最大值。
一些函數(shù)默認(rèn)是可用的,比如abs和round,但是大部分內(nèi)置于 Python 語言的函數(shù)都存儲(chǔ)在一個(gè)稱為模塊的函數(shù)集合中。 導(dǎo)入語句用于訪問模塊,如math或operator。
import math
import operator
math.sqrt(operator.add(4, 5))
3.0
可以使用+和**運(yùn)算符來表達(dá)等價(jià)的表達(dá)式。
(4 + 5) ** 0.5
3.0
運(yùn)算符和調(diào)用表達(dá)式可以在表達(dá)式中一起使用。 兩個(gè)值之間的百分比差異用于比較一些值,它們明顯既不是initial也不是changed。 例如,2014 年,佛羅里達(dá)農(nóng)場生產(chǎn)了 27.2 億個(gè)蛋,而愛荷華州農(nóng)場生產(chǎn)了 162.5 億個(gè)雞蛋 [1]。 百分比差值是數(shù)值之差的絕對(duì)值的 100 倍,再除以它們的平均值。 在這種情況下,差值大于平均值,所以百分比差異大于 100。
florida = 2.72
iowa = 16.25
100*abs(florida-iowa)/((florida+iowa)/2)
142.6462836056932
學(xué)習(xí)不同函數(shù)的行為,是學(xué)習(xí)編程語言的重要組成部分。 Jupyter 筆記本可以幫助你記住不同函數(shù)的名稱和效果。 編輯代碼單元格時(shí),在輸入名稱的開頭之后按 Tab 鍵,來顯示補(bǔ)全該名稱的方式列表。 例如,在math后按 Tab 鍵,來查看math模塊中所有可用函數(shù)。 打字將縮小選項(xiàng)列表的范圍。 為了了解函數(shù)的更多信息,請(qǐng)?jiān)谒拿Q之后放置一個(gè)?。 例如,輸入math.log將顯示math模塊中log函數(shù)的描述。
math.log?
log(x[, base])
Return the logarithm of x to the given base.
If the base not specified, returns the natural logarithm (base e) of x.
示例調(diào)用中的方括號(hào)表示參數(shù)是可選的。 也就是說,可以用一個(gè)或兩個(gè)參數(shù)來調(diào)用log。
math.log(16, 2)
4.0
math.log(16)/math.log(2)
4.0
Python 的內(nèi)建函數(shù)列表非常長,包含了許多在數(shù)據(jù)科學(xué)應(yīng)用中不需要的函數(shù)。 math模塊中的數(shù)學(xué)函數(shù)列表同樣很長。 本文將在上下文中介紹最重要的函數(shù),而不是期望讀者記住或理解這些列表。
示例
1869 年,一位名叫查爾斯·約瑟夫·米納德(Charles Joseph Minard)的法國土木工程師,創(chuàng)造了一個(gè)圖表,仍被認(rèn)為是有史以來最偉大的圖表之一。 它顯示了拿破侖軍隊(duì)從莫斯科撤退期間的損失。 1812 年,拿破侖開始征服俄羅斯,他的軍隊(duì)中有超過 35 萬人。 他們確實(shí)到達(dá)了莫斯科,但是沿路一直受到損失的困擾。 俄國軍隊(duì)不斷撤退到俄羅斯深處,故意焚燒田野,并在撤退時(shí)摧毀村莊。 這使法國軍隊(duì)在俄羅斯冬季來臨之時(shí),沒有食物或避難所。法國軍隊(duì)在莫斯科沒有取得決定性的勝利就撤退了。 之后天氣變冷,死了更多的人。 回來的人還不到一萬。

Minard 的地圖
這個(gè)圖表繪制在東歐地圖上。 它始于左端的波蘭-俄羅斯邊界。 淺棕色的條形表示拿破侖的軍隊(duì)正在向莫斯科進(jìn)軍,黑色的條形代表軍隊(duì)的撤退。 在圖表的每個(gè)點(diǎn)上,軍隊(duì)的寬度與軍隊(duì)中士兵的數(shù)量成正比。在圖表的底部,Minard 包括了回程的溫度。
注意當(dāng)軍隊(duì)撤退時(shí),黑色條形變窄。 渡過貝爾齊納河是個(gè)特別的災(zāi)難,你能在圖表上看到嗎?
由于其簡單和有力,這個(gè)圖標(biāo)是出色的。 Minard 展示了六個(gè)變量:
- 士兵的數(shù)量
- 行軍的方向
- 位置的經(jīng)緯度
- 回程的溫度
- 十一月和十二月的具體日期的位置
Tufte 說 Minard 的圖是“可能是有史以來最好的統(tǒng)計(jì)圖表”。
這里是 Minard 數(shù)據(jù)的一個(gè)子集,取自 Leland Wilkinson 的 The Grammar of Graphics。

Minard 的子集
每一行表示特定位置的軍隊(duì)狀態(tài)。 列以度為單位展示經(jīng)度和緯度,位置的名稱,軍隊(duì)是前進(jìn)還是撤退,以及估計(jì)的人數(shù)。
在這個(gè)表格中,連續(xù)兩個(gè)地點(diǎn)之間的人數(shù)的最大變化是在莫斯科撤退的時(shí)候,也是最大的百分比變化。
moscou = 100000
wixma = 55000
wixma - moscou
-45000
(wixma - moscou)/moscou
-0.45
在莫斯科的戰(zhàn)斗中,人數(shù)下降了 45%。 換句話說,進(jìn)入莫斯科的拿破侖的軍隊(duì)中,有幾乎一半的人沒有繼續(xù)前進(jìn)。
正如你在圖表中看到的,Moiodexno 非常接近軍隊(duì)出發(fā)位置 Kowno。 在前進(jìn)期間進(jìn)入 Smolensk 的人中,只有不到 10% 的人在返回的途中到達(dá)了 Moiodexno。
smolensk_A = 145000
moiodexno = 12000
(moiodexno - smolensk_A)/smolensk_A
-0.9172413793103448
是的,只要使用沒有名稱的數(shù)字就可以做這些計(jì)算。 但是這些名稱使得閱讀代碼和解釋結(jié)果變得更容易。
值得注意的是,更大的絕對(duì)變化并不總是對(duì)應(yīng)更大的百分比變化。
在前進(jìn)期間,從 Smolensk 到 Dorogobouge 的絕對(duì)損失是 5000 人,而撤退期間,從 Smolensk 到 Orscha 的相應(yīng)損失是 4000 人。
然而,Smolensk 和 Orscha 之間的百分比變化要大得多,因?yàn)?,在撤退期間,Smolensk 的人員總數(shù)要小得多。
dorogobouge = 140000
smolensk_R = 24000
orscha = 20000
abs(dorogobouge - smolensk_A)
5000
abs(dorogobouge - smolensk_A)/smolensk_A
0.034482758620689655
abs(orscha - smolensk_R)
4000
abs(orscha - smolensk_R)/smolensk_R
0.16666666666666666