三、Python 編程
譯者:飛龍
自豪地采用谷歌翻譯
編程可以極大地提高我們收集和分析世界信息的能力,而這些信息又可以通過上一節所述的謹慎推理來發現。 在數據科學中,編寫程序的目的是,指示計算機執行分析步驟。 電腦無法自行研究世界。 人們必須準確描述計算機應該執行什么步驟來收集和分析數據,這些步驟是通過程序來表達的。
表達式
編程語言比人類語言簡單得多。 盡管如此,在任何語言中,還是有一些語法規則需要學習,這里就是我們開始的地方。 在本文中,我們將使用 Python 編程語言。 學習語法規則是必不可少的,最基本的程序中使用的規則也是更復雜程序的核心。
程序由表達式組成,向計算機描述了如何組合數據片段。 例如,乘法表達式由兩個數字表達式之間的*
符號組成。表達式,例如3*4
,由計算機求值。在這種情況下,(IPython 中的)每個單元格中的最后一個表達式的值(求值結果)將顯示在單元格下方,這里是 12。
3 * 4
12
編程語言的語法規則是僵化的。 在 Python 中,*
符號不能連續出現兩次。 計算機不會試圖解釋一個與規定的表達式結構不同的表達式。 相反,它會顯示SyntaxError
錯誤。 語言的語法是其語法規則的集合,SyntaxError
表示表達式結構不匹配任何語法規則。
3 * * 4
File "<ipython-input-4-d90564f70db7>", line 1
3 * * 4
^
SyntaxError: invalid syntax
表達式的小改動可以完全改變它的含義。 下面,*
之間的空格已被刪除。 因為**
出現在兩個數字表達式之間,所以表達式是一個格式良好的指數表達式(第一個數字的第二個數字次方,3*3*3*3
)。 符號*
和**
稱為運算符,它們組合的值稱為操作數。
3 ** 4
81
常用操作符。 數據科學通常涉及數值的組合,而編程語言中的一組操作符,是為了使得表達式可以用于表示任何類型的算術。 在Python中,以下操作符是必不可少的。
表達式類型 | 運算符 | 示例 | 值 |
---|---|---|---|
加法 | + |
2 + 3 |
5 |
減法 | - |
2 - 3 |
-1 |
乘法 | * |
2 * 3 |
6 |
除法 | / |
7 / 3 |
2.66667 |
取余 | % |
7 % 3 |
1 |
指數 | ** |
2 ** 0.5 |
1.41421 |
Python 表達式遵循熟悉的優先級規則,與代數中相同:乘法和除法在加法和減法之前計算。 圓括號可以用來在較大的表達式中,將較小的表達式組合在一起。
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
示例
這里是一個圖表,來自 20 世紀 80 年代初期的“華盛頓郵報”(The Washington Post),試圖比較幾十年來醫生的收入與其他專業人員的收入。 我們是否真的需要在每個條形上看到兩個頭(一個帶有聽診器)? 耶魯大學教授愛德華·圖夫特(Edward Tufte)是世界上量化信息可視化的專家之一,他為這種不必要的修飾創造了“垃圾圖表”(chartjunk)一詞。 這張圖也是 Tufte 痛恨的“數據與油墨比例過低”的一個例子。
華盛頓郵報圖片
最重要的是,圖的橫軸不是按比例繪制的。 這對條形圖的形狀有顯著的影響。 當按規模繪制并把裝飾修剪掉時,圖表顯示的趨勢非常不同于原來明顯的線性增長。 下面的優雅圖表由統計系統 R 的創始人之一 Ross Ihaka 提供。
Ross Ihaka 的圖片版本
在 1939 年到 1963 年間,醫生的收入從 3,262 美元增加到 25,050 美元。 所以在這個時期,每年的平均收入增加了大約 900 美元。
(25050 - 3262)/(1963 - 1939)
907.8333333333334
在 Ross Ihaka 的圖表中可以看到,在這個時期,醫生的收入大致呈線性上升,并且保持在一個相對穩定的水平。 正如我們剛剛計算的那樣,這個比率大約是 900 美元。
但是從 1963 年到 1976 年,這個比例是三倍多:
(62799 - 25050)/(1976 - 1963)
2903.769230769231
這就是 1963 年之后,這個圖形急劇上升的原因。
本章介紹了許多類型的表達式。 學習編程需要結合學到所有的東西,調查計算機的行為。 如果你連續除兩次會發生什么? 你并不需要總是問專家(或互聯網);許多這些細節可以通過自己嘗試發現。
數值
整數值
計算機為執行數值計算而設計,但是關于處理數字有一些重要的細節,每個處理定量數據的程序員都應該知道它。 Python(和大多數其他編程語言)區分兩種不同類型的數字:
- 整數在 Python 語言中稱為
int
值。 它們只能表示沒有小數部分的整數(負數,零或正數) - 實數在 Python 語言中被稱為
float
值(或浮點值)。 他們可以表示全部或部分數字,但有一些限制。
數值的類型在展示方式上是明顯的:int
值沒有小數點,float
值總是有一個小數點。
# 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
當一個float
值和一個int
值,通過算術運算符組合在一起時,結果總是一個float
值。 在大多數情況下,兩個整數的組合形成另一個整數,但任何數字(int
或float
)除以另一個將是一個float
值。 非常大或非常小的float
值可以使用科學記數法表示。
浮點值
浮點值非常靈活,但他們有限制。
float
可以表示非常大和非常小的數字。存在限制,但你很少遇到他們。
浮點數只能表示任何數字的 15 或 16 位有效數字;剩下的精度就會丟失。 這個有限的精度對于絕大多數應用來說已經足夠了。
將浮點值與算術運算結合后,最后的幾位數字可能不正確。 第一次遇到時,微小的舍入錯誤往往令人困惑。
第一個限制可以通過兩種方式來觀察。 如果一個計算的結果是一個非常大的數字,那么它被表示為無限大。 如果結果是非常小的數字,則表示為零。
2e306 * 10
2e+307
2e306 * 100
inf
2e-322 / 10
2e-323
2e-322 / 100
0.0
第二個限制可以通過涉及超過 15 位有效數字的表達式來觀察。 在進行任何算術運算之前,這些額外的數字被丟棄。
0.6666666666666666 - 0.6666666666666666123456789
0.0
當兩個表達式應該相等時,可以觀察到第三個限制。 例如,表達式2 ** 0.5
計算 2 的平方根,但是該值的平方不會完全恢復成 2。
2 ** 0.5
1.4142135623730951
(2 ** 0.5) * (2 ** 0.5)
2.0000000000000004
(2 ** 0.5) * (2 ** 0.5) - 2
4.440892098500626e-16
上面的最終結果是0.0000000000000004440892098500626
,這個數字非常接近零。 這個算術表達式的正確答案是 0,但是最后的有效數字中的一個小錯誤,在科學記數法中顯得非常不同。 這種行為幾乎出現在所有的編程語言中,因為它是在計算機上進行算術運算的標準方式的結果。
盡管float
并不總是精確的,但它們當然是可靠的,并且在所有不同種類的計算機和編程語言中,以相同的方式工作。
名稱
名稱通過賦值語句在 Python 中得到一個值。 在賦值中,名稱后面是=
,再后面是任何表達式。 =
右邊的表達式的值被賦給名稱。 一旦名稱有了賦給它的值,在將來的表達式中,值會替換為這個名稱。
a = 10
b = 20
a + b
30
之前賦值的名稱可以在=
右邊的表達式中使用。
quarter = 1/4
half = 2 * quarter
half
0.5
但是,僅僅是表達式的當前值賦給了名稱。 如果該值稍后改變,則由該值定義的名稱將不會自動更改。
quarter = 4
half
0.5
名稱必須以字母開頭,但可以包含字母和數字。 名稱不能包含空格;相反,通常使用下劃線字符_
來替換每個空格。名稱只在你編寫的時候是有用的;程序員可以選擇易于理解的名稱。 通常,比起a
和b
,你可以創造更有意義的名字。 例如,為了描述加利福尼亞州伯克利 5 美元商品的銷售稅,以下名稱闡明了各種相關數量的含義。
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
示例:增長率
相同數量在不同時間取得的兩次測量值之間的關系通常表示為增長率。 例如,美國聯邦政府在 2002 年雇用了 276.6 萬人,在 2012 年雇用了 281.4 萬人。為了計算增長率,我們必須首先決定將哪個值作為初始值。 對于隨著時間變化的數值,較早的值是一個自然的選擇。 然后,我們將變動值和初始值之間的差除以初始值。
initial = 2766000
changed = 2814000
(changed - initial) / initial
0.01735357917570499
通常從兩個測量值的比例中減去 1,這產生相同的值。
(changed/initial) - 1
0.017353579175704903
這個值是 10 年間的增長率。 增長率的一個實用屬性是,即使值以不同的單位表示,它們也不會改變。 所以,例如,我們可以以千人為單位,在 2002 年和 2012 年之間表達同樣的關系。
initial = 2766
changed = 2814
(changed/initial) - 1
0.017353579175704903
10 年以來,美國聯邦政府的雇員人數僅增長了 1.74%。 那個時候,美國聯邦政府的總支出從 2.37 萬億美元增加到 2012 年的 3.38 萬億美元。
initial = 2.37
changed = 3.38
(changed/initial) - 1
0.4261603375527425
聯邦預算增長 42.6% 遠高于聯邦雇員增長 1.74%。 實際上,聯邦雇員的數量增長速度遠遠低于美國人口。美國人口同期增長 9.21%,從 2002 年的 2.8760 億人增加到 2012 年的 3.41 億。
initial = 287.6
changed = 314.1
(changed/initial) - 1
0.09214186369958277
增長率可能是負值,表示某種值的下降。 例如,美國的制造業就業崗位從 2002 年 的 1530 萬減少到 2012 年的 1190 萬,增長率為 -22.2%。
initial = 15.3
changed = 11.9
(changed/initial) - 1
-0.2222222222222222
年增長率是一年之內的某個數量的增長率。 年增長率為 0.035,累計十年,十年增長率為 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
相同的計算可以使用名稱和指數表達。
annual_growth_rate = 0.035
ten_year_growth_rate = (1 + annual_growth_rate) ** 10 - 1
ten_year_growth_rate
0.410598760621121
同樣,十年的增長率可以用來計算等價的年增長率。 下面,t
是兩次測量值之間經過的年數。 下面計算過去 10 年聯邦支出的年增長率。
initial = 2.37
changed = 3.38
t = 10
(changed/initial) ** (1/t) - 1
0.03613617208346853
十年來的總增長率相當于每年增長 3.6%。
總之,增長率g
用來描述initial
(初始值)和經過一段時間t
之后的changed
(變化值)的相對大小。 為了計算changed
,使用指數來重復應用增長率g
t
次。
initial * (1 + g) ** t
為了計算g
,計算總增長率的1/t
次方并減一。
(changed/initial) ** (1/t) - 1
調用表達式
調用表達式調用函數,這些函數是具名操作。 函數名稱首先出現,然后是括號中的表達式。
abs(-12)
12
round(5 - 1.3)
4
max(2, 2 + 3, 4)
5
在這最后一個例子中,max
函數在三個參數:2
, 5
和4
上調用。圓括號內每個表達式的值被傳遞給函數,函數返回整個調用表達式的最終值。 max
函數可以接受任意數量的參數并返回最大值。
一些函數默認是可用的,比如abs
和round
,但是大部分內置于 Python 語言的函數都存儲在一個稱為模塊的函數集合中。 導入語句用于訪問模塊,如math
或operator
。
import math
import operator
math.sqrt(operator.add(4, 5))
3.0
可以使用+
和**
運算符來表達等價的表達式。
(4 + 5) ** 0.5
3.0
運算符和調用表達式可以在表達式中一起使用。 兩個值之間的百分比差異用于比較一些值,它們明顯既不是initial
也不是changed
。 例如,2014 年,佛羅里達農場生產了 27.2 億個蛋,而愛荷華州農場生產了 162.5 億個雞蛋 [1]。 百分比差值是數值之差的絕對值的 100 倍,再除以它們的平均值。 在這種情況下,差值大于平均值,所以百分比差異大于 100。
florida = 2.72
iowa = 16.25
100*abs(florida-iowa)/((florida+iowa)/2)
142.6462836056932
學習不同函數的行為,是學習編程語言的重要組成部分。 Jupyter 筆記本可以幫助你記住不同函數的名稱和效果。 編輯代碼單元格時,在輸入名稱的開頭之后按 Tab 鍵,來顯示補全該名稱的方式列表。 例如,在math
后按 Tab 鍵,來查看math
模塊中所有可用函數。 打字將縮小選項列表的范圍。 為了了解函數的更多信息,請在它的名稱之后放置一個?
。 例如,輸入math.log
將顯示math
模塊中log
函數的描述。
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.
示例調用中的方括號表示參數是可選的。 也就是說,可以用一個或兩個參數來調用log
。
math.log(16, 2)
4.0
math.log(16)/math.log(2)
4.0
Python 的內建函數列表非常長,包含了許多在數據科學應用中不需要的函數。 math
模塊中的數學函數列表同樣很長。 本文將在上下文中介紹最重要的函數,而不是期望讀者記住或理解這些列表。
示例
1869 年,一位名叫查爾斯·約瑟夫·米納德(Charles Joseph Minard)的法國土木工程師,創造了一個圖表,仍被認為是有史以來最偉大的圖表之一。 它顯示了拿破侖軍隊從莫斯科撤退期間的損失。 1812 年,拿破侖開始征服俄羅斯,他的軍隊中有超過 35 萬人。 他們確實到達了莫斯科,但是沿路一直受到損失的困擾。 俄國軍隊不斷撤退到俄羅斯深處,故意焚燒田野,并在撤退時摧毀村莊。 這使法國軍隊在俄羅斯冬季來臨之時,沒有食物或避難所。法國軍隊在莫斯科沒有取得決定性的勝利就撤退了。 之后天氣變冷,死了更多的人。 回來的人還不到一萬。
Minard 的地圖
這個圖表繪制在東歐地圖上。 它始于左端的波蘭-俄羅斯邊界。 淺棕色的條形表示拿破侖的軍隊正在向莫斯科進軍,黑色的條形代表軍隊的撤退。 在圖表的每個點上,軍隊的寬度與軍隊中士兵的數量成正比。在圖表的底部,Minard 包括了回程的溫度。
注意當軍隊撤退時,黑色條形變窄。 渡過貝爾齊納河是個特別的災難,你能在圖表上看到嗎?
由于其簡單和有力,這個圖標是出色的。 Minard 展示了六個變量:
- 士兵的數量
- 行軍的方向
- 位置的經緯度
- 回程的溫度
- 十一月和十二月的具體日期的位置
Tufte 說 Minard 的圖是“可能是有史以來最好的統計圖表”。
這里是 Minard 數據的一個子集,取自 Leland Wilkinson 的 The Grammar of Graphics。
Minard 的子集
每一行表示特定位置的軍隊狀態。 列以度為單位展示經度和緯度,位置的名稱,軍隊是前進還是撤退,以及估計的人數。
在這個表格中,連續兩個地點之間的人數的最大變化是在莫斯科撤退的時候,也是最大的百分比變化。
moscou = 100000
wixma = 55000
wixma - moscou
-45000
(wixma - moscou)/moscou
-0.45
在莫斯科的戰斗中,人數下降了 45%。 換句話說,進入莫斯科的拿破侖的軍隊中,有幾乎一半的人沒有繼續前進。
正如你在圖表中看到的,Moiodexno 非常接近軍隊出發位置 Kowno。 在前進期間進入 Smolensk 的人中,只有不到 10% 的人在返回的途中到達了 Moiodexno。
smolensk_A = 145000
moiodexno = 12000
(moiodexno - smolensk_A)/smolensk_A
-0.9172413793103448
是的,只要使用沒有名稱的數字就可以做這些計算。 但是這些名稱使得閱讀代碼和解釋結果變得更容易。
值得注意的是,更大的絕對變化并不總是對應更大的百分比變化。
在前進期間,從 Smolensk 到 Dorogobouge 的絕對損失是 5000 人,而撤退期間,從 Smolensk 到 Orscha 的相應損失是 4000 人。
然而,Smolensk 和 Orscha 之間的百分比變化要大得多,因為,在撤退期間,Smolensk 的人員總數要小得多。
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