提交 c026d40d 编写于 作者: W wizardforcel

8.16-17

上级 ff4487d4
# 8.16 地理数据和 Basemap
> 原文:[Geographic Data with Basemap](https://nbviewer.jupyter.org/github/donnemartin/data-science-ipython-notebooks/blob/master/matplotlib/04.13-Geographic-Data-With-Basemap.ipynb)
>
> 译者:[飞龙](https://github.com/wizardforcel)
>
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
>
> 本节是[《Python 数据科学手册》](https://github.com/jakevdp/PythonDataScienceHandbook)(Python Data Science Handbook)的摘录。
数据科学中一种常见的可视化类型是地理数据。Matplotlib 用于此类可视化的主要工具是 Basemap 工具包,它是位于``mpl_toolkits``命名空间下的几个 Matplotlib 工具包之一。不可否认,Basemap 使用时有点笨拙,甚至简单的可视化渲染也要花费更长的时间,超出你的想象。
更传统的解决方案(如 leaflet 或 Google Maps API)可能是更加密集的地图可视化的更好选择。尽管如此,Basemap 仍然是 Python 用户在其虚拟工具栏中拥有的有用工具。在本节中,我们将展示使用此工具包可以实现的地图可视化类型的几个示例。
Basemap 的安装很简单;如果你正在使用 conda,你可以输入这个,然后下载包:
```
$ conda install basemap
```
我们只在标准样板中添加一个新的导入:
```py
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
```
一旦安装并导入了 Basemap 工具包,地理绘图就能在几行之内实现(下面的图形也需要 Python 2 中的``PIL``包,或者 Python 3 中的``pillow``包):
```py
plt.figure(figsize=(8, 8))
m = Basemap(projection='ortho', resolution=None, lat_0=50, lon_0=-100)
m.bluemarble(scale=0.5);
```
![png](../img/8-16-1.png)
`Basemap`参数的含义将立即讨论。
有用的是这里显示的地球不仅仅是一个图像; 它是一个功能齐全的 Matplotlib 轴域,它可以理解球面坐标,这使我们可以轻松地在地图上绘制数据!例如,我们可以使用不同的地图投影,放大到北美并绘制西雅图的位置。我们将使用 etopo 图像(显示陆地和海底的地形特征)作为地图背景:
```py
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution=None,
width=8E6, height=8E6,
lat_0=45, lon_0=-100,)
m.etopo(scale=0.5, alpha=0.5)
# 将 (long, lat) 映射为 (x, y) 以便绘图
x, y = m(-122.3, 47.6)
plt.plot(x, y, 'ok', markersize=5)
plt.text(x, y, ' Seattle', fontsize=12);
```
![png](../img/8-16-2.png)
通过几行 Python,你可以轻松了解可能的地理可视化类型。我们现在将更深入地讨论 Basemap 的功能,并提供几个可视化地图数据的示例。使用这些简短的示例作为积木,你应该能够创建几乎任何你想要的地图可视化。
## 地图投影
使用地图时要决定的第一件事,是要使用什么投影。你可能已经熟悉这样一个事实:不可能将球形地图(例如地球的地图)投影到平坦的表面上,而不会以某种方式扭曲或破坏其连续性。这些预测是在人类历史进程中发展起来的,有很多选择!取决于地图投影的预期用途,有一些地图特征,保留它们很有用(例如,方向,区域,距离,形状或其他考虑因素)。
Basemap 包实现了几十个这样的投影,全部由短格式代码引用。在这里,我们将简要介绍一些比较常见的。我们首先定义一个便利例程来绘制我们的世界地图以及经纬线:
```py
from itertools import chain
def draw_map(m, scale=0.2):
# 绘制阴影浮雕图像
m.shadedrelief(scale=scale)
# 将 lats 和 lons 作为字典返回
lats = m.drawparallels(np.linspace(-90, 90, 13))
lons = m.drawmeridians(np.linspace(-180, 180, 13))
# 键包含 plt.Line2D 实例
lat_lines = chain(*(tup[1][0] for tup in lats.items()))
lon_lines = chain(*(tup[1][0] for tup in lons.items()))
all_lines = chain(lat_lines, lon_lines)
# 遍历这些线并设置所需的样式
for line in all_lines:
line.set(linestyle='-', alpha=0.3, color='w')
```
### 圆柱投影
最简单的地图投影是圆柱投影,其中恒定纬度和经度的线分别映射到水平线和垂直线。这种类型的映射很好地代表了赤道区域,但产生了极点附近的极端扭曲。纬线的间距在不同的圆柱投影之间变化,产生不同的保留特征,并且在极点附近的不同的变形。在下图中,我们展示了等距圆柱投影的示例,它选择了沿子午线保留距离的纬度缩放。其他圆柱投影是墨卡托(``projection='merc'``)和圆柱等积(``projection='cea'``)投影。
```py
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(projection='cyl', resolution=None,
llcrnrlat=-90, urcrnrlat=90,
llcrnrlon=-180, urcrnrlon=180, )
draw_map(m)
```
![png](../img/8-16-3.png)
此视图的 Basemap 的附加参数,为所需的地图指定左下角(``llcrnr``)和右上角(``urcrnr``)的纬度(``lat``)和经度(``lon``),以度为单位。
### 伪圆柱投影
伪圆柱投影放松了子午线(恒定经线)保持垂直的要求; 这可以在投影的极点附近提供更好的特性。墨卡托投影(``projection ='moll'``)是这方面的一个常见例子,其中所有经线都是椭圆弧。它的构造是为了保留地图上的区域:尽管两极附近存在扭曲,但小块的区域反映了真实区域。其他伪圆柱投影是正弦曲线(``projection='sinu'``)和罗宾逊(``projection='robin'``)投影。
```py
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(projection='moll', resolution=None,
lat_0=0, lon_0=0)
draw_map(m)
```
![png](../img/8-16-4.png)
这里 Basemap 的额外参数是指所需映射的中心纬度(``lat_0``)和经度(``lon_0``)。
### 透视投影
透视投影使用透视点的特定选择构建,类似于你从空间中的特定点拍摄地球(对于某些投影,技术上点位于地球内部!)。一个常见的例子是正交投影(``projection='ortho'``),它显示了远处的观察者看到的地球的一侧。因此,它一次只能显示全球的一半。
其他基于视角的投影包括 gnomonic 投影(``projection='gnom'``)和立体投影(``projection='stere'``)。这些通常对于显示地图的一小部分最有用。
以下是正交投影的示例:
```py
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='ortho', resolution=None,
lat_0=50, lon_0=0)
draw_map(m);
```
![png](../img/8-16-5.png)
### 圆锥投影
圆锥投影将地图投影到单个圆锥上,然后展开。这可以产生非常好的局部特性,但是远离圆锥焦点的区域可能变得非常扭曲。其中一个例子是 Lambert Conformal 圆锥投影(``projection='lcc'``),我们之前在北美地图中看到过。
它将地图投影到一个圆锥上,这个圆锥的排列方式使得两个标准平行线(在 Basemap 中由``lat_1````lat_2``规定)的距离是良好表示的,比例在它们之间减小并且在它们之外增加。其他有用的圆锥投影是等距圆锥投影(``projection='eqdc'``)和 Albers 等面投影(``projection='aea'``)。圆锥投影,就像透视投影,往往是表示地球中小块区域的良好选择。
```py
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution=None,
lon_0=0, lat_0=50, lat_1=45, lat_2=55,
width=1.6E7, height=1.2E7)
draw_map(m)
```
![png](../img/8-16-6.png)
### 其它投影
如果你要对基于地图的可视化做很多事情,我建议你了解其他可用的投影,以及它们的属性,优点和缺点。
最有可能的是,它们可以在[ Basemap 包](http://matplotlib.org/basemap/users/mapsetup.html)中找到。
如果你深入研究这个主题,你会发现一个令人难以置信的极客的亚文化 geo-viz,他们为热烈争论做好了准备,来为任何给定应用支持他们最喜欢的投影!
## 绘制地图背景
之前我们看过``bluemarble()````shadedrelief()``方法,用于在地图上投影全球图像,以及``drawparallels()````drawmeridians()``方法用于绘制恒定经纬度的线。Basemap 包包含一系列有用的函数,用于绘制物理特征的边界,例如大陆,海洋,湖泊和河流等,以及政治边界,例如国家地区,以及美国各州和县。以下是一些可用绘图功能,你可能希望使用 IPython 帮助特性来探索:
- **物理边界和水体**
- ``drawcoastlines()``:绘制大陆海岸线
- ``drawlsmask()``:绘制陆地和海洋之间的掩码,用于在一个或另一个上投射图像
- ``drawmapboundary()``:绘制地图边界,包括海洋的填充颜色。
- ``drawrivers()``:在地图上绘制河流
- ``fillcontinents()``:用给定的颜色填充大陆;可选择用另一种颜色填充湖泊
- **政治边界**
- ``drawcountries()``:绘制国界
- ``drawstates()``:绘制美国国界
- ``drawcounties()``:绘制美国县界
- **地图功能**
- ``drawgreatcircle()``:在两点之间绘制大圆圈
- ``drawparallels()``:绘制恒定纬度的线条
- ``drawmeridians()``:绘制恒定经度的线条
- ``drawmapscale()``:在地图上绘制线性刻度
- **全球图像**
- ``bluemarble()``:将 NASA 的蓝色大理石图像投影到地图上
- ``shadedrelief()``:将阴影浮雕图像投影到地图上
- ``etopo()``:在地图上绘制一个 etopo 浮雕图像
- ``warpimage()``:将用户提供的图像投影到地图上
对于基于边界的特性,必须在创建 Basemap 图像时设置所需的分辨率。``Basemap``类的``resolution``参数设置边界中的细节级别,他们是``'c'``(原始),``'l'``(低),``'i'`(中),``'h'``(高),``'f'``(完整)或`None`(如果没有使用边界)。这个选项很重要:例如,在全局地图上设置高分辨率边界可能非常慢。
这是绘制陆地/海洋边界,以及分辨率参数的效果的示例。我们将创建苏格兰的美丽的斯凯岛的低分辨率和高分辨率地图。它位于北纬 57.3°,6.2°W,90,000×120,000 公里的地图很好显示它:
```py
fig, ax = plt.subplots(1, 2, figsize=(12, 8))
for i, res in enumerate(['l', 'h']):
m = Basemap(projection='gnom', lat_0=57.3, lon_0=-6.2,
width=90000, height=120000, resolution=res, ax=ax[i])
m.fillcontinents(color="#FFDDCC", lake_color='#DDEEFF')
m.drawmapboundary(fill_color="#DDEEFF")
m.drawcoastlines()
ax[i].set_title("resolution='{0}'".format(res));
```
![png](../img/8-16-7.png)
请注意,低分辨率海岸线不适合此级别的缩放,而高分辨率的工作正常。然而,低水平对于全局视图来说效果会很好,并且比加载整个地球的高分辨率边界数据要快得多!可能需要进行一些实验,才能找到给定视图的正确分辨率参数:最佳路径是从快速低分辨率的绘图开始,并根据需要增加分辨率。
## 在 Basemap 上绘制数据
也许 Basemap 工具包中最有用的部分,是将各种数据绘制到地图背景上的能力。对于简单的绘图和文本,任何``plt``函数都可以在地图上执行;你可以使用``Basemap``实例将纬度和经度坐标投影到``(x, y)``坐标,用于``plt``的绘图,正如我们在西雅图示例中所见。
除此之外,还有许多特定于地图的函数,可用作``Basemap``实例的方法。这些东西与它们的标准 Matplotlib 对应物非常相似,但是有一个额外的布尔参数``latlon``,如果设置为``True``,它允许你将原始纬度和经度传递给方法,而不是投影``(x, y)``坐标。
其中一些特定于地图的方法是:
- ``contour()/contourf()``:绘制等高线或填充的等高线
- ``imshow()``:绘制图像
- ``pcolor()/pcolormesh()``:为不规则/规则网格绘制伪彩色图
- ``plot()``:绘制线条和/或标记。
- ``scatter()``:绘制带标记的点。
- ``quiver()``:绘制向量。
- ``barbs()``:绘制风向。
- ``drawgreatcircle()``:绘制大圆圈。
我们将继续并看到其中一些例子。这些函数的更多信息,包括几个示例图,请参阅[在线 Basemap 文档](http://matplotlib.org/basemap/)。
## 示例:加利福尼亚的城市
回想一下,在“自定义图例”中,我们演示了在散点图中使用大小和颜色,来传达加州城市的位置,大小和人口的信息。在这里,我们将再次创建此绘图,但使用 Basemap 将数据放在上下文中。
我们开始加载数据,就像我们之前做的那样:
```py
import pandas as pd
cities = pd.read_csv('data/california_cities.csv')
# 提取我们感兴趣的数据
lat = cities['latd'].values
lon = cities['longd'].values
population = cities['population_total'].values
area = cities['area_total_km2'].values
```
接下来,我们设置地图投影,绘制数据的散点图,然后创建颜色条和图例:
```py
# 1. 绘制地图北京
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution='h',
lat_0=37.5, lon_0=-119,
width=1E6, height=1.2E6)
m.shadedrelief()
m.drawcoastlines(color='gray')
m.drawcountries(color='gray')
m.drawstates(color='gray')
# 2. 绘制城市数据的散点图,其中颜色反映人口
# 尺寸反映面积
m.scatter(lon, lat, latlon=True,
c=np.log10(population), s=area,
cmap='Reds', alpha=0.5)
# 3. 创建颜色条和图例
plt.colorbar(label=r'$\log_{10}({\rm population})$')
plt.clim(3, 7)
# 使用虚拟的点生成图例
for a in [100, 300, 500]:
plt.scatter([], [], c='k', alpha=0.5, s=a,
label=str(a) + ' km$^2$')
plt.legend(scatterpoints=1, frameon=False,
labelspacing=1, loc='lower left');
```
![png](../img/8-16-8.png)
这向我们展示了大量人口在加利福尼亚定居的地方:它们聚集在洛杉矶和旧金山地区的海岸附近,沿着平坦的中央山谷中的高速公路延伸,几乎完全避开了沿着国界的山区。
## 示例:表面温度数据
作为一些更连续的地理数据的可视化示例,让我们考虑一下 2014 年 1 月袭击美国东部的“极涡”。任何气候数据的重要来源是[美国宇航局 Goddard 空间研究所](http://data.giss.nasa.gov/)。这里我们将使用 GIS 250 温度数据,我们可以使用 shell 命令下载(在 Windows 机器上,这些命令可能必须修改)。此处使用的数据下载于 2016 年 6 月 12 日,文件大小约为 9MB:
```py
# !curl -O http://data.giss.nasa.gov/pub/gistemp/gistemp250.nc.gz
# !gunzip gistemp250.nc.gz
```
数据采用NetCDF格式,可以通过``netCDF4``库在 Python 中读取。你可以像此处所示安装此库:
```
$ conda install netcdf4
```
我们这样读取数据:
```py
from netCDF4 import Dataset
data = Dataset('gistemp250.nc')
```
该文件包含不同日期的许多全球温度读数;我们需要选择我们感兴趣的日期的索引 - 这里是 2014 年 1 月 15 日:
```py
from netCDF4 import date2index
from datetime import datetime
timeindex = date2index(datetime(2014, 1, 15),
data.variables['time'])
```
现在我们可以加载经纬度数据,以及这个索引的温度异常:
```py
lat = data.variables['lat'][:]
lon = data.variables['lon'][:]
lon, lat = np.meshgrid(lon, lat)
temp_anomaly = data.variables['tempanomaly'][timeindex]
```
最后,我们将使用``pcolormesh()``方法绘制数据的颜色网格。我们将看看北美,并在背景中使用阴影浮雕地图。请注意,对于此数据,我们专门选择了一个离散颜色表,其中零处为中性色,负值和正值为两个对比色。我们还会在颜色上轻轻划出海岸线以供参考:
```py
fig = plt.figure(figsize=(10, 8))
m = Basemap(projection='lcc', resolution='c',
width=8E6, height=8E6,
lat_0=45, lon_0=-100,)
m.shadedrelief(scale=0.5)
m.pcolormesh(lon, lat, temp_anomaly,
latlon=True, cmap='RdBu_r')
plt.clim(-8, 8)
m.drawcoastlines(color='lightgray')
plt.title('January 2014 Temperature Anomaly')
plt.colorbar(label='temperature anomaly (°C)');
```
![png](../img/8-16-9.png)
该数据描绘了该月发生的局部极端温度异常。美国东部比正常情况要冷得多,而西部和阿拉斯加的温度要高得多。没有记录温度的区域显示地图背景。
# 8.17 使用 Seaborn 的可视化
> 原文:[Visualization with Seaborn](https://nbviewer.jupyter.org/github/donnemartin/data-science-ipython-notebooks/blob/master/matplotlib/04.14-Visualization-With-Seaborn.ipynb)
>
> 译者:[飞龙](https://github.com/wizardforcel)
>
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
>
> 本节是[《Python 数据科学手册》](https://github.com/jakevdp/PythonDataScienceHandbook)(Python Data Science Handbook)的摘录。
Matplotlib 据证明是一种非常有用和流行的可视化工具,但即使狂热的用户也会承认它经常会有很多不足之处。有几个对 Matplotlib 的有效的抱怨常常出现:
- 在 2.0 版之前,Matplotlib 的默认值并不是最佳选择。 它基于大约 1999 年的 MATLAB,经常是这样。
- Matplotlib 的 API 相对较低。 可以进行复杂的统计可视化,但通常需要大量的样板代码。
- Matplotlib 比 Pandas 早了十多年,因此不适合与 Pandas 的``DataFrame`一起使用。 为了可视化来自 Pandas ``DataFrame``的数据,你必须提取每个``Series``并经常将它们连接成正确的格式。 如果有一个绘图库可以智能地在绘图中使用`DataFrame`标签会更好。
这些问题的答案是[Seaborn](http://seaborn.pydata.org/)。 Seaborn 在 Matplotlib 之上提供 API,为绘图样式和颜色默认值提供合理的选择,为常见的统计绘图类型定义简单的高级函数,并与 Pandas `DataFrame`提供的功能集成。
公平地说,Matplotlib 团队正在解决这个问题:它最近添加了“自定义 Matplotlib:配置和样式表”中讨论的``plt.style``工具,并且正在开始 更无缝地处理 Pandas 数据。该库的 2.0 版本将包含新的默认样式表,它将改善现状。但出于所讨论的所有原因,Seaborn 仍然是一个非常有用的插件。
## Seaborn VS Matplotlib
下面是 Matplotlib 中简单随机游走图的示例,使用其经典的绘图格式和颜色。我们从典型的导入开始:
```py
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
import numpy as np
import pandas as pd
```
现在我们创建一些随机游走数据:
```py
# 创建一些数据
rng = np.random.RandomState(0)
x = np.linspace(0, 10, 500)
y = np.cumsum(rng.randn(500, 6), 0)
```
并执行简单的绘图:
```py
# 使用 Matplotlib 默认值绘制数据
plt.plot(x, y)
plt.legend('ABCDEF', ncol=2, loc='upper left');
```
![png](../img/8-17-1.png)
虽然结果包含了我们希望传达的所有信息,但它的确以一种并非好看的方式,甚至在 21 世纪数据可视化的背景下看起来有点过时。
现在让我们来看看它如何与 Seaborn 一起使用。我们将要看到,Seaborn 有许多自己的高级绘图例程,但它也可以覆盖 Matplotlib 的默认参数,反过来甚至可以使简单的 Matplotlib 脚本产生非常出色的输出。我们可以通过调用 Seaborn 的``set()``方法来设置样式。按照惯例,Seaborn 被导入为``sns``:
```py
import seaborn as sns
sns.set()
```
现在让我们重新运行与以前相同的两行:
```py
# 和上面一样的绘图代码
plt.plot(x, y)
plt.legend('ABCDEF', ncol=2, loc='upper left');
```
![png](../img/8-17-2.png)
啊,好多了!
## 探索 Seaborn 绘图
Seaborn 的主要思想是它提供高级命令,来创建用于统计数据探索,甚至是一些统计模型拟合的各种绘图类型。
我们来看看Seaborn中可用的一些数据集和绘图类型。 请注意,以下所有都可以使用原始 Matplotlib 命令完成(事实上,这是 Seaborn 所做的事情),但 Seaborn API 更方便。
### 直方图,KDE,和密度
通常在统计数据可视化中,你只需要绘制直方图和变量的联合分布。我们已经看到这在 Matplotlib 中相对简单:
```py
data = np.random.multivariate_normal([0, 0], [[5, 2], [2, 2]], size=2000)
data = pd.DataFrame(data, columns=['x', 'y'])
for col in 'xy':
plt.hist(data[col], normed=True, alpha=0.5)
```
![png](../img/8-17-3.png)
我们可以使用核密度估计来获得对分布的平滑估计,而不是直方图,Seaborn 使用``sns.kdeplot``来执行:
```py
for col in 'xy':
sns.kdeplot(data[col], shade=True)
```
![png](../img/8-17-4.png)
直方图和 KDE 可以使用``distplot``组合:
```py
sns.distplot(data['x'])
sns.distplot(data['y']);
```
![png](../img/8-17-5.png)
如果我们将完整的二维数据集传递给``kdeplot``,我们将获得数据的二维可视化:
```py
sns.kdeplot(data);
```
![png](../img/8-17-6.png)
我们可以使用``sns.jointplot``查看联合分布和边缘分布。对于此图,我们将样式设置为白色背景:
```py
with sns.axes_style('white'):
sns.jointplot("x", "y", data, kind='kde');
```
![png](../img/8-17-7.png)
还有其他参数可以传递给``jointplot`` - 例如,我们可以使用基于六边形的直方图:
```py
with sns.axes_style('white'):
sns.jointplot("x", "y", data, kind='hex')
```
![png](../img/8-17-8.png)
### 配对绘图
将联合绘图推广到高维数据集时,最终会得到配对绘图。 当你想要绘制所有值对于彼此的配对时,这对于探索多维数据之间的相关性非常有用。
我们将使用着名的鸢尾花数据集进行演示,该数据集列出了三种鸢尾花物种的花瓣和萼片的测量值:
```py
iris = sns.load_dataset("iris")
iris.head()
```
| | sepal_length | sepal_width | petal_length | petal_width | species |
| --- | --- | --- | --- | --- | --- |
| 0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa |
可视化样本之间的多维关系就像调用``sns.pairplot``一样简单:
```py
sns.pairplot(iris, hue='species', size=2.5);
```
![png](../img/8-17-9.png)
### 分面直方图
有时,查看数据的最佳方式是通过子集的直方图。 Seaborn 的``FacetGrid``使其非常简单。我们将根据各种指标数据查看一些数据,它们显示餐厅员工在小费中收到的金额:
```py
tips = sns.load_dataset('tips')
tips.head()
```
| | total_bill | tip | sex | smoker | day | time | size |
| --- | --- | --- | --- | --- | --- | --- |
| 0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
| 1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
| 2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
| 3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
| 4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
```py
tips['tip_pct'] = 100 * tips['tip'] / tips['total_bill']
grid = sns.FacetGrid(tips, row="sex", col="time", margin_titles=True)
grid.map(plt.hist, "tip_pct", bins=np.linspace(0, 40, 15));
```
![png](../img/8-17-10.png)
### 因子图
因子图也可用于此类可视化。 这允许你查看由任何其他参数定义的桶中的参数分布:
```py
with sns.axes_style(style='ticks'):
g = sns.factorplot("day", "total_bill", "sex", data=tips, kind="box")
g.set_axis_labels("Day", "Total Bill");
```
![png](../img/8-17-11.png)
### 联合分布
与我们之前看到的配对图类似,我们可以使用``sns.jointplot``来显示不同数据集之间的联合分布以及相关的边缘分布:
```py
with sns.axes_style('white'):
sns.jointplot("total_bill", "tip", data=tips, kind='hex')
```
![png](../img/8-17-12.png)
联合图甚至可以做一些自动的核密度估计和回归:
```py
sns.jointplot("total_bill", "tip", data=tips, kind='reg');
```
![png](../img/8-17-13.png)
### 条形图
时间序列可以使用``sns.factorplot``绘制。 在下面的示例中,我们将使用我们在“聚合和分组”中首次看到的行星数据:
```py
planets = sns.load_dataset('planets')
planets.head()
```
| | method | number | orbital_period | mass | distance | year |
| --- | --- | --- | --- | --- | --- | --- |
| 0 | Radial Velocity | 1 | 269.300 | 7.10 | 77.40 | 2006 |
| 1 | Radial Velocity | 1 | 874.774 | 2.21 | 56.95 | 2008 |
| 2 | Radial Velocity | 1 | 763.000 | 2.60 | 19.84 | 2011 |
| 3 | Radial Velocity | 1 | 326.030 | 19.40 | 110.62 | 2007 |
| 4 | Radial Velocity | 1 | 516.220 | 10.50 | 119.47 | 2009 |
```py
with sns.axes_style('white'):
g = sns.factorplot("year", data=planets, aspect=2,
kind="count", color='steelblue')
g.set_xticklabels(step=5)
```
![png](../img/8-17-14.png)
通过查看每个行星的发现方法,我们可以了解更多信息:
```py
with sns.axes_style('white'):
g = sns.factorplot("year", data=planets, aspect=4.0, kind='count',
hue='method', order=range(2001, 2015))
g.set_ylabels('Number of Planets Discovered')
```
![png](../img/8-17-15.png)
对于使用 Seaborn 进行绘图的更多信息,请参阅[ Seaborn 文档](http://seaborn.pydata.org/),[教程](http://seaborn.pydata.org/tutorial.htm)和[ Seaborn 画廊](http://seaborn.pydata.org/examples/index.html)。
## 示例:探索马拉松结束时间
在这里,我们将使用 Seaborn 来帮我们可视化和理解马拉松的结果。我从 Web 上的数据源抓取数据,汇总并删除任何身份信息,并将其放在 GitHub 上,可以在那里下载(如果你有兴趣使用 Python 抓取网页,我建议阅读 Ryan Mitchell 的[《Web Scraping with Python》](http://shop.oreilly.com/product/0636920034391.do)。我们首先从 Web 下载数据并将其加载到 Pandas 中:
```py
# !curl -O https://raw.githubusercontent.com/jakevdp/marathon-data/master/marathon-data.csv
data = pd.read_csv('marathon-data.csv')
data.head()
```
| | age | gender | split | final |
| --- | --- | --- | --- | --- |
| 0 | 33 | M | 01:05:38 | 02:08:51 |
| 1 | 32 | M | 01:06:26 | 02:09:28 |
| 2 | 31 | M | 01:06:49 | 02:10:42 |
| 3 | 38 | M | 01:06:16 | 02:13:45 |
| 4 | 31 | M | 01:06:32 | 02:13:59 |
默认情况下,Pandas 将时间列加载为 Python 字符串(类型``object``);我们可以通过查看`DataFrame`的``dtypes``属性来看到它:
```py
data.dtypes
'''
age int64
gender object
split object
final object
dtype: object
'''
```
让我们通过为时间提供转换器来解决这个问题:
```py
def convert_time(s):
h, m, s = map(int, s.split(':'))
return pd.datetools.timedelta(hours=h, minutes=m, seconds=s)
data = pd.read_csv('marathon-data.csv',
converters={'split':convert_time, 'final':convert_time})
data.head()
```
| | age | gender | split | final |
| --- | --- | --- | --- | --- |
| 0 | 33 | M | 01:05:38 | 02:08:51 |
| 1 | 32 | M | 01:06:26 | 02:09:28 |
| 2 | 31 | M | 01:06:49 | 02:10:42 |
| 3 | 38 | M | 01:06:16 | 02:13:45 |
| 4 | 31 | M | 01:06:32 | 02:13:59 |
```py
data.dtypes
'''
age int64
gender object
split timedelta64[ns]
final timedelta64[ns]
dtype: object
'''
```
这看起来好多了。 出于我们的 Seaborn 绘图工具的目的,让我们接下来添加以秒为单位的列:
```py
data['split_sec'] = data['split'].astype(int) / 1E9
data['final_sec'] = data['final'].astype(int) / 1E9
data.head()
```
| | age | gender | split | final | split_sec | final_sec |
| --- | --- | --- | --- | --- | --- | --- |
| 0 | 33 | M | 01:05:38 | 02:08:51 | 3938.0 | 7731.0 |
| 1 | 32 | M | 01:06:26 | 02:09:28 | 3986.0 | 7768.0 |
| 2 | 31 | M | 01:06:49 | 02:10:42 | 4009.0 | 7842.0 |
| 3 | 38 | M | 01:06:16 | 02:13:45 | 3976.0 | 8025.0 |
| 4 | 31 | M | 01:06:32 | 02:13:59 | 3992.0 | 8039.0 |
为了了解数据的样子,我们可以在数据上绘制一个``jointplot``:
```py
with sns.axes_style('white'):
g = sns.jointplot("split_sec", "final_sec", data, kind='hex')
g.ax_joint.plot(np.linspace(4000, 16000),
np.linspace(8000, 32000), ':k')
```
![png](../img/8-17-16.png)
虚线表示如果他们以完全稳定的速度跑马拉松,那么某人的时间会在哪里。 分布高于此的事实表明(正如你所料)大多数人在马拉松比赛过程中减速。如果你有竞争力,那么你就会知道那些在比赛后半段跑得更快的人 - 被称为将比赛负分割(negative-split)。
让我们在数据中创建另一个列,即分割分数,它测量每个运动员将比赛负分割或正分割(positive-split)的程度:
```py
data['split_frac'] = 1 - 2 * data['split_sec'] / data['final_sec']
data.head()
```
| | age | gender | split | final | split_sec | final_sec | split_frac |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 33 | M | 01:05:38 | 02:08:51 | 3938.0 | 7731.0 | -0.018756 |
| 1 | 32 | M | 01:06:26 | 02:09:28 | 3986.0 | 7768.0 | -0.026262 |
| 2 | 31 | M | 01:06:49 | 02:10:42 | 4009.0 | 7842.0 | -0.022443 |
| 3 | 38 | M | 01:06:16 | 02:13:45 | 3976.0 | 8025.0 | 0.009097 |
| 4 | 31 | M | 01:06:32 | 02:13:59 | 3992.0 | 8039.0 | 0.006842 |
如果此分割差异小于零,则这个人将比赛以这个比例负分割。让我们绘制这个分割分数的分布图:
```py
sns.distplot(data['split_frac'], kde=False);
plt.axvline(0, color="k", linestyle="--");
```
![png](../img/8-17-17.png)
```py
sum(data.split_frac < 0)
# 251
```
在近 40,000 名参与者中,只有 250 人将马拉松负分割。
让我们看看这个分割分数和其他变量之间是否存在任何相关性。我们将使用``pairgrid``来绘制所有这些相关性:
```py
g = sns.PairGrid(data, vars=['age', 'split_sec', 'final_sec', 'split_frac'],
hue='gender', palette='RdBu_r')
g.map(plt.scatter, alpha=0.8)
g.add_legend();
```
![png](../img/8-17-18.png)
看起来分割分数与年龄没有特别的关联,但确实与最终时间相关:更快的运动员往往将马拉松时间等分。(我们在这里看到,当涉及到绘图样式时,Seaborn 不是 Matplotlib 弊病的灵丹妙药:特别是,`x`轴标签重叠。因为输出是一个简单的 Matplotlib 图,但是,“自定义刻度”中的方法可以用来调整这些东西。)
这里男女之间的区别很有意思。 让我们看看这两组的分割分数的直方图:
```py
sns.kdeplot(data.split_frac[data.gender=='M'], label='men', shade=True)
sns.kdeplot(data.split_frac[data.gender=='W'], label='women', shade=True)
plt.xlabel('split_frac');
```
![png](../img/8-17-19.png)
这里有趣的是,有更多的男人比女人更接近等分!这几乎看起来像男女之间的某种双峰分布。 让我们看看,我们是否可以通过将分布看做年龄的函数,来判断发生了什么。
比较分布的好方法是使用提琴图:
```py
sns.violinplot("gender", "split_frac", data=data,
palette=["lightblue", "lightpink"]);
```
![png](../img/8-17-20.png)
这是比较男女之间分布的另一种方式。
让我们看得深入一些,然后将这些提琴图作为年龄的函数进行比较。我们首先在数组中创建一个新列,指定每个人的年龄,以十年为单位:
```py
data['age_dec'] = data.age.map(lambda age: 10 * (age // 10))
data.head()
```
| | age | gender | split | final | split_sec | final_sec | split_frac | age_dec |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 33 | M | 01:05:38 | 02:08:51 | 3938.0 | 7731.0 | -0.018756 | 30 |
| 1 | 32 | M | 01:06:26 | 02:09:28 | 3986.0 | 7768.0 | -0.026262 | 30 |
| 2 | 31 | M | 01:06:49 | 02:10:42 | 4009.0 | 7842.0 | -0.022443 | 30 |
| 3 | 38 | M | 01:06:16 | 02:13:45 | 3976.0 | 8025.0 | 0.009097 | 30 |
| 4 | 31 | M | 01:06:32 | 02:13:59 | 3992.0 | 8039.0 | 0.006842 | 30 |
```py
men = (data.gender == 'M')
women = (data.gender == 'W')
with sns.axes_style(style=None):
sns.violinplot("age_dec", "split_frac", hue="gender", data=data,
split=True, inner="quartile",
palette=["lightblue", "lightpink"]);
```
![png](../img/8-17-21.png)
考虑到这一点,我们可以看到男性和女性的分布在哪里不同:与同龄(或任何年龄的)女性相比,20 到 50 岁男性的分割分布,与较低的分割相比,表现出明显的过度密集。
同样令人惊讶的是,这位 80 岁的女性在分割时间方面表现优于每个人。 这可能是因为我们估计来自小数字的分布,因为在该范围内只有少数运动员:
```py
(data.age > 80).sum()
# 7
```
回到带有负分割的男性:谁是这些运动员? 这个分割分数是否与快速结束相关? 我们可以很容易地绘制这个图。 我们将使用``regplot``,它将自动拟合数据的线性回归:
```py
g = sns.lmplot('final_sec', 'split_frac', col='gender', data=data,
markers=".", scatter_kws=dict(color='c'))
g.map(plt.axhline, y=0.1, color="k", ls=":");
```
![png](../img/8-17-22.png)
显然,带有快速分割的人是精英运动员,他们在约 15,000 秒或约 4 小时内结束。 慢于此的人不太可能具有快速的第二次分割。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册