数据预处理|5 数据可视化
能够将数据可视化是数据分析的骨干。数据可视化的领域是非常令人兴奋的,因为在绘制可视化的过程中,有无限的可能性来实现新颖性和创造性,从而更好地讲述数据的故事。然而,即使是最具创新性的图形,其核心机制也是相似的。在本章中,我们将介绍这些可视化的基本机制,它们赋予数据以生命,使我们能够比较、分析和看到其中的模式。
在你学习这些基本机制的过程中,你也将为你的数据预处理目标发展一个更好的骨干/技能组合。如果你能充分理解数据和它的可视化之间的联系,你就会更有效地对数据进行预处理以获得有效的视觉效果。在本章中,你将使用我已经预处理过的数据,但在后面的章节中,我们将介绍导致这些预处理数据集的概念和技术。
1 总结一个群体
你可以使用简单的工具,如直方图、箱型图或柱状图,来可视化数据集的某一列数值在数据对象的人群中的变化。这些可视化是非常有用的,因为它们可以帮助你一目了然地看到一个属性的值。
使用这些可视化的最常见的原因之一是为了熟悉一个数据集。了解你的数据这个词在数据科学家中很有名,并被反复说成是成功的数据分析和数据预处理的最必要步骤之一。
我们所说的了解一个数据集是指了解和探索数据集的每个属性的统计信息。也就是说,我们想知道每个属性有哪些类型的值,以及这些值在数据集的人群中是如何变化的。
为此,我们使用数据可视化工具对每个属性的数据对象群体进行总结。数字属性和分类属性对每种类型的属性需要不同的工具。对于数字属性,我们可以使用直方图或boxplot来总结属性,而对于分类属性,最好是使用柱状图。下面的例子将引导你了解如何对任何数据集一次完成。
1.1 总结数字属性的例子
写一些代码,做以下工作。
- 将 adult.csv 数据集读入 adult_df pandas DataFrame
- 为adult_df的数字属性创建直方图和boxplots
import pandas as pd
adult_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter05/adult.csv')
adult_df.head()
import matplotlib.pyplot as plt
import numpy as np
numerical_attributes = ['age','fnlwgt','education-num','capitalGain','capitalLoss','hoursPerWeek']
plt.figure(figsize=(20,8))
plt.style.use('ggplot')
for i,att in enumerate(numerical_attributes):
plt.subplot(2,6,i+1)
adult_df[att].plot.hist()
plt.title(att)
plt.subplot(2,6,i+7)
adult_df[att].plot.box(vert=False)
plt.yticks([1],[att])
plt.tight_layout()
plt.show()
绝对文件路径包括根元素和完整的目录路径。然而,相对路径是在了解到你已经在一个特定的目录中的情况下给出的。
在前面的代码中,当使用plt.savefig()
时,我们没有在文件路径中包括根元素,所以 Python 正确地将其理解为一个相对路径,并假定你希望文件被保存在与你的 Jupyter 笔记本文件中的目录相同。
在这个例子中,你看到了应用boxplots和直方图来总结数据集的数字属性。现在,让我们看看另一个例子,向你展示分类属性的类似步骤。对于分类属性,我们总是使用柱状图。
1.2 总结分类属性的例子
写一些代码,做以下工作。
- 为adult_df的分类属性创建条形图
categorial_attributes = ['workclass','education','marital-status','occupation','relationship','race','sex','nativeCountry','income']
plt.figure(figsize=(20,20))
plt.style.use('ggplot')
for i,att in enumerate(categorial_attributes):
plt.subplot(3,3,i+1)
adult_df[att].value_counts().plot.barh()
plt.title(att)
plt.tight_layout()
plt.show()
良好做法建议:
从技术上讲,你也可以用饼状图来总结分类属性的情况。
然而,我建议不要这样做。原因是饼状图不像柱状图那样容易被我们的大脑消化。事实证明,我们在欣赏长度的差异方面比欣赏大块的饼的差异要好得多。
2 比较种群
把这些不同人群的总结性的可视化资料放在一起,将有助于创建帮助我们比较这些人群的可视化资料。这可以用直方图、箱型图和柱状图来完成。让我们通过以下三个例子来看看这是如何做到的。
2.1 使用boxplots对种群进行比较的例子
编写一些代码,创建以下两个相邻的boxplots。
- 对于收入值<=5万的数据对象,教育-人数的boxplot
- 对于收入值>5万的数据对象,教育-人数的boxplot
在看下面的代码之前,先自己试一下前面的例子。
income_possibilities = adult_df.income.unique()
plt.style.use('ggplot')
box_sr = pd.Series('',index=income_possibilities)
for poss in income_possibilities:
BM = adult_df['income'] == poss
box_sr[poss] = adult_df[BM]['education-num']
print(box_sr['<=50K'])
plt.boxplot(box_sr,vert=False)
plt.yticks([1,2],income_possibilities)
plt.show()
income_possibilities = adult_df.income.unique()
dataForBox_dic = {}
for poss in income_possibilities:
BM = adult_df.income == poss
dataForBox_dic[poss] = adult_df[BM]['education-num']
plt.boxplot(dataForBox_dic.values(),vert=False)
plt.yticks([1,2],income_possibilities)
plt.show()
在讨论前面的视觉效果之前,我们先来看看这段代码。要完全理解前面的代码的运作,你需要理解三个概念。
- 代码首先循环浏览所有我们希望包含在视觉中的人群。这里,我们有两个人群:收入<=5万的数据对象和收入>5万的数据对象。在循环的每个迭代中,代码使用布尔掩码从adult_df中提取每个特定的人群。
- 代码使用
dataForBox_dic
,它是一个字典数据结构
,作为占位符。在循环的每个迭代中,代码添加了一个新的键和它的特定值。在这段代码的例子中,有两个迭代。第一次迭代将'<=50K'作为第一个键,将特定人群的所有教育值作为键的值。所有这些值都作为Pandas系列分配给每个键。在第二次迭代中,代码对'>50K'做同样的事情。 - 循环完成后,dataForBox_dic中充满了必要的数据,因此可以应用plt.boxplot()来创建两个boxplots的视觉效果。传递
dataForBox_dic.values()
而不是dataForBox_dic的原因是,plt.boxplot()要求传递的用于绘制的字典只有字符串作为键,数字列表作为键的值。在循环前后添加print(dataForBox_dic)
和print(dataForBox_dic.values())
,可以自己看到所有这些区别。
现在,让我们把注意力带到前面代码的输出的优点上,如图5.3所示。正如你所看到的,这个视觉效果清楚地讲述了教育对高收入的重要性。
2.2 使用直方图比较种群的例子
写一些代码,在同一个图中创建以下两个直方图。
- 收入值<=5万的数据对象的教育-人数直方图
- 收入值>5万的数据对象的教育-人数直方图
在看下面的代码之前,自己先试试前面的例子。
income_possibilities = adult_df.income.unique()
for poss in income_possibilities:
BM = adult_df.income == poss
plt.hist(adult_df[BM]['education-num'],label=poss,histtype='step')
plt.legend()
plt.show()
创建直方图的代码没有创建boxplots的代码复杂。
主要的区别是,对于直方图,你不需要使用占位符来为plt.boxplot()
准备数据。对于plt.hist()
,你可以根据需要多次调用它,Matplotlib会把这些视觉效果放在彼此的上面。
然而,该代码使用了plt.hist()的两个属性:label=poss
和histtype='step'
。下面将解释这两个属性的必要性。
- label=poss被添加到代码中,以便plt.legend()能够将图例添加到视觉中。从代码中删除label=poss,研究一下运行更新代码给你带来的警告。
- histtype='step'是设置柱状图的类型。有两种不同的直方图供你选择:'条形'或'阶梯'。将histtype='step'改为histtype='bar',然后运行代码,看看它们之间的区别。
income_possibilities = adult_df.income.unique()
plt.figure(figsize=(10,10))
plt.subplot(2,1,1)
box_sr = pd.Series('',index=income_possibilities)
for poss in income_possibilities:
BM = adult_df['income'] == poss
box_sr[poss] = adult_df[BM]['education-num']
plt.boxplot(box_sr,vert=False)
plt.yticks([1,2],income_possibilities)
plt.subplot(2,1,2)
for poss in income_possibilities:
BM = adult_df.income == poss
plt.hist(adult_df[BM]['education-num'],label=poss,histtype='step')
plt.legend()
plt.tight_layout()
plt.show()
这两个紧挨着的视觉图可以帮助我们很容易地看到两个种群之间的差异和相似之处,这就是我们从创建它们并有意义地将它们组织在一起所获得的价值。
2.3 使用柱状图比较人口的例子
创建一个可视化的图表,使用条形图来比较以下两个人群的分类属性种族。
- 收入值<=5万的数据对象
- 收入值>5万的数据对象 在继续阅读之前,请自己先试一试。
你可以用六种不同但有意义的方式来做这件事。让我们来看看所有可能的方法,这可以做到。
2.3.1 解决问题的第一种方式
下面的截图显示了第一种方法的代码及其输出。在这种解决问题的方式中,我们使用了plt.subplot()
将两个人口的条形图放在彼此的上面。
income_possibilities = adult_df.income.unique()
for i,poss in enumerate(income_possibilities):
plt.subplot(2,1,i+1)
BM = adult_df.income == poss
adult_df[BM].race.value_counts().plot.barh()
plt.xlim([0,22000])
plt.ylabel(poss)
plt.gca().invert_yaxis()
plt.show()
2.3.2 解决问题的第二种方式
我们已经合并了上图中的两个视觉效果,我们只有一个包含所有信息的柱状图。然而,这种合并是以必须使图表的Y刻度更加复杂为代价的。
import matplotlib.pyplot as plt
plt.style.use('ggplot')
adult_df.groupby(['income','race']).size().plot.barh()
plt.show()
2.3.3 解决问题的第三种方式
在这种方式中,我们用图例和不同的颜色来表示种族属性下的每种可能性。
adult_df.groupby(['income','race']).size().unstack()
adult_df.groupby(['income','race']).size().unstack().plot.barh()
plt.show()
2.3.4 解决问题的第四种方式
在这种方式中,我们对视觉进行了编码,使收入属性的两种可能性与种族属性的每种可能性相邻。这种方式使我们能够将两个收入群体的人口(收入<=50K和收入>50K)与每个种族属性进行比较。
adult_df.groupby(['race','income']).size().plot.barh()
plt.show()
2.3.5 解决问题的第五种方式
下面的代码显示了第五种方法的代码及其输出。这种方式与前一种方式的唯一区别是使用了图例和颜色,使视觉效果更加美观和整洁。
adult_df.groupby(['race','income']).size().unstack().plot.barh()
plt.legend(loc=4)
# loc 参数 4:表示在右下角 1:右上角 2:左上角
plt.show()
2.3.6 解决问题的第六种方式
从这段代码中创建的视觉效果被称为叠加条形图。
adult_df.groupby(['race','income']).size().unstack().plot.barh(stacked=True)
plt.legend(loc=4)
plt.show()
当我们知道每种可能性下的数据对象的总数比人口之间的比较更重要时,我们更倾向于使用堆叠条形图而不是典型的条形图。在这种情况下,由于我们创建这个视觉效果是为了比较两个收入群体的情况,所以使用堆积条形图并不是很明智的。
3 研究两个属性之间的关系
视觉上调查属性之间关系的最好方法是成对进行。
我们用于调查一对属性之间的关系的工具取决于属性的类型。在下面的内容中,我们将根据以下几对来介绍这些工具:数字-数字,分类-分类,以及分类-数字。
3.1 将两个数字属性之间的关系可视化
描绘两个数字属性之间关系的最好工具是散点图。在下面的例子中,我们将使用一个叫做 "散点矩阵 "的工具,为一个带有数字属性的数据集创建一个散点图的矩阵。
3.1.1 使用散点图研究数字属性之间关系的例子
在这个例子中,我们将使用一个新的数据集,Universities_imputed_reduced.csv。
这个数据集的数据对象的定义是美国的大学,这些数据对象用以下属性描述。学院名称、州、公立/私立、申请人数、录取人数、新入学人数、州内学费、州外学费、有博士学位的教师比例、学生/教师比例、毕业率。
比率,以及毕业率。这些属性的命名是非常直观的,不需要进一步描述。
下面的代码使用seaborn
模块的pariplot()
函数,为uni_df
数据框架中的数字属性的每一对组合创建一个散点图。
import seaborn as sns
import pandas as pd
uni_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter05/Universities_imputed_reduced.csv')
sns.pairplot(uni_df)
利用这个图,你可以研究uni_df中任何两个属性之间的关系。例如,你可以看到num_appl_accepted
和num_new_stud_enrolled
之间有很强的关系,这很合理。随着接受的申请数量的增加,我们期望新入学的学生数量也会增加。
此外,通过研究下图中散点矩阵的最后一列或最后一行,你可以逐一研究毕业率和所有其他属性之间的关系。这样做之后,你可以看到,令人惊讶和有趣的是,毕业率属性与州内学费和州外学费的关系最强。有趣的是,毕业率与其他属性,如num_new_stud_enrolled, % fac. w/PHD, and stud./fac. 比率。
3.2 将两个分类属性之间的关系可视化
检查两个分类属性之间关系的最佳视觉工具是彩色编码的或然率表。或然率表是一个矩阵,它显示了数据对象在两个属性的所有可能的数值组合中的频率。虽然你可以为数字属性创建一个或然率表,但在大多数情况下这样做不会导致有效的可视化;或然率表几乎总是用于分类属性。
3.2.1 使用或然率表考察两个分类(二元)属性之间关系的例子
在这个例子中,我们有兴趣看看在adult_df的数据对象中,性别和收入这两个分类属性之间是否存在关系。为了研究这种关系,我们将使用一个或然率表。下面的截图显示了如何使用pd.crosstab()
pandas函数来完成这一工作。这个函数得到了两个属性,并为它们输出了或然率表。
contingency_tb1 = pd.crosstab(adult_df.income,adult_df.sex)
contingency_tb1
在前面的截图中,你可以看到,虽然大约11%的女性数据对象的收入大于5万,但大约30%的男性数据对象的收入大于5万。为了从或然率表中得出这样的结论,我们通常会做一些简单的计算,比如刚才的计算;我们计算了每个性别的收入总数的相对百分比。然而,我们可以对或然率表进行颜色编码,这样就不需要这些额外的步骤了。下面的截图显示了通过使用seaborn模块中的sns.heatmapt()
函数来实现这一目的的两步过程。
# 将所有行的属性值汇总,即汇总一列
contingency_tb1.sum(axis=0)
'''
sex
Female 10771
Male 21790
dtype: int64
'''
probablity_tb1 = contingency_tb1/contingency_tb1.sum()
print(probablity_tb1)
'''
sex Female Male
income
<=50K 0.890539 0.694263
>50K 0.109461 0.305737
'''
sns.heatmap(probablity_tb1,annot=True,center=0.5,cmap='Greys')
plt.show()
从原始或然率表创建彩色编码的或然率表的两个步骤如下。
- 通过将每一列的数值除以该列所有数值的总和,从或然率表中创建一个概率表。
- 使用
sns.heatmap()
创建彩色编码的或然率表。除了输入上一步计算的概率表(probablity_tbl)外,还增加了三个输入:annot=True,center=0.5,cmap="Greys"。将它们逐一删除,并运行前面截图中所示的相同代码,以了解每一个添加项的作用。
现在,通过简单观察前面截图中的彩色编码的或然表,我们可以看到,虽然在男性和女性中,更多的数据对象收入<=5万,但男性数据对象比女性数据对象更有可能收入>5万。因此,我们可以得出结论,性别和收入之间确实存在着有意义的、可观的关系。
这个例子研究了两个二进制属性之间的关系。当属性不是二进制时,我们创建彩色编码的或然率表的步骤是相同的。让我们在一个例子中看到这一点。
3.2.2 使用或然率表处理两个分类(非二元)属性之间关系的例子
创建一个可视化,检查 adult_df中数据对象的种族和职业属性之间的关系。
contingency_tb2 = pd.crosstab(adult_df.occupation,adult_df.race)
probablity_tb2 = contingency_tb2/contingency_tb2.sum()
sns.heatmap(probablity_tb2,annot=True,center=True)
plt.show()
在彩色编码的表格中,你可以清楚地看到以下模式。
- 种族属性值为白人的数据对象更有可能拥有职业属性值为工艺-修理、执行-管理、或专业的数据对象。
-种族属性值为黑人的数据对象更有可能拥有Adm-clerical和Other-service的职业属性值 - 种族属性值为Asian-Pac-Islander的数据对象更有可能拥有Prof-specialty的职业属性值
-种族属性值为Amer-Indian-Eskimo的数据对象更可能拥有Craft-repair的职业属性值。
同样,使用或然率表,我们可以看到在adult_df中的数据对象中,种族和职业之间存在着一种可观的、有意义的关系。
3.3 将数字属性和分类属性之间的关系可视化
使得这种情况更具挑战性的原因很明显:属性的类型是不同的。为了能够直观地看到分类属性和数字属性之间的关系,必须将其中一个属性转换为另一种类型的属性。几乎所有的情况下,最好是将数字属性转化为分类属性,然后使用或然率表来检验这两个属性之间的关系。下面的例子显示了如何做到这一点。
3.3.1 考察分类属性和数字属性之间关系的例子
首先,创建一个可视化,检查 adult_df中数据对象的种族和年龄属性之间的关系。
年龄属性是数字的,而种族属性是分类的。因此,首先,我们需要将年龄转化为一个分类属性。然后,我们可以用一个或然率表来可视化它们的关系。
age_discretized = pd.cut(adult_df.age,bins=5)
print(age_discretized)
'''
0 (31.6, 46.2]
1 (46.2, 60.8]
2 (31.6, 46.2]
3 (46.2, 60.8]
4 (16.927, 31.6]
...
32556 (16.927, 31.6]
32557 (31.6, 46.2]
32558 (46.2, 60.8]
32559 (16.927, 31.6]
32560 (46.2, 60.8]
Name: age, Length: 32561, dtype: category
Categories (5, interval[float64, right]): [(16.927, 31.6] < (31.6, 46.2] < (46.2, 60.8] < (60.8, 75.4] < (75.4, 90.0]]
'''
contingency_tb3 = pd.crosstab(age_discretized,adult_df.race)
probablity_tb3 = contingency_tb3/contingency_tb3.sum()
sns.heatmap(probablity_tb3,annot=True,center=True,cmap='Greys')
plt.show()
前面的截图中显示的解决方案有以下三个步骤。
- 使用
pd.cut()
pandas函数将adult_df.age转化为一个有五种可能性的分类属性。选择5个仓是任意的,但这是一个很好的数字,除非有很好的理由将数据归入不同数量的仓。Discretization就是我们所说的将数字属性转化为分类属性;这就是为什么我们用age_discretized作为转化后的adult_df.age属性的名称。 - 使用
pd.crosstab()
pandas函数为age_discretized和adult_df.race创建一个或然率表。 - 使用上一步创建的或然率表创建一个概率表,然后使用
sns.heatmap()
来创建彩色编码的或然率表。
输出的视觉效果显示,这两个属性之间存在着有意义的、可观的关系。具体来说,种族属性为其他的数据对象比种族属性为白人、黑人、亚洲-太平洋-斯拉夫人和美国-印第安-爱斯基摩人的数据对象要年轻。
这个例子展示了一种常见的情况,即数字属性将被转化为分类属性,以检查其与另一个分类属性的关系。虽然这几乎是所有情况下的最佳方式,但在有些情况下,将分类属性转化为数字属性是有好处的。下面的例子显示了一种罕见的情况,这种转换是首选。
3.3.2 另一个考察分类属性和数字属性之间关系的例子
首先,创建一个可视化,检查 adult_df 中数据对象的教育和年龄属性之间的关系。
同样,我们有一个分类属性和一个数字属性。然而,这一次,分类属性有两个特点,使我们有可能选择不太常见的方式来处理这种情况。这两个特点如下。
- 教育是一个序数分类属性,而不是一个名义分类属性。
- 通过一些合理的假设,可以将该属性变成一个数字属性。
将一个序数属性转化为数字属性的默认方法是排序转化。例如,你可以对教育属性进行排序转换,用一个整数替换 adult_df.education 下的每个可能性。有趣的是,adult_df数据集已经有了另一个属性,它是教育属性的等级转换,这个转换后的属性被称为 education-num。下图显示了这两个属性之间的一对一关系。
你可以通过运行以下代码,自己看到前述图中描绘的两个属性之间的关系:adult_df.groupby(['education','education-num']).size()
当你运行这段代码时,你会看到.groupy()函数并没有对education-num的每个可能性进行分割;其原因是这两个属性之间存在一对一的关系。
现在我们有了教育属性的数字版本,我们可以使用散点图来可视化教育和年龄之间的关系。下面的截图显示了代码和可视化的情况。
adult_df.plot.scatter(x='age',y='education-num')
plt.show()
使用可视化的关系,我们可以看到,年龄和教育这两个属性没有关系。为了实践的需要,我们也反过来做这个分析;让我们把年龄离散化,然后创建一个或然率表,看看我们是否会得到同样的结论。
下面的截图显示了这个分析的代码和输出的视觉效果。
age_discretized = pd.cut(adult_df.age,bins=5)
contingency_tb4 = pd.crosstab(adult_df.education,age_discretized)
probablity_tb4 = contingency_tb4/contingency_tb4.sum()
sns.heatmap(probablity_tb4,annot=True,center=0.5,cmap='Greens')
plt.show()
我们可以看到,这种视觉效果也给人一种印象,即年龄和教育这两个属性之间没有关系。
4 添加视觉维度
到目前为止,我们所创建的可视化只有两个维度。当使用数据可视化作为讲述故事或分享发现的方式时,有很多很好的理由不要给你的可视化添加太多的维度。例如,有太多维度的视觉效果可能会让你的观众不知所措。然而,当可视化被用作探测数据模式的探索性工具时,能够在可视化中添加维度可能正是数据分析师所需要的。
有很多方法可以给视觉添加维度,比如使用颜色、大小、色调、线条样式等等。在这里,我们将通过使用颜色、大小和时间添加维度来介绍三种最适用的方法。在这种情况下,我们将展示在散点图的情况下添加维度,但如果适用的话,所展示的技术可以很容易地推断到其他视觉效果。下面的例子展示了在散点图上添加额外的维度是如何产生重大价值的。
4.1 五维散点图的例子
使用WH Report_preprocessed.csv创建一个可视化,显示该数据集中以下五列的相互作用。
- 健康预期寿命
- 人均GDP对数
- 年份
- 大陆
- 人口
要解决这个问题,我们必须一步步来。所以,请自始至终跟着我。
我们在这个例子中使用的数据集取自《世界幸福报告》,其中包括2010年至2019年122个国家的数据。在开始接触这个例子所给出的解决方案之前,请花些时间熟悉一下这个数据集。
更好地学习的建议 随着我们学习越来越复杂的分析、算法和代码,我们在这些页面中可能没有空间去了解书中涉及的每一个新数据集。每当本书中介绍一个新的数据集时,我强烈建议你采取第1章《核心模块NumPy和Pandas的回顾》中Pandas函数探索DataFrame部分所规定的步骤。当然,这也适用于此。在继续阅读之前,花点时间了解一下WH Report_preprocessed.csv数据集。
下面的代码使用plt.subplot()
和plt.scatter()
将三个维度结合起来。出生时的健康预期寿命、人均GDP对数和年份。
country_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter05/WH%20Report_preprocessed.csv')
country_df.head()
plt.figure(figsize=(15,8))
year_poss = country_df.year.unique()
for i,y in enumerate(year_poss):
BM = country_df.year == y
X = country_df[BM].Healthy_life_expectancy_at_birth
Y = country_df[BM].Log_GDP_per_capita
plt.subplot(2,5,i+1)
plt.scatter(X,Y)
plt.title(y)
plt.xlim([30,80])
plt.ylim([6,12])
plt.tight_layout()
plt.show()
前面代码的输出如上图所示。这幅图设法实现了以下重要的事情。
- 该图一次性地将三个维度可视化。
- 该图显示了国家在X和Y维度上的向上和向右的运动。这种运动有可能说明全球在健康预期寿命和人均GDP这两个维度上的成功。
然而,视觉效果在显示2010年和2019年之间的国家运动方面是波澜不惊和草率的,所以我们可以做得更好。
现在,我们想通过在一个视觉中无缝整合时间,而不是使用子图来改进前述的图。下图显示了我们在这个环节的最终目标。该图是交互式的,通过滑动顶部小部件上的控制条,我们可以改变视觉的年份,从而看到各国在健康预期寿命和人均GDP对数两个维度下的变化。当然,我们无法在纸上做到这一点,但我将在这里分享可以实现这一目标的代码。但是,我们必须分两步来做这件事。
- 创建一个函数,输出所输入年份的相关可视化数据。
def plotyear(year):
BM = country_df.year == year
X = country_df[BM].Healthy_life_expectancy_at_birth
Y = country_df[BM].Log_GDP_per_capita
plt.scatter(X,Y)
plt.xlabel('Healthy_life_expectancy_at_birth')
plt.ylabel('Log_GDP_per_capita')
plt.xlim([30,80])
plt.ylim([6,12])
plt.show()
在创建这个函数后,在继续前进之前,通过调用该函数将其投入使用--例如,运行plotyear(2011)、plotyear(2018)和plotyear(2015)。如果一切运行良好,你会在每次运行中得到一个新的散点图.
在你有了一个运作良好的plotyear()
之后,编写并运行下面的代码就可以得到上图中的交互式视觉效果为了创建这个交互式视觉效果,我们使用了ipywidgets
模块中的interactive
和widgets
编程对象。
- 使用新的模块和编程对象来创建滑动条。
from ipywidgets import interact, widgets
interact(plotyear,year=widgets.IntSlider(min=2010,max=2019,step=1,value=2010))
### 4.1.1 第四维度
到目前为止,我们只能在视觉上包括三个维度。出生时的健康预期寿命、人均GDP对数和年份。我们还有两个维度要做。
我们用散点图来包括前两个维度,我们用时间来包括第三个维度,即年份。现在,让我们用颜色来包括第四个维度--大陆。
下面的代码将颜色添加到我们已经建立的内容中。密切注意一个for循环是如何被用来遍历所有大洲的,并将每个大洲的数据逐一添加到视觉中,从而将它们分开。
Continent_poss = country_df.Continent.unique()
color_dic = {
'Asia':'b',
'Europe':'g',
'Africa':'r',
'South America':'c',
'Oceania':'m',
'North America':'y',
'Antarctica':'k'
}
# 南极洲 Antarctica
def plotyear(year):
for continent in Continent_poss:
BM1 = (country_df.year == year)
BM2 = (country_df.Continent == continent)
BM = BM1 & BM2
X = country_df[BM].Healthy_life_expectancy_at_birth
Y = country_df[BM].Log_GDP_per_capita
plt.scatter(X,Y,c=color_dic[continent],marker='o',
linewidths=0.5,edgecolors='w',label=continent)
plt.xlabel('Healthy_life_expectancy_at_birth')
plt.ylabel('Log_GDP_per_capita')
plt.xlim([30,80])
plt.ylim([6,12])
plt.legend()
plt.show()
interact(plotyear,year=widgets.IntSlider(min=2010,max=2019,step=1,value=2010))
思考和与前面的视觉互动,不仅为我们眼前的视觉增加了额外的维度,而且也为我们一直在发展的故事增加了更多的维度。我们可以看到世界各大洲之间的明显差距,但同时,我们也看到了所有国家的国内生产总值和预期寿命都在不断上升的趋势。
4.1.2 第五维度
到目前为止,我们只能在一个视觉中包括以下四个维度:健康预期寿命、人均GDP对数、年份和大陆。现在,让我们增加第五个维度,也就是人口,用标记的大小来表示。下面的代码将人口的维度添加为标记物的大小。
Continent_poss = country_df.Continent.unique()
color_dic = {
'Asia':'b',
'Europe':'g',
'Africa':'r',
'South America':'c',
'Oceania':'m',
'North America':'y',
'Antarctica':'k'
}
# 南极洲 Antarctica
# 为了让人口较多的国家首先被加入到视觉中,因此,他们的标记将进入背景,不会掩盖人口较少的国家。
country_df.sort_values(['population'],inplace=True,ascending=False)
def plotyear(year):
for continent in Continent_poss:
BM1 = (country_df.year == year)
BM2 = (country_df.Continent == continent)
BM = BM1 & BM2
# 为了缩减大的人口数字以创建视觉效果而添加的。这个数字纯粹是在一些试验和错误之后找到的。
size = country_df[BM].population/200000
X = country_df[BM].Healthy_life_expectancy_at_birth
Y = country_df[BM].Log_GDP_per_capita
plt.scatter(X,Y,c=color_dic[continent],marker='o',
linewidths=0.5,edgecolors='w',label=continent,s=size)
plt.xlabel('Healthy_life_expectancy_at_birth')
plt.ylabel('Log_GDP_per_capita')
plt.xlim([30,80])
plt.ylim([6,12])
# markerscale=0.5是为了缩放图例中显示的标记而添加的,因为如果没有这个,它们就会太大。
plt.legend(markerscale=0.5)
plt.show()
interact(plotyear,year=widgets.IntSlider(min=2010,max=2019,step=1,value=2010))
5 展示和比较趋势
当数据对象由彼此高度相关的属性描述时,趋势可以被可视化。这种数据集的一个很好的例子是时间序列数据。时间序列数据集的数据对象是由时间属性描述的,并且它们之间有相等的时间长度。例如,下面的数据集是一个时间序列数据集,显示了亚马逊和苹果股票在2020年前10个交易日的每日收盘价。在这个例子中,你可以看到数据集的所有属性都具有时间性质,而且它们之间的时间长度相等,都是一天。
将时间序列数据可视化的最好方法是使用线图。
线状图在股市分析中非常流行。如果你搜索任何股票代码,你会看到谷歌会向你展示价格趋势的线图。它还为你提供了一个选项,可以改变你希望线状图直观显示价格趋势的时间长度。试一试吧--例如,尝试一些搜索。亚马逊股票,谷歌股票,和沃尔玛股票。
线形图在股市分析中很受欢迎;然而,它们在其他领域也非常有用。任何有时间序列数据的数据集都有可能利用线状图来显示趋势。下面的例子说明了另一个应用线段来可视化和比较趋势的例子。
5.1 趋势的可视化和比较的例子
使用WH Report_preprocessed.csv来创建一个可视化,显示和比较2010年和2019年之间所有大陆的腐败感知属性的趋势。明确地说,我们只想要2010年和2019年这两年的数据。
在继续阅读之前,请先试一试这个例子。这个例子可以通过我们到目前为止所学的所有编程和可视化工具来轻松解决。下面的代码创建了要求的可视化。
country_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter05/WH%20Report_preprocessed.csv')
country_df.head()
Continent_poss = country_df.Continent.unique()
# 根据大陆和年份这两个属性对数据进行分组,然后计算Perceptions_of_corruption属性的聚合函数.mean()。分组的结果被记录在byContinentYear_df中,它是一个数据框架。
byContinentYear_df = country_df.groupby(['Continent','year']).Perceptions_of_corruption.mean()
# 为了更好地分离各大洲,代码使用了标记物。首先,代码创建了一个可能的标记列表供以后使用。
Marker_options = ['o','^','P','8','s','p','*']
for i,c in enumerate(Continent_poss):
plt.plot([2010,2019],byContinentYear_df.loc[c,[2010,2019]],label=c,marker=Marker_options[i])
plt.xticks([2010,2019])
#以将图例框置于视觉之外。
plt.legend(bbox_to_anchor=(1.05,1.0))
plt.title('Aggreated values per each continent in 2010 and 2019')
plt.ylabel('Perceptions_of_corruption')
plt.show()
欣赏一下下面这个视觉告诉我们的故事。这是该视觉清晰显示的以下五点。- 对于大多数大陆,即非洲、北美、亚洲和欧洲,对腐败的看法都有所下降。
- 在所有这些改善的大陆中,欧洲的腐败认知下降得最快。
- 亚洲的改善速度比北美快,因此与2010年相比,2019年亚洲的情况比北美要好。
- 腐败感知度上升的两个大洲是南美洲和南极洲。
- 大洋洲的腐败感知值没有变化,正因为如此,该大洲取得了所有大洲中腐败感知值最低的地位。
6 箱型图
箱型图(Boxplot)也称箱须图(Box-whisker Plot)、盒式图或箱线图,是利用数据中的五个统计量:最小值、上四分位数、中位数、下四分位数与最大值来描述数据的一种统计图。它能够直观地显示数据的异常值,分布的离散程度以及数据的对称性。
6.1 箱型图的特征
1.直观地观察到异常值,如果数据存在离群点,即位于上下边缘区域之外,以圆点的形式表示
2.当箱型图很短时,意味着很多数据多集中分布在很小的范围内
3.当箱型图很长时,意味着数据分布比较离散,数据间的差异比较大
4.当中位数接近底部时,说明大部分的数据值比较小
5.当中位数接近顶部时,说明大部分的数据值比较大
6.中位数所处的高低位置能反映数据的偏斜程度
7.如果上下虚线比较长,说明上下四分位数之外的数据变化比较大,整体数据的方差和标准偏差也比较大
8.箱型图的上下边缘并非最大值或最小值
6.2 箱型图的缺点
1.箱型图虽然能显示出数据的分布偏态,但是不能提供关于数据分布偏态和尾重程度的精确度量;
2.对于批量较大的数据批,箱线图反映的形状信息更加模糊;
3.用中位数代表总体平均水平有一定的局限性。
7 练习
person_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter05/whickham.csv')
person_df.head()
person_df['age_discretized'] = pd.cut(person_df.age,bins=4,labels=False)
person_df.head()
person_df.groupby(['age_discretized','smoker']).outcome.value_counts()
person_df.groupby(['age_discretized','smoker']).outcome.value_counts().unstack()
person_df.groupby(['age_discretized','smoker']).outcome.value_counts().unstack().unstack()
person_df.groupby(['age_discretized','smoker']).outcome.value_counts().unstack().unstack().plot.bar(stacked=True)
plt.show()
[
](https://blog.csdn.net/tangxianyu/article/details/124210558)
[
](https://blog.csdn.net/tangxianyu/article/details/124210558)