[ZT] 可视化多维数据的艺术

原创: Dipanjan Sarkar 论智 2018-01-23
作者:Dipanjan Sarkar 编译:weakish

编者按:Intel数据科学家、《Practical Machine Learning with Python》作者Dipanjan Sarkar上周在Towards Data Science发表了一篇详尽的数据可视化指南,介绍了可视化数据(特别是高维数据)的策略。

image

介绍

描述性分析是任何数据科学相关的分析的生命周期的核心部分,甚至可能是特定研究的核心部分。数据聚合、总结、可视化是数据分析领域的几大主要支柱。从传统的商业智能直到今日的人工智能,数据可视化一直是一个强大的工具,并且,由于数据可视化在抽象正确的信息、清晰易懂地理解和解释结果方面的有效性,各种组织广泛使用数据可视化技术。然而,处理通常包含超过两个属性的多维数据集开始变得麻烦,因为数据分析和交流的媒介通常局限在二维。本文探索了一些可视化多维数据的有效策略(从一维到六维)。

动机

一图胜千言

这是一句我们都很熟悉的非常流行的英语习语,这句习语足以启发和驱动我们理解和利用数据可视化这一分析的有效工具。牢记“有效的数据可视化既是科学,也是艺术。”在我们开始前,我还想引用一句话,这句话与此密切相关,强化了数据可视化的必要性。

图片的最大价值在于,它迫使我们注意到我们从未预见的东西。

—— John Tukey

快速温习

我假设大部分读者了解绘制和可视化数据的基本图形和图表,因此我不会解释它们的细节。不过在接下来的上手试验中,我们将简要地介绍其中大部分的图形和图表。我们应当基于数据进行数据可视化,以便如著名的可视化先锋和统计学家Edward Tufte所说的那样,“清晰、精确、高效地”交流模式和洞见。

结构化的数据通常包括由行表示的观测数据以及由列表示的特征和数据属性。每一列也可以被称为数据集的特定维度。大部分普通数据类型包括连续的数值数据和离散的分类数据。因而任何数据可视化基本上会以散点图、直方图、箱形图等易于理解的形式刻画一个或多个数据属性。我将介绍单元(一维)和多元(多维)的数据可视化策略。我们将使用Python的机器学习生态系统,建议你看下数据分析和可视化的一些框架,包括pandas、matplotlib、seaborn、plotly、bokeh。除此以外,如果你对做出美观而有意义的数据可视化感兴趣,那很有必要了解下D3.js。Edward Tufte的The Visual Display of Quantitative Information(量化数据的可视化呈现)一书可供感兴趣的读者参考。

空谈无益,拿可视化(和代码)来!

与其听我喋喋不休地讲述理论和概念,不如动手实做。我们将使用UCI机器学习仓库(UCI Machine Learning Repository)中的红酒品质数据集(Wine Quality Data Set)。其中实际上包括两个数据集,分别刻画葡萄牙绿酒(Vinho Verde)的红酒和白酒变体。下文所有的分析都以Jupyter Notebook的形式发布在我的GitHub 仓库中,供很想自己尝试的读者使用。

首先,我们加载依赖:

import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib as mpl
import numpy as np
import seaborn as sns
%matplotlib inline

我们将主要使用matplotlibseaborn作为我们的可视化框架,不过你可以查看下其他框架然后使用自己钟意的框架。下面是一些基本的数据预处理:

white_wine = pd.read_csv('winequality-white.csv', sep=';')
red_wine = pd.read_csv('winequality-red.csv', sep=';')

# 将酒的种类保存为属性
red_wine['wine_type'] = 'red'   
white_wine['wine_type'] = 'white'


# 将酒的品质评分封装为酒的品质标签
red_wine['quality_label'] = red_wine['quality'].apply(lambda value: 'low' 
                                                      if value <= 5 else 'medium' 
                                                      if value <= 7 else 'high')

red_wine['quality_label'] = pd.Categorical(red_wine['quality_label'],
                                          categories=['low', 'medium', 'high'])

white_wine['quality_label'] = white_wine['quality'].apply(lambda value: 'low' 
                                                           if value <= 5 else 'medium' 
                                                           if value <= 7 else 'high')

white_wine['quality_label'] = pd.Categorical(white_wine['quality_label'], 
                                            categories=['low', 'medium', 'high'])

# 合并红酒和白酒数据集
wines = pd.concat([red_wine, white_wine])

# 重新打散记录,以随机化数据点
wines = wines.sample(frac=1, random_state=42).reset_index(drop=True)

我们将与红酒和白酒样本有关的两个数据集合并为一个数据框架。我们同时基于葡萄酒样本的quality(品质)属性创建了一个新的分类变量quality_label(品质标签)。让我们大概看一下当前的数据。

wines.head()
葡萄酒品质数据集

很明显,酒的样本有一些数值和分类属性。每项数据属于一个红酒或白酒样本,属性为特定的属性或经物理化学测试得到的性质。如果你想了解每个属性的详细解释,你可以查看Jupyter notebook,不过,这些属性的名称基本上已经解释了自己。让我们对感兴趣的一些信息做一个快速的描述性总结统计。

subset_attributes = ['residual sugar', 'total sulfur dioxide', 'sulphates', 
                     'alcohol', 'volatile acidity', 'quality']

rs = round(red_wine[subset_attributes].describe(),2)
ws = round(white_wine[subset_attributes].describe(),2)

pd.concat([rs, ws], axis=1, keys=['Red Wine Statistics', 'White Wine Statistics'])
基于葡萄酒的品种的基本描述性统计

对比不同葡萄酒品种的统计指标很容易。注意某些属性明显的不同。我们在之后的一些可视化操作中将重点关注这些属性。

单元分析

单元分析基本上是数据分析或可视化中最简单的形式。进行单元分析时,我们只关注分析数据的一个属性或变量,可视化同理(只关注一维)。

一维可视化

最快、最高效地可视化所有数值数据及其分布的方法之一是使用pandas绘制直方图。

wines.hist(bins=15, color='steelblue', edgecolor='black', linewidth=1.0,
           xlabelsize=8, ylabelsize=8, grid=False)    

plt.tight_layout(rect=(0, 0, 1.2, 1.2))

上面的图像很好地描述了任意属性的基本数据分布。

让我们深入探讨下可视化一个连续的数值属性。基本上直方图密度图效果不错。

# 直方图
fig = plt.figure(figsize = (6,4))
title = fig.suptitle("Sulphates Content in Wine", fontsize=14)
fig.subplots_adjust(top=0.85, wspace=0.3)

ax = fig.add_subplot(1,1, 1)
ax.set_xlabel("Sulphates")
ax.set_ylabel("Frequency") 
ax.text(1.2, 800, r'$\mu$='+str(round(wines['sulphates'].mean(),2)), 
         fontsize=12)
freq, bins, patches = ax.hist(wines['sulphates'], color='steelblue', bins=15,
                              edgecolor='black', linewidth=1)

# 密度图
fig = plt.figure(figsize = (6, 4))
title = fig.suptitle("Sulphates Content in Wine", fontsize=14)
fig.subplots_adjust(top=0.85, wspace=0.3)

ax1 = fig.add_subplot(1,1, 1)
ax1.set_xlabel("Sulphates")
ax1.set_ylabel("Frequency") 
sns.kdeplot(wines['sulphates'], ax=ax1, shade=True, color='steelblue')
可视化一维连续的数值数据

从上面的图表可以很明显地看出,葡萄酒的sulphates(硫酸盐)偏向左侧。

可视化一个离散的分类数据属性略有不同,条形图是最有效的方法之一。你也可以使用饼图,不过一般避免使用它,特别是当类别数目超过三的时候。

# 条形图
ig = plt.figure(figsize = (6, 4))
title = fig.suptitle("Wine Quality Frequency", fontsize=14)
fig.subplots_adjust(top=0.85, wspace=0.3)

ax = fig.add_subplot(1,1, 1)
ax.set_xlabel("Quality")
ax.set_ylabel("Frequency") 
w_q = wines['quality'].value_counts()
w_q = (list(w_q.index), list(w_q.values))
ax.tick_params(axis='both', which='major', labelsize=8.5)
bar = ax.bar(w_q[0], w_q[1], color='steelblue', 
             edgecolor='black', linewidth=1)
可视化一维离散的分类数据

接下来,让我们看看高维数据的情形。

多元分析

多元分析带来了更多的复杂度,也带来了更多乐趣。我们分析多个数据维度或属性(两个以上)。多元分析不仅考察分布,还考察属性间潜在的联系、模式和相关性。取决于手头要解决的问题,如有必要,你也可以利用推断统计和假设检验来考察不同属性的统计学的显著性、分组,等等。

二维可视化

考察不同数据属性间的潜在联系或相关性的最佳方法之一是使用配对相关性矩阵,并将其刻画为热图。

# 相关性矩阵热图
f, ax = plt.subplots(figsize=(10, 6))
corr = wines.corr()
hm = sns.heatmap(round(corr,2), annot=True, ax=ax, cmap="coolwarm",
                 fmt='.2f', linewidths=.05)
f.subplots_adjust(top=0.93)
t= f.suptitle('Wine Attributes Correlation Heatmap', fontsize=14)
基于相关性热图可视化二维数据

热图的梯度因相关性的强度而不同,你可以很清楚地发现属性与其自身具有最强的相关性。另一种可视化方法是绘制感兴趣的属性的配对散点图

# 配对散点图
cols = ['density', 'residual sugar', 'total sulfur dioxide', 'fixed acidity']
pp = sns.pairplot(wines[cols], size=1.8, aspect=1.8,
                  plot_kws=dict(edgecolor="k", linewidth=0.5),
                  diag_kind="kde", diag_kws=dict(shade=True))

fig = pp.fig 
fig.subplots_adjust(top=0.93, wspace=0.3)
t = fig.suptitle('Wine Attributes Pairwise Plots', fontsize=14)
基于配对散点图可视化二维数据

你可以看到,散点图也是一个不错的观察二维数据属性的潜在联系和模式的方法。另一个同时可视化多元数据的多个属性的方法是平行坐标。

# 缩放属性值以避免离散值过少
cols = ['density', 'residual sugar', 'total sulfur dioxide', 'fixed acidity']
subset_df = wines[cols]

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()

scaled_df = ss.fit_transform(subset_df)
scaled_df = pd.DataFrame(scaled_df, columns=cols)
final_df = pd.concat([scaled_df, wines['wine_type']], axis=1)
final_df.head()

# 绘制平行坐标
from pandas.plotting import parallel_coordinates
pc = parallel_coordinates(final_df, 'wine_type', color=('#FFE888', '#FF9999'))
使用平行坐标来可视化多维数据

基本上,在上面的可视化中,数据点以相连的线段表示。每条竖线表示一种数据属性。横跨所有属性的一组完整的相连线段表示一个数据点。因此,趋向于聚类的数据点会看上去聚集在一起。从图中我们可以清楚地看到,相比白酒,红酒的density(总密度)略高。同时,白酒的residual sugar(残留糖分)和total sulfur dioxide(总二氧化硫)比红酒要高,而红酒的fixed acidity(固定酸)比白酒要高。参考我们先前得出的数据表格可以验证这些假设。

让我们看下可视化两个连续的数值属性的一些方法。scatter plots(散点图)和joint plots是特别好的两个方法,不仅可以查看属性间的模式和联系,还可以查看单个属性的分布。

# Scatter Plot
plt.scatter(wines['sulphates'], wines['alcohol'],
            alpha=0.4, edgecolors='w')

plt.xlabel('Sulphates')
plt.ylabel('Alcohol')
plt.title('Wine Sulphates - Alcohol Content',y=1.05)

# Joint Plot
jp = sns.jointplot(x='sulphates', y='alcohol', data=wines,
                   kind='reg', space=0, size=5, ratio=4)
基于scatter plots和joint plots可视化二维连续的数值数据

上图左侧是scatter plot,右侧是joint plot。如前所述,在joint plot中,我们不仅可以查看相关性、联系,还可以查看单个分布。

如何可视化两个离散的分类数据?其中一种方法是利用单独的图形(子图,subplot)或刻面(facet)作为一个分类的维度。

# 基于子图或刻面使用条形图
fig = plt.figure(figsize = (10, 4))
title = fig.suptitle("Wine Type - Quality", fontsize=14)
fig.subplots_adjust(top=0.85, wspace=0.3)

# 红酒 —— 品质
ax1 = fig.add_subplot(1,2, 1)
ax1.set_title("Red Wine")
ax1.set_xlabel("Quality")
ax1.set_ylabel("Frequency") 
rw_q = red_wine['quality'].value_counts()
rw_q = (list(rw_q.index), list(rw_q.values))
ax1.set_ylim([0, 2500])
ax1.tick_params(axis='both', which='major', labelsize=8.5)
bar1 = ax1.bar(rw_q[0], rw_q[1], color='red', 
       edgecolor='black', linewidth=1)
使用条形图和子图(刻面)可视化二维离散的分类数据

如你所见,尽管这是一个很好的可视化分类数据的方法,使用matplotlib还是要写不少代码。另一个好方法是在同一张图中使用堆叠条形(stacked bars)或多条形(multiple bars)表示不同的属性。用seaborn来写很容易。

# 多条形图
cp = sns.countplot(x="quality", hue="wine_type", data=wines, 
palette={"red": "#FF9999", "white": "#FFE888"})
使用单张条形图可视化二维离散的分类数据

这看起来清爽不少,你也可以很方便地比较不同的分类。

让我们看下如何可视化二维混合种类的属性(基本上就是数值和分类混在一起)。一种方法是结合刻面/子图使用通用的直方图或密度图。

# 结合刻面使用直方图
ig = plt.figure(figsize = (10,4))
title = fig.suptitle("Sulphates Content in Wine", fontsize=14)
fig.subplots_adjust(top=0.85, wspace=0.3)

ax1 = fig.add_subplot(1,2, 1)
ax1.set_title("Red Wine")
ax1.set_xlabel("Sulphates")
ax1.set_ylabel("Frequency") 
ax1.set_ylim([0, 1200])
ax1.text(1.2, 800, r'$\mu$='+str(round(red_wine['sulphates'].mean(),2)), 
         fontsize=12)

r_freq, r_bins, r_patches = ax1.hist(red_wine['sulphates'], color='red', bins=15,
                                     edgecolor='black', linewidth=1)

ax2 = fig.add_subplot(1,2, 2)
ax2.set_title("White Wine")
ax2.set_xlabel("Sulphates")
ax2.set_ylabel("Frequency")
ax2.set_ylim([0, 1200])
ax2.text(0.8, 800, r'$\mu$='+str(round(white_wine['sulphates'].mean(),2)), 
         fontsize=12)

w_freq, w_bins, w_patches = ax2.hist(white_wine['sulphates'], color='white', bins=15, edgecolor='black', linewidth=1)

# 结合刻面使用密度图
fig = plt.figure(figsize = (10, 4))
title = fig.suptitle("Sulphates Content in Wine", fontsize=14)
fig.subplots_adjust(top=0.85, wspace=0.3)

ax1 = fig.add_subplot(1,2, 1)
ax1.set_title("Red Wine")
ax1.set_xlabel("Sulphates")
ax1.set_ylabel("Density") 
sns.kdeplot(red_wine['sulphates'], ax=ax1, shade=True, color='r')

ax2 = fig.add_subplot(1,2, 2)
ax2.set_title("White Wine")
ax2.set_xlabel("Sulphates")
ax2.set_ylabel("Density") 
sns.kdeplot(white_wine['sulphates'], ax=ax2, shade=True, color='y')
基于刻面和直方图/密度图可视化二维混合数据

尽管这不错,但同样有很多套路化的代码,使用seaborn,我们不用写这些套路化的代码,并且可以合并到一张图。

# 多直方图
fig = plt.figure(figsize = (6, 4))
title = fig.suptitle("Sulphates Content in Wine", fontsize=14)
fig.subplots_adjust(top=0.85, wspace=0.3)
ax = fig.add_subplot(1,1, 1)
ax.set_xlabel("Sulphates")
ax.set_ylabel("Frequency") 

g = sns.FacetGrid(wines, hue='wine_type', palette={"red": "r", "white": "y"})
g.map(sns.distplot, 'sulphates', kde=False, bins=15, ax=ax)
ax.legend(title='Wine Type')
plt.close(2)
利用多直方图表示二维混合属性

你可以看到,上面的图形更为清晰凝练,也易于我们比较两者的分布。此外,箱形图(box plot)也是一个基于分类属性的不同值刻画数值数据分组的有效方法。箱形图是了解数据的四分位数及潜在的离散值的好方法。

# 箱形图
f, (ax) = plt.subplots(1, 1, figsize=(12, 4))
f.suptitle('Wine Quality - Alcohol Content', fontsize=14)

sns.boxplot(x="quality", y="alcohol", data=wines,  ax=ax)
ax.set_xlabel("Wine Quality",size = 12,alpha=0.8)
ax.set_ylabel("Wine Alcohol %",size = 12,alpha=0.8)
箱形图是二维混合属性的高效表示

提琴形图(violin plot)是另一种类似的可视化方法。它是使用核密度图(刻画数据在不同值处的概率密度)可视化分组数值数据的另一种有效方法。

# 提琴形图
f, (ax) = plt.subplots(1, 1, figsize=(12, 4))
f.suptitle('Wine Quality - Sulphates Content', fontsize=14)

sns.violinplot(x="quality", y="sulphates", data=wines,  ax=ax)
ax.set_xlabel("Wine Quality",size = 12,alpha=0.8)
ax.set_ylabel("Wine Sulphates",size = 12,alpha=0.8)
提琴形图是二维混合属性的高效表示

从上图你可以很清楚地看到不同quality(品质)分类的葡萄酒的sulphate(硫酸盐)的核密度图。

直到二维为止,可视化数据还是挺直截了当的。然而,随着维度(属性)数目的增加,可视化数据开始变得复杂了。原因在于,我们受展示媒介和环境的二维所限。

对于三维数据,我们可以在图表中放置一根z轴以引入一个虚假的深度概念,或利用子图和刻面。

然而,对于超过三维的数据,可视化变得更难了。可视化三维以上的数据的最好方法是使用刻面颜色形状尺寸深度等。通过制作一个动画图表,你也可以将时间作为一个维度(将时间看成数据的一个维度,图表描绘其他属性)。Hans Roslin作了一个关于这个话题的出色演讲。

三维可视化

我们可以考虑使用配对散点图并引入色彩色度的概念以区分不同分类维度的值,这样,我们可以可视化数据的三个属性或维度。

# 色度散点图以可视化三维数据
cols = ['density', 'residual sugar', 'total sulfur dioxide', 'fixed acidity', 'wine_type']
pp = sns.pairplot(wines[cols], hue='wine_type', size=1.8, aspect=1.8, 
                  palette={"red": "#FF9999", "white": "#FFE888"},
                  plot_kws=dict(edgecolor="black", linewidth=0.5))

fig = pp.fig 
fig.subplots_adjust(top=0.93, wspace=0.3)
t = fig.suptitle('Wine Attributes Pairwise Plots', fontsize=14)
使用色度散点图可视化三维数据

以上图形让你能够查看葡萄酒群组间的相关性和模式,并进行比较。比如,我们可以很清楚地看到白酒的total sulfur dioxide(总二氧化硫)和residual sugar(残留糖分)比红酒高。

让我们看下可视化三个连续的数值属性的策略。其中一种方法是在两个常规维度(由x轴表示的长度和由y轴表示的宽度)的基础上,另外引入深度(z轴)概念作为第三个维度。

# 基于散点图可视化三维数值数据
# 长度、宽度、深度
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

xs = wines['residual sugar']
ys = wines['fixed acidity']
zs = wines['alcohol']
ax.scatter(xs, ys, zs, s=50, alpha=0.6, edgecolors='w')

ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Fixed Acidity')
ax.set_zlabel('Alcohol')
引入深度概念可视化三维数值数据

我们也可以仍然使用常规的2维坐标轴,然后引入尺寸概念作为第三维度(基本上是气泡图),点的尺寸指明了第三维度的量。

# 使用气泡图可视化三维数值数据
# 长度、宽度、尺寸
plt.scatter(wines['fixed acidity'], wines['alcohol'], s=wines['residual sugar']*25, 
            alpha=0.4, edgecolors='w')

plt.xlabel('Fixed Acidity')
plt.ylabel('Alcohol')
plt.title('Wine Alcohol Content - Fixed Acidity - Residual Sugar',y=1.05)
引入尺寸概念可视化三维数值数据

因此,你可以看到,上面的图标不再是一个传统的散点图,而成为一个由因residual sugar(残留糖分)的量不同而大小不一的点(气泡)组成的气泡图。我们可以看到在另外两个维度上尺寸不同的点,当然,你并不总能像这个例子一样在数据中找到确定的模式。

至于可视化三个离散的分类属性,我们可以使用传统的条形图,只不过我们可以同时利用色度和刻面/子图来表示三维。seaborn框架帮助我们将代码量减到最少,高效地绘制这样的图表。

# 使用条形图可视化三维数值数据
# 使用色度和刻面概念
fc = sns.factorplot(x="quality", hue="wine_type", col="quality_label", 
                    data=wines, kind="count",
palette={"red": "#FF9999", "white": "#FFE888"})
引入色度和刻面概念可视化三维分类数据

上面的图标清楚地显示了各个维度的频率,你可以看到,通过这样的可视化,理解相关的洞见是多么容易和有效。

考虑可视化三个混合属性的情形,我们可以使用色度概念基于分类属性进行分组,同时使用散点图之类的传统可视化方法来可视化另外两个维度的数值属性。

# 使用散点图可视化三维混合数据
# 使用色度概念表示分类维度
jp = sns.pairplot(wines, x_vars=["sulphates"], y_vars=["alcohol"], size=4.5,
                  hue="wine_type", palette={"red": "#FF9999", "white": "#FFE888"},
plot_kws=dict(edgecolor="k", linewidth=0.5))

# 如有必要,我们也可以查看联系/相关性
lp = sns.lmplot(x='sulphates', y='alcohol', hue='wine_type', 
                palette={"red": "#FF9999", "white": "#FFE888"},
                data=wines, fit_reg=True, legend=True,
scatter_kws=dict(edgecolor="k", linewidth=0.5))
使用色度散点图可视化三维混合属性

因此,色度是一个很好的分类或分组的分离者,尽管从上图我们观察到,两个分类间不存在或只存在非常弱的相关性,我们仍然可以通过上图中的点了解到红酒的sulphates(硫酸盐)比白酒的略高。除了使用散点图,你也可以使用核密度图来理解三维数据。

# 使用核密度图可视化三维混合数据
# 使用色度概念表示分类维度
ax = sns.kdeplot(white_wine['sulphates'], white_wine['alcohol'],
                  cmap="YlOrBr", shade=True, shade_lowest=False)
ax = sns.kdeplot(red_wine['sulphates'], red_wine['alcohol'],
cmap="Reds", shade=True, shade_lowest=False)
使用色度核密度图可视化三维混合属性

很明显,正如我们预期的,红酒样本的sulphates(硫酸盐)比白酒要高。你也可以通过色度的亮度看出密度的凝聚度。

如果三维中我们需要处理不止一个分类属性,我们可以使用色度和一个常规轴来可视化数据,然后使用箱形图或提琴形图之类的可视化形式来可视化不同分组的数据。

# 使用提琴形图可视化三维混合数据
# 使用色度概念和坐标轴来表示不止一个分类属性
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))
f.suptitle('Wine Type - Quality - Acidity', fontsize=14)

sns.violinplot(x="quality", y="volatile acidity",
               data=wines, inner="quart", linewidth=1.3,ax=ax1)
ax1.set_xlabel("Wine Quality",size = 12,alpha=0.8)
ax1.set_ylabel("Wine Volatile Acidity",size = 12,alpha=0.8)

sns.violinplot(x="quality", y="volatile acidity", hue="wine_type", 
               data=wines, split=True, inner="quart", linewidth=1.3,
               palette={"red": "#FF9999", "white": "white"}, ax=ax2)
ax2.set_xlabel("Wine Quality",size = 12,alpha=0.8)
ax2.set_ylabel("Wine Volatile Acidity",size = 12,alpha=0.8)

l = plt.legend(loc='upper right', title='Wine Type')
使用分割提琴形图和色度概念可视化三维混合属性

上图右侧,我们用x轴表示红酒的quality(品质),使用色度表示wine_type(品种)。我们可以清楚地看到一些有趣的洞见,例如红酒的volatile acidity(挥发性酸)比白酒高。

我们也可以使用类似的方式使用箱形图表示不止一个分类变量的混合属性。

# 使用箱形图可视化3维混合数据
# 使用色度概念和坐标轴来表示不止一个分类属性
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))
f.suptitle('Wine Type - Quality - Alcohol Content', fontsize=14)

sns.boxplot(x="quality", y="alcohol", hue="wine_type",
               data=wines, palette={"red": "#FF9999", "white": "white"}, ax=ax1)
ax1.set_xlabel("Wine Quality",size = 12,alpha=0.8)
ax1.set_ylabel("Wine Alcohol %",size = 12,alpha=0.8)

sns.boxplot(x="quality_label", y="alcohol", hue="wine_type",
               data=wines, palette={"red": "#FF9999", "white": "white"}, ax=ax2)
ax2.set_xlabel("Wine Quality Class",size = 12,alpha=0.8)
ax2.set_ylabel("Wine Alcohol %",size = 12,alpha=0.8)
l = plt.legend(loc='best', title='Wine Type')
使用箱形图和色度概念可视化三维混合属性

我们可以看到,对于quality(品质)和quality_label(品质标签)属性而言,葡萄酒的alcohol(酒精)含量随着品质的提升而升高。同时,同一品质分类的红酒的alcohol(酒精)含量的中位值有比白酒高的倾向。然而,如果我们查看品质评分,我们可以发现,对于评分较低的葡萄酒(3和4)而言,白酒的alcohol(酒精)含量的中位值要比红酒样本高。除此之外,一般而言,红酒的alcohol(酒精)含量的中位值看起来比白酒略高。

四维可视化

前面我们使用了图表的多个成分来可视化不同的维度。可视化四维数据的一种方法是在散点图之类的传统图形中使用深度色度作为特定数据的维度。

# 使用散点图可视化四维混合数据
# 利用色度和深度概念
fig = plt.figure(figsize=(8, 6))
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Type', fontsize=14)
ax = fig.add_subplot(111, projection='3d')

xs = list(wines['residual sugar'])
ys = list(wines['alcohol'])
zs = list(wines['fixed acidity'])
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
colors = ['red' if wt == 'red' else 'yellow' for wt in list(wines['wine_type'])]

for data, color in zip(data_points, colors):
    x, y, z = data
    ax.scatter(x, y, z, alpha=0.4, c=color, edgecolors='none', s=30)

ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
使用引入色度和深度概念的散点图可视化四维数据

从上图的点中,我们可以明显地看到用色度表示的wine_type(品种)属性。同时,尽管由于点的复杂性,解释这些可视化开始变得困难,你仍然可以收集一些洞见,比如,红酒的fixed acidity(固定酸)较高,白酒的residual sugar(残留糖分)较高。当然,如果alcohol(酒精)和fixed acidity(固定酸)具有某种关联,我们可能可以看到逐渐升高或下降的数据点平面,显示某种趋势。

另一种策略是保持二维点,但是使用色度和数据点的尺寸作为数据维度。通常这会类似我们先前可视化的气泡图。

# 使用气泡图可视化四维混合数据
# 利用色度和尺寸概念
size = wines['residual sugar']*25
fill_colors = ['#FF9999' if wt=='red' else '#FFE888' for wt in list(wines['wine_type'])]
edge_colors = ['red' if wt=='red' else 'orange' for wt in list(wines['wine_type'])]

plt.scatter(wines['fixed acidity'], wines['alcohol'], s=size, 
            alpha=0.4, color=fill_colors, edgecolors=edge_colors)

plt.xlabel('Fixed Acidity')
plt.ylabel('Alcohol')
plt.title('Wine Alcohol Content - Fixed Acidity - Residual Sugar - Type',y=1.05)
使用引入色度和尺寸概念的气泡图可视化四维数据

我们使用色度来表示wine_type(品种),使用数据点尺寸来表示residual sugar(残留糖分)。我们确实看到了和之前图表观察所得类似的模式,一般而言,白酒的气泡尺寸较大,这意味着白酒的residual sugar(残留糖分)比红酒高。

如果我们需要表示超过两种分类属性,我们可以重用之前的思路,使用色度和刻面刻画分类属性,使用散点图子类的常规图表表示数值属性。让我们看一些例子。

# 使用散点图可视化四维混合数据
# 使用色度和刻面来表示不止一个分类属性
g = sns.FacetGrid(wines, col="wine_type", hue='quality_label', 
                  col_order=['red', 'white'], hue_order=['low', 'medium', 'high'],
                  aspect=1.2, size=3.5, palette=sns.light_palette('navy', 4)[1:])
g.map(plt.scatter, "volatile acidity", "alcohol", alpha=0.9, 
      edgecolor='white', linewidth=0.5, s=100)
fig = g.fig 
fig.subplots_adjust(top=0.8, wspace=0.3)
fig.suptitle('Wine Type - Alcohol - Quality - Acidity', fontsize=14)
l = g.add_legend(title='Wine Quality Class')
使用引入色度和刻面概念的散点图可视化四维数据

我们可以轻易地找出多个模式,这验证了这一可视化方法的有效性。白酒的volatile acidity(挥发性酸)较低,而品质较高的葡萄酒也较低。同时,我们可以从白酒样本看到,品质较高的葡萄酒的alcohol(酒精度)较高,品质较低的alcohol(酒精度)最低!

让我们看下由其他属性构成的另一个相似的例子:

# 使用散点图可视化四维混合数据
# 使用色度和刻面来表示不止一个分类属性
g = sns.FacetGrid(wines, col="wine_type", hue='quality_label', 
                  col_order=['red', 'white'], hue_order=['low', 'medium', 'high'],
                  aspect=1.2, size=3.5, palette=sns.light_palette('green', 4)[1:])
g.map(plt.scatter, "volatile acidity", "total sulfur dioxide", alpha=0.9, 
      edgecolor='white', linewidth=0.5, s=100)
fig = g.fig 
fig.subplots_adjust(top=0.8, wspace=0.3)
fig.suptitle('Wine Type - Sulfur Dioxide - Acidity - Quality', fontsize=14)
l = g.add_legend(title='Wine Quality Class')
使用引入色度和刻面概念的散点图可视化四维数据

我们可以很清楚地看到,品质较高的葡萄酒的total sulfur dioxide(总二氧化硫)含量较低,如果你具备葡萄酒工艺的必备领域知识的话,你会知道这两者是相关的。我们同时看到红酒的total sulfur dioxide(总二氧化硫)比白酒低。然而,红酒的一些数据点的total sulfur dioxide(总二氧化硫)较高。

五维可视化

我们再一次使用和前一节相似的策略,利用多种绘图的成分,可以可视化五维数据。让我们使用深度色度尺寸来表示数据的三个维度,此外使用常规坐标轴表示其他两个维度。既然我们使用了尺寸的概念,基本上我们将绘制一个三维的气泡图

# 使用气泡图可视化五维混合数据
# 使用色度、尺寸、深度概念
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)

xs = list(wines['residual sugar'])
ys = list(wines['alcohol'])
zs = list(wines['fixed acidity'])
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]

ss = list(wines['total sulfur dioxide'])
colors = ['red' if wt == 'red' else 'yellow' for wt in list(wines['wine_type'])]

for data, color, size in zip(data_points, colors, ss):
    x, y, z = data
    ax.scatter(x, y, z, alpha=0.4, c=color, edgecolors='none', s=size)

ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
使用引入色度、深度和尺寸概念的气泡图可视化五维数据

上面的图表刻画了我们在之前的小节讨论过的模式和洞见。然而,我们也可以看到,数据点尺寸的大小(表示total sulfur dioxide即总二氧化硫)显示白酒的total sulfur dioxide(总二氧化硫)含量比红酒高。

我们也可以不用深度,使用刻面和色度来表示五个数据维度中不止一个的分类属性。用尺寸表示的属性可以是数值(连续的),甚至是分类(但我们可能需要用数字表示数据点的尺寸)。由于缺乏分类属性,我们这里不刻画这样的情形,不过你尽可以在你自己的数据集上进行这样的尝试。

# 使用气泡图可视化五维混合数据
# 使用色度、尺寸、刻面概念
g = sns.FacetGrid(wines, col="wine_type", hue='quality_label', 
                  col_order=['red', 'white'], hue_order=['low', 'medium', 'high'],
                  aspect=1.2, size=3.5, palette=sns.light_palette('black', 4)[1:])
g.map(plt.scatter, "residual sugar", "alcohol", alpha=0.8, 
      edgecolor='white', linewidth=0.5, s=wines['total sulfur dioxide']*2)
fig = g.fig 
fig.subplots_adjust(top=0.8, wspace=0.3)
fig.suptitle('Wine Type - Sulfur Dioxide - Residual Sugar - Alcohol - Quality', fontsize=14)
l = g.add_legend(title='Wine Quality Class')
使用引入色度、刻面和尺寸概念的气泡图可视化五维数据

这基本上是我们之前绘制的图形的替代方案。查看我们之前绘制的图形时,额外的深度维度可能让很多人困惑,与此不同,基于刻面的优势,这一图形仍然很好地保持在了二维平面上,因此解读起来更高效、更容易。

上到更高之处

我们已经看到,处理这么多数据维度变得很复杂!你们当中可能有人会想,为什么不再加上一个维度?让我们勇往直前,来尝试一下。

六维可视化

现在我们兴致正浓(我希望!),让我们给我们的可视化加上另一个数据维度。我们将在常规的坐标轴之外使用深度、色度、尺寸、形状来刻画六个数据维度。

# 使用散点图可视化六维混合数据
# 使用色度、尺寸、深度、形状
fig = plt.figure(figsize=(8, 6))
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type - Quality', fontsize=14)
ax = fig.add_subplot(111, projection='3d')

xs = list(wines['residual sugar'])
ys = list(wines['alcohol'])
zs = list(wines['fixed acidity'])
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]

ss = list(wines['total sulfur dioxide'])
colors = ['red' if wt == 'red' else 'yellow' for wt in list(wines['wine_type'])]
markers = [',' if q == 'high' else 'x' if q == 'medium' else 'o' for q in list(wines['quality_label'])]

for data, color, size, mark in zip(data_points, colors, ss, markers):
    x, y, z = data
    ax.scatter(x, y, z, alpha=0.4, c=color, edgecolors='none', s=size, marker=mark)

ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
使用引入色度、深度、形状和尺寸概念的气泡图可视化六维数据

哇,一张图表示六个维度!我们通过形状刻画葡萄酒quality_label(品质标签),高级(方形)、中级(X符号)、低级(圆圈)。色度表示wine_type(品种),深度表示fixed acidity(固定酸),数据点尺寸表示total sulfur dioxide(总二氧化硫)含量。

解释这张图可能看起来很费力,让我们每次只考虑其中一些成分,来理解发生了什么。

  1. 考虑形状和y轴,和低级品质的葡萄酒相比,高级和中级品质的葡萄酒的alcohol(酒精度)更高。
  2. 考虑色度和尺寸,白酒的total sulfur dioxide(总二氧化硫)比红酒高。
  3. 考虑深度和色度,白酒的fixed acidity(固定酸)比红酒低。
  4. 考虑色度和x轴,红酒的residual sugar(残余糖分)比白酒低。
  5. 考虑色度和形状,与红酒相比,高级品质的白酒比例更高(可能是因为白酒的样本更多)。

我们可以移除深度成分,转而使用刻面成分表示分类属性,以此创建6维可视化。

# 使用散点图可视化六维混合数据
# 使用色度、尺寸、刻面概念
g = sns.FacetGrid(wines, row='wine_type', col="quality", hue='quality_label', size=4)
g.map(plt.scatter,  "residual sugar", "alcohol", alpha=0.5, 
      edgecolor='k', linewidth=0.5, s=wines['total sulfur dioxide']*2)
fig = g.fig 
fig.set_size_inches(18, 8)
fig.subplots_adjust(top=0.85, wspace=0.3)
fig.suptitle('Wine Type - Sulfur Dioxide - Residual Sugar - Alcohol - Quality Class - Quality Rating', fontsize=14)
l = g.add_legend(title='Wine Quality Class')
使用引入色度、刻面、尺寸概念的气泡图可视化六维数据

在这一场景中,我们利用刻面和色度来表示三个分类属性,然后使用两个常规坐标和尺寸来表示6维数据可视化的其他三个数值属性。

总结

数据可视化既是科学,也是艺术。如果你正阅读这段话,我真要为你花工夫读完这篇长文点赞。我的意图并不是让你记忆任何东西,或者给出一些可视化数据的固定规则。本文的主要目标是理解和学习一些可视化数据的有效策略,尤其是在维数开始增加的情况下。我鼓励你以后使用上面的代码片段可视化你自己的数据集。欢迎在评论中留下你的反馈,也欢迎分享你自己的数据可视化策略,“特别是如果你可以上到更高之处”

本文所用的所有代码和数据集可以通过我的GitHub访问。

代码同时以Jupyter notebook的形式提供。