3.1、清理数值型变量

为此,首先将部署pump的代码复习一下,并对其中的定量变量做一个初步的描述统计分析,如图3.1所示。从中可以看到一下重要设置。第一、水库本身(航空数据)是被随机排序后的(airline.csv.shuffle);第二、因为数据是被随机排序的,所以可以采用序贯采样(pm.seq=True);第三、因为是序贯采样,所以可以设定一个相对而言比较大的再抽样样本量(pm.subsize=10**4)。最后,基于前期分析,设定了一系列的字段为数值型变量。

如何清洗深度学习的数据集里的数据_字段

 图3.1:航空数据泵的参数设置已经数值型变量分析

从图3.1中能够发现什么问题呢?首先mp的值都很低,不超过3%,这说明这些字段确实很像数字。但是,它们是不是真的是数字,而且是否有其他潜在问题,需要逐个小心分析。

值得注意的是,不少变量非中心化的峰度值(Kurt)都非常高。如果一个数据是严格正态分布的,那么峰度值应该是3,而如果一个数据的峰度严重大于3,那么这个数据分分布形状是令人担忧的,非常值得关注。例如,首先对ArrDelay这个变量的分布做一个动态直方图,如图3.2所示。从中可以看到,这个变量是严重厚尾的(heavy-tailed),尤其右侧的尾部分布非常厚重。结合该变量的实际意义,可以确认这是一个正常现象。航班延误时有发生,大多数航班的延误时间并不是太长,但是也有不少航班延误非常严重。

如何清洗深度学习的数据集里的数据_字段_02

图3.2:ArrDelay的直方图

而这会产生一个非常有趣的问题:什么样的航班容易延误?为此需要将ArrDelay做一些必要的变换,并建立相应的回归模型。具体应该怎么做呢?传统的做法是在原始数据里做计算操作,将原始数据的ArrDelay修改成我们想要的样子,然后再进行数据分析。但是,面对如此大的数据量,这样做效率是非常低的,而且风险很大。这里的风险是什么?风险就是数据处理的不确定性。因此,面对大规模数据的时候,这样的不确定性大量存在。因此,最好摈弃对原始数据操作的执念,改为对pump的各种修正。但是,遗憾的是,clubear提供的pump类的主要任务是抽水(或者抽样),对于如何处理水(例如:生成一个新的ArrDelay变量)并不擅长。为此,clubear提供了一个新的功能丰富的类,专门用于数据清理,这个类就是水池tank。

具体操作流程如图3.3所示。第一行代码就是生成一个蓄水池tank。蓄水池的输入必须也是一个水泵类型的类。也就是说,该输入类必须有一个被实现的函数叫做go(),而且通过调用go(),可以获得pandas.DataFrame类型的输出。这个输出是从哪里来的呢?答:来自原始水泵pm。

如何清洗深度学习的数据集里的数据_如何清洗深度学习的数据集里的数据_03

图3.3:初始化一个蓄水池tank,用于数据清理

这很像一个真实的污水处理厂,水泵pump负责将水抽上来,然后放在一个蓄水池tank里。请注意,这不是一个静态的蓄水池,而是动态的。如果不对tank做任何设定,那么有多少水从pump流入tank,就会有多少水从tank的go()出来。当然,这显然不是人们想要的。我们需要在蓄水池tank这里完成一系列的水处理操作。而第一个需要完成的操作,就是将原来的连续型ArrDelay变量,变成一个0-1离散型。应该如何完成呢?

如何清洗深度学习的数据集里的数据_如何清洗深度学习的数据集里的数据_04

 图3.4:通过app对ArrDelay做函数变换

前面提到,ArrDelay这个变量非常重要,它描述的是一个重要的业务现象:航班到达延误。但是,这个变量的分布非常厚尾。遗憾的是,这个变量取值有正(延误)也有负(提前到达),因此没法直接采用对数变换。因此,这里考虑一个奇葩的变换,希望达到两个目的。第一、部分保留其绝对值信息,但是改善其尾部分布的厚度;第二、保留正负号信息。因此,考虑一个变换:sign(x)*log(1+|x|)。该函数可以在Python中可以通过严格的def定义。也可以通过lambda函数来快速便捷地定义一个。然后,目标是将这个函数作用(apply)到ArrDelay这个变量上。那么新的ArrDelay就生成了,旧的被替代了。在clubear的帮助下,这可以非常轻松地实现。代码见图3.4。变换后对该变量的各种统计量再做计算检查,结果图3.5所示。从中可见,该变量的峰度已经变小了很多。

如何清洗深度学习的数据集里的数据_Time_05

图3.5:变换后的ArrDelay的各种统计量

从上面的演示可以看到,通过调用tank的app()函数,可以将一个lambda函数作用在原来的ArrDelay变量上,从而实现变量的转换。tank可以接受任何对标量合理定义的函数,既包括自定义的各种函数,也包括Python各种函数库中定义的函数(例如:numpy中的各种函数)。但是,如果调用Python函数库中的函数,请注意一定要提前import。

接下来关注一下Cancelled这个变量,它的峰度值非常高(见图3.1),因此值得关注。通过动态直方图做一个初步的分析,如图3.6所示。从中可以看到,该变量是一个0-1型变量。绝大多数情况下取值为0(航班没有被取消),这部分样本占比为98.11%,而取值为1的样本仅占1.89%左右。同样的问题也在Diverted中出现。样本的0-1分布严重不均衡是造成过高峰度的主要原因。

如何清洗深度学习的数据集里的数据_Time_06

 图3.6:对Cancelled变量接受tank处理前的频数分析

请注意,图3.6中做的检查是对pump做的,也就是还没有被tank处理过的污水。从中发现了不到2%的样本Cancelled状态为1。如果,将pm改为tk,对tk输出的数据再做检查,会发现1全部消失了,只有一个类0还存在(见图3.7)。这是为什么呢?

如何清洗深度学习的数据集里的数据_如何清洗深度学习的数据集里的数据_07

图3.7:对Cancelled变量接受tank处理后的频数分析

原因是这部分数据被tank自动过滤掉了,这是为什么?tank会自动检查每一个数值型变量,如果发现不合理取值(例如:numpy.nan),就会删除整条观测。tank在这个问题上非常的严格,因为它的输出将直接用于建模分析了,因此要求从头tank输出的水质必须非常清澈。但是,这并不能解释Cancelled=1的样本为什么消失了。因为从数据的角度看,Cancelled=1是一个非常合理的数值型观测,没有被删除的可能。为此,截取一段从pump出来但还没有被tank处理的数据仔细观察分析一下,如图3.8所示。

如何清洗深度学习的数据集里的数据_如何清洗深度学习的数据集里的数据_08

图3.8:基于pump数据分析Cancelled=1的样本情况

从中不难发现原因。原来Cancelled=1表示航班被取消,而航班一旦被取消,就没法定义一系列的变量(例如:实际延误时间ArrDelay)。因此,这些变量的取值会被设置为numpy.nan。而tank认为这些字段(例如:ArrDelay)是数值型字段,是不能缺失的,因此将这部分样本删除了。同样的问题也发生在Diverted(改道)上,删除这部分样本似乎非常合理。 

不难获得一个结论,如果分析目标是对航班到达延误,那么Cancelled和Diverted都不是好的解释性变量。此外,各种Delay相关的变量(例如:CarrierDelay)也都不是好的解释性变量。原因很简单,这里研究目的是:预测。因此,需要用事前(航班到达是否延误情况知晓之前)就知道的指标,预测事后指标。而各种Delay跟ArrDelay一样都是事后指标。例如,一旦航班发生天气延误(WeatherDelay>0),那显然到达ArrDelay>0就会发生,因为天气延误是到达延误的一个特例。此外两个关于时间消耗ElapsedTime和Taxi相关的指标(ActualElapsedTime,CRSElapsedTime,TaxiIn,还有TaxiOut)也有类似问题,需要删除。因此,需要抛弃一系列的不相干字段,减轻tank的工作压力。代码如图3.9所示。不难发现,这些被drop的变量消失了。

如何清洗深度学习的数据集里的数据_数据_09

 图3.9:对抛弃大量字段后的tank做描述统计分析

对Distance做一个对数变换后,航空数据中的定量变量就被基本上清理干净了。可以对清理后的数据也做一个check,代码结果如图3.10所示。所有变量的峰度都被控制在一个比较小的水平上了,其他各个统计量的检查结果也没有发现任何异常,因此可以认为对定量数据的清理工作告一段落。

如何清洗深度学习的数据集里的数据_字段_10

图3.10:对Distance做对数变换后的tank做描述统计分析

3.2、清理字符型变量 

接下来研究字符型字段的清理。首先结合航空数据各个字段的实际含义(见第2章第2.1节),可以快速删除以下变量,简单解释如下。

1.首先删除的是:AirTime,这是一趟航班在空中的实际滞留时间。该指标如果被看做是一个数值型变量有大量缺失。在不缺失的情况下,它严格等于实际消耗时间ActualElapsedTime减去起飞滑行时间TaxiOut,再减去降落滑行时间TaxiIn。此时,AirTime所包含的信息,完全可以被ActualElapsedTime,TaxiOut,以及TaxiIn线性表出。同ActualElapsedTime一样,这是一个事后指标,没法用于做预测性模型。

2. CancellationCode是在解释Cancelled的原因,因此绝大多数情况下,它的取值不是NA,就是空白。再考虑到之前删除Cancelled原因,这个变量也应该删除。

3. 起飞机场Origin和降落机场Dest其实可能是非常有用的指标。例如,通过匹配更多的信息,可以了解各个机场的大小,以及所在城市的经济指标,并因此支撑更丰富的分析。但是,作为本章的案例演示而言,这似乎过于复杂。如果把这两个指标看作是简单的定性变量,它们的取值水平又太多了。如果转化成哑变量,会消耗过多的自由度,因此暂时也将他们删除。

4. 飞机尾部编号TailNum也是同样的问题。本身这个编号可能是非常有意义的,通过搜索匹配该编号,能够知道该飞机的型号、服役年限等一系列信息。但是,这涉及到非常大的额外工作,不是本书的重点。但是,如果简单地把它处理成离散型变量,它所产生的的水平个数非常高(高达1万+)。因此,也将该变量暂时删除。

在clubear的帮助下,可以通过tank的drop参数将他们全部删除。同样的操作也可以在pump阶段解决。具体代码如图3.11所示。

如何清洗深度学习的数据集里的数据_字段_11

图3.11:经历drop后tank输出的字符型数据 

从图3.11可见,剩下的变量里有大量的Delay变量。以UniqueCarrier为例,通过check的table函数详细观察一下它的取值情况,如图3.12所示。

如何清洗深度学习的数据集里的数据_大数据中数据清理怎么做的_12

图3.12:对UniqueCarrier的详细频数分析

3.3、处理各种Time变量

如果你仔细地学习了前一章第2.1节对航空数据的介绍,你会发现本章前面对各种Time相关的变量(例如:DepTime)处理方式是有问题的。这些字段被简单粗暴地当成数值型了。当然,从纯粹检查数据类型设置是否合理的角度,这样做没问题。但是,如果要做严格的模型分析,那就大错特错了。如果你实在想不起来这些变量是怎么回事,可以通过图3.13的代码展示一下:

如何清洗深度学习的数据集里的数据_Time_13

图3.13:各种Time相关的变量展示

从图中可以看到实际到达时间ArrTime,计划到达时间CRSArrTime,实际离港时间DepTime,以及计划到达时间CRSDepTime,都是4位数的整数。前面两位表示的是小时(hh),而后两位是分钟(mm),都是当地时间(local time)。因此,应该把这些数据都用他们的小时替代(假设本次分析忽略分钟),并计算一下它们的相关系数,代码如图3.14。

如何清洗深度学习的数据集里的数据_Time_14

 图3.14:为各个本地Time相关变量变成小时

不难看到,它们的相关系数非常高,因此信息重叠程度很大。为了演示的简单方便,这里就只保留计划离港时间CRSDepTime,因为这个变量的直观性最好。当我们平时买机票时,对机票时间的第一个感觉是:计划离港时间。当然,这绝不意味着其他几个变量毫无用处。严格地讲,其他变量对于预测ArrDelay是否有用,应该让后面的模型自己去判定。这里的做法并不十分稳妥,纯粹是为了简化问题,便于展示tank的各个功能而已。

请注意,最终我们获得的tk是一个非常棒的水池,里面能够源源不断流出我们需要的高质量的“清水”。所有多余的变量都被抛弃,留下的变量被做出合理变化和处理,而且生成了新的变量,所有数值型字段都不是缺失的。可以将这个阶段性成果保存。当然,你可以直接存这个notebook文件(以.ipynb格式)的格式,或者python文件(以.py格式)。但是,这并不令人满意。因为这个文件里面有太多的交互分析流程和结果(例如:大量的stats和table)。这些交互分析非常重要,他们恰恰是再抽样方法的美丽所在。因为它们的存在,才能够快速形成数据清理方案,并最终通过pump和tank实现。

如何清洗深度学习的数据集里的数据_大数据中数据清理怎么做的_15

 图3.15:生成数据清理文件pmconfig.py

但是,一旦这个数据清理方案形成了,我们并不希望把这些过程也存下来。因为将来分析的时候,加载这些交互式分析的代码非常耗时。而接下来的(例如)模型分析所需要的仅仅是pump和tank而已,其他的代码都是多余的。因此,我们需要一个类似于与config文件类似的记录,将所有tk相关的pump和参数全部记录在一个叫做pmconfig.py的文件里。代码如图3.15所示。

请注意,pmconfig.py本质上就是一个.py文件(见图3.16),可以直接执行。这个而文件设置了一系列的pump和tank,所有的设置参数都跟前文的tk保持一致,并最终以cpm的名字输出(configured pump)。cpm是个tank,因此在这个基础上还可以做进一步的修改。但是,假设前面的数据清理工作到位,我们预期关于cpm的进一步调制修改应该是非常少的。

如何清洗深度学习的数据集里的数据_如何清洗深度学习的数据集里的数据_16