做者:Vamei 出處:http://www.cnblogs.com/vamei 嚴禁轉載。html
統計最開始的主要任務就是描述數據。正如咱們在統計概述中提到的,羣體的數據可能包含大量的數字,每每讓人讀起來頭昏腦漲。電影《美麗心靈》中,數學家納什不自覺地沉浸在一串數字中。這樣的電影橋段常常讓觀衆感到慚愧。但真相是,每一個人的注意力和短時間記憶都頗有限,只能集中在不多量的信息。數據描述就是要用必定的方法來提取少許信息,從而讓人更容易明白數據的含義。數據描述的方法能夠分爲兩大門類,即羣體參數和數據繪圖。二者都起到了簡化信息做用,從而讓數據變得更加易讀。數據庫
羣體參數是用一些數字來表示羣體的特徵。咱們在統計概述中已經介紹了兩個羣體參數,羣體平均值和羣體方差。羣體平均值(population mean)反映羣體整體情況,定義以下:app
mean: 172.075924
variance: 102.570849846
standard deviation: 10.1277267857
median: 172.21
lower percentile: 165.31
upper percentile: 178.9025
IQR: 13.5925
代碼以下:函數
import numpy as np with open("xiangbei_height.txt", "r") as f: lines = f.readlines() x = list(map(float, lines)) print("mean:", np.mean(x)) print("variance:", np.var(x)) print("standard deviation:", np.std(x)) print("median:", np.median(x)) print("lower percentile:", np.percentile(x, 25)) print("upper percentile:", np.percentile(x, 75)) print("IQR:", np.percentile(x, 75) - np.percentile(x, 25))
數據繪圖利用了人類對形狀的敏感。在經過數據繪圖,咱們能夠將數字轉換的幾何圖形,讓數據中的信息變得更容易消化。數據繪圖曾經是個費時費力的手工活,但計算機圖形的發展讓數據繪圖變得簡單。這兩年更是新興起「數據可視化」,用不少炫目的手段來呈現數據。但說到底,經典的繪圖只有那麼幾種,如餅圖、散點圖、曲線圖。「數據可視化」中的創新手法,也只不過是從這些經典方法中衍生出來的。因爲人們已經造成了約定俗成的數據繪圖習慣,繪圖方式上的過分創新甚至會誤導讀者。因此,這裏出現的,也是經典的統計繪圖形式。工具
因爲這一系列統計教程主要用Python,我將基於Matplotlib介紹幾種經典的數據繪圖方式。Matplotlib是基於numpy的一套Python工具包,提供了豐富的數據繪圖工具。固然,Matplotlib並不是惟一的選擇。有的統計學家更偏心R語言,而Web開發者流行使用D3.js。熟悉了一種繪圖工具後,總能夠舉一反三,很快地掌握其餘的工具。oop
咱們將以2011年幾個國家的GDP數據爲例子,看看如何繪製經典的餅圖和條形圖。數據以下:spa
USA 15094025
China 11299967
India 4457784
Japan 4440376
Germany 3099080
Russia 2383402
Brazil 2293954
UK 2260803
France 2217900
Italy 1846950
這是一個只有10個成員的羣體。羣體成員的取值即該成員的2011年的GDP總額。這裏的單位是(百萬美圓)。3d
咱們先來繪製餅圖 (pie plot)。繪製餅圖就像分披薩。整個披薩表明成員取值的總和。每一個成員根據本身取值的大小,拿相應大小的那塊兒披薩。把上面的數據繪製成餅圖:code
從圖中能夠看到,在這場「分大餅」的遊戲中,美國和中國佔了大的份額。不過,人們從餅圖中讀到的只是比例,沒辦法得到成員的具體數值。所以,餅圖適用於表示成員取值在總和中所佔的百分比。上面餅圖的代碼以下:orm
import matplotlib.pyplot as plt # quants: GDP # labels: country name
labels = [] quants = [] # Read data
with open('major_country_gdp.txt', 'r') as f: for line in f: info = line.split() labels.append(info[0]) quants.append(float(info[1])) print(quants) # make a square figure
plt.figure(1, figsize=(6,6)) # For China, make the piece explode a bit
def explode(label, target='China'): if label == target: return 0.1
else: return 0 expl = list(map(explode,labels)) # Colors used. Recycle if not enough.
colors = ["pink","coral","yellow","orange"] # Pie Plot # autopct: format of "percent" string;
plt.pie(quants, explode=expl, colors=colors, labels=labels, autopct='%1.1f%%',pctdistance=0.8, shadow=True) plt.title('Top 10 GDP Countries (2011)', bbox={'facecolor':'0.8', 'pad':5}) plt.show()
餅圖的缺點是沒法表達成員的具體取值,而條形圖(bar plot)正是用於呈現數據取值。條形圖繪製的是一個個豎直的長條,這個長條的高度就表明了取值。仍是用上面2011年GDP的數據,用條形圖繪製出來就是:
條形圖有水平和豎直兩個方向。水平方向上標出了每一個豎條對應的國家,豎直方向標出了GDP的數值。這樣,讀者就能夠讀出每一個國家的GDP了。上面繪圖的代碼以下:
import matplotlib.pyplot as plt import numpy as np # quants: GDP # labels: country name
labels = [] quants = [] # Read data
with open('major_country_gdp.txt') as f: for line in f: info = line.split() labels.append(info[0]) quants.append(float(info[1])) width = 0.4 ind = np.linspace(0.5,9.5,10) # make a square figure
fig = plt.figure(1, figsize=(12,6)) ax = fig.add_subplot(111) # Bar Plot
ax.bar(ind-width/2,quants,width,color='coral') # Set the ticks on x-axis
ax.set_xticks(ind) ax.set_xticklabels(labels) # labels
ax.set_xlabel('Country') ax.set_ylabel('GDP (Million US dollar)') # title
ax.set_title('Top 10 GDP Countries (2011)', bbox={'facecolor':'0.8', 'pad':5}) plt.show()
基本的條形圖就是這樣一種標記數據取值的繪圖方式。若是想知道數值,那麼能夠直接從數據表中讀出來,大能夠沒必要畫條形圖。統計繪圖中更經常使用一種從條形圖中衍生出來的繪圖方式:直方圖(histogram)。直方圖會對羣體數據進行預處理,而後再把預處理結果用條形圖的形式畫出來。舉一個簡單的例子,在繪圖中呈現湘北高中全部學生的身高數據。想象一下,若是讓每一個學生的身高對應一個豎條,那麼圖上就會密密麻麻地擠滿數千個豎條,很難提供有價值的信息。但若是畫成直方圖的形式,看起來就會以下圖:
在這幅圖中,橫座標成了身高取值。每一個豎條的寬度對應了必定的身高範圍,例如170cm到172cm。豎條的高度,對應了身高在該區間內的學生數。所以,直方圖先進行了一次分組的預處理,而後用條形圖的辦法,畫出了每一個組中包含的成員總數。在分組的處理中,一些原始信息丟失,以致於從豎條中沒辦法讀出學生的具體身高。但獲得簡化的信息變得更容易理解。看了這個圖以後,咱們能夠有信心地說,大部分學生的身高在170cm附近。而身高低於150cm或者身高高於190cm的學生佔據的比例不多。若是一我的只是讀原始數據,很難短期內得到上面的結論。
直方圖繪圖程序以下:
import numpy as np import matplotlib.pyplot as plt with open("xiangbei_height.txt", "r") as f: lines = f.readlines() x = list(map(float, lines)) plt.title("Heights of Students (Shohoku High School)") plt.hist(x, 50) plt.xlabel("height (cm)") plt.ylabel("count") plt.show()
代碼中的hist()函數用於繪製直方圖,其中的50說明了要生成的區間分組的個數。根據須要,你也能夠具體說明在哪些區間造成分組。
趨勢圖(run chart)又稱爲折線圖,常常用於呈現時間序列。時間序列是隨着時間產生的一組數據,好比上海去年每一天的氣溫,再好比中國最近50年的GDP。趨勢圖會把相鄰時間點的數據用直線鏈接起來,從而從視覺上體現出數據隨時間變化的特徵。趨勢圖在生活中很常見,例如股民就常常會經過相似的圖來了解股價隨時間的變化。下面是中國1960-2015年GDP的趨勢圖:
在這個趨勢圖中很容易看到,中國的GDP隨着時間快速增加。繪圖的代碼以下:
import numpy as np import matplotlib.pyplot as plt # read data
with open("China_GDP.csv", "r") as f: lines = f.readlines() info = lines[1].split(",") # convert data
x = [] y = [] def convert(info_item): return float(info_item.strip('"')) for count, info_item in enumerate(info): try: y.append(convert(info_item)) x.append(1960 + count) except ValueError: print("%s is not a float" % info_item) # plot
plt.title("China GDP") plt.plot(x, y) plt.xlabel("year") plt.ylabel("GDP (USD)") plt.show()
上面的繪圖方式,本質上都是二維統計圖。餅圖是國別和比例的二維信息,直方圖體現了身高和人數的二維關係,趨勢圖的兩個維度則是時間和GDP。散點圖(scatter plot)是一種最直接的表達二維關係的繪圖方式。二維繪圖的其餘方式,均可以理解成散點圖的一個變種。
散點圖經過在二維平面上標記出數據點來呈現數據。若是咱們想研究湘北高中學生身高和體重的關係,就能夠在表示「身高-體重」的二維平面上,標記出全部成員的數據:
在這個散點圖中,二維平面的橫向表明身高,縱向表明體重,每個點表明了一個學生。經過這個點對應的橫縱座標,就能夠讀出該學生的身高和體重。散點圖能夠直觀地呈現全部數據,所以上能夠告訴咱們總體分佈上有何特徵。咱們從圖中能夠看到,體重大致上隨着身高增加而增加。
繪圖代碼以下:
import numpy as np import matplotlib.pyplot as plt def read_data(filename): with open(filename) as f: lines = f.readlines() return np.array(list(map(float, lines))) height = read_data("xiangbei_height.txt") weight = read_data("xiangbei_weight.txt") plt.scatter(height, weight) plt.title("Shohoku High School") plt.xlabel("height(cm)") plt.ylabel("weight(kg)") plt.ylim([20, 120]) plt.show()
散點是經過二維的位置來表示數據。在應用中,還能夠經過散點的大小來表示三維的數據。這種進化了的散點圖稱爲泡泡圖(bubble plot)。除了散點的大小,泡泡圖有時還會用散點的顏色來表達更高維度的信息。
咱們來看泡泡圖的一個例子。下圖中繪出了亞洲主要城市的人口。城市的位置包含了二維的信息,即經度和緯度。此外,人口構成了第三維。咱們用散點的大小來表示這一維度。
數據以下:
Shanghai 23019148 31.23N 121.47E China
Mumbai 12478447 18.96N 72.82E India
Karachi 13050000 24.86N 67.01E Pakistan
Delhi 16314838 28.67N 77.21E India
Manila 11855975 14.62N 120.97E Philippines
Seoul 23616000 37.56N 126.99E Korea(South)
Jakarta 28019545 6.18S 106.83E Indonesia
Tokyo 35682460 35.67N 139.77E Japan
Peking 19612368 39.91N 116.39E China
代碼中使用了matplotlib的Basemap模塊來繪製地圖:
from mpl_toolkits.basemap import Basemap import matplotlib.pyplot as plt import numpy as np #============================================# read data
names = [] pops = [] lats = [] lons = [] countries = [] with open("major_city.txt", "r") as f: for line in f: info = line.split() names.append(info[0]) pops.append(float(info[1])) lat = float(info[2][:-1]) if info[2][-1] == 'S': lat = -lat lats.append(lat) lon = float(info[3][:-1]) if info[3][-1] == 'W': lon = -lon + 360.0 lons.append(lon) country = info[4] countries.append(country) #============================================ # set up map projection with # use low resolution coastlines.
map = Basemap(projection='ortho',lat_0=35,lon_0=120,resolution='l') # draw coastlines, country boundaries, fill continents.
map.drawcoastlines(linewidth=0.25) map.drawcountries(linewidth=0.25) # draw the edge of the map projection region (the projection limb)
map.drawmapboundary(fill_color='#689CD2') # draw lat/lon grid lines every 30 degrees.
map.drawmeridians(np.arange(0,360,30)) map.drawparallels(np.arange(-90,90,30)) # Fill continent wit a different color
map.fillcontinents(color='#BF9E30',lake_color='#689CD2',zorder=0) # compute native map projection coordinates of lat/lon grid.
x, y = map(lons, lats) max_pop = max(pops) # Plot each city in a loop. # Set some parameters
size_factor = 160.0 y_offset = 15.0 rotation = 30 adjust_size = lambda k: size_factor*(k-10000000)/max_pop for i,j,k,name in zip(x,y,pops,names): cs = map.scatter(i,j,s=adjust_size(k),marker='o',color='#FF5600') plt.text(i,j+y_offset,name,rotation=rotation,fontsize=10) print(i, j) examples = [12000000, 24000000, 36000000] pop = 12000000 plt.scatter(300000, 300000,s=adjust_size(pop),marker='o',color='red') plt.text(300000, 300000+y_offset,str(pop/1000000) + "million",rotation=0,fontsize=10) pop = 24000000 plt.scatter(3300000, 300000,s=adjust_size(pop),marker='o',color='red') plt.text(3300000, 300000+y_offset,str(pop/1000000) + "million",rotation=0,fontsize=10) pop = 36000000 plt.scatter(6300000, 300000,s=adjust_size(pop),marker='o',color='red') plt.text(6300000, 300000+y_offset,str(pop/1000000) + "million",rotation=0,fontsize=10) plt.title('Major Cities in Asia & Population') plt.show()
以前的繪圖方式側重點在原始數據。還有一些繪圖是爲了呈現羣體參數,好比箱形圖(box plot)。好比湘北高中身高數據繪製成箱形圖:
如圖中標註的,箱形圖體現的主要是中位數和四分位數。上下四分位數構成了箱子,其中包含了一半的數據成員。此外,上下還有兩個邊界,位於箱子的上下邊緣各外推1.5個箱子高度的位置。若是外推1.5個箱子位置超出了數據庫的極值,那麼邊界換成極值的高度。不然,將有數據點超出邊界。這些數據點被認爲是異常值(outlier),用散點的方式畫出。
代碼以下:
import matplotlib.pyplot as plt with open("xiangbei_height.txt", "r") as f: lines = f.readlines() x = list(map(float, lines)) plt.boxplot(x) plt.title("box plot of Shohoku High School") plt.xticks([1], ['Shohoku']) plt.ylabel("height (cm)") plt.show()
箱形圖體現了一個思路,就是在繪製原始數據的同時畫出羣體參數,從而輔助咱們理解數據。好比,咱們能夠在直方圖中標出平均值和標準差:
代碼以下:
import numpy as np import matplotlib.pyplot as plt with open("xiangbei_height.txt", "r") as f: lines = f.readlines() x = list(map(float, lines)) plt.title("Heights of Students (Shohoku High School)") plt.hist(x, 50) plt.xlabel("height (cm)") plt.ylabel("count") mu = np.mean(x) std = np.std(x) h = 120 text_color = "white" plt.axvline(x=mu, color="red") plt.text(mu, h,'mean',rotation=90,color=text_color) plt.axvline(x=mu-std, color="coral") plt.text(mu-std, h,'mean-std',rotation=90,color=text_color) plt.axvline(x=mu+std, color="coral") plt.text(mu+std, h,'mean+std',rotation=90,color=text_color) plt.show()
儘管這裏說明了一些經常使用的數據繪圖方法,但數據繪圖的過程當中有不少人爲創做的因素在。所以,同一個數據庫,甚至同一種繪圖形式,均可能產生多種多樣的數據圖像。不一樣的數據圖像,在傳遞信息的有效性上,會產生不小的差異。怎樣畫好數據圖呢?我根據本身的經驗,總結了下面幾個標準:
在介紹一副數據圖時,也能夠遵循必定的順序:
固然,對於存在人爲創做因素的數據繪圖來講,也沒有定法。但創建必定的流程,能提升繪圖的效率。因此我也建議你創建本身的繪圖流程。
在這一篇文章裏,我主要用參數和繪圖呈現羣體的數據。相似的方法還常常用於呈現樣品數據。因爲在描繪樣品時須要涉及到統計推斷,因此我把樣品描繪的方法放在將在統計推斷的相關文章中講解。
若是你想更多地瞭解Matplotlib,能夠參考官方文檔,以及我之前寫的這篇文章:matplotlib核心剖析 。
歡迎繼續閱讀「數據科學」系列文章