数据预处理|10 数据清理第二级--解包、重组和重编表

1 例子1--解压列并重新制定表格
在这个例子中,我们将使用经过一级清理的speech_df数据集来创建以下条形图。我们在第九章《数据清理第一级--清理表》的例1--不明智的数据收集一节中清理了这个数据框架。经过I级清理的speech_df数据库只有两列。文件名和内容。为了能够创建下面的视觉效果,我们需要诸如演讲的月份和四个词(投票、税收、竞选和经济)在每个演讲中重复出现的次数等列。虽然经过一级清理的speech_df数据集包含所有这些信息,但它在某种程度上被埋没在这两列里面。
speech_df.head()

以下是我们需要的信息列表,以及这些信息所存储的speech_df的列。
- 讲话的月份。这一信息在FileName列中。
- 投票、税收、竞选和经济这些词在每个演讲中重复出现的次数。此信息在内容栏中。

因此,为了能够达到我们的分析目标,也就是创建之前的可视化,我们需要解开这两列的包装,然后重新制定表格进行可视化。
让我们一步步来做这个。首先,我们将解压FileName,然后我们把注意力转移到解压Content上。之后,我们将为要求的可视化重新编制表格。
1.1 解压文件名
让我们来看看FileName列的值。要做到这一点,你可以运行speech_ df.FileName 并研究该列下的数值。你会注意到,这些值遵循一个可预测的模式。这个模式是CitynameMonthDD_YYYY .txt;Cityname是演讲所在城市的名称,Month是演讲所在月份的三个字母版本,DD是代表该月的一天或两位数字,YYYY是代表演讲所在年份的四位数字,.txt是文件扩展名,所有的值都是一样的。
你可以看到,文件名一栏包含了数据集中有关演讲的以下信息。
- 城市。发表演讲的城市
- 日期。发表演讲的日期
- 年份。演讲的年份
- 月份。月:发表演讲的月份
- 日:发表演讲的日期。
在下面的代码中,我们将使用我们的编程技巧来解包FileName列,并将前面的信息作为独立的列。让我们先计划一下我们的解包工作,然后把它放到代码中。以下是我们在解包过程中需要采取的步骤。
-
提取城市。使用
CitynameMonthDD_YYYY .txt模式中的Month来提取城市。根据这个模式,在Month之前的所有内容都是Cityname。
2.提取日期。使用提取的城市名来提取日期。 -
从Date中提取年、月、日。
现在,让我们把这些步骤变成代码。 -
提取城市。下面的代码创建了
SeparateCity()函数,并将其应用到speech_df.FileName系列中。SeparateCity()函数在之前创建的Months列表中进行循环,以找到代表月份的三个字母的单词,这将用于每个文件名。然后,我们可以使用.find()函数和Python字符串的切分能力来返回城市的名称。
Months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Oct','Sep','Nov','Dec']
def SeparateCity(v):
for mon in Months:
if (mon in v):
return v[:v.find(mon)]
speech_df['City'] = speech_df.FileName.apply(SeparateCity)
speech_df.head()

请注意! 在这里,我们不得不用Month作为Cityname和日期之间的分隔符。
如果文件的命名规则更有条理,我们可以更容易地完成这个工作;在speech_df.FileName一栏中,有些日子是以一个数字呈现,如LatrobeSep3_2020.txt,而有些日子是以两个数字呈现,如BattleCreekDec19_2019。
txt。如果所有的日子都以两位数呈现,即用LatrobeSep03_2020.txt而不是LatrobeSep3_2020.txt,那么从编程的角度来看,解压列的任务就会简单很多。关于这个例子,请看本章后面的练习2。
- 提取日期。下面的代码创建了
SeparateDate()函数并将其应用于speech_df。这个函数使用提取的城市作为起点,并使用.find()函数将日期与城市分开。每次我们处理日期信息时,最好确保Pandas知道记录的是一个日期时间编程对象,这样我们就可以使用它的属性,比如按日期排序或访问日、月、年的值。下面的代码使用pd.to_datetime()函数将代表日期的字符串转换为日期时间编程对象。为了有效地使用pd.to_datetime()函数,你需要能够编写代表日期的字符串所遵循的格式模式。这里,格式模式是'%b%d_%Y',这意味着字符串以三个字母的月份表示开始(%b),然后是数字表示的一天(%d),后面是下划线(_),然后是四位数的年份表示(%Y)。为了能够想出一个正确的格式模式,你需要知道每个指令的含义,如%b、%d等。转到https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
def SeparateDate(r):
return r.FileName[len(r.City):r.FileName.find('.txt')]
speech_df['Date'] = speech_df.apply(SeparateDate,axis=1)
speech_df.Date = pd.to_datetime(speech_df.Date,format='%b%d_%Y')
speech_df.head()
# https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior

- 从Date中提取年、月、日。下面的代码创建了
extractDMY()函数,并将其应用于speech_df,为每一行增加了三个新的列。请注意,该代码利用了 speech_df 列是一个数据时间编程对象的事实,该对象具有 .day 和 .month 等属性,以访问每个日期的日期和月份。
def extractDMY(r):
r['Day'] = r.Date.day
r['Month'] = r.Date.month
r['Year'] = r.Date.year
return r
speech_df = speech_df.apply(extractDMY,axis=1)
speech_df.head()

成功运行前面的代码片段后,你将成功地解开speech_df的FileName列。由于打包在FileName中的所有信息都没有在其他列下呈现,我们可以通过运行下面的命令,继续删除这一列。在解读另一列 "内容 "之前,让我们先看看数据的状态,享受一下看我们取得的进展。下面的截图显示了数据的前五行。
speech_df.drop(columns=['FileName'],inplace=True)
speech_df.head()

现在我们已经将FileName解压成了五个新的列,分别是City、Date、Day、Month和Year,我们已经向最终目标迈出了一步:我们已经有了创建图10.1中所示的X轴的权限。现在,我们需要注意解压列 "内容"。
1.2 拆封内容列
解压缩列 "内容 "与解压缩 "文件名 "有一定的区别。由于列FileName只有有限的信息量,我们能够解包这个列的所有内容。然而,列Content有很多信息,它可以以许多不同的方式解包。然而,我们只需要解读栏目内容下的一小部分内容;我们需要了解四个词的使用比例:投票、税收、竞选和经济。
我们可以在一个步骤中把我们需要的东西从列Content中解压出来。下面的代码创建了FindWordRatio()函数并将其应用于speech_df。该函数使用for循环向DataFrame添加四个新列,四个词各一列。每个词的计算很简单:每个词的返回值是该词在语音中的总出现次数(row.Content.count(w)),除以语音中的总词数(total_n_words)。运行前面的代码后,产生的speech_df将有10列,如以下截图所示。
Words = ['vote','tax','campaign','economy']
def FindWordRatio(row):
total_n_words = len(row.Content.split(' '))
for w in Words:
row['r_{}'.format(w)] = row.Content.count(w)/total_n_words
return row
speech_df = speech_df.apply(FindWordRatio,axis=1)
speech_df.head()

到目前为止,我们已经对表格进行了重组,所以我们正在逐步接近绘制图10.1;我们已经得到了X轴和Y轴的信息。然而,在我们能够将图10.1可视化之前,还需要进一步修改数据集。
1.3 重新制定一个新的表格以实现可视化
到目前为止,我们已经为我们的分析目标清理了 speech_df。然而,我们在图10.1中需要的表需要每一行都是唐纳德-特朗普在一个月内的演讲,而 speech_df中的每一行都是唐纳德-特朗普的一次演讲。换句话说,为了能够画出可视化,我们需要重新制定一个新的表格,使我们的数据对象的定义是唐纳德-特朗普在一个月内的演讲,而不是一个唐纳德-特朗普的演讲。
唐纳德-特朗普在一个月内的演讲的新定义是一些被定义为唐纳德-特朗普演讲的数据对象的聚合。当我们需要重新表述一个数据集,使其新定义的数据对象是当前定义的数据对象的聚合,我们需要执行两个步骤。
- 创建一个列,该列可以作为重新制定的数据集的唯一标识符。
- 使用一个能够在应用聚合函数的同时重新表述数据集的函数。能够做到这一点的pandas函数是
.groupby()和.pivot_table()。
所以,让我们对speech_df执行这两个步骤,以创建新的DataFrame,称为vis_df,这是我们的分析目标所需要的重构的表。 - 下面的代码应用了一个lambda函数,附加了每一行的年和月属性,创建了一个新的列,叫做Y_M。这个新列将是我们试图创建的重构数据集的唯一标识符。
Months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Oct','Sep','Nov','Dec']
lambda_func = lambda r: '{}_{}'.format(r.Year,Months[r.Month-1])
speech_df['Y_M'] = speech_df.apply(lambda_func,axis=1)
speech_df.head()

前面的代码在单独的一行中创建了lambda函数(lambda_func),目的是为了使代码更易读。这一步本可以跳过,lambda函数可以 "即时 "创建。
- 下面的代码使用
.pivot_table()函数将speech_df重新表述为vis_df。如果你已经忘记了.pivot_table()函数是如何工作的,请重温第一章《NumPy和Pandas的核心模块回顾》中的pandas pivot和melt函数部分。
Words = ['vote','tax','campaign','economy']
vis_df = speech_df.pivot_table(
index= ['Y_M'],
values= ['r_{}'.format(w) for w in Words],
aggfunc= np.mean)
vis_df.head()

前面的代码使用了.pivot_table()函数的aggfunc属性,这在第一章《NumPy和Pandas的核心模块回顾》中没有提到。理解aggfunc很简单,当.pivot_table()的索引和值被指定为需要将一个以上的值移入重新制定的表中的一个单元格时,.pivot_table()使用传递给aggfunc的函数,将这些值聚合为一个值。
前面的代码还使用了一个列表理解来指定这些值。列表理解是['r_{}'.format(w) for w in Words],它本质上是来自speech_df的四列列表。分别运行列表理解并研究其输出。
- 我们也可以使用
.groupby()将数据重新编入vis_df。以下是备选代码。
vis_df = pd.DataFrame({
'r_vote': speech_df.groupby('Y_M').r_vote.mean(),
'r_tax': speech_df.groupby('Y_M').r_tax.mean(),
'r_campaign': speech_df.groupby('Y_M').r_campaign.mean(),
'r_economy': speech_df.groupby('Y_M').r_economy.mean()
})
vis_df.head()

虽然前面的代码可能感觉更直观,因为使用.groupby()函数可能比使用.pivot_table()更容易,但第一段代码的可扩展性更高。
更加可扩展的代码 在编码时,如果可能的话,你想避免对一个项目集合重复使用同一行代码。例如,在前面两个代码块中的第二个备选方案中,我们不得不使用.groupby()函数四次,对四个词各使用一次。如果不是4个词,而是我们需要对100,000个词做这种分析呢?第一种方法当然更具有可扩展性,因为单词是以列表形式传递的,无论列表中有多少单词,代码都是一样的。
在这一点上,你已经创建了重新制定的vis_df,我们创建它是为了绘制图10.1。下面的屏幕截图显示了vis_df。
现在我们有了vis_df,剩下的就是把vis_df中的信息以柱状图的形式表现出来。下面的小节展示了如何做到这一点。
1.4 最后一步--绘制可视化图
图10.2和图10.1本质上呈现的是相同的信息。图10.2(vis_df)使用了一个表格来呈现信息,而图10.1使用了一个柱状图。换句话说,我们几乎已经成功了,我们需要再执行一个步骤来创建要求的可视化。
下面的代码块显示了创建图10.1中所示的可视化的代码。
在运行下面的代码之前请注意,因为你必须先导入matplotlib.
pyplot模块。你可以使用import matplotlib.pyplot作为plt来完成这个任务。
column_order = vis_df.sum().sort_values(ascending=False).index
row_order = speech_df.sort_values('Date').Y_M.unique()
vis_df[column_order].loc[row_order].plot.bar(figsize=(10,4))
plt.legend(['vote','tax','campaign','economy'],ncol=2)
plt.xlabel('Year_Month')
plt.ylabel('Average Word Frequency')
plt.show()

上面的代码创建了两个列表:column_order和row_order。正如它们的名字所示,这些列表是列和行将在视觉上显示的顺序。
column_order是基于其出现率的总和的单词列表,而row_order是基于其在日历中的自然顺序的Y_M的列表。
在这个例子中,我们了解了二级数据清理的不同技术;我们学会了如何为分析工具和目标解压列和重新表述数据。
下一个例子将涵盖数据预处理以重组数据集。
重组和重新表述数据集之间有什么区别?当数据对象的定义需要为新的数据集而改变时,我们倾向于使用重新制定。相反,当表结构不支持我们的分析目标或工具,而我们不得不使用替代结构,如字典时,我们会使用重组。在这个例子中,我们把一个数据对象的定义从一个唐纳德-特朗普的演讲改为唐纳德-特朗普在一个月内的演讲,所以我们把这称为数据集重新表述。
在这里,我们在沉浸在例子中的同时,也被介绍给了新的材料。
在这个例子中,我们学习了解包列和重构表。在下一个例子中,我们将接触到一个需要重组表格的情况。
2 例2--重组表格
在这个例子中,我们将使用Customer Churn.csv数据集。这个数据集包含了一家电信公司的3150个客户的记录。这些行由人口统计学列描述,如性别和年龄,以及活动列,如9个月内的不同电话数量。该数据集还规定了在收集客户活动数据的9个月后,每个客户是否有流失。从电信公司的角度来看,客户流失意味着客户停止使用该公司的服务并接受该公司竞争对手的服务。
我们想用箱形图来比较流失客户和非流失客户这两个人群的以下活动列。呼叫失败,订阅长度,使用秒数,使用频率,短信频率,以及不同的被叫号码。
让我们首先将客户流失率.csv文件读入customer_df数据框架。下面的截图显示了这个步骤。
import pandas as pd
customer_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter10/Customer%20Churn.csv')
customer_df.head()

乍一看,我们可以看到这个数据集需要一些一级的数据清理。虽然列的标题是直观的,但它们可以变得更加可编码。下面这行代码可以确保这些列也是可编纂的。
customer_df.columns = ['Call_Failure', 'Complains', 'Subscription_Length', 'Seconds_of_Use',
'Frequency_of_use', 'Frequency_of_SMS', 'Distinct_Called_Numbers',
'Status', 'Churn']
customer_df.head()

在你继续前进之前,确保你研究了运行前面的代码后customer_df的新状态。
现在,数据集已经进行了第一级清理,我们可以注意第二级数据清理。
这个例子需要我们画六个箱形图。让我们专注于第一个箱形图;其余的将遵循同样的数据清理过程。
让我们专注于创建多个箱形图,比较流失客户和非流失客户的Call_Failure属性。箱形图是一种分析工具,需要比数据集更简单的数据结构。一个盒式图只需要一个字典。
数据集和字典之间的区别是什么?数据集是一个包含由列描述的行的表。正如第3章 "数据--它到底是什么?"中描述的那样,在最通用的数据结构--表一节中,我们明确指出,表的粘合剂是每一行所代表的数据对象的定义。每一列也是对行的描述。另一方面,字典是一个更简单的数据结构,其值与一个唯一的键相关联。
对于我们要画的箱形图,我们需要的字典有两个键--流失和非流失--每个将被呈现的人口都有一个。每个键的值是每个人群的Call_Failure记录的集合。请注意一个事实,与有两个维度(行和列)的表数据结构不同,字典只有一个维度。
下面的代码显示了pandas系列作为一个字典的用法,为箱形图准备数据。在这段代码中,box_sr是一个pandas系列,它有两个名为0和1的键,0代表非流失,1代表流失。该代码使用一个循环和布尔掩码来过滤流失和非流失的数据对象,并将它们记录在box_sr中。
churn_possibilities = customer_df.Churn.unique()
box_sr = pd.Series('',index=churn_possibilities)
for poss in churn_possibilities:
BM = customer_df.Churn == poss
box_sr[poss] = customer_df[BM].Call_Failure.values
box_sr

在继续前进之前,执行print(box_sr)并研究其输出。请注意,与数据的初始结构相比,数据结构很简单。
现在我们已经为我们要使用的分析工具重组了数据,数据已经准备好用于可视化了。下面的代码使用plt.boxplot()来可视化我们在box_sr中准备的数据。在运行下面的代码之前,不要忘记导入matplotlib.pyplot作为plt。
import matplotlib.pyplot as plt
plt.boxplot(box_sr,vert=False)
plt.yticks([1,2],['Not Churn','Churn'])
plt.show()

如果前面的代码运行成功,你的计算机将显示多个比较两个人群的箱形图。
到目前为止,我们已经画了一个箱形图,对流失人口和非流失人口的Call_Failure进行了比较。现在,让我们创建一些代码,可以对所有要求的列做同样的处理和可视化,以比较人群。正如我们之前提到的,这些列是Call_Failure, Subscription_Length, Seconds_of_Use, Frequency_of_use, Frequency_of_SMS, and Distinct_ Called_Numbers。
下面的代码使用一个循环和plt.subplot()来组织这个分析所需的六个视觉效果,使它们彼此相邻。下图显示了该代码的输出。图中所示的每个箱形图都需要进行数据重组,以绘制箱形图。作为练习,试着在下面的代码中发现它们并研究它们。如果你不知道enumerate()、plt.subplot()和plt.tight_layout()函数是什么,我建议你回顾一下第1章,核心模块的回顾--NumPy和pandas,以及第2章,另一个核心模块的回顾--Matplotlib.
select_columns = ['Call_Failure', 'Subscription_Length', 'Seconds_of_Use',
'Frequency_of_use', 'Frequency_of_SMS', 'Distinct_Called_Numbers']
churn_possibilities = customer_df.Churn.unique()
plt.figure(figsize=(15,5))
for i,sc in enumerate(select_columns):
for poss in churn_possibilities:
BM = customer_df.Churn == poss
box_sr[poss] = customer_df[BM][sc].values
plt.subplot(2,3,i+1)
plt.boxplot(box_sr,vert=False)
plt.yticks([1,2],['Not Churn','Churn'])
plt.title(sc)
plt.tight_layout()
plt.show()

在这个例子中,我们看了一种情况,我们需要重组数据,以便为我们选择的分析工具--箱形图做好准备。在下一个例子中,我们将看到一个更复杂的情况,我们需要同时进行数据集的重构和重组来进行预测。
3 例3--I级和II级数据清理
在这个例子中,我们想用Electric_Production.csv来进行预测。我们特别感兴趣的是能够预测1个月后的月度电力需求。在预测模型中设计了这1个月的差距,以便从模型中得出的预测结果具有决策价值;也就是说,决策者将有时间对预测值作出反应。
我们希望使用线性回归来进行这种预测。这个预测的独立和依赖属性如下图所示。

让我们来看看前述图表中显示的独立属性。
- 多年来该月的平均需求量。例如,如果我们要预测的月份是2022年3月,我们要使用前几年每个3月的平均需求。因此,我们将整理从数据收集过程开始(1985年)到2021年3月的历史需求,并计算其平均值。这在下图中显示。
- 多年来该月需求的变化斜率。例如,如果我们要预测的月份是2022年3月,我们要使用3月的需求量历年的变化斜率。如下图所示,我们可以在3月份的需求数据点上拟合一条直线。该拟合线的斜率将被用于预测。
- t-2、t-3和t-4月份的平均需求:在上图中,t、t-2、t-3和t-4的符号被用来建立一个时间参照。这个时间参照就是,如果我们想预测某个月的需求,我们要使用以下数据点的平均需求:2个月前的月度需求,3个月前的月度需求,4个月前的月度需求。例如,如果我们想预测2021年3月的月度需求,我们要计算2021年1月、2020年12月和2020年11月的平均值。请注意,我们跳过了2021年2月,因为它是我们计划中的决策缺口。

现在我们对数据分析的目标有了清晰的认识,我们将把重点放在数据的预处理上。让我们从读取数据开始,涵盖其一级数据清洗过程。下面的截图显示了将Electric_ Production.csv文件读入month_df的代码,并显示其前五行和最后五行。
month_df = pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Hands-On-Data-Preprocessing-in-Python/main/Chapter10/Electric_Production.csv')
month_df.head()

month_df.tail()

3.1 一级清洗
month_df数据集可以做以下一级数据清理的步骤。
- 第二列的标题可以更直观一些。
- DATE列的数据类型可以切换为datetime,这样我们就可以利用datetime的编程特性。
- 可以改进pandas分配给数据的默认索引,因为DATE列会提供一个更好的、更独特的标识。
month_df.columns = ['Date','Demand']
month_df.set_index(pd.to_datetime(month_df.Date,format='%m/%d/%Y'),inplace=True)
month_df.drop(columns=['Date'],inplace=True)
month_df.head()

3.2 二级清洗
观察图10.3和图10.4可能会让你觉得图10.3中规定的预测模型是不可能的,因为图10.4中显示的数据集只有一列,而预测模型需要四个属性。这既是一个正确的观察,也是一个不正确的观察。虽然数据只有一列数值是正确的观察,但图10.3中建议的独立属性可以通过一些列的解包和重组从month_df中驱动出来。这就是我们需要做的第二级数据清理。
我们将首先构造一个我们想要重组当前表的DataFrame。下面的代码创建了predict_df,它是我们在规定的预测任务中需要的表结构。
attributes_dic={'IA1':'Average demand of the month',
'IA2':'Slope of change for the demand of the month',
'IA3': 'Average demands of months t-2, t-3 and t-4',
'DA': 'Demand of month t'}
predict_df = pd.DataFrame(index=month_df.iloc[24:].index,columns=attributes_dic.keys())
predict_df.head()

在创建新的表结构predict_dt时,起草代码时考虑到了以下几点。
- 前面的代码使用attributes_dic字典来创建直观和简洁的列,这些列也是可以编码的。由于predict_df需要包括相当长的属性标题,如图10.3所示,字典允许标题列简洁、直观和可编码,同时,你可以通过attributes_dic访问标题的长版本。这是一种一级数据清理的形式,正如第9章,数据清理一级--清理表,在例3--直观但较长的列标题一节中所示。然而,既然我们是创建这个新表的人,为什么不从第一级清理的表结构开始呢?
- 我们创建的表结构,predict_df,使用了month_df的索引,但不是所有的索引。它使用了所有的索引,除了前24行,在代码中由
month_df.iloc[24:].index指定。为什么前24个索引不包括在内?这是由于第二个独立属性的原因。多年来该月需求的变化斜率。由于每个月的需求变化斜率将被描述的预测模型所需要,我们不能在predict_df中的month_df的前24行有一个有意义的斜率值。这是因为我们至少需要每个月的两个历史数据点才能计算出第二个独立属性的斜率。

我们将完成描绘的数据处理,并按以下顺序填写predict_df中的列。DA、IA1、IA2和IA3。
3.2.1 填写DA
这是最简单的列填充过程。我们只需要指定month_df.Demand中正确的部分,放在predict_df.DA下即可。下面的截图显示了代码和它对predict_df.DA的影响。
predict_df.DA = month_df.loc['1987-01-01':].Demand
predict_df.head()

3.2.2 填报IA1
为了计算IA1,也就是多年来每月的平均需求量,我们需要能够使用月份的值来过滤month_df。为了创建这种能力,下面的代码将一个lambda函数映射到month_df,并提取每一行的月份。
month_df['Month'] = list(map(lambda v:v.month,month_df.index))
month_df.head()

下面的代码创建了ComputeIA1()函数,它使用month_df来过滤出predict_df.IA1下每个单元格的正确值所需的数据点。一旦它被创建,ComputeIA1()函数就会被应用到predict_df.IAI中。
def ComputeIA1(r):
row_date = r.name
wdf = month_df.loc[:row_date].iloc[:-1]
BM = wdf.Month == row_date.month
return wdf[BM].Demand.mean()
predict_df.IA1 = predict_df.apply(ComputeIA1,axis=1)
predict_df.head()

编写的函数ComputeIA1()将应用于predict_df的行,执行以下步骤。
- 首先,它使用计算出的row_date过滤出month_df,以去除日期在row_date之后的数据点。
- 第二,该函数使用一个布尔掩码来保留与行的月份(row_date.month)相同的数据点。
- 接下来,该函数计算过滤后的数据点的平均需求量,然后返回。
注意 在继续之前,让我分享一个附带说明。在前面的代码中创建的wdf变量是Working DataFrame的缩写。每当我在循环或函数中需要一个DataFrame,但之后又不需要它时,我就会使用wdf这个缩写。
在成功运行前面的代码后,确保你打印出predict_df,并在继续前进之前研究它的新状态。
到目前为止,我们已经填写了DA和IA1。接下来,我们将填写IA2。
3.2.3 填报IA2
为了填写IA2,我们将遵循与填写IA1相同的一般步骤。不同的是,我们将创建并应用于predict_df以计算IA2值的函数更加复杂;对于IA1,我们创建并应用了ComputeIA1(),而对于IA2,我们将创建并应用ComputeIA2()。不同的是,ComputeIA2()更加复杂。
创建和应用ComputeIA2()函数的代码显示在这里。试着研究一下这段代码,弄清楚它是如何工作的,然后再继续前进。
from sklearn.linear_model import LinearRegression
def ComputeIA2(r):
row_date = r.name # r.name 在此处为Timestamp格式
wdf = month_df.loc[:row_date].iloc[:-1]
BM = wdf.Month == row_date.month
wdf = wdf[BM]
wdf.reset_index(drop=True,inplace=True)
wdf.drop(columns = ['Month'],inplace=True)
wdf['integer'] = range(len(wdf))
wdf['ones'] = 1
lm = LinearRegression()
lm.fit(wdf.drop(columns=['Demand']), wdf.Demand)
return lm.coef_[0]
predict_df.IA2 = predict_df.apply(ComputeIA2,axis=1)
predict_df.head()

前面的代码与我们用来填写IA1的代码既相似又不同。它是相似的,因为ComputeIA1()和ComputeIA2()都是从过滤掉month_df开始的,以得到一个DataFrame,其中只包括计算数值所需的数据对象。你可能注意到,def ComputeIA1(r):和def ComputeIA2(r):下的三行代码是一样的。两者之间的区别就从这里开始。
由于计算IA1只是计算一列数值的平均值,所以ComputeIA1()的其余部分也非常简单。然而,对于ComputeIA2(),代码需要对过滤后的数据点进行线性回归,这样就可以计算出多年来变化的斜率。ComputeIA2()函数使用sklearn.linear_model中的LinearRegression来找到拟合的回归方程,然后返回模型的计算系数。
在成功运行前面的代码后,确保打印出predict_df并研究它的新状态,然后继续前进。
为了理解ComputeIA2()找到predict_df.IA2下每个单元格的变化斜率的方法,请看下面的截图,它显示了计算predict_df.IA2下一个单元格的斜率的代码及其输出。下面的截图计算了索引为2017-10-01的行的IA2值。
row_date = '2017-10-01'
wdf = month_df.loc[:row_date].iloc[:-1]
BM = wdf.Month == 10
wdf = wdf[BM]
wdf.reset_index(drop=True,inplace=True)
wdf.drop(columns = ['Month'],inplace=True)
wdf['integer'] = range(len(wdf))
wdf['ones'] = 1
lm = LinearRegression()
lm.fit(wdf.drop(columns=['Demand']), wdf.Demand)
print('Slope = {}'.format(lm.coef_[0])) #Slope = 1.1857728189149568
wdf.plot.scatter(x='integer',y='Demand',marker='*',
label='Data points',c='C0')
b = lm.intercept_
a = lm.coef_[0]
X = wdf.integer
y = b + a*X
plt.plot(X,y,label = 'Fitted regression',linestyle='--',c='C1')
plt.show()

3.2.4 填报IA3
在所有独立属性中,IA3是最容易处理的一个。IA3是t-2、t-3和t-4月份的平均需求。下面的代码创建了ComputeIA3()函数并将其应用于predict_df。这个函数使用predict_df的索引来查找2个月前、3个月前和4个月前的需求值。它通过使用.loc[:row_date]过滤掉所有在row_date之后的数据,然后通过使用.iloc[-5:-2]只保留底部剩余数据的第四、第三和第二行来做到这一点。一旦数据过滤过程完成,将返回三个需求值的平均值。
def ComputeIA3(r):
row_date = r.name
wdf = month_df.loc[:row_date].iloc[-5:-2]
return wdf.Demand.mean()
predict_df.IA3 = predict_df.apply(ComputeIA3,axis=1)
predict_df.head()

3.3 做好分析工作--使用线性回归创建一个预测模型
首先,我们将从sklearn.linear_model中导入LinearRegression,将数据拟合为一个回归方程。正如我们在第6章 "预测 "中所学到的,为了将预测算法应用于我们的数据,我们需要将数据分成独立和依赖属性。习惯上,我们用X来表示独立属性,用y来表示因果属性。下面的代码执行了这些步骤,并将数据送入模型。
from sklearn.linear_model import LinearRegression
X = predict_df.drop(columns=['DA'])
y = predict_df.DA
lm = LinearRegression()
lm.fit(X,y)
正如我们在第6章预测中所学到的,一旦前面的代码被执行,几乎什么都不会发生,但分析已经被执行了。我们可以使用lm来访问估计的βs,也可以进行预测。
下面的代码从lm中提取βs。
print('intercept (b0) ', lm.intercept_)
coef_names = ['b1','b2','b3']
print(pd.DataFrame({'Predictor': X.columns,
'coefficient Name':coef_names,
'coefficient Value': lm.coef_}))

利用前面代码的输出,我们可以算出下面的回归方程。

为了找出预测模型的质量,我们可以看到该模型在因果属性DA中找到的模式有多好。下面的截图显示了绘制线性回归模型的实际和拟合数据的代码。
plt.figure(figsize=(10,4))
plt.plot(X.index,y, label='Actual')
plt.plot(X.index,lm.predict(X),
label = 'Fitted',linestyle='--')
plt.legend()
plt.show()

从前面的图表中,我们可以看到,这个模型已经能够很好地捕捉到数据的趋势,而且它是一个很好的模型,可以用来预测未来的数据点。
在继续之前,花点时间考虑一下我们为设计和实现一个有效的预测模型所做的一切。我们所做的大部分步骤是数据预处理步骤,而不是分析步骤。正如你所看到的,能够进行有效的数据预处理将使你在数据分析方面获得更大的成功。

浙公网安备 33010602011771号