Python-探索性数据分析实用指南-二-

69 阅读1小时+

Python 探索性数据分析实用指南(二)

原文:Hands-On Exploratory Data Analysis with Python

协议:CC BY-NC-SA 4.0

五、描述性统计学

在本章中,我们将探讨描述性统计及其各种技术。顾名思义,描述性统计通过提供与所提供的数据集相关的简短摘要来帮助描述和理解数据集。最常见的描述性统计类型包括中心倾向的度量、偏差的度量以及其他。在这一章中,我们将熟悉这些技术,并通过可视化来探索这些实际测量。我们将使用盒子图等工具从统计数据中获取知识。

在本章中,我们将涵盖以下主题:

  • 理解统计
  • 集中趋势测量
  • 分散的度量

技术要求

本章的代码可以在本书的 GitHub 资源库中找到,位于Chapter 5文件夹内:

理解统计

在数据科学中,定性和定量分析都是重要的方面。特别是,任何数据集的定量分析都需要理解统计概念。统计学是数学的一个分支,研究收集、组织和解释数据。因此,通过使用统计概念,我们可以了解数据的性质、数据集的摘要以及数据的分布类型。

分布函数

为了理解分布函数的概念,理解连续函数的概念是至关重要的。那么,当我们提到一个concontinuous函数时,我们指的是什么呢?基本上,连续函数是任何在值上没有任何意外变化的函数。这些突然或意外的变化被称为不连续。例如,考虑以下三次函数:

如果你画出这个函数的图,你会发现在这个系列的值中没有跳跃或空洞。因此,该功能是连续的:

了解了连续函数之后,现在让我们试着了解一下概率密度函数 ( PDF )是什么。PDF 可以用连续函数来定义,换句话说,对于任何连续函数,PDF 是变量具有 x 值的概率。

现在,如果你一直在关注,一个显而易见的问题应该会出现在你的脑海中。如果函数与离散的随机变量而不是连续的随机变量相关联呢?那么这个函数被称为概率质量函数**(PMF)。关于更正式的定义,请参考中的参考文献【6】,进一步阅读部分。**

离散随机变量的概率** 能力 分布概率 f 函数是一个概率列表,与每个可达到的值相关联。让我们假设一个随机变量,A,取实数区间上的所有值。然后,A 在结果列表 Z 中的概率,P(Z),是在 Z 之上和描述满足以下条件的函数 p(a)的曲线之下的区域:

  1. 曲线不能有负值(所有 a 的 p(a) > 0)。
  2. 曲线下的总面积始终等于 1。

这种曲线被称为密度曲线。连续概率分布包括正态分布、指数分布、均匀分布、伽玛分布、泊松分布和二项式分布。

均匀分布

任何连续均匀分布的均匀概率分布函数由下式给出:

让我们使用 Python 库、seaborn 和 matplotlib 绘制均匀分布的图表。首先,让我们导入生成图形所需的重要库:

import matplotlib.pyplot as plt
from IPython.display import Math, Latex
from IPython.core.display import Image
import seaborn as sns

sns.set(color_codes=True)
sns.set(rc={'figure.figsize':(10,6)})

现在,让我们生成一个均匀分布:

from scipy.stats import uniform
number = 10000
start = 20
width = 25

uniform_data = uniform.rvs(size=number, loc=start, scale=width)
axis = sns.distplot(uniform_data, bins=100, kde=True, color='skyblue', hist_kws={"linewidth": 15})
axis.set(xlabel='Uniform Distribution ', ylabel='Frequency')

代码很明显,对吧?我们只需从统计库中导入统一函数并生成数据。一旦生成数据集,我们就绘制图表。前面代码的输出图如下:

uniform函数用于在给定的开始位置(loc)和参数宽度(scale)之间生成一个统一的连续变量。size参数指定所考虑的随机变量的数量。该图说明了数据集是均匀分布的事实。

正态分布

Norm al 分布,或 Gaussia n 分布是一个函数,它将随机变量列表分布在一个形状像对称钟的图形中。我非常肯定,在你的数据科学生涯中,你会多次遇到这个术语。但是你了解它的概念吗?嗯,正态分布有一条关于其平均值对称的密度曲线,其分布通常由其标准差来定义。它有两个参数——均值和标准差。正态分布主要基于中心极限定理,这一事实使它具有相关性。如果一个总体中所有可能样本的大小为n,均值为*μ*,方差为*σ2*,则分布接近正态分布。数学上,给出如下:

现在,让我们看看如何使用 Python stats库绘制正态分布的插图:

from scipy.stats import norm

normal_data = norm.rvs(size=90000,loc=20,scale=30)
axis = sns.distplot(normal_data, bins=100, kde=True, color='skyblue', hist_kws={"linewidth": 15,'alpha':0.568})
axis.set(xlabel='Normal Distribution', ylabel='Frequency')

前面代码的输出如下:

我们可以通过norm.rvs()方法使用scipy.stats模块得到正态分布图。它允许loc参数设置分布的平均值,scale参数设置标准差,最后size参数指示随机变量的数量。

指数分布

一些事件以恒定的平均速率连续独立发生的过程被称为泊肃T2【n】T3T6】proT8 过程。指数分布描述了这种泊松点过程中事件之间的时间,指数分布的概率密度函数如下:

通过应用expon.rvs()函数,我们可以使用scipy.stats模块可视化指数分布的随机变量。检查以下代码:

# Exponential distribution
from scipy.stats import expon

expon_data = expon.rvs(scale=1,loc=0,size=1000)
axis = sns.distplot(expon_data, kde=True, bins=100, color='skyblue', hist_kws={"linewidth": 15})
axis.set(xlabel='Exponential Distribution', ylabel='Frequency')

前面代码的输出如下:

上图所示的图表说明了递减的指数函数。曲线在 x 轴上递减。

二项分布

顾名思义,二项式分布只有两种可能的结果,成功或失败。结果不需要同样可能,每个试验都是相互独立的。

让我们通过binom方法使用scipy.stats模块生成二项式分布图:

from scipy.stats import binom

binomial_data = binom.rvs(n=10, p=0.8,size=10000)

axis = sns.distplot(binomial_data, kde=False, color='red', hist_kws={"linewidth": 15})
axis.set(xlabel='Binomial Distribution', ylabel='Frequency')

下图给出了上述代码的输出:

来自scipy.stats模块的binom.rvs()方法将n作为试参数,p作为成功概率作为形状参数生成图形。

累积分布函数

现在,cumudistribution解决方案** 函数 ( CDF )是变量取小于等于x的值的概率。数学上,它写如下:**

**

当分布是标量连续时,它提供 PDF 下的面积,范围从负无穷大到 CDF 指定多元随机变量的分布。

描述性统计学

描述性统计处理简单的数据摘要的表述,以便清楚地理解它们。数据摘要可以是数字表示,也可以是简单图形的可视化,以便进一步理解。通常,这样的总结有助于统计分析的初始阶段。描述性统计有两种类型:

  1. 集中趋势测量
  2. 可变性的度量(扩散)

中心趋势的度量包括meanmedianmode,而可变性的度量包括standard deviation(或方差)、变量的最小值和最大值、kurtosisskewness。我们将在下一节讨论这两个类别。

集中趋势测量

中心趋势的度量倾向于描述数据集的平均值或均值,该平均值或均值被认为提供了整个度量集的最佳汇总。该值在某种程度上是集合的核心。分析数据分布频率最常用的方法是平均值、中位数和众数。

平均值

平均值是一个数字,观察到的连续变量分布在这个数字周围。这个数字估计整个数据集的值。从数学上来说,它是数据集中数字的总和除以整数的结果。

设 x 是一组整数:

x = (12,2,3,5,8,9,6,4,2)

因此, x 的平均值可以计算如下:

接下来,我们来看看中位数。

中位数

给定按升序或降序排序的数据集,中位数将数据分为两部分。计算中位数的一般公式如下:

这里,n是数据中的项数。计算中位数的步骤如下:

  1. 以升序或降序对数字进行排序。
  2. 如果n是奇数,求(n+1)/2<sup>th</sup>项。该术语对应的值是中位数。
  3. 如果n为偶数,求(n+1)/2<sup>th</sup>项。中间值是中间位置两边数字的平均值。

对于x这样的一组整数,我们必须按照升序排列,然后选择中间的整数。

x按升序= (2,2,3,4,5,6,8,9,12)。

这里,中位数是 5。

方式

模式是在数据集中出现次数最多的整数。它恰好是数据集中出现频率最高的值。在中值示例的x数据集中,模式为 2,因为它在集合中出现两次。

Python 提供了不同的库来操作数据集中的描述性统计。常用的库有pandasnumpyscipy。这些中心趋势的度量可以简单地通过numpypandas功能来计算。

为了实践描述性统计,我们需要一个包含多个数字记录的数据集。这是一个汽车数据集,收集了汽车的不同特征和属性,如象征、标准化损失、渴望和许多其他特征,对这些特征和属性的分析将提供一些与该数据集中的汽车相关的有价值的见解和发现。

让我们从导入所需的数据集和 Python 库开始:

import pandas as pd
import numpy as np

现在,让我们加载汽车数据库:

df = pd.read_csv("data.csv")
df.head()

在前面的代码中,我们假设数据库存储在当前驱动器中。或者,您可以将路径更改到正确的位置。到目前为止,您应该已经熟悉了数据加载技术。代码的输出如下所示:

Data cleaning:In the previous chapter, we discussed several ways in which we can clean our dataset. We need to clean numeric columns. Since we have already discussed several ways in which we can clean the dataset, I have skipped the codes for doing so. However, you can find a section entitled Data cleaning in the Python notebook attached to this chapter in the GitHub repository. 

现在,让我们从计算中心倾向的度量开始。在为所有行建立这些之前,让我们看看如何获得单个列的中心趋势。例如,我们希望获得代表高度的列的平均值、中间值和模式。在 Pandas 中,我们可以通过将列名指定为dataframe["column_name"]来轻松获得单个列。在我们的例子中,我们的数据帧存储在df变量中。因此,我们可以得到所有的身高数据项目作为df["height"]。现在,Pandas 提供了简单的内置功能来测量中枢倾向。让我们这样计算:

height =df["height"]
mean = height.mean()
median =height.median()
mode = height.mode()
print(mean , median, mode)

前面代码的输出如下:

53.766666666666715 54.1 0 50.8
dtype: float64

现在,重要的是解释结果。光是这些简单的统计就可以明白,汽车的平均高度在53.766左右,模式值为50.8的汽车很多。同样,我们可以获得数据类型为数字的任何列的中心趋势的度量。下面的屏幕截图显示了类似的有用功能列表:

除了查找单个列的统计信息之外,还可以一次查找整个数据集的描述性统计信息。Pandas 为此提供了一个非常有用的功能df.describe:

df.describe()

下面的屏幕截图显示了前面代码的输出:

如果你以前用过 Pandas,我很肯定你听说过或者可能用过几次这个功能。但是你真的了解你获得的输出吗?在上表中,您可以看到我们有所有列的统计数据,不包括 NaN 值。该函数在计算过程中同时考虑了数值序列和对象序列。在这些行中,我们得到该列的计数、平均值、标准偏差、最小值、百分位数和最大值。我们可以更好地理解我们的数据集。事实上,如果您查看前面的表格,您可以回答以下问题:

  • 我们数据集中的总行数是多少?
  • 汽车的平均长度、宽度、高度、价格和压缩比是多少?
  • 汽车的最小高度是多少?汽车的最大高度是多少?
  • 汽车整备质量的最大标准偏差是多少?。

事实上,我们现在可以回答很多问题,仅仅基于一张表。挺好的,对吧?现在,每当您开始任何数据科学工作时,执行一些健全性检查总是被认为是好的做法。我所说的健全性检查,是指在实际拟合机器学习模型之前了解你的数据。获取数据集的描述就是这样一种健全性检查。

在分类变量具有离散值的情况下,我们可以使用value_counts()函数来总结分类数据。嗯,身教胜于言教。在我们的数据集中,我们有一个分类数据列make。我们先按照这样的类别统计条目总数,然后取前 30 个最大值,画一个条形图:

df.make.value_counts().nlargest(30).plot(kind='bar', figsize=(14,8))
plt.title("Number of cars by make")
plt.ylabel('Number of cars')
plt.xlabel('Make of the cars')

到目前为止,前面的代码应该很熟悉了。我们正在使用 Pandas 图书馆的value_counts()功能。一旦我们有了列表,我们就可以使用nlargest()函数获得前 30 个最大值。最后,我们利用 Pandas 图书馆提供的绘图功能。这里显示了前面代码片段的输出:

如图所示,表格很有帮助。为了增加理解的程度,我们可以使用可视化技术,如上图所示。从图表中可以很清楚地看出,丰田品牌是最受欢迎的品牌。同样,我们可以很容易地在列表中可视化连续的品牌。

分散的度量

第二类描述性统计是离差的度量,也称为变异性的度量。它用于描述数据集的可变性,数据集可以是样本或总体。它通常与中心趋势的度量结合使用,以提供一组数据的总体描述。对分散性/可变性/分散性的衡量让我们了解了中心趋势在多大程度上代表了数据。如果我们仔细分析数据集,有时平均值可能不是数据的最佳表示,因为当数据之间存在较大差异时,平均值会有所不同。在这种情况下,离差的度量将更准确地表示数据集中的可变性。

多种技术在我们的数据集中提供了分散的度量。一些常用的方法是标准差(或方差)、变量的最小值和最大值、范围、峰度和偏斜度。

标准偏差

用简单的语言来说,标准差是数据集中每个值与其平均值/均值之间的差值的平均值/均值;也就是说,数据是如何从平均值展开的。如果数据集的标准偏差较低,则数据点倾向于接近数据集的平均值,否则,数据点分布在更大的值范围内。

不同的 Python 库都有获取数据集标准差的函数。NumPy 库具有numpy.std(dataset)功能。统计库有statistics.stdev(dataset)。功能。使用 pandas 库,我们使用df.std()函数计算df数据框中的标准偏差:

#standard variance of dataset using std() function
std_dev =df.std()
print(std_dev)
# standard variance of the specific column
sv_height=df.loc[:,"height"].std()
print(sv_height)

前面代码的输出如下:

接下来,让我们看看方差。

差异

方差是数据集中每个值与其平均值/均值之差的平均值/均值的平方;也就是说,它是标准差的平方。

不同的 Python 库都有获取数据集方差的函数。NumPy 库具有numpy.var(dataset)功能。统计库具有statistics.variance(dataset)功能。使用 Pandas 库,我们使用df.var()函数计算df数据框中的方差:

# variance of dataset using var() function
variance=df.var()
print(variance)

# variance of the specific column
var_height=df.loc[:,"height"].var()
print(var_height)

前面代码的输出如下:

有必要从这里提供的代码片段中注意到以下观察结果:

  • 需要注意的是df.var()默认情况下会计算给定数据帧中跨列的方差。另外,我们可以指定axis=0表示需要按列或按行计算方差。
  • 指定df.var(axis=1)将计算给定数据帧中的行方向方差。
  • 最后,还可以通过指定位置来计算任何特定列中的方差。例如,df.loc[:,"height"].var()计算前面数据集中列高度的方差。

歪斜

在概率论和统计学中,偏斜度是数据集中变量关于其平均值的不对称性的度量。偏斜度值可以是正的或负的,或者是未定义的。偏斜度值告诉我们数据是偏斜的还是对称的。下面是一个正倾斜数据集、对称数据和一些负倾斜数据的示例:

请注意上图中的以下观察结果:

  • 右边的图的尾巴比右边的长。这表明数据的分布向左倾斜。如果选择左侧长尾巴中的任何一点,平均值小于模式。这种情况被称为 偏度
  • 左侧的图表在右侧有一条较长的尾巴。如果选择右侧尾部的任何点,平均值大于模式。这种情况被称为 偏度
  • 中间的图有一个右手边的尾巴,和左手边的尾巴一样。这种状态被称为对称状态

不同的 Python 库都有获取数据集偏斜度的函数。SciPy 库有一个scipy.stats.skew(dataset)功能。使用 Pandas 库,我们可以使用df.skew()函数计算df数据帧中的偏斜度。

这里,在我们的汽车数据框架中,让我们使用df.skew()函数来获得偏斜度:

df.skew()

前面代码的输出如下:

此外,我们还可以在列级别计算偏斜。例如,柱高的偏斜可以使用df.loc[:,"height"].skew()来计算。功能。

峭度

我们已经讨论过正态分布。你还记得钟形图吗?如果没有,就再看一遍本章的第一节。你很可能会问自己,你为什么要记得这些?为了理解峰度的概念,这是必要的。基本上,峰度是一个统计量,说明分布的尾部与正态分布的尾部有多大的不同。这种技术可以识别给定的分布是否包含极值。

But hold on, isn't that similar to what we do with skewness? Not really. Skewness typically measures the symmetry of the given distribution. On the other hand, kurtosis measures the heaviness of the distribution tails. 

峰度,不像偏斜度,不是关于峰值或平坦度。它是给定分布中异常值存在的度量。峰度的高低都是数据需要进一步调查的指标。峰度越高,异常值越高。

峰度类型

峰度有三种类型——中峰度、细峰度和扁峰度。让我们一个一个来看:

  • 中库分布:如果任何数据集遵循正态分布,则它遵循中库分布。峰度在 0 左右。
  • 细峰度:这种情况下,分布的峰度大于 3,肥尾表示分布产生更多的异常值。
  • **板状峰度:**在这种情况下,分布具有负峰度,与正态分布相比,尾部非常细。

下图显示了所有三种峰度:

不同的 Python 库都有获取数据集峰度的函数。SciPy 库具有scipy.stats.kurtosis(dataset)功能。使用 Pandas 库,我们使用df.kurt()函数计算df数据帧的峰度:

# Kurtosis of data in data using skew() function
kurtosis =df.kurt()
print(kurtosis)

# Kurtosis of the specific column
sk_height=df.loc[:,"height"].kurt()
print(sk_height)

这里给出了前面代码的输出:

同样,我们可以计算任何特定数据列的峰度。例如,我们可以将柱高的峰度计算为df.loc[:,"height"].kurt()

计算百分比

百分位数衡量任何数据集中低于特定值的值的百分比。为了计算百分位数,我们需要确保我们的列表是有序的。举个例子,如果你说第 80 个百分位数的数据是 130:那这意味着什么?嗯,这只是意味着 80%的数值低于 130。很简单,对吧?我们将对此使用以下公式:

假设我们有给定的数据:1,2,2,3,4,5,6,7,7,8,9,10。那么百分位值 4 = (4/12) * 100 = 33.33%。

这仅仅意味着 33.33%的数据小于 4。

现在,让我们从到目前为止一直使用的同一数据帧中计算height列的百分比:

height = df["height"]
percentile = np.percentile(height, 50,)
print(percentile)

前面代码的输出如下:

54.1

The preceding formula is very simple. But do you see any pattern with the measures of central tendencies? What would be the 50th percentile? This corresponds to the median. Were you able to deduce that?

四重奏乐团

给定按升序排序的数据集,四分位数是将给定数据集拆分为四分位数的值。四分位数指的是将给定数据集分成四个相等部分的三个数据点,这样每个分割就占数据集的 25%。就百分位数而言,第 25 个百分位数被称为第一个四分位数(Q1),第 50 个百分位数被称为第二个四分位数(Q2),第 75 个百分位数被称为第三个四分位数(Q3)。

基于四分位数,还有另一个度量称为四分位数间范围,它也度量数据集的可变性。其定义如下:

IQR 不受异常值的影响。让我们从到目前为止一直使用的相同数据帧中获取price列的 IQR:

price = df.price.sort_values()
Q1 = np.percentile(price, 25)
Q2 = np.percentile(price, 50)
Q3 = np.percentile(price, 75)

IQR = Q3 - Q1
IQR

前面片段的输出如下:

8718.5

接下来,让我们使用方框图来可视化四分位数。

可视化四分位数

首先,让我们生成一些数据。让我们假设以下是学生在三个不同科目中获得的分数:

scorePhysics = [34,35,35,35,35,35,36,36,37,37,37,37,37,38,38,38,39,39,40,40,40,40,40,41,42,42,42,42,42,42,42,42,43,43,43,43,44,44,44,44,44,44,45,45,45,45,45,46,46,46,46,46,46,47,47,47,47,47,47,48,48,48,48,48,49,49,49,49,49,49,49,49,52,52,52,53,53,53,53,53,53,53,53,54,54,54,54,54,54,54,55,55,55,55,55,56,56,56,56,56,56,57,57,57,58,58,59,59,59,59,59,59,59,60,60,60,60,60,60,60,61,61,61,61,61,62,62,63,63,63,63,63,64,64,64,64,64,64,64,65,65,65,66,66,67,67,68,68,68,68,68,68,68,69,70,71,71,71,72,72,72,72,73,73,74,75,76,76,76,76,77,77,78,79,79,80,80,81,84,84,85,85,87,87,88]

scoreLiterature = [49,49,50,51,51,52,52,52,52,53,54,54,55,55,55,55,56,56,56,56,56,57,57,57,58,58,58,59,59,59,60,60,60,60,60,60,60,61,61,61,62,62,62,62,63,63,67,67,68,68,68,68,68,68,69,69,69,69,69,69,70,71,71,71,71,72,72,72,72,73,73,73,73,74,74,74,74,74,75,75,75,76,76,76,77,77,78,78,78,79,79,79,80,80,82,83,85,88]

scoreComputer = [56,57,58,58,58,60,60,61,61,61,61,61,61,62,62,62,62,63,63,63,63,63,64,64,64,64,65,65,66,66,67,67,67,67,67,67,67,68,68,68,69,69,70,70,70,71,71,71,73,73,74,75,75,76,76,77,77,77,78,78,81,82,84,89,90]

现在,如果我们想要绘制单个对象的方框图,我们可以使用plt.boxplot()功能来完成:

plt.boxplot(scoreComputer, showmeans=True, whis = 99)

让我们打印计算机学科分数的方框图:

上图说明了这样一个事实,即盒子从上四分位数到下四分位数(大约 62 和 73),而胡须(从盒子中伸出的条)最小为 56,最大为 90。红线是中间值(大约 67),而小三角形(绿色)是平均值。

现在,让我们也给其他主题添加方框图。我们可以通过将所有分数组合成一个变量来轻松做到这一点:

scores=[scorePhysics, scoreLiterature, scoreComputer]

接下来,我们绘制方框图:

box = plt.boxplot(scores, showmeans=True, whis=99)

plt.setp(box['boxes'][0], color='blue')
plt.setp(box['caps'][0], color='blue')
plt.setp(box['caps'][1], color='blue')
plt.setp(box['whiskers'][0], color='blue')
plt.setp(box['whiskers'][1], color='blue')

plt.setp(box['boxes'][1], color='red')
plt.setp(box['caps'][2], color='red')
plt.setp(box['caps'][3], color='red')
plt.setp(box['whiskers'][2], color='red')
plt.setp(box['whiskers'][3], color='red')

plt.ylim([20, 95]) 
plt.grid(True, axis='y') 
plt.title('Distribution of the scores in three subjects', fontsize=18) 
plt.ylabel('Total score in that subject') 
plt.xticks([1,2,3], ['Physics','Literature','Computer'])

plt.show()

这里给出了前面代码的输出:

从图中可以明显看出,学生获得的最低分在 32 分左右,而获得的最高分是 90 分,属于计算机科学科目。

摘要

在本章中,我们讨论了描述性统计的几个方面。描述性统计通常指定量描述给定数据集的汇总统计。我们讨论了该领域中最常用的汇总度量,包括中心趋势度量(均值、中值和模式)和可变性度量(标准差、最小值、最大值、峰度和偏斜度)。

在下一章中,我们将继续使用分组技术进行更高级的描述性统计。这些分组技术由 Pandas 图书馆提供。

进一步阅读

  • 偏斜度和峰度的测量 :
  • Pandas 食谱西奥多·彼得罗帕克特出版
  • 学习 Pandas——第二版迈克尔·海特帕克特出版
  • 掌握 Pandas费米·安东尼帕克特出版
  • Pandas 动手数据分析斯蒂芬妮莫林帕克特出版****

六、分组数据集

在数据分析过程中,根据某些标准将数据聚集或分组通常是很重要的。例如,电子商务商店可能希望对圣诞节期间完成的所有销售或黑色星期五收到的订单进行分组。这些分组概念出现在数据分析的几个部分。在本章中,我们将介绍分组技术的基础,以及这样做如何改进数据分析。我们将讨论不同的groupby()机制,这些机制将我们的数据集累积到我们可以对其执行聚合的各种类中。我们还将弄清楚如何利用数据透视表和交叉列表来可视化地剖析这些分类数据。

在本章中,我们将涵盖以下主题:

  • 了解 groupby()
  • Groupby 力学
  • 数据聚合
  • 数据透视表和交叉列表

技术要求

本章的代码可以在本书的 GitHub 资源库中的Chapter 6文件夹中找到。

我们将在本章中使用的数据集可以通过 Kaggle 在开放访问下获得。可以从www.kaggle.com/toramky/aut…下载。

在本章中,我们将使用pandas库,所以请确保您已经安装了它。

了解 groupby()

在数据分析阶段,将数据集分为多个类别或组通常是至关重要的。我们可以使用pandas库进行这样的分类。Pandasgroupby功能是这样做最有效、最节省时间的功能之一。Groupby提供的功能允许我们在整个数据框中拆分-应用-合并;也就是说,该函数可用于拆分、应用和组合数据帧。

类似于结构化查询语言 ( SQL ),我们可以使用 Pandas 和 Python 通过使用任何接受pandas对象或numpy数组的内置函数来执行更复杂的组操作。

在下一节中,我们将使用pandas库来研究机械组。

Groupby 力学

在处理pandas数据帧时,我们的分析可能要求我们按照一定的标准分割数据。Groupby mechanics 将我们的数据集聚集到不同的类中,在这些类中我们可以执行练习和进行更改,例如:

  • 按特征分组,分级
  • 按组聚合数据集
  • 将自定义聚合函数应用于组
  • 分组转换数据集

Pandasgroupby方法执行两个基本功能:

  • 它根据一些标准将数据分成组。
  • 它对每个组独立应用一个函数。

为了使用groupby功能,我们需要一个数据集,其中有多个数字和分类记录,这样我们就可以根据不同的类别和范围进行分组。

让我们来看一个汽车数据集,它收集了汽车的不同特征和属性,如symbollingnormalized-lossesmakeaspirationbody-styledrive-wheelsengine-location等。让我们开始吧:

  1. 让我们从导入所需的 Python 库和数据集开始:
import pandas as pd
df = pd.read_csv("/content/automobileEDA.csv")
df.head()

这里,我们假设数据库存储在当前驱动器中。如果没有,您可以将路径更改到正确的位置。到目前为止,您应该已经熟悉了实现这一点的适当的数据加载技术,所以我们在这里不再赘述。

前面代码的输出如下:

如您所见,有多列带有分类变量。

  1. 使用groupby()函数,我们可以根据body-style列对该数据集进行分组:
df.groupby('body-style').groups.keys()

前面代码的输出如下:

dict_keys(['convertible', 'hardtop', 'hatchback', 'sedan', 'wagon'])

从前面的输出中,我们知道body-style列有五个唯一的值,包括convertiblehardtophatchbacksedanwagon

  1. 现在,我们可以根据body-style列对数据进行分组。接下来,让我们打印包含在具有convertiblebody-style值的组中的值。这可以使用以下代码来完成:
# Group the dataset by the column body-style
style = df.groupby('body-style')

# Get values items from group with value convertible 
style.get_group("convertible")

前面代码的输出如下:

在前面的例子中,我们使用单个body-style列进行分组。我们还可以选择列的子集。我们将在下一节学习如何做到这一点。

选择列的子集

为了基于多个类别形成组,我们可以简单地在groupby()函数中指定列名。分组将与第一个类别、第二个类别同时进行,依此类推。

让我们使用两个类别body-styledrive wheels,如下所示:

double_grouping = df.groupby(["body-style","drive-wheels"])
double_grouping.first()

前面代码的输出如下:

我们不仅可以用特定的标准对数据集进行分组,还可以同时对整个组直接执行算术运算,并将输出打印为系列或数据帧。有max()min()mean()first()last()等功能可以直接应用于GroupBy对象,以获取各组的汇总统计数据。

在下一节中,我们将逐一讨论这些函数。

最大和最小

让我们计算每个组的最大和最小条目。在这里,我们将找到normalized-losses列的最大值和最小值:

# max() will print the maximum entry of each group 
style['normalized-losses'].max()

# min() will print the minimum entry of each group 
style['normalized-losses'].min()

前面代码的输出如下:

body-style
convertible 122
hardtop 93
hatchback 65
sedan 65
wagon 74
Name: normalized-losses, dtype: int64

如前面的输出所示,给出了每个类别的最小值。

均值

我们可以找到每组中数值列的平均值。这可以使用df.mean()方法来完成。

求平均值的代码如下:

# mean() will print mean of numerical column in each group
style.mean()

前面代码的输出如下:

请注意,我们可以通过指定一个列来获得每个列的平均值,如下所示:

# get mean of each column of specific group
style.get_group("convertible").mean()

前面代码的输出如下:

接下来,我们还可以统计每组symboling/records的个数。为此,请使用以下代码:

# get the number of symboling/records in each group
style['symboling'].count()

前面代码的输出如下:

body-style
convertible 6
hardtop 8
hatchback 68
sedan 94
wagon 25
Name: symboling, dtype: int64

了解了计数部分后,在下一节中,我们将讨论不同类型的数据聚合技术。

数据聚合

聚合是对数据集或其子集实施任何数学运算的过程。聚合是 Pandas 中用于操纵数据框中的数据进行数据分析的众多技术之一。

Dataframe.aggregate()函数用于在一列或多列之间应用聚合。一些最常用的聚合如下:

  • sum:返回请求轴的值的总和
  • min:返回请求轴的最小值
  • max:返回请求轴的最大值

我们可以在DataFramedf中应用聚合,如df.aggregate()df.agg()

由于聚合仅适用于数值类型的列,因此让我们从数据集中提取一些数值列,并对它们应用一些聚合函数:

# new dataframe that consist length,width,height,curb-weight and price
new_dataset = df.filter(["length","width","height","curb-weight","price"],axis=1)
new_dataset

前面代码片段的输出如下:

接下来,让我们应用单个聚合来获取列的平均值。为此,我们可以使用agg()方法,如下代码所示:

# applying single aggregation for mean over the columns
new_dataset.agg("mean", axis="rows")

前面代码的输出如下:

length 0.837102
width 0.915126
height 53.766667
curb-weight 2555.666667
price 13207.129353
dtype: float64

我们可以将多个函数聚合在一起。例如,我们可以通过使用以下代码一次找到所有列的总和和最小值:

# applying aggregation sum and minimum across all the columns 
new_dataset.agg(['sum', 'min']) 

前面代码的输出如下:

输出是一个数据帧,其中的行包含应用于列的各个聚合的结果。要跨不同的列应用聚合函数,可以传递一个字典,该字典包含列名和值,这些列名和值包含任何特定列的聚合函数列表:

# find aggregation for these columns 
new_dataset.aggregate({"length":['sum', 'min'], 
              "width":['max', 'min'], 
              "height":['min', 'sum'], 
              "curb-weight":['sum']}) 
# if any specific aggregation is not applied on a column
# then it has NaN value corresponding to it

前面代码的输出如下:

检查前面的输出。行的最大值、最小值和总和代表每一列的值。请注意,有些值是基于其列值的NaN

分组操作

最重要的操作groupBy工具是聚合、过滤、转换和应用。在数据集中实现聚合函数的一种有效方法是在对所需的列进行分组后进行。聚合函数将为每个组返回一个聚合值。一旦创建了这些组,我们就可以对分组的数据应用一些聚合操作。

让我们通过传递一个聚合函数字典将DataFramedfbody-styledrive-wheels以及extract stats from each group分组:

# Group the data frame df by body-style and drive-wheels and extract stats from each group
df.groupby(
   ["body-style","drive-wheels"]
).agg(
    {
         'height':min, # minimum height of car in each group
         'length': max, # maximum length of car in each group
         'price': 'mean', # average price of car in each group

    }
)

前面代码的输出如下:

前面的代码根据body-styledriver-wheels对数据帧进行分组。然后,聚合函数应用于heightlengthprice列,这些列返回各自组中的最小高度、最大长度和平均价格。

我们可以为要分组执行的功能创建一个聚合字典,然后在以后使用它:

# create dictionary of aggregations
aggregations=(
    {
         'height':min, # minimum height of car in each group
         'length': max, # maximum length of car in each group
         'price': 'mean', # average price of car in each group

    }
)
# implementing aggregations in groups
df.groupby(
   ["body-style","drive-wheels"]
).agg(aggregations) 

前面代码的输出如下:

我们也可以在聚合中使用numpy函数:

# import the numpy library as np
import numpy as np
# using numpy libraries for operations
df.groupby(
   ["body-style","drive-wheels"])["price"].agg([np.sum, np.mean, np.std])

前面代码的输出如下:

如前面截图所示,我们选择了两个类别,body-styledrive-wheels。这里可以看到每行的总和、平均值和标准差。很简单,对吧?现在,让我们学习如何重命名分组聚合列。

重命名分组聚合列

如果我们可以用在该列或组中执行的操作来重命名列名,您不认为输出数据帧会更有信息吗?

我们可以在每个组中执行聚合,并根据执行的操作重命名列。这对于理解输出数据集非常有用:

df.groupby(
   ["body-style","drive-wheels"]).agg(
    # Get max of the price column for each group
    max_price=('price', max),
    # Get min of the price column for each group
    min_price=('price', min),
    # Get sum of the price column for each group
    total_price=('price', 'mean') 
)

前面代码的输出如下:

如上图截图所示,我们只选择了两类:body-styledrive-wheels。对于这些类别中的每一行,最高价、最低价和总价都是在连续的列中计算的。

分组转换

使用groupby()和聚合,你一定想过,为什么我们不能将数据分组,应用聚合,并将结果直接追加到数据框中? *这一切有可能一步到位吗?*是的,它是。

对组或列执行转换会返回一个对象,该对象按与其自身相同的轴长度进行索引。这是一个结合groupby()使用的操作。聚合操作必须返回数据的简化版本,而转换操作可以返回完整数据的转换版本。让我们来看看:

  1. 让我们首先使用一个简单的转换函数,使用lambda函数将每辆车的价格提高 10%:
df["price"]=df["price"].transform(lambda x:x + x/10)
df.loc[:,'price']

前面代码的输出如下:

0 14844.5
1 18150.0
2 18150.0
3 15345.0
4 19195.0
        ... 
196 18529.5
197 20949.5
198 23633.5
199 24717.0
200 24887.5
Name: price, Length: 201, dtype: float64
  1. 让我们通过body-styledrive-wheels来观察每个分组的汽车平均价格:
df.groupby(["body-style","drive-wheels"])["price"].transform('mean')

前面代码的输出如下:

0 26344.560000
1 26344.560000
2 15771.555556
3 10792.980000
4 13912.066667
           ... 
196 23883.016667
197 23883.016667
198 23883.016667
199 23883.016667
200 23883.016667
Name: price, Length: 201, dtype: float64

如果您查看前面的输出,您会注意到这是如何从我们正常的groupby()函数返回不同大小的数据集的。

  1. 现在,为原始数据框中的平均价格创建一个新列:
df["average-price"]=df.groupby(["body-style","drive-wheels"])["price"].transform('mean')

# selecting columns body-style,drive-wheels,price and average-price
df.loc[:,["body-style","drive-wheels","price","average-price"]]

前面代码的输出如下:

前面截图中显示的输出非常明显。我们计算了两个类别的价格和平均价格:body-styledrive-wheels。接下来,我们将讨论如何使用透视表和交叉制表技术。

数据透视表和交叉列表

Pandas 为分组和汇总数据提供了几种选择。我们已经讨论了groupby、聚合和转换,但是还有其他选项可用,例如pivot_tablecrosstab。首先,让我们了解透视表。

数据透视表

pandas.pivot_table()函数创建一个电子表格样式的数据透视表作为数据框。数据透视表中的级别将存储在结果数据框的索引和列上的多索引对象(分层索引)中。

最简单的数据透视表必须有一个数据框和一个索引/索引列表。让我们看看如何做到这一点:

  1. 让我们制作一个由body-styledrive-wheelslengthwidthheightcurb-weightprice列组成的新数据框的透视表:
new_dataset1 = df.filter(["body-style","drive-wheels",
                          "length","width","height","curb-weight","price"],axis=1)
#simplest pivot table with dataframe df and index body-style
table = pd.pivot_table(new_dataset1, index =["body-style"]) 
table

前面代码的输出如下:

输出表类似于我们如何根据body-style对数据帧进行分组。上表中的值是相应类别中值的平均值。让我们制作一个更精确的透视表。

  1. 现在,用new_dataset1数据框设计一个透视表,以body-styledrive-wheels为索引。请注意,提供多个索引将首先对数据帧进行分组,然后汇总数据:
#pivot table with dataframe df and index body-style and drive-wheels
table = pd.pivot_table(new_dataset1, index =["body-style","drive-wheels"]) 
table

前面代码的输出如下:

输出是按body-styledrive-wheels分组的透视表。它包含相应列数值的平均值。

数据透视表的语法包含一些参数,如 c、值、索引、列和聚合函数。我们可以同时将聚合函数应用于透视表。我们可以传递聚合函数、值和要应用聚合的列,以便创建数据框的汇总子集的数据透视表:

# import numpy for aggregation function
import numpy as np

# new data set with few columns
new_dataset3 = df.filter(["body-style","drive-wheels","price"],axis=1)

table = pd.pivot_table(new_dataset3, values='price', index=["body-style"],
                       columns=["drive-wheels"],aggfunc=np.mean,fill_value=0)
table

就语法而言,前面的代码表示以下内容:

  • 数据集名为new_dataset3的透视表。
  • 这些值是要应用聚合函数的列。
  • 索引是用于对数据进行分组的列。
  • 用于指定数据类别的列。
  • aggfunc是要应用的聚合函数。
  • fill_value用于填写缺失值。

前面代码的输出如下:

前面的数据透视表代表了那些body-style不同body-style和可用drive-wheels的汽车的平均价格。

  1. 我们还可以对不同的列应用不同的聚合函数:
table = pd.pivot_table(new_dataset1, values=['price','height','width'],
                       index =["body-style","drive-wheels"],
                       aggfunc={'price': np.mean,'height': [min, max],'width': [min, max]},
                       fill_value=0)
table

前面代码的输出如下:

这个数据透视表代表了指数中提到的各个类别的汽车的高度、宽度和平均价格的最大值和最小值。

交叉列表

我们可以用另一种叫做交叉制表的技术定制pandas数据框。这使我们能够应对groupby和聚合,以进行更好的数据分析。Pandas 有crosstab功能,这有助于建立交叉列表。交叉列表显示了某些数据组出现的频率。让我们来看看:

  1. 我们用pd.crosstab()来看看不同厂家生产了多少不同车身风格的车:
pd.crosstab(df["make"], df["body-style"])

前面代码的输出如下:

让我们应用边距和margins_name属性来显示交叉表的行和列总和,如以下代码所示:

# apply margins and margins_name attribute to displays the row wise 
# and column wise sum of the cross table
pd.crosstab(df["make"], df["body-style"],margins=True,margins_name="Total Made")

前面代码的输出如下:

在行索引或列索引或两者的crosstab函数中应用多列将自动打印分组输出。

  1. 让我们看看数据是如何在 T2 的汽车制造商和他们的门类型中按body-typedrive_wheels列分布的:
pd.crosstab([df["make"],df["num-of-doors"]], [df["body-style"],df["drive-wheels"]],
            margins=True,margins_name="Total Made")

前面代码的输出如下:

现在,让我们重命名列和行索引。重命名让我们更好地理解交叉列表,如下面的代码所示:

# rename the columns and row index for better understanding of crosstab
pd.crosstab([df["make"],df["num-of-doors"]], [df["body-style"],df["drive-wheels"]],
            rownames=['Auto Manufacturer', "Doors"],
            colnames=['Body Style', "Drive Type"],
            margins=True,margins_name="Total Made").head()

前面代码的输出如下:

这些是一些交叉列表的例子,给了我们数据在各自类别中的频率分布。

pd.crosstab的透视表语法也接受一些参数,如 dataframe 列、值、normalize 和聚合函数。我们可以同时将聚合函数应用于交叉表。传递聚合函数和值(聚合将应用于这些列)会为我们提供一个数据框汇总子集的交叉表。

  1. 首先,让我们通过对crosstable应用mean()聚合函数来查看不同制造商生产的汽车相对于其body-style的平均值curb-weight:
# values are the column in which aggregation function is to be applied
# aggfunc is the aggregation function to be applied
# round() to round the output

pd.crosstab(df["make"], df["body-style"],values=df["curb-weight"],
            aggfunc='mean').round(0)

前面代码的输出如下:

归一化的crosstab将显示每个组合出现的时间百分比。这可以使用normalize参数来完成,如下所示:

pd.crosstab(df["make"], df["body-style"],normalize=True).head(10)

前面代码的输出如下:

当我们试图分析两个或更多变量时,交叉列表技术会很方便。这有助于我们考察它们之间的关系。

摘要

将数据分组到相似的类别中是任何数据分析任务中必不可少的操作。在本章中,我们讨论了不同的分组技术,包括分组机制、重新排列、重塑数据结构、数据聚合方法和交叉制表方法。除此之外,我们还检查了每个案例的各种示例。

在下一章中,我们将学习相关性,它描述了两个或多个变量是如何相关的。除此之外,我们将通过合适的例子来看看不同类型的相关技术及其应用。

进一步阅读

  • Pandas 食谱:科学计算食谱使用 Python 进行时间序列分析和数据可视化第一版,作者:Theodore petroPACKT 出版物,2017
  • 掌握 Pandas-第二版,作者:阿希什·库马尔, *PACKT 出版,*2019 年 10 月 25 日
  • 学习 Pandas-第二版,作者:迈克尔·海特, *PACKT 出版,*2017 年 6 月 29 日

七、互相关

在本章中,我们将探讨不同因素之间的相关性,并估计这些不同因素的可靠程度。此外,我们将了解我们可以进行的不同类型的检查,以发现数据之间的关系,包括单变量分析、双变量分析和多变量分析。我们将使用泰坦尼克号数据集进行这些分析。我们还将介绍辛普森悖论。同样,我们将深入了解一个众所周知的事实:相关性并不意味着因果关系。

在本章中,我们将涵盖以下主题:

  • 引入相关性
  • 理解单变量分析
  • 理解二元分析
  • 理解多元分析
  • 使用泰坦尼克号数据集讨论多元分析
  • 概述辛普森悖论
  • 相关性并不意味着因果关系

技术要求

本章的代码可以在chapter 7文件夹内的 GitHub 资源库(https://GitHub . com/PacktPublishing/用 python 动手探索数据分析)中找到:

引入相关性

我们要分析的任何数据集都将有代表不同事实的多个观察值(即变量)的不同字段(即列)。数据集的列很可能是相互关联的,因为它们是从同一事件中收集的。记录的一个字段可能会也可能不会影响另一个字段的值。为了检查这些列之间的关系类型并分析它们之间的因果关系,我们必须努力找出变量之间存在的依赖关系。数据集的两个字段之间的这种关系的强度称为相关性,由-1 到 1 之间的数值表示。

换句话说,检验关系并解释变量对是否相互关联以及关联程度的统计技术被称为相关性。相关性回答了诸如一个变量相对于另一个变量如何变化的问题。如果它确实改变了,那么改变到什么程度或强度?此外,如果这些变量之间的关系足够强,那么我们可以对未来的行为做出预测。

比如身高和体重都有关系;也就是说,个子高的人往往比个子矮的人更重。如果我们有一个比我们之前观察到的平均身高更高的新人,那么他们的体重更有可能超过我们观察到的平均体重。

相关性告诉我们变量是如何一起变化的,既有相同的方向,也有相反的方向,还有关系的大小(即强度)。为了找到相关性,我们计算皮尔逊相关系数,用ρ(希腊字母ρ)表示。这是通过将协方差除以变量标准差的乘积得到的:

就关系的强度而言,两个变量 AB 之间的相关值在+1 和-1 之间变化。如果相关是+1,那么就说是完美的正/线性相关(即变量 A 与变量 B 成正比),而-1 的相关是完美的负相关(即变量 A 与变量 B 成反比)。请注意,接近 0 的值根本不应该相关。如果相关系数的绝对值接近 1,那么这些变量就被称为强相关;相比之下,那些接近 0.5 的被称为弱相关。

让我们看一些使用散点图的例子。散点图显示一个变量受另一个变量影响的程度:

如第一个和最后一个图表所示,当绘制成直线时,数据点之间的距离越近,相关变量之间的相关性越高。它们之间的相关性越高,变量之间的关系就越强。绘制时数据点越分散(因此没有模式),两个变量之间的相关性越低。在这里,您应该注意以下四个要点:

  • 当数据点图有一条直线穿过原点到达 xy 值时,则称变量间有正相关
  • 当数据点绘制成从 y 轴上的高值到 x 轴上的高值的线时,变量被称为具有负相关
  • 完美相关性的值为 1。
  • 完美负相关的值为-1。

高度正相关的值接近 1。高度负相关的值接近-1。在上图中,+0.8 表示高度正相关,-0.8 表示高度负相关。数字越接近 0(在图中,这是+0.3 和-0.3),相关性越弱。

在分析数据集中的相关性之前,让我们了解各种类型的分析。

分析类型

在本节中,我们将探讨不同类型的分析。我们将从单变量分析开始,然后进行双变量分析,最后,我们将讨论多变量分析。

理解单变量分析

还记得我们在第五章描述性统计中使用的描述性统计的测量变量吗?我们有一组从 2 到 12 的整数。我们计算了该集合的平均值、中值和模式,并分析了整数的分布模式。然后,我们计算了每种类型汽车数据集的高度列中可用值的平均值、模式、中值和标准偏差。对单一类型数据集的这种分析称为单变量分析

单变量分析是分析数据的最简单形式。这意味着我们的数据只有一种类型的变量,我们对它进行分析。单变量分析的主要目的是获取数据,总结数据,并在值之间找到模式。它不涉及原因或价值观之间的关系。描述单变量数据中发现的模式的几种技术包括中心趋势(即平均值、模式和中值)和离差(即范围、方差、最大和最小四分位数(包括四分位数之间的范围)和标准偏差)。

你为什么不试着对同一组数据再做一次分析呢?这一次,记住这是单变量分析:

  1. 首先导入所需的库并加载数据集:
#import libraries
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
  1. 现在,加载数据:
# loading dataset as Pandas dataframe
df = pd.read_csv("data.csv")
df.head()

该代码的输出如下所示:

  1. 首先,检查每列的数据类型。现在,您必须熟悉以下内容:
df.dtypes

输出如下:

symboling int64
normalized-losses int64
make object
aspiration object
num-of-doors object
body-style object
drive-wheels object
engine-location object
wheel-base float64
length float64
width float64
height float64
curb-weight int64
engine-type object
num-of-cylinders object
engine-size int64
fuel-system object
bore float64
stroke float64
compression-ratio float64
horsepower float64
peak-rpm float64
city-mpg int64
highway-mpg int64
price float64
city-L/100km float64
horsepower-binned object
diesel int64
gas int64
dtype: object
  1. 现在计算高度柱中心趋势的度量。回想一下,我们在第 5 章*描述性统计:*中讨论了几个描述性统计
#calculate mean, median and mode of dat set height
mean = df["height"].mean()
median =df["height"].median()
mode = df["height"].mode()
print(mean , median, mode)

这些描述性函数的输出如下:

53.766666666666715 54.1 0 50.8
dtype: float64
  1. 现在,让我们在图表中可视化这个分析:
#distribution plot
sns.FacetGrid(df,size=5).map(sns.distplot,"height").add_legend()

该代码将在高度列中生成值的分布图:

从图表中,我们可以观察到最大汽车的最大高度在 53 到 57 之间。现在,让我们对价格列进行同样的操作:

#distribution plot
sns.FacetGrid(df,size=5).map(sns.distplot,"price").add_legend()

该代码的输出如下所示:

看这张图,可以说价格在 5000 到 45000 之间,但是最高车价在 5000 到 10000 之间。

箱线图也是直观表示单变量分析中的中位数和四分位数等统计指标的有效方法:

#boxplot for price of cars
sns.boxplot(x="price",data=df)
plt.show()

根据前面的代码生成的方框图如下所示:

方框的右边框是 Q3,也就是第三个四分位数,方框的左边框是 Q1,也就是第一个四分位数。线条从方框边界的两侧向最小值和最大值延伸。基于我们的绘图工具使用的惯例,虽然,他们可能只扩展到某个统计数据;超出这些统计数据的任何值都被标记为异常值(使用点)。

该分析针对的是仅包含单一类型变量的数据集。现在,让我们看一下下一种对具有两种类型变量的数据集的分析形式,称为二元分析。

理解二元分析

顾名思义,这是对一个以上(也就是正好两个)类型变量的分析。双变量分析用于找出两个不同变量之间是否存在关系。当我们在笛卡尔平面上绘制一个变量与另一个变量的散点图时(想想 xy 轴),它给了我们一幅数据试图告诉我们的画面。如果数据点似乎符合直线或曲线,则两个变量之间存在关系或相关性。一般来说,如果我们知道自变量的值,二元分析有助于我们预测一个变量(即因变量)的值。

这是一张图表,显示了一段时间内广告费用和销售费率的散点图:

这个图是二元分析的散点图,其中销售额广告费是两个变量。绘制散点图时,我们可以看到销售价值取决于广告费用;也就是说,随着广告费的增加,销售价值也随之增加。对两个变量之间关系的这种理解将指导我们未来的预测:

  1. 现在是时候对我们的汽车数据集进行二元分析了。让我们看看马力是否是汽车定价的一个依赖因素:
# plot the relationship between “horsepower” and ”price”
plt.scatter(df["price"], df["horsepower"])
plt.title("Scatter Plot for horsepower vs price")
plt.xlabel("horsepower")
plt.ylabel("price")

该代码将生成散点图,其 y 轴上的price范围和 x 轴上的horsepower值如下:

正如你在上图中看到的,汽车的马力是价格的一个依赖因素。随着汽车马力的增加,汽车的价格也随之增加。

方框图也是查看一些统计度量以及两个值之间关系的好方法。

  1. 现在,让我们在汽车的发动机位置和价格之间画一个方框图:
#boxplot
sns.boxplot(x="engine-location",y="price",data=df)
plt.show()

该代码将生成一个方框图,价格范围在 y 轴,发动机位置类型在 x 轴:

该图显示,前置发动机位置为的汽车价格比后置发动机位置为的汽车价格低得多。此外,还有一些异常值具有前置引擎位置,但价格要高得多。****

  1. 接下来,用价格范围和驱动轮类型绘制另一个方框图:
#boxplot to visualize the distribution of "price" with types of "drive-wheels"
sns.boxplot(x="drive-wheels", y="price",data=df)

该代码的输出如下所示:

这张图表显示了不同车轮类型汽车的价格范围。这里,方框图显示了各个车轮类型的平均和中间价格以及一些异常值。

这是一个简短的介绍,以及一些二元分析的实践例子。现在,让我们了解一种更有效的数据分析实践,多元分析。

理解多元分析

多元分析是对三个或更多变量的分析。这使我们能够观察相关性(即一个变量相对于另一个变量如何变化),并试图比二元分析更准确地预测未来行为。

最初,我们探索了单变量分析和双变量分析的可视化;同样,让我们把多元分析的概念形象化。

绘制多变量数据的一种常见方法是绘制矩阵散点图,称为对图。矩阵图或成对图显示了每对变量之间的互相关。配对图让我们可以看到单个变量的分布和两个变量之间的关系:

  1. 我们可以使用pandas.tools.plotting包中的scatter_matrix()功能或seaborn包中的seaborn.pairplot()功能来完成此操作:
# pair plot with plot type regression
sns.pairplot(df,vars = ['normalized-losses', 'price','horsepower'], kind="reg")
plt.show()

该代码将绘制一个 3×3 矩阵,其中包含标准化损失、价格和马力列中的不同数据:

如前图所示,对角线上的直方图允许我们说明单个变量的分布。上三角形和下三角形上的回归图展示了两个变量之间的关系。第一行中间的图为回归图;这表明正常损失和汽车价格之间没有相关性。相比之下,最下面一排中间的回归图说明了价格和马力之间有着巨大的相关性。

  1. 同样,我们可以通过指定颜色、标签、绘图类型、对角线绘图类型和变量,使用成对绘图进行多元分析。那么,让我们制作另一个配对图:
#pair plot (matrix scatterplot) of few columns 
sns.set(style="ticks", color_codes=True)
sns.pairplot(df,vars = ['symboling', 'normalized-losses','wheel-base'], hue="drive-wheels")
plt.show()

该代码的输出如下所示:

这是一对符号化、标准化损失、轮基和驱动轮柱的记录图。

对角线上的密度图允许我们看到单个变量的分布,而上下三角形上的散点图显示了两个变量之间的关系(或相关性)。色调参数是用于数据点标签的列名;在此图中,驱动轮类型用颜色标注。第二行最左边的图显示了归一化损失与轴距的散点图。

如前所述,相关性分析是发现多元数据集中的任何变量是否相关的有效技术。要计算一对变量的线性(皮尔逊)相关系数,我们可以使用pandas包中的dataframe.corr(method ='pearson')函数和scipy.stats包中的pearsonr()函数:

  1. 例如,要计算价格和马力的相关系数,请使用以下公式:
from scipy import stats
corr = stats.pearsonr(df["price"], df["horsepower"])
print("p-value:\t", corr[1])
print("cor:\t\t", corr[0])

输出如下:

p-value: 6.369057428260101e-48 
cor: 0.8095745670036559

这里这两个变量的相关性为0.80957,接近+1。因此,我们可以确保价格和马力高度正相关。

  1. 使用 pandas corr(函数,整个数值记录之间的相关性可以计算如下:
correlation = df.corr(method='pearson')
correlation

该代码的输出如下所示:

  1. 现在,让我们使用热图来可视化这种相关性分析。热图是让它看起来更漂亮、更容易理解的最佳技术:
sns.heatmap(correlation,xticklabels=correlation.columns,
            yticklabels=correlation.columns)

该代码的输出如下所示:

系数接近 1 意味着这两个变量之间有很强的正相关。对角线是变量与其自身的相关性,所以它们当然是 1。

这是一个简单的介绍,以及一些多元分析的实践例子。现在,让我们用流行的数据集泰坦尼克号来练习它们,泰坦尼克号在世界各地经常被用来练习数据分析和机器学习算法。数据来源在本章技术要求部分有提及。

使用泰坦尼克号数据集讨论多元分析

1912 年 4 月 15 日,当时制造的最大客轮在处女航时与冰山相撞。泰坦尼克号沉没时,2224 名乘客和船员中有 1502 人遇难。titanic.csv(https://web . Stanford . edu/class/archive/cs/cs109/cs109.1166/stuff/Titanic . CSV)文件中包含了 887 名真实泰坦尼克号乘客的数据。每行代表一个人。这些列描述了船上人员的不同属性,其中PassengerId列是乘客的唯一 ID,Survived是幸存(1)或死亡(0)的数字,Pclass是乘客的级别(即第一、第二或第三),Name是乘客的姓名,Sex是乘客的性别,Age是乘客的年龄,Siblings/Spouses Aboard是泰坦尼克号上的兄弟姐妹/配偶的数量,Parents/Children Aboard是泰坦尼克号上的父母/子女的数量,TicketFare是每张票的票价,Cabin是舱号,Embarked是乘客上船的地点(例如:C 指瑟堡,S 指南安普顿,Q 指皇后镇)。

让我们分析一下泰坦尼克号的数据集,找出那些对乘客生存有最大依赖性的属性:

  1. 首先加载数据集和所需的库:
# load python libraries
import numpy as np 
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
#load dataset
titanic=pd.read_csv("/content/titanic.csv")
titanic.head()

该代码的输出如下所示:

让我们看看代码中数据框的形状:

titanic.shape

输出如下:

(891, 12)
  1. 让我们看一下数据集中丢失的记录数:
total = titanic.isnull().sum().sort_values(ascending=False)
total

输出如下:

Cabin 687
Age 177
Embarked 2
Fare 0
Ticket 0
Parch 0
SibSp 0
Sex 0
Name 0
Pclass 0
Survived 0
PassengerId 0
dtype: int64

除了EmbarkedAgeCabin之外,所有记录似乎都很好。Cabin功能需要进一步研究才能填充这么多,但我们不要在分析中使用它,因为它缺少 77%。此外,处理Age功能会相当棘手,它有 177 个缺失值。我们不能忽视年龄因素,因为它可能与存活率相关。Embarked功能只有两个缺失值,可以轻松填充。

由于PassengerIdTicketName列具有唯一的值,因此它们与高存活率不相关。

  1. 首先,让我们找出从灾难中幸存下来的女性和男性的百分比:
#percentage of women survived
women = titanic.loc[titanic.Sex == 'female']["Survived"]
rate_women = sum(women)/len(women)

#percentage of men survived
men = titanic.loc[titanic.Sex == 'male']["Survived"]
rate_men = sum(men)/len(men)

print(str(rate_women) +" % of women who survived." )
print(str(rate_men) + " % of men who survived." )

输出如下:

0.7420382165605095 % of women who survived.
0.18890814558058924 % of men who survived.
  1. 在这里,你可以看到幸存的女性数量很高,所以性别可能是一个有助于分析任何变量(人)存活率的属性。让我们用男性和女性的存活数来形象化这个信息:
titanic['Survived'] = titanic['Survived'].map({0:"not_survived", 1:"survived"})

fig, ax = plt.subplots(1, 2, figsize = (10, 8))
titanic["Sex"].value_counts().plot.bar(color = "skyblue", ax = ax[0])
ax[0].set_title("Number Of Passengers By Sex")
ax[0].set_ylabel("Population")
sns.countplot("Sex", hue = "Survived", data = titanic, ax = ax[1])
ax[1].set_title("Sex: Survived vs Dead")
plt.show()

该代码的输出如下所示:

  1. 让我们想象一下不同类别的幸存者和死亡人数:
fig, ax = plt.subplots(1, 2, figsize = (10, 8))
titanic["Pclass"].value_counts().plot.bar(color = "skyblue", ax = ax[0])
ax[0].set_title("Number Of Passengers By Pclass")
ax[0].set_ylabel("Population")
sns.countplot("Pclass", hue = "Survived", data = titanic, ax = ax[1])
ax[1].set_title("Pclass: Survived vs Dead")
plt.show()

该代码的输出如下所示:

  1. 嗯,看起来Pclass的乘客数量很高,大部分都活不下去。在Pclass 2 中,死亡人数高。并且,PclassT7】1 显示了幸存乘客的最大数量:
fig, ax = plt.subplots(1, 2, figsize = (10, 8))
titanic["Embarked"].value_counts().plot.bar(color = "skyblue", ax = ax[0])
ax[0].set_title("Number Of Passengers By Embarked")
ax[0].set_ylabel("Number")
sns.countplot("Embarked", hue = "Survived", data = titanic, ax = ax[1])
ax[1].set_title("Embarked: Survived vs Unsurvived")
plt.show()

代码的输出如下所示:

大多数乘客似乎是从南安普敦上船的,其中近 450 人没有生还。

  1. 为了可视化Age记录,我们将使用distplot()方法绘制数据分布。正如我们之前分析的那样,Age记录中有 177 个空值,因此我们将在绘制数据之前删除它们:
sns.distplot(titanic['Age'].dropna())

该代码的输出如下所示:

  1. 现在,让我们首先使用SurvivedPclassFearAge变量对泰坦尼克号数据集进行多元分析:
sns.set(style="ticks", color_codes=True)
sns.pairplot(titanic,vars = [ 'Fare','Age','Pclass'], hue="Survived")
plt.show()

该代码的输出如下所示:

  1. 现在,让我们用热图查看相关表。请注意,第一个Embarked映射记录带有整数值,因此我们也可以将Embarked包括在我们的相关性分析中:
titanic['Embarked'] = titanic['Embarked'].map({"S":1, "C":2,"Q":2,"NaN":0})
Tcorrelation = titanic.corr(method='pearson')
Tcorrelation

该代码的输出如下所示:

  1. 结果很简单。它显示了各个列之间的相关性。如您所见,在该表中,PassengerIdFareAge列呈弱正相关关系:
sns.heatmap(Tcorrelation,xticklabels=Tcorrelation.columns,
            yticklabels=Tcorrelation.columns)

该代码的输出如下所示:

如果你想练习更多的分析和预测算法,你可以在 Kaggle 中获得相同的数据集。

到目前为止,您已经了解了数据分析的相关性和类型。您也对数据集进行了不同的分析。现在,我们需要仔细考虑事实,然后根据我们获得的输出对分析做出任何结论。

概述辛普森悖论

通常,我们根据数据集做出的决策会受到我们应用于它们的统计度量的输出的影响。这些输出告诉我们相关的类型和数据集的基本可视化。然而,有时,当我们将数据分组并应用统计度量时,或者当我们将数据聚合在一起并应用统计度量时,决策会有所不同。同一数据集结果中的这种异常行为一般称为辛普森悖论。简而言之,辛普森悖论是在两种不同情况下分析数据集时出现在分析趋势中的差异:第一,当数据被分成组时,第二,当数据被聚合时。

这里有一个表格,分别代表了男性和女性对两种不同游戏主机的推荐率,也包括:

| | 推荐****PS4 | 推荐****Xbox One | | 男性的 | 50/150=30% | 180/360=50% | | 女性的 | 200/250=80% | 36/40=90% | | 联合的;共同的 | 250/400=62.5% | 216/400=54% |

上表按男性和女性分别列出了 PS4 和 Xbox One 两款不同游戏机的推荐率,包括单独推荐和组合推荐。

假设你打算买一个有最大推荐的游戏机。如上表所示,Xbox One 的男女推荐比例均高于 PS4。但是,使用相同的数据组合时,根据所有用户,PS4 具有更高的推荐百分比(62.5%)。那么,你怎么决定选哪一个呢?这些计算看起来不错,但从逻辑上讲,决策似乎并不顺利。这就是辛普森悖论。这里的同一个数据集证明了两个相反的论点。

在这种情况下,主要问题是,当我们只看到单独数据的百分比时,它没有考虑样本量。由于每个分数都显示了通过询问的数量推荐游戏控制台的用户数量,因此考虑整个样本大小是有意义的。男性和女性单独数据中的样本量相差很大。比如男性对 PS4 的推荐是 50,而对 Xbox One 的推荐是 180。这些数字有着巨大的差异。Xbox One 的男性响应远远多于女性,而 PS4 的情况正好相反。因为推荐 PlayStation 的男性更少,这导致数据合并后 PS4 的平均评分更低,这导致了矛盾。

为了就我们应该使用哪个控制台做出单一决定,我们需要决定数据是可以合并还是应该单独查看。在这种情况下,我们必须找出哪个控制台最有可能同时满足男性和女性。可能还有其他因素影响这些评论,但我们没有这些数据,所以我们寻找最大数量的好评论,而不考虑性别偏见。在这里,聚合数据最有意义。我们将结合评审结果,与总体平均水平进行比较。因为我们的目标是合并评论并查看总平均值,所以数据的汇总更有意义。

看起来辛普森悖论是一个牵强的问题,理论上是可能的,但实际上从未发生过,因为我们对整体可用数据的统计分析是准确的。然而,现实世界中有许多关于辛普森悖论的著名研究。

现实世界的一个例子是精神健康治疗,如抑郁症。下表列出了两种治疗方法对患者的疗效:

| | 疗法 A | 疗法 B | | 轻度萧条 | 81/87=93% | 234/270=87% | | 严重抑郁 | 192/263=73% | 55/80=69% | | 两者 | 273/350=78% | 289/350=83% |

如你所见,在上表中,有两种疗法: T 疗法 A疗法 B 。治疗 A 似乎对轻度和重度抑郁症都更有效,但汇总数据显示,治疗 B 对两种情况都更有效。这怎么可能?嗯,我们不能断定数据汇总后的结果是正确的,因为每种治疗方法的样本量是不同的。为了就我们应该采用哪种疗法做出一个单一的决定,我们需要实事求是地思考:数据是如何产生的?还有哪些因素影响了根本看不到的结果?

现实中,轻度抑郁被医生认为是不太严重的情况,A 疗法比 b 疗法更便宜,因此,医生建议轻度抑郁采用更简单的 A 疗法。

我们的数据集中没有提到这两种治疗类型的细节和事实。抑郁症的种类和病情的严重程度会导致混杂变量(混杂变量是我们在数据表中看不到的,但它们可以通过对数据的背景分析来确定),因为它会影响选择何种治疗和康复方法的决定。因此,决定哪种治疗对患者更有效的因素取决于混杂变量,这就是这里情况的严重性。为了确定哪种疗法效果更好,我们需要病例严重性的报告,然后需要比较两种疗法的恢复率,而不是跨组的汇总数据。

从一组数据中回答我们想要的问题有时需要更多的分析,而不仅仅是查看可用的数据。从辛普森悖论中得到的教训是,光有数据是不够的。数据从来都不是纯粹客观的,最终的剧情也不是。因此,在处理一组数据时,我们必须考虑我们是否得到了整个故事。

在根据我们得到的输出结论分析之前,必须考虑的另一个事实是相关性并不意味着因果关系。这在统计领域非常重要,所以维基百科有一篇单独的文章介绍了这一说法。

相关性并不意味着因果关系

C 或关联 并不意味着因果关系是一个有趣的短语,你会在统计学和详细学习数据科学时听到它。但这意味着什么呢?嗯,这只是表明,仅仅因为两件事相关,并不总是意味着一个导致另一个。例如,挪威的冬天很冷,人们往往比夏天花更多的钱购买汤等热食。然而,这并不意味着寒冷的天气会导致人们在汤上花更多的钱。因此,虽然挪威人的支出与天气寒冷有关,但支出并不是天气寒冷的原因。因此,相关性不是因果关系。

请注意,在这个短语中有两个基本术语:相关性和因果关系。相关性揭示了一对变量之间的相互关联和共同变化的程度。因果关系解释说,一个变量的值的任何变化都会导致另一个变量的量的差异。在这种情况下,一个变量使另一个变量发生。这种现象被称为因果。比如你运动的时候( X ),你燃烧的热量( Y )每分钟都比较高。因此, X 导致 Y 。根据逻辑理论,我们可以这样说:

任何数据分析书中最常见的例子都是关于冰淇淋的销售和凶杀案的兴衰。根据这个例子,随着冰淇淋销量的增加,凶杀案的数量也在增加。基于相关性,这两个事件是高度相关的。然而,冰淇淋的消费并没有导致人的死亡。这两件事不是基于因果理论。因此,相关性并不意味着因果关系。

那么,这个关键短语的要点是什么?嗯,首先,我们不应该根据相关性太快地形成我们的结论。为了理解任何关键的、隐藏的因素,有必要投入一些时间来寻找数据的潜在因素。

摘要

在本章中,我们讨论了相关性。相关性是一种统计度量,可用于检查一对变量之间的关系。理解这些关系可以帮助你从一组变量中决定最重要的特征。一旦我们理解了相关性,我们就可以用它来做出更好的预测。变量之间的关系越高,预测的准确性越高。由于相关性更重要,在本章中,我们已经介绍了几种相关方法和不同类型的分析,包括单变量分析、双变量分析和多变量分析。

在下一章中,我们将仔细研究时间序列分析。我们将使用几个真实的数据库,包括时间序列分析,以便进行探索性的数据分析。

进一步阅读

  • 关联和相关性,作者:李·贝克帕克特出版,2019 年 6 月 28 日
  • Python 数据科学,作者:罗汉·乔普拉艾伦·英伦穆罕默德·努尔登·阿劳登帕克特出版,2019 年 7 月
  • 与 NumPy 和 Pandas 的实践数据分析,作者:柯蒂斯·米勒帕克特出版*,2018 年 6 月*

八、时间序列分析

时间序列数据包括时间戳,通常是在监控工业过程或跟踪任何业务指标时生成的。等间隔的时间戳值的有序序列被称为时间序列。对这种时间序列的分析被用于许多应用,例如销售预测、效用研究、预算分析、经济预测、库存研究等等。有太多的方法可以用来建模和预测时间序列。

在本章中,我们将使用 Python 库来探索时间序列分析 ( TSA )。时间序列数据是关于一个系统或过程的一系列定量观察的形式,是在连续的时间点上产生的。

在本章中,我们将涵盖以下主题:

  • 了解时间序列数据集
  • 开放电力系统数据的美国运输安全管理局

技术要求

本章使用的所有代码和数据集都可以在 GitHub 存储库中找到(https://GitHub . com/PacktPublishing/用 python 动手探索数据分析):

  • 代码:本章需要的代码可以在标有Chapter 8/的文件夹中找到。
  • 数据集:我们将为美国运输安全管理局使用开放电力系统数据。可以从open-power-system-data.org/下载。也可以在Chapter 9/datasets里面的 GitHub 资源库里面找到数据集。

了解时间序列数据集

最基本的问题是,*我们所说的时间序列数据是什么意思?*当然,我们已经听过好几次了。或许我们可以给它下定义?当然可以。本质上,时间序列是按时间顺序进行的观察的集合。请注意,这里有两个重要的关键短语— 一组观察数据按时间顺序排列。因为它是一个系列,它必须是一个观察的集合,因为它处理时间,它必须以顺序的方式处理它。

让我们以时间序列数据为例:

上图为 2016 年前六个月的太阳能产量(单位为千兆瓦 小时 ( 千兆瓦)。它还显示了每日和每周的用电量。

运输安全协议基础

为了理解时间序列数据集,让我们随机生成一个规范化的数据集:

  1. 我们可以使用numpy库生成数据集:
import os
import numpy as np
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns

zero_mean_series = np.random.normal(loc=0.0, scale=1., size=50)
zero_mean_series

我们使用了 NumPy 库来生成随机数据集。因此,这里给出的输出对您来说会有所不同。这里给出了前面代码的输出:

array([-0.73140395, -2.4944216 , -1.44929237, -0.40077112,  0.23713083, 0.89632516, -0.90228469, -0.96464949, 1.48135275,  0.64530002, -1.70897785,  0.54863901, -1.14941457, -1.49177657, -2.04298133, 1.40936481,  0.65621356, -0.37571958, -0.04877503, -0.84619236, -1.46231312,  2.42031845, -0.91949491,  0.80903063,  0.67885337, -0.1082256 , -0.16953567,  0.93628661,  2.57639376, -0.01489153, 0.9011697 , -0.29900988,  0.04519547,  0.71230853, -0.00626227, 1.27565662, -0.42432848,  1.44748288,  0.29585819,  0.70547011, -0.6838063 ,  1.61502839, -0.04388889,  1.06261716,  0.17708138, 0.3723592 , -0.77185183, -3.3487284 ,  0.59464475, -0.89005505])
  1. 接下来,我们将使用seaborn库绘制时间序列数据。检查这里给出的代码片段:
plt.figure(figsize=(16, 8))
g = sns.lineplot(data=zero_mean_series)
g.set_title('Zero mean model')
g.set_xlabel('Time index')
plt.show()

我们使用seaborn库提供的内置方法seaborn.lineplot()函数绘制了时间序列图。这里给出了前面代码的输出:

  1. 我们可以对列表执行累计求和,然后使用时间序列图绘制数据。剧情给出了更有趣的结果。检查以下代码片段:
random_walk = np.cumsum(zero_mean_series)
random_walk

它生成一个累积和的数组,如下所示:

array([ -0.73140395,  -3.22582556,  -4.67511792,  -5.07588904,-4.83875821,  -3.94243305,  -4.84471774,  -5.80936723,-4.32801448,  -3.68271446,  -5.39169231,  -4.8430533 ,-5.99246787,  -7.48424444,  -9.52722576,  -8.11786095,-7.46164739,  -7.83736697,  -7.886142  ,  -8.73233436, -10.19464748,  -7.77432903,  -8.69382394,  -7.88479331,-7.20593994,  -7.31416554,  -7.4837012 ,  -6.5474146 ,-3.97102084,  -3.98591237,  -3.08474267,  -3.38375255,-3.33855708,  -2.62624855,  -2.63251082,  -1.35685419,-1.78118268,  -0.3336998 ,  -0.03784161,   0.66762849,-0.01617781,   1.59885058,   1.55496169,   2.61757885, 2.79466023,   3.16701943,   2.3951676 ,  -0.9535608 ,-0.35891606,  -1.2489711 ])

请注意,对于任何特定值,下一个值是以前值的总和。

  1. 现在,如果我们使用这里所示的时间序列图绘制列表,我们会得到一个有趣的图表,显示值随时间的变化:
plt.figure(figsize=(16, 8))
g = sns.lineplot(data=random_walk)
g.set_title('Random Walk')
g.set_xlabel('Time index')
plt.show()

这里给出了前面代码的输出:

请注意上图中显示的图表。它显示了值随时间的变化。太好了——到目前为止,我们已经生成了不同的时间序列数据,并使用内置的seaborn.tsplot()方法绘制出来。

单变量时间序列

当我们捕捉到同一变量在特定持续时间内的一系列观测值时,该序列被称为**单变量时间序列。**一般来说,在单变量时间序列中,观测是在规则的时间段内进行的,例如一天中温度随时间的变化。

时间序列数据的特征

使用时间序列数据时,可以观察到几个独特的特征。一般来说,时间序列往往表现出以下特征:

  • 看时间序列数据的时候,看有没有趋势是必不可少的。观察趋势意味着平均测量值似乎随着时间的推移而减少或增加。
  • 时间序列数据可能包含大量异常值。当绘制在图表上时,可以注意到这些异常值。
  • 时间序列中的一些数据往往会在一定的时间间隔内以某种模式重复出现。我们称这样的重复模式为季节性
  • 有时,时间序列数据会出现不均匀的变化。我们把这种不均匀的变化称为突变。观察时间序列中的突变是至关重要的,因为它揭示了基本的潜在现象。
  • 随着时间的推移,一些序列趋向于遵循恒定方差。因此,有必要查看时间序列数据,看看数据是否随时间呈现恒定的变化。

前面列出的特征有助于我们对运输安全管理局进行更好的分析。既然我们知道了在时间序列数据中应该看到什么和期望什么,那么看到一些实际的例子将会很有用。接下来,让我们导入一个真实的数据库,并对其执行各种 TSA 方法。

开放电力系统数据的美国运输安全管理局

在本节中,我们将使用开放电力系统数据来了解 TSA。我们将研究时间序列数据结构、基于时间的索引以及可视化时间序列数据的几种方法。

我们将从导入数据集开始。看看这里给出的代码片段:

# load time series dataset
df_power = pd.read_csv("https://raw.githubusercontent.com/jenfly/opsd/master/opsd_germany_daily.csv")
df_power.columns

这里给出了前面代码的输出:

Index(['Consumption', 'Wind', 'Solar', 'Wind+Solar'], dtype='object')

这里描述了数据框的列:

  • 日期:日期格式为yyyy-mm-dd
  • 消耗:表示GWh用电量。
  • 太阳能:表示GWh太阳能发电。
  • 风能+太阳能:这代表GWh的太阳能和风能发电量之和。

请注意日期列,它包含时间序列数据集。我们可以使用这个数据集来发现德国的电力消费和生产是如何随着时间的推移而变化的。

数据清理

现在让我们清除数据集的异常值:

  1. 我们可以从检查数据集的形状开始:
df_power.shape

这里给出了前面代码的输出:

(4383, 5)

dataframe 包含 4,283 行和 5 列。

  1. 我们还可以检查数据框中的一些条目。让我们检查最后 10 个条目:
df_power.tail(10)

这里给出了前面代码的输出:

  1. 接下来,让我们回顾一下df_power数据框中每一列的数据类型:
df_power.dtypes

这里给出了前面代码的输出:

Date object
Consumption float64
Wind float64
Solar float64
Wind+Solar float64
dtype: object
  1. 注意Date列的数据类型为object。这是不正确的。所以,下一步是修正Date一栏,如下图所示:
#convert object to datetime format
df_power['Date'] = pd.to_datetime(df_power['Date'])
  1. 它应该将Date列转换为Datetime格式。我们可以再次验证这一点:
df_power.dtypes

这里给出了前面代码的输出:

Date datetime64[ns]
Consumption float64
Wind float64
Solar float64
Wind+Solar float64
dtype: object

请注意,Date列已更改为正确的数据类型。

  1. 接下来,让我们将数据框的索引更改为Date列:
df_power = df_power.set_index('Date')
df_power.tail(3)

这里给出了前面代码的输出:

从前面的截图中注意到Date列已经被设置为DatetimeIndex

  1. 我们可以使用下面给出的代码片段来验证这一点:
df_power.index

这里给出了前面代码的输出:

DatetimeIndex(['2006-01-01', '2006-01-02', '2006-01-03', '2006-01-04', '2006-01-05', '2006-01-06', '2006-01-07', '2006-01-08', '2006-01-09', '2006-01-10', ... '2017-12-22', '2017-12-23', '2017-12-24', '2017-12-25', '2017-12-26', '2017-12-27', '2017-12-28', '2017-12-29', '2017-12-30', '2017-12-31'],dtype='datetime64[ns]', name='Date', length=4383, freq=None)
  1. 由于我们的索引是DatetimeIndex对象,现在我们可以使用它来分析数据帧。让我们在数据框架中添加更多的列,让我们的生活更轻松。让我们添加YearMonthWeekday Name:
# Add columns with year, month, and weekday name
df_power['Year'] = df_power.index.year
df_power['Month'] = df_power.index.month
df_power['Weekday Name'] = df_power.index.weekday_name
  1. 让我们显示数据框中的五个随机行:
# Display a random sampling of 5 rows
df_power.sample(5, random_state=0)

此代码的输出如下所示:

请注意,我们又添加了三列— YearMonthWeekday Name。添加这些列有助于简化数据分析。

基于时间的索引

对于时间序列数据,基于时间的索引是pandas库非常强大的方法。具有基于时间的索引允许使用格式化的字符串来选择数据。例如,请参见下面的代码:

df_power.loc['2015-10-02']

这里给出了前面代码的输出:

Consumption 1391.05
Wind 81.229
Solar 160.641
Wind+Solar 241.87
Year 2015
Month 10
Weekday Name Friday
Name: 2015-10-02 00:00:00, dtype: object

请注意,我们使用了 Pandas 数据框loc访问器。在前面的示例中,我们使用日期作为字符串来选择一行。我们可以使用各种技术来访问行,就像我们可以使用普通的 dataframe 索引一样。

可视化时间序列

让我们可视化时间序列数据集。我们将继续使用相同的df_power数据帧:

  1. 第一步是导入seabornmatplotlib库:
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(rc={'figure.figsize':(11, 4)})
plt.rcParams['figure.figsize'] = (8,5)
plt.rcParams['figure.dpi'] = 150
  1. 接下来,让我们生成德国每日用电量的完整时间序列的线图:
df_power['Consumption'].plot(linewidth=0.5)

这里给出了前面代码的输出:

如前一张截图所示,y 轴显示的是用电量,x 轴显示的是年份。然而,数据集太多,无法涵盖所有年份。

  1. 让我们用点来绘制所有其他列的数据:
cols_to_plot = ['Consumption', 'Solar', 'Wind']
axes = df_power[cols_to_plot].plot(marker='.', alpha=0.5, linestyle='None',figsize=(14, 6), subplots=True)
for ax in axes:
    ax.set_ylabel('Daily Totals (GWh)')

这里给出了前面代码的输出:

输出显示,电力消耗可以分为两种不同的模式:

  • 一个大约 1400 千兆瓦时及以上的集群
  • 另一个大约低于 1400 千兆瓦时的星团

此外,太阳能产量夏季较高,冬季较低。这些年来,风力发电的产量似乎有一个强劲的增长趋势。

  1. 我们可以进一步调查一年,以便更仔细地观察。检查此处给出的代码:
ax = df_power.loc['2016', 'Consumption'].plot()
ax.set_ylabel('Daily Consumption (GWh)');

这里给出了前面代码的输出:

从前面的截图中,我们可以清楚地看到 2016 年的用电量。该图显示了年底(12 月)和 8 月期间的用电量大幅下降。我们可以在任何特定的月份寻找进一步的细节。让我们用下面的代码块来检查 2016 年 12 月:

ax = df_power.loc['2016-12', 'Consumption'].plot(marker='o', linestyle='-')
ax.set_ylabel('Daily Consumption (GWh)');

这里给出了前面代码的输出:

如上图所示,工作日用电量较高,周末最低。我们可以看到一个月中每天的消耗量。我们可以进一步放大观察 12 月最后一周的消费情况。

为了指示 12 月的特定一周,我们可以提供如下所示的特定日期范围:

ax = df_power.loc['2016-12-23':'2016-12-30', 'Consumption'].plot(marker='o', linestyle='-')
ax.set_ylabel('Daily Consumption (GWh)');

如前面的代码所示,我们希望看到2016-12-232016-12-30之间的用电量。这里给出了前面代码的输出:

如前一张截图所示,圣诞节当天用电量最低,可能是因为人们忙于聚会。圣诞节后,消费增加。

将时间序列数据分组

我们可以按不同的时间段对数据进行分组,并以方框图的形式呈现出来:

  1. 我们可以先将数据按月分组,然后使用箱线图来可视化数据:
fig, axes = plt.subplots(3, 1, figsize=(8, 7), sharex=True)
for name, ax in zip(['Consumption', 'Solar', 'Wind'], axes):
  sns.boxplot(data=df_power, x='Month', y=name, ax=ax)
  ax.set_ylabel('GWh')
  ax.set_title(name)
  if ax != axes[-1]:
    ax.set_xlabel('') 

这里给出了前面代码的输出:

上图说明了用电量一般冬季较高,夏季较低。夏季风力较高。此外,还有许多与电力消耗、风力发电和太阳能发电相关的异常值。

  1. 接下来,我们可以按一周中的某一天对耗电量进行分组,并以方框图的形式呈现出来:
sns.boxplot(data=df_power, x='Weekday Name', y='Consumption');

这里给出了前面代码的输出:

上图截图显示工作日用电量高于周末。有趣的是,工作日的异常值更多。

重新采样时间序列数据

通常需要以较低或较高的频率对数据集进行重新采样。这种重采样是基于聚合或分组操作完成的。例如,我们可以基于周平均时间序列对数据进行重新采样,如下所示:

  1. 我们可以使用这里给出的代码对数据进行重新采样:
columns = ['Consumption', 'Wind', 'Solar', 'Wind+Solar']

power_weekly_mean = df_power[columns].resample('W').mean()
power_weekly_mean

这里给出了前面代码的输出:

如前一张截图所示,第一行标记为2006-01-01,包含所有数据的平均值。我们可以绘制每日和每周的时间序列,以比较六个月期间的数据集。

  1. 让我们看看 2016 年的最后六个月。让我们从初始化变量开始:
start, end = '2016-01', '2016-06'
  1. 接下来,让我们使用这里给出的代码绘制图表:
fig, ax = plt.subplots()

ax.plot(df_power.loc[start:end, 'Solar'],
marker='.', linestyle='-', linewidth=0.5, label='Daily')
ax.plot(power_weekly_mean.loc[start:end, 'Solar'],
marker='o', markersize=8, linestyle='-', label='Weekly Mean Resample')
ax.set_ylabel('Solar Production in (GWh)')
ax.legend();

这里给出了前面代码的输出:

前面的截图显示,每周平均时间序列随着时间的推移而增加,并且比每日时间序列平滑得多。

摘要

在本章中,我们讨论了如何使用 pandas 库导入、清理、分析和可视化时间序列数据集。此外,我们使用matplotlibseaborn库可视化了时间序列数据集。最后,我们使用 Python 来加载和检查开放电力系统数据数据集,并执行了几项与 TSA 相关的技术。

在下一章中,我们将学习使用经典机器学习技术和三种不同类型的机器学习进行模型开发的不同方法,即监督学习、无监督机器学习和强化学习。

进一步阅读

  • P 实用时间序列分析,作者:阿维石·帕尔博士PKS·普拉卡什博士**帕克特出版
  • Python 机器学习-第三版,作者:塞巴斯蒂安·拉什卡瓦希德·米尔贾利利**帕克特出版
  • Python 数据分析大卫·泰伯帕克特出版
  • Python 回归分析,作者:卢卡·马萨龙阿尔贝托·博切蒂**帕克特出版
  • 机器学习统计,作者:普拉塔·丹盖提T5】帕克特出版
  • 数据科学统计,作者:詹姆斯·米勒帕克特出版
  • 一周内的数据科学算法-第二版,作者:达维德·纳廷加帕克特出版
  • sci kit 机器学习-学习快速入门指南,作者:凯文·乔利帕克特出版

九、假设检验和回归

在本章中,我们将深入探讨两个重要的概念,假设检验和回归。首先,我们将讨论假设检验的几个方面,假设检验的基本原则和假设检验的类型,并通过一些工作实例来运行。接下来,我们将讨论回归的类型,并使用 scikit-learn 库开发模型。

在本章中,我们将涵盖以下主题:

  • 假设检验
  • 黑客攻击
  • 理解回归
  • 回归类型
  • 模型开发和评估

技术要求

本章的代码可以在第九章假设检验和回归的文件夹内的 GitHub 存储库中找到。

假设检验

假设检验经常被用来促进使用实验数据集的统计决策。测试用于验证关于总体参数的假设。例如,考虑以下语句:

  • 在尼泊尔大学学习机器学习课程的学生平均成绩是 78 分。
  • 在学习机器学习课程的学生中,男生的平均身高高于女生。

在所有这些例子中,我们假设一些统计事实来证明这些说法。像这样的情况是假设检验有帮助的。假设检验评估关于任何特定人群的两个互斥陈述,并确定哪一个陈述最好由样本数据建立。这里,我们使用了两个基本关键词:人口和样本。总体包括一组数据中的所有元素,而样本由取自任何特定总体的一个或多个观察值组成。

在下一节中,我们将讨论假设测试,并讨论如何使用 Python 库来执行假设测试。

假设检验原理

假设检验基于统计学的两个基本原则,即标准化和标准标准化:

  • 规范化:规范化的概念因上下文而异。为了容易理解标准化的概念,它是在执行描述性统计之前,将在不同尺度上测量的值调整到公共尺度的过程,并由以下等式表示:

  • 标准归一化:标准归一化除了均值为 0,标准差为 1 之外,与归一化类似。标准归一化由以下等式表示:

除了这些概念,我们还需要了解假设检验的一些重要参数:

  • 零假设是基于领域知识做出的最基本的假设。比如一个人的平均打字速度是每分钟 38-40 个字。
  • 一个替代假设是反对无效假设的不同假设。这里的主要任务是我们是否接受或拒绝基于实验结果的替代假设。比如一个人的平均打字速度总是低于每分钟 38-40 个字。基于某些事实,我们可以接受或拒绝这个假设。例如,我们可以找到一个可以以每分钟 38 个单词的速度打字的人,这将推翻这个假设。因此,我们可以拒绝这种说法。
  • 第一类错误第二类错误:当我们接受或拒绝一个假设时,我们可能会犯两种错误。它们被称为第一类和第二类错误:
    • 假阳性:第一类错误是当 H0 为真时,我们拒绝零假设(H0)。
    • 假阴性:第二类错误是当 H0 为假时,我们不拒绝零假设(H0)。
  • P 值:这也称为概率值或渐近显著性。假设零假设为真,这是特定统计模型的概率。通常,如果 P 值低于预定阈值,我们拒绝零假设。
  • 显著性水平:这是你在使用假设之前应该熟悉的最重要的概念之一。重要程度是我们接受或拒绝无效假设的重要程度。我们必须注意,100%的准确性是不可能接受或拒绝的。我们通常根据我们的主题和领域来选择重要程度。一般是 0.05 或 5%。这意味着我们的输出应该有 95%的把握支持我们的零假设。

总而言之,在选择或拒绝零假设之前,请查看条件:

# Reject H0
p <= α
# Accept the null hypothesis
p > α

通常,在开始计算新值之前,我们会设置显著性级别。接下来,我们将看到如何使用统计库来执行假设检验。

statsmodels 库

让我们使用统计库进行假设检验。让我们考虑以下场景。

在一项关于青少年心理健康的研究中,48%的父母认为社交媒体是青少年压力的原因:

  • 人口:有青少年的父母(年龄> = 18 岁)
  • 感兴趣参数 : p
  • 无效假设 : p = 0.48
  • 替代假设 : p > 0.48

数据:调查了 4500 人,65%的被调查者认为自己青少年的压力来源于社交媒体。

让我们开始假设检验:

  1. 首先,导入所需的库:
import statsmodels.api as sm
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
  1. 接下来,让我们声明变量:
n = 4500
pnull= 0.48
phat = 0.65
  1. 现在,我们可以使用proportions_ztest方法来计算新的 P 值。查看以下代码片段:
sm.stats.proportions_ztest(phat * n, n, pnull, alternative='larger')

前面代码的输出如下:

(23.90916877786327, 1.2294951052777303e-126)

我们计算的1.2294951052777303e-126的 P 值相当小,我们可以拒绝零假设,即社交媒体是青少年压力的原因。

平均阅读时间

假设一个阅读比赛是和一些成年人一起进行的。数据如下所示:

[236, 239, 209, 246, 246, 245, 215, 212, 242, 241, 219, 242, 236, 211, 216, 214, 203, 223, 200, 238, 215, 227, 222, 204, 200, 208, 204, 230, 216, 204, 201, 202, 240, 209, 246, 224, 243, 247, 215,249, 239, 211, 227, 211, 247, 235, 200, 240, 213, 213, 209, 219,209, 222, 244, 226, 205, 230, 238, 218, 242, 238, 243, 248, 228,243, 211, 217, 200, 237, 234, 207, 217, 211, 224, 217, 205, 233, 222, 218, 202, 205, 216, 233, 220, 218, 249, 237, 223]

现在,我们的假设问题是这样的:随机学生(成人)的平均阅读速度是否超过每分钟 212 个单词?

我们可以将前面的概念分解为以下参数:

  • 人口:所有成年人
  • 感兴趣参数 : μ,教室人数
  • 无效假设 : μ = 212
  • 替代假设 : μ > 212
  • 置信水平 : α = 0.05

我们知道所有需要的参数。现在,我们可以使用带有alternate="larger"statsmodels包进行 Z 测试:

import numpy as np

sdata = np.random.randint(200, 250, 89)
sm.stats.ztest(sdata, value = 80, alternative = "larger")

前面代码的输出如下:

(91.63511530225408, 0.0)

由于计算的 P 值(0.0)低于标准置信水平(α = 0.05),我们可以拒绝零假设。也就是说成年人平均阅读速度为每分钟 212 个单词的说法被否决了。

假设检验的类型

假设检验有不同的类型。最常用的如下:

  • z 检验
  • t 检验
  • 方差分析检验
  • 卡方测验

每种类型的测试都超出了本书的范围。我们建议查看维基百科或进一步阅读部分的链接,以获得关于它们的详细信息。然而,我们将在本书中研究 Z 测试和 T 测试。在前面的例子中,我们只使用了 Z 检验。

t 检验

T 检验是推理统计学中最常用的一种检验。该测试最常用于我们需要了解两组平均值之间是否存在显著差异的场景。例如,假设我们有一个特定班级学生的数据集。数据集包含每个学生的身高。我们正在检查平均身高是否为 175 厘米:

  • 人口:该班所有学生
  • 感兴趣参数 : μ,教室人数
  • 零假设:平均身高μ = 175
  • 替代假设 : μ > 175
  • 置信水平 : α = 0.05

我们已经列出了所有的参数。现在,我们可以使用假设检验:

  1. 让我们首先设置数据集:
import numpy as np
height = np.array([172, 184, 174, 168, 174, 183, 173, 173, 184, 179, 171, 173, 181, 183, 172, 178, 170, 182, 181, 172, 175, 170, 168, 178, 170, 181, 180, 173, 183, 180, 177, 181, 171, 173, 171, 182, 180, 170, 172, 175, 178, 174, 184, 177, 181, 180, 178, 179, 175, 170, 182, 176, 183, 179, 177])
  1. 接下来,我们将使用 SciPy 库中的统计模块。注意,在前面的例子中,我们使用了 statsmodels API 库。我们还可以继续使用它,但是我们这里的目的是向您介绍 SciPy 库的新模块。让我们导入库:
from scipy.stats import ttest_1samp
import numpy as np
  1. 现在,让我们使用 NumPy 库来计算平均高度:
height_average = np.mean(height)
print("Average height is = {0:.3f}".format(height_average))

前面代码的输出如下:

Average height is = 175.618
  1. 现在,让我们用 T 检验来计算新的 P 值:
tset,pval = ttest_1samp(height, 175)

print("P-value = {}".format(pval))

if pval < 0.05:
 print("We are rejecting the null Hypothesis.")
else:
  print("We are accepting the null hypothesis.")

前面代码的输出如下:

Average height is = 175.618
P-value = 0.35408130524750125
We are accepting the null hypothesis

请注意,我们的显著性水平(α= 0.05)和计算的 P 值是 0.354。因为它大于α,所以我们接受零假设。这意味着学生的平均身高为 175 厘米,置信度为 95%。

黑客攻击

黑客攻击是一个严重的方法论问题。它也被称为数据钓鱼、数据屠杀或数据挖掘。滥用数据分析来检测数据中可能具有统计意义的模式。这是通过进行一项或多项测试并只发布那些具有更高意义的结果来完成的。

我们在前一节假设检验中已经看到,我们依靠 P 值得出结论。简而言之,这意味着我们计算 P 值,这是结果的概率。如果 P 值很小,则结果被宣布为具有统计学意义。这意味着,如果你创建一个假设并用一些标准来检验它,并报告一个小于 0.05 的 P 值,读者很可能会相信你已经找到了真正的相关性或效果。然而,这在现实生活中可能是完全错误的。根本不会有任何影响或关联。所以,无论报道什么都是一个fT4】也是一个阳性。这一点在出版物领域见得很多。许多期刊只会发表至少能报告一个统计学显著效应的研究。因此,研究人员试图争论数据集,或者实验得到一个明显更低的 P 值。这叫做黑客入侵。

已经介绍了数据挖掘的概念,现在是我们开始学习如何构建模型的时候了。我们将从最常见和最基本的模型之一——回归开始。

理解回归

我们用统计学术语中的相关性来表示两个定量变量之间的关联。请注意,我们使用了术语定量变量。这对你应该是有意义的。如果没有,我们建议您在此暂停,浏览第 1 章探索性数据分析基础

谈到数量变量和相关性,我们也假设关系是线性的,即一个变量增加或减少一个固定的量,而另一个变量增加或减少。要确定类似的关系,还有另一种在这些情况下经常使用的方法,回归,包括确定关系的最佳直线。一个简单的方程,称为回归方程,可以表示以下关系:

让我们来看看这个公式:

  • Y =因变量(你试图预测的变量)。它通常被称为结果变量
  • X =自变量(你用来预测 Y 的变量)。它通常被称为预测因子,或协变量特征
  • a =截距。
  • b =坡度。
  • u =回归残差。

如果y代表因变量,x代表自变量,这种关系被描述为yx的回归。xy之间的关系一般用一个方程来表示。等式显示了y相对于x的变化程度。

人们使用回归分析有几个原因。最明显的原因如下:

  • 我们可以使用回归分析来预测未来的经济状况、趋势或价值。
  • 我们可以使用回归分析来确定两个或多个变量之间的关系。
  • 我们可以使用回归分析来理解当一个变量改变时,另一个变量是如何改变的。

在后面的部分,我们将使用模型开发的回归函数来预测因变量,同时在函数中实现一个新的解释变量。基本上,我们会建立一个预测模型。所以,让我们深入研究回归。

回归类型

两种主要的回归类型是线性回归和多元线性回归。大多数简单的数据可以用线性回归来表示。一些复杂的数据遵循多元线性回归。在本章中,我们将研究 Python 的回归类型。最后,我们将以一个非线性例子的不同方面来结束讨论。

简单线性回归

线性回归又称简单线性回归,用一条直线定义两个变量之间的关系。在线性回归过程中,我们的目标是通过找到定义直线的斜率和截距来绘制最接近数据的直线。简单线性回归的方程一般如下:

X为单一特征,Y为目标,ab分别为截距和斜率。问题是,我们如何选择ab?答案是选择误差函数最小的线路,u。这个误差函数也被称为损失或成本函数,它是直线和数据点之间垂直距离差的平方(忽略正负抵消)之和。

这个计算叫做普通最小二乘 ( OLS )。请注意,解释回归的每个方面都超出了本书的范围,我们建议您探索进一步阅读部分,以拓宽您对该主题的知识。

多元线性回归

多元线性回归的情况下,两个或多个自变量或解释变量与目标或因变量呈线性关系。自然界中大多数可线性描述的现象都是通过多元线性回归得到的。例如,任何项目的价格都取决于采购的数量、一年中的时间以及库存中可用的项目数量。例如,一瓶葡萄酒的价格主要取决于你买了多少瓶。此外,在圣诞节等节日期间,价格会高一点。此外,如果库存中的瓶子数量有限,价格可能会更高。在这种情况下,葡萄酒的价格取决于三个变量:数量、一年中的时间和库存数量。这种类型的关系可以使用多元线性回归来捕捉。

多元线性回归方程一般如下:

这里 Y 为因变量, X i s 为自变量。

非线性回归

非线性回归是一种回归分析,其中数据遵循模型,然后表示为数学函数。简单线性回归涉及两个变量( XY )与直线函数, ,而非线性回归必须生成一条曲线。非线性回归使用回归方程,如下所示:

让我们看看这个公式:

  • X= p 个预测因子的向量
  • β= k 个参数的向量
  • f(-) =一个已知的回归函数
  • ε =误差项

非线性回归可以拟合各种各样的曲线。它使用对数函数、三角函数、指数函数和许多其他拟合方法。这种建模类似于线性回归建模,因为两者都试图从一组变量中图形化地控制特定的答案。开发这些模型比开发线性模型更复杂,因为函数是通过一系列近似(迭代)生成的,这些近似可能是反复试验的结果。数学家使用各种各样的既定方法,如高斯-牛顿法和莱文伯格-马夸特法。这种非线性模型生成曲线的目的是使 OLS 尽可能小。OLS 越小,函数越适合数据集的点。它测量有多少观测值不同于数据集平均值。

在下一节中,我们将学习如何使用 Python 库开发和评估回归模型。

模型开发和评估

在前一节中,我们从理论上讨论了不同类型的回归。既然我们已经涵盖了理论概念,是时候获得一些实践经验了。在本节中,我们将使用 scikit-learn 库来实现线性回归并评估模型。为此,我们将使用著名的波士顿住房定价数据集,该数据集被研究人员广泛使用。我们将讨论回归情况下使用的不同模型评估技术。

让我们尝试在前面看到的解释的基础上开发一些回归模型。

构建线性回归模型

任何数据科学专业人员在解决任何回归问题时想到的第一个概念是构建线性回归模型。线性回归是最古老的算法之一,但它仍然非常有效。我们将使用示例数据集在 Python 中构建一个线性回归模型。该数据集在 scikit-learn 中作为一个名为波士顿房价数据集的样本数据集提供。我们将使用sklearn库加载数据集并构建实际模型。让我们从加载和理解数据开始:

  1. 让我们从导入所有必要的库并创建我们的数据框开始:
# Importing the necessary libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns 
from sklearn.datasets import load_boston

sns.set(style="ticks", color_codes=True)
plt.rcParams['figure.figsize'] = (8,5)
plt.rcParams['figure.dpi'] = 150

# loading the data
df = pd.read_csv("https://raw.githubusercontent.com/PacktPublishing/hands-on-exploratory-data-analysis-with-python/master/Chapter%209/Boston.csv")
  1. 现在,我们已经将数据集加载到boston变量中。我们可以按如下方式查看数据框的键:
print(df.keys())

这会将所有键和值作为 Python 字典返回。前面代码的输出如下:

Index(['CRIM', ' ZN ', 'INDUS ', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'LSTAT', 'MEDV'], dtype='object')
  1. 现在我们的数据已经加载,让我们快速准备好我们的数据帧并继续工作:
df.head()
# print the columns present in the dataset
print(df.columns)
# print the top 5 rows in the dataset
print(df.head()) 

前面代码的输出如下:

Figure 9.1: The first five rows of the DataFrame

MEDV是目标变量,在建立模型时,它将被用作目标变量。目标变量( y )与特征变量( x )是分开的。

  1. 在新的整体数据框架中,让我们检查是否有任何缺失的值:
df.isna().sum()

看看下面的输出:

CRIM 0
ZN 0
INDUS 0
CHAS 0
NOX 0
RM 0
AGE 0
DIS 0
RAD 0
TAX 0
PTRATIO 0
LSTAT 0
MEDV 0
dtype: int64

特别是在回归的情况下,确保我们的数据没有任何缺失值是很重要的,因为如果数据有缺失值,回归就不会起作用。

相关性分析是建立任何模型的关键部分。我们必须了解数据的分布以及自变量与因变量之间的关系。

  1. 让我们绘制一个热图,描述数据集中各列之间的相关性:
#plotting heatmap for overall data set
sns.heatmap(df.corr(), square=True, cmap='RdYlGn')

输出图如下所示:

Figure 9.2: Correlation matrix generated from the preceding code snippet

既然要建立线性回归模型,那就找几个与MEDV有显著相关性的自变量。从前面的热图来看,RM(每套住房的平均房间数)与MEDV(1000 美元中自有住房的中位数)正相关,因此我们将把RM作为特征( X )和MEDV作为线性回归模型的预测因子( y )。

  1. 我们可以用lmplot的方法从海鸟身上看到RMMEDV之间的关系。查看以下代码片段:
sns.lmplot(x = 'RM', y = 'MEDV', data = df)

前面代码的输出如下:

Figure 9.3: Lmplot illustrating the relationship between the RM and MEDV columns

前面的截图显示了这两个变量之间的强相关性。然而,我们可以很容易地从图表中发现一些异常值。接下来,让我们为模型开发创造条件。

Scikit-learn 需要在数组中创建特征和目标变量,所以在给Xy分配列时要小心:

# Preparing the data
X = df[['RM']]
y = df[['MEDV']]

现在我们需要将数据分成训练集和测试集。Sklearn 提供了一些方法,通过这些方法,我们可以将原始数据集分割成训练数据集和测试数据集。我们已经知道,回归模型开发背后的原因是为了得到一个预测未来产量的公式。但是我们如何确定模型预测的准确性呢?衡量模型准确性的一种逻辑技术是将正确预测的数量除以测试的观察总数。

对于这个任务,我们必须有一个已经知道输出预测的新数据集。在模型开发过程中,最常用的技术是数据集的训练/测试分割。这里,您将数据集分为训练数据集和测试数据集。我们将模型训练或拟合到训练数据集,然后通过对测试(标记或预测)数据集进行预测来计算精度。

  1. 这是使用sklearn.model_selection中的train_test_split()功能完成的:
# Splitting the dataset into train and test sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 10)

X是我们这里的自变量,Y是我们的目标(输出)变量。在train_test_split中,test_size表示测试数据集的大小。test_size是用于测试数据集的数据比例。在这里,我们为test_size传递了一个0.3的值,这意味着我们的数据现在分为 70%的训练数据和 30%的测试数据。最后,random_state为随机数生成器设置种子,随机数生成器分割数据。train_test_split()函数将返回四个数组:训练数据、测试数据、训练输出和测试输出。

  1. 现在最后一步是训练线性回归模型。从极其强大的sklearn库中,我们导入LinearRegression()函数来将我们的训练数据集拟合到模型中。当我们运行LinearRegression().fit()时,该函数会自动计算我们前面讨论过的 OLS,并生成一个适当的直线函数:
#Training a Linear Regression Model
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()

# Fitting the training data to our model
regressor.fit(X_train, y_train)

现在,我们有了一个名为regressor的模型,它是在训练数据集上完全训练的。下一步是评估模型对目标变量的正确预测程度。

模型评估

我们的线性回归模型现在已经被成功训练。请记住,我们从数据集中分离了一些数据用于测试,我们打算使用这些数据来发现模型的准确性。我们将用它来评估我们模型的效率。r2-统计学是测量回归模型精度的常用方法:

  1. R 2 可以使用我们在LinearRegression.score()方法中的测试数据集来确定:
#check prediction score/accuracy
regressor.score(X_test, y_test)

score()功能的输出如下:

0.5383003344910231

score(y_test, y_pred)方法预测输入集XY值,并将它们与真实的Y值进行比较。R 2 的值一般在 0 到 1 之间。R 2 的值越接近 1,模型越精确。这里 R 2 评分是 0.53 ≈ 53%准确率,还可以。有了一个以上的自变量,我们将提高模型的性能,这将是我们接下来要研究的。

  1. 在此之前,让我们用我们的模型来预测 y 值,并对其进行更多的评估。并且还建立了目标变量DataFrame:
# predict the y values
y_pred=regressor.predict(X_test)
# a data frame with actual and predicted values of y
evaluate = pd.DataFrame({'Actual': y_test.values.flatten(), 'Predicted': y_pred.flatten()})
evaluate.head(10)

目标变量DataFrame如下:

Figure 9.4: The first 10 entries showing the actual values and the predicted values

前面的截图显示了实际值和预测值之间的差异。如果我们把它们画出来,我们就能看到它们:

evaluate.head(10).plot(kind = 'bar')

前面代码的输出如下:

Figure 9.5: Stacked bar plot showing the actual values and the predicted values

更容易理解,对吧?请注意,大多数预测值低于实际值。

计算精度

Sklearn 提供的指标可以帮助我们用多种公式评估我们的模型。用于评估模型的三个主要指标是平均绝对误差、均方误差和 R 2 分数。

让我们快速尝试这些方法:

# Scoring the model
from sklearn.metrics import r2_score, mean_squared_error,mean_absolute_error

# R2 Score
print(f"R2 score: {r2_score(y_test, y_pred)}")

# Mean Absolute Error (MAE)
print(f"MSE score: {mean_absolute_error(y_test, y_pred)}")

# Mean Squared Error (MSE)
print(f"MSE score: {mean_squared_error(y_test, y_pred)}")

前面代码的输出如下:

R2 score: 0.5383003344910231
MSE score: 4.750294229575126
MSE score: 45.0773394247183

请注意,我们不是在评估我们在前面的输出中获得的精度。在任何机器学习场景中,我们都试图通过执行几种优化技术来提高准确性。

理解准确性

我们使用 scikit-learn 库来训练回归模型。除此之外,我们还使用训练好的模型来预测一些数据,然后计算精度。例如查看图 9.5 。第一个条目说实际值是 28.4,但是我们训练的回归模型预测它是 25.153909。因此,我们有一个 28.4 - 25.153909 = 3.246091 的差异。让我们试着理解这些差异是如何理解的。让 x i 为实际值,为任意样本i的模型预测值。

误差由以下公式给出:

对于任何样本i,我们都可以得到预测值和实际值的差值。我们可以通过对误差求和来计算平均误差,但是由于有些误差是负的,有些是正的,所以它们很可能会相互抵消。那么,问题仍然存在,我们如何知道我们的训练模型在所有数据集上的表现有多准确?这就是我们使用平方误差概念的地方。你应该知道正数和负数的平方总是正数。因此,他们没有机会互相抵消。因此,我们可以用下面的等式来表示平方误差:

一旦我们知道如何计算平方误差,我们就可以计算均方差。那很容易,对吗?当然,为了计算均方误差,我们可以使用以下公式:

现在,如果我们取均方误差的根,我们得到另一个精度度量,称为均方误差的根 ( RMSE )。等式现在变成了:

另一种广泛使用的精度测量方法叫做相对均方误差 ( 均方根误差)。不要把它和 RMSE 混淆了。计算均方根误差的公式如下:

在上式中, E(x) 被称为 x 的期望值,除了 rMSE,我们还使用了 R 2 方法。R 2 的计算公式如下:

在数据科学中经常看到的另一种精度度量是绝对误差。顾名思义,它取绝对值并计算总和。测量绝对误差的公式如下:

最后,除了绝对误差之外,还可以使用另一种类型的误差,即平均绝对误差。平均绝对误差的计算公式如下:

太多了吗?曾经是。然而,如果你仔细检查这些方程,你会发现它们是非常密切相关的。试着把重点放在名字上,这解释了准确度测量的作用。现在,每当你看到任何数据科学模型使用这些精度测量,它会更有意义,不是吗?

祝贺您了解准确度测量。在下一节中,让我们深入研究多元线性回归,我们将尝试使用这些精度度量。

实现多元线性回归模型

当因变量依赖于几个自变量时,可以使用多元线性回归来获取关系。多元线性回归可以看作是简单线性回归的延伸。说到使用 sklearn 实现多元线性回归,简单和多元线性回归没有太大区别:

  1. 只需在X变量中包含额外的列并运行代码。因此,让我们包含X变量的附加列,并遵循相同的代码。
  2. 记住,二维线性回归模型是一条直线;它是三维的平面,也是三维以上的超平面:
# Preparing the data
X = df[['LSTAT','CRIM','NOX','TAX','PTRATIO','CHAS','DIS']]
y = df[['MEDV']]

# Splitting the dataset into train and test sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 10)

# Fitting the training data to our model
regressor.fit(X_train, y_train)

#score of this model
regressor.score(X_test, y_test)

score()功能的输出如下:

0.6446942534265363
  1. 让我们用我们的模型预测y值并评估它:
# predict the y values
y_pred=regressor.predict(X_test)
# a data frame with actual and predicted values of y
evaluate = pd.DataFrame({'Actual': y_test.values.flatten(), 'Predicted': y_pred.flatten()})
evaluate.head(10)

目标变量DataFrame如下:

Figure 9.6: The first 10 entries showing the actual values and the predicted values

  1. 让我们制作另一个特征较少的多元线性回归模型:
# Preparing the data
X = df[['LSTAT','CRIM','NOX','TAX','PTRATIO']]
y = df[['MEDV']]

# Splitting the dataset into train and test sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 10)

# Fitting the training data to our model
regressor.fit(X_train, y_train)

#score of this model
regressor.score(X_test, y_test)

score()功能的输出如下:

0.5798770784084717

该模型的准确率为 57%。下表显示了此构建模型的目标变量 MEDV 的实际值和预测值,如下所示:

如你所见,在X中改变特征会使模型的精度发生变化。因此,您必须仔细分析特征之间的相关性,然后使用它们以更高的精度构建模型。

摘要

在本章中,我们讨论了两个重要的概念:假设检验和回归分析。在假设检验中,我们了解了假设、其基本原理以及不同类型的假设检验,并且我们使用了两个不同的 Python 库(statsmodels 和 SciPy)来创建不同的假设检验。此外,我们还讨论了 p-hacking,这是假设测试期间最常遇到的挑战之一。接下来,我们讨论了不同类型的回归,并使用 scikit-learn 来构建、测试和评估一些回归模型。

在下一章中,我们将更详细地讨论模型开发和评估。除了回归之外,我们还将讨论其他几种可以使用的模型。

进一步阅读

  • Python 的回归分析,作者:卢卡·马萨隆阿尔贝托·博切蒂帕克特出版,2016 年 2 月 29 日
  • 机器学习统计普拉塔·丹盖提帕克特出版,2017 年 7 月 20 日
  • 数据科学统计詹姆斯·d·米勒帕克特出版,2017 年 11 月 17 日
  • 一周内的数据科学算法-第二版,作者:达维德·纳廷加帕克特出版,2018 年 10 月 31 日
  • sci kit 机器学习-学习快速入门指南,作者:凯文·乔利**帕克特出版*,2018 年 10 月 30 日*