做者: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核心剖析 。
欢迎继续阅读“数据科学”系列文章