9.2 使用pandas和seaborn绘图

matplotlib实际上是一种比较低级的工具。要绘制一张图表,你组装一些基本组件就行:数据展示(即图表类型:线型图、柱状图、盒形图、散布图、等值线图等)、图例、标题、刻度标签以及其他注解型信息。

在pandas中,我们有多列数据,还有行和列标签。pandas自身就有内置的方法,用于简化从DataFrame和Series绘制图形。另一个库seaborn(https://seaborn.pydata.org/),由Michael Waskom创建的静态图形库。Seaborn简化了许多常见可视类型的创建。

提示:引入seaborn会修改matplotlib默认的颜色方案和绘图类型,以提高可读性和美观度。即使你不使用seaborn API,你可能也会引入seaborn,作为提高美观度和绘制常见matplotlib图形的简化方法。

线型图

Series和DataFrame都有一个用于生成各类图表的plot方法。默认情况下,它们所生成的是线型图(如图9-13所示):

  1. In [60]: s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10))
  2. In [61]: s.plot()

图9-13 简单的Series图表示例

该Series对象的索引会被传给matplotlib,并用以绘制X轴。可以通过use_index=False禁用该功能。X轴的刻度和界限可以通过xticks和xlim选项进行调节,Y轴就用yticks和ylim。plot参数的完整列表请参见表9-3。我只会讲解其中几个,剩下的就留给读者自己去研究了。

9.2 使用pandas和seaborn绘图 - 图2

表9-3 Series.plot方法的参数

pandas的大部分绘图方法都有一个可选的ax参数,它可以是一个matplotlib的subplot对象。这使你能够在网格布局中更为灵活地处理subplot的位置。

DataFrame的plot方法会在一个subplot中为各列绘制一条线,并自动创建图例(如图9-14所示):

  1. In [62]: df = pd.DataFrame(np.random.randn(10, 4).cumsum(0),
  2. ....: columns=['A', 'B', 'C', 'D'],
  3. ....: index=np.arange(0, 100, 10))
  4. In [63]: df.plot()

图9-14 简单的DataFrame绘图

plot属性包含一批不同绘图类型的方法。例如,df.plot()等价于df.plot.line()。后面会学习这些方法。

笔记:plot的其他关键字参数会被传给相应的matplotlib绘图函数,所以要更深入地自定义图表,就必须学习更多有关matplotlib API的知识。

DataFrame还有一些用于对列进行灵活处理的选项,例如,是要将所有列都绘制到一个subplot中还是创建各自的subplot。详细信息请参见表9-4。

表9-4 专用于DataFrame的plot参数

注意: 有关时间序列的绘图,请见第11章。

柱状图

plot.bar()和plot.barh()分别绘制水平和垂直的柱状图。这时,Series和DataFrame的索引将会被用作X(bar)或Y(barh)刻度(如图9-15所示):

  1. In [64]: fig, axes = plt.subplots(2, 1)
  2. In [65]: data = pd.Series(np.random.rand(16), index=list('abcdefghijklmnop'))
  3. In [66]: data.plot.bar(ax=axes[0], color='k', alpha=0.7)
  4. Out[66]: <matplotlib.axes._subplots.AxesSubplot at 0x7fb62493d470>
  5. In [67]: data.plot.barh(ax=axes[1], color='k', alpha=0.7)

图9-15 水平和垂直的柱状图

color=’k’和alpha=0.7设定了图形的颜色为黑色,并使用部分的填充透明度。对于DataFrame,柱状图会将每一行的值分为一组,并排显示,如图9-16所示:

  1. In [69]: df = pd.DataFrame(np.random.rand(6, 4),
  2. ....: index=['one', 'two', 'three', 'four', 'five', 'six'],
  3. ....: columns=pd.Index(['A', 'B', 'C', 'D'], name='Genus'))
  4. In [70]: df
  5. Out[70]:
  6. Genus A B C D
  7. one 0.370670 0.602792 0.229159 0.486744
  8. two 0.420082 0.571653 0.049024 0.880592
  9. three 0.814568 0.277160 0.880316 0.431326
  10. four 0.374020 0.899420 0.460304 0.100843
  11. five 0.433270 0.125107 0.494675 0.961825
  12. six 0.601648 0.478576 0.205690 0.560547
  13. In [71]: df.plot.bar()

图9-16 DataFrame的柱状图

注意,DataFrame各列的名称”Genus”被用作了图例的标题。

设置stacked=True即可为DataFrame生成堆积柱状图,这样每行的值就会被堆积在一起(如图9-17所示):

  1. In [73]: df.plot.barh(stacked=True, alpha=0.5)

图9-17 DataFrame的堆积柱状图

笔记:柱状图有一个非常不错的用法:利用value_counts图形化显示Series中各值的出现频率,比如s.value_counts().plot.bar()。

再以本书前面用过的那个有关小费的数据集为例,假设我们想要做一张堆积柱状图以展示每天各种聚会规模的数据点的百分比。我用read_csv将数据加载进来,然后根据日期和聚会规模创建一张交叉表:

  1. In [75]: tips = pd.read_csv('examples/tips.csv')
  2. In [76]: party_counts = pd.crosstab(tips['day'], tips['size'])
  3. In [77]: party_counts
  4. Out[77]:
  5. size 1 2 3 4 5 6
  6. day
  7. Fri 1 16 1 1 0 0
  8. Sat 2 53 18 13 1 0
  9. Sun 0 39 15 18 3 1
  10. Thur 1 48 4 5 1 3
  11. # Not many 1- and 6-person parties
  12. In [78]: party_counts = party_counts.loc[:, 2:5]

然后进行规格化,使得各行的和为1,并生成图表(如图9-18所示):

  1. # Normalize to sum to 1
  2. In [79]: party_pcts = party_counts.div(party_counts.sum(1), axis=0)
  3. In [80]: party_pcts
  4. Out[80]:
  5. size 2 3 4 5
  6. day
  7. Fri 0.888889 0.055556 0.055556 0.000000
  8. Sat 0.623529 0.211765 0.152941 0.011765
  9. Sun 0.520000 0.200000 0.240000 0.040000
  10. Thur 0.827586 0.068966 0.086207 0.017241
  11. In [81]: party_pcts.plot.bar()

图9-18 每天各种聚会规模的比例

于是,通过该数据集就可以看出,聚会规模在周末会变大。

对于在绘制一个图形之前,需要进行合计的数据,使用seaborn可以减少工作量。用seaborn来看每天的小费比例(图9-19是结果):

  1. In [83]: import seaborn as sns
  2. In [84]: tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])
  3. In [85]: tips.head()
  4. Out[85]:
  5. total_bill tip smoker day time size tip_pct
  6. 0 16.99 1.01 No Sun Dinner 2 0.063204
  7. 1 10.34 1.66 No Sun Dinner 3 0.191244
  8. 2 21.01 3.50 No Sun Dinner 3 0.199886
  9. 3 23.68 3.31 No Sun Dinner 2 0.162494
  10. 4 24.59 3.61 No Sun Dinner 4 0.172069
  11. In [86]: sns.barplot(x='tip_pct', y='day', data=tips, orient='h')

图9-19 小费的每日比例,带有误差条

seaborn的绘制函数使用data参数,它可能是pandas的DataFrame。其它的参数是关于列的名字。因为一天的每个值有多次观察,柱状图的值是tip_pct的平均值。绘制在柱状图上的黑线代表95%置信区间(可以通过可选参数配置)。

seaborn.barplot有颜色选项,使我们能够通过一个额外的值设置(见图9-20):

  1. In [88]: sns.barplot(x='tip_pct', y='day', hue='time', data=tips, orient='h')

图9-20 根据天和时间的小费比例

注意,seaborn已经自动修改了图形的美观度:默认调色板,图形背景和网格线的颜色。你可以用seaborn.set在不同的图形外观之间切换:

  1. In [90]: sns.set(style="whitegrid")

直方图和密度图

直方图(histogram)是一种可以对值频率进行离散化显示的柱状图。数据点被拆分到离散的、间隔均匀的面元中,绘制的是各面元中数据点的数量。再以前面那个小费数据为例,通过在Series使用plot.hist方法,我们可以生成一张“小费占消费总额百分比”的直方图(如图9-21所示):

  1. In [92]: tips['tip_pct'].plot.hist(bins=50)

图9-21 小费百分比的直方图

与此相关的一种图表类型是密度图,它是通过计算“可能会产生观测数据的连续概率分布的估计”而产生的。一般的过程是将该分布近似为一组核(即诸如正态分布之类的较为简单的分布)。因此,密度图也被称作KDE(Kernel Density Estimate,核密度估计)图。使用plot.kde和标准混合正态分布估计即可生成一张密度图(见图9-22):

  1. In [94]: tips['tip_pct'].plot.density()

图9-22  小费百分比的密度图

seaborn的distplot方法绘制直方图和密度图更加简单,还可以同时画出直方图和连续密度估计图。作为例子,考虑一个双峰分布,由两个不同的标准正态分布组成(见图9-23):

  1. In [96]: comp1 = np.random.normal(0, 1, size=200)
  2. In [97]: comp2 = np.random.normal(10, 2, size=200)
  3. In [98]: values = pd.Series(np.concatenate([comp1, comp2]))
  4. In [99]: sns.distplot(values, bins=100, color='k')

图9-23 标准混合密度估计的标准直方图

散布图或点图

点图或散布图是观察两个一维数据序列之间的关系的有效手段。在下面这个例子中,我加载了来自statsmodels项目的macrodata数据集,选择了几个变量,然后计算对数差:

  1. In [100]: macro = pd.read_csv('examples/macrodata.csv')
  2. In [101]: data = macro[['cpi', 'm1', 'tbilrate', 'unemp']]
  3. In [102]: trans_data = np.log(data).diff().dropna()
  4. In [103]: trans_data[-5:]
  5. Out[103]:
  6. cpi m1 tbilrate unemp
  7. 198 -0.007904 0.045361 -0.396881 0.105361
  8. 199 -0.021979 0.066753 -2.277267 0.139762
  9. 200 0.002340 0.010286 0.606136 0.160343
  10. 201 0.008419 0.037461 -0.200671 0.127339
  11. 202 0.008894 0.012202 -0.405465 0.042560

然后可以使用seaborn的regplot方法,它可以做一个散布图,并加上一条线性回归的线(见图9-24):

  1. In [105]: sns.regplot('m1', 'unemp', data=trans_data)
  2. Out[105]: <matplotlib.axes._subplots.AxesSubplot at 0x7fb613720be0>
  3. In [106]: plt.title('Changes in log %s versus log %s' % ('m1', 'unemp'))

图9-24 seaborn的回归/散布图

在探索式数据分析工作中,同时观察一组变量的散布图是很有意义的,这也被称为散布图矩阵(scatter plot matrix)。纯手工创建这样的图表很费工夫,所以seaborn提供了一个便捷的pairplot函数,它支持在对角线上放置每个变量的直方图或密度估计(见图9-25):

  1. In [107]: sns.pairplot(trans_data, diag_kind='kde', plot_kws={'alpha': 0.2})

图9-25 statsmodels macro data的散布图矩阵

你可能注意到了plot_kws参数。它可以让我们传递配置选项到非对角线元素上的图形使用。对于更详细的配置选项,可以查阅seaborn.pairplot文档字符串。

分面网格(facet grid)和类型数据

要是数据集有额外的分组维度呢?有多个分类变量的数据可视化的一种方法是使用小面网格。seaborn有一个有用的内置函数factorplot,可以简化制作多种分面图(见图9-26):

  1. In [108]: sns.factorplot(x='day', y='tip_pct', hue='time', col='smoker',
  2. .....: kind='bar', data=tips[tips.tip_pct < 1])

图9-26 按照天/时间/吸烟者的小费百分比

除了在分面中用不同的颜色按时间分组,我们还可以通过给每个时间值添加一行来扩展分面网格:

  1. In [109]: sns.factorplot(x='day', y='tip_pct', row='time',
  2. .....: col='smoker',
  3. .....: kind='bar', data=tips[tips.tip_pct < 1])

图9-27 按天的tip_pct,通过time/smoker分面

factorplot支持其它的绘图类型,你可能会用到。例如,盒图(它可以显示中位数,四分位数,和异常值)就是一个有用的可视化类型(见图9-28):

  1. In [110]: sns.factorplot(x='tip_pct', y='day', kind='box',
  2. .....: data=tips[tips.tip_pct < 0.5])

图9-28 按天的tip_pct的盒图

使用更通用的seaborn.FacetGrid类,你可以创建自己的分面网格。请查阅seaborn的文档(https://seaborn.pydata.org/)。