3.6 数据化运营要抽样还是全量数据

抽样是从整体样本中通过一定的方法选择一部分样本,抽样是数据处理的基本步骤之一,也是科学实验、质量检验、社会调查普遍采用的一种经济有效的工作和研究方法。

3.6.1 什么时候需要抽样

抽样工作在数据获取较少或处理大量数据比较困难的时代非常流行,这主要有以下几方面原因:

数据计算资源不足。计算机软硬件的限制是导致抽样产生的基本原因之一,尤其是在数据密集的生物、科学工程等领域,不抽样往往无法对海量数据进行计算。

数据采集限制。很多时候抽样从数据采集端便已经开始,例如做社会调查必须采用抽样方法进行研究,因为根本无法对所有人群做调查。

时效性要求。抽样带来的以局部反应全局的思路,如果方法正确,可以以极小的数据计算量来实现对整体数据的统计分析,在时效性上会大大增强。

如果存在上述条件限制或有类似强制性要求,那么抽样工作仍然必不可少。但是在当前数据化运营的大背景下,数据计算资源充足、数据采集端可以采集更多的数据并且可以通过多种方式满足时效性的要求。抽样工作是否就没有必要了?其实不是的,即使上述限制条件都满足,还有很多场景依然需要通过抽样方法来解决具体问题:

通过抽样来实现快速的概念验证。数据工作中可能会包括创新性或常识性项目,对于这类项目进行快速验证、迭代和交付结论往往是概念验证的关键,通过抽样方法带来的不仅是计算效率的提升,还有前期数据准备、数据预处理、算法实现等各个方面的开发,以及服务器、硬件的配套方案的部署等内容的可行性、简单性和可操作性。

通过抽样来解决样本不均衡问题。在3.4节中,我们提到了通过欠抽样、过抽样以及组合/集成的方法解决不均衡的问题,这个过程就用到了抽样方法。

无法实现对全部样本覆盖的数据化运营场景。典型场景包括市场研究、客户线下调研分析、产品品质检验、用户电话满意度调查等,这些场景下无法实现对所有样本的采集、分析、处理和建模。

定性分析的工作需要。在定性分析工作中,通常不需要定量分析时的完整假设、精确数据和复杂统计分析过程,更多的是采用访问、观察和文献法收集资料并通过主观理解和定性分析找到问题答案,该过程中主要依靠人自身的能力而非密集的计算机能力来完成研究工作。如果不使用抽样方法,那么定性分析将很难完成。

3.6.2 如何进行抽样

抽样方法从整体上分为非概率抽样和概率抽样两种。非概率抽样不是按照等概率的原则进行抽样,而是根据人类的主观经验和状态进行判断;概率抽样则是以数学概率论为基础,按照随机的原则进行抽样。本节介绍的抽样方法属于概率抽样。

(1)简单随机抽样

该抽样方法是按等概率原则直接从总中抽取n个样本,这种随机样本方法简单,易于操作;但是它并不能保证样本能完美代表总体,这种抽样的基本前提是所有样本个体都是等概率分布的,但真实情况却是很多数样本都不是或无法判断是否是等概率分布的。在简单随机抽样中,得到的结果是不重复的样本集,还可以使用有放回的简单随机抽样,这样得到的样本集中会存在重复数据。该方法适用于个体分布均匀的场景。

(2)等距抽样

等距抽样是先将总体中的每个个体按顺序编号,然后计算出抽样间隔,再按照固定抽样间隔抽取个体。这种操作方法易于理解、简便易行,但当总体样本的分布呈现明显的分布规律时容易产生偏差,例如增减趋势、周期性规律等。该方法适用于个体分布均匀或呈现明显的均匀分布规律,无明显趋势或周期性规律的数据。

(3)分层抽样

分层抽样是先将所有个体样本按照某种特征划分为几个类别,然后从每个类别中使用随机抽样或等距抽样的方法选择个体组成样本。这种操作方法能明显降低抽样误差,并且便于针对不同类别的数据样本进行单独研究,因此是一种较好的实现方法。该方法适用于带有分类逻辑的属性、标签等特征的数据。

(4)整群抽样

整群抽样是先将所有样本分为几个小群体集,然后随机抽样几个小群体集来代表总体。这种操作方法跟之前的3种方法的差异点在于该方法抽取的是小群体集,而不是每个数据个体本身。该方法虽然简单易行,但是样本的分布受限于小群体集的划分,抽样误差较大。这种方法适用于小群体集的特征差异比较小,并且对划分小群体集有更高要求。

3.6.3 抽样需要注意的几个问题

1.数据抽样要能反映运营背景

数据能正确反映运营背景看起来非常简单,但实际上需要数据工作者对于运营环节和流程非常熟悉才有可能实现。以下是常见的抽样不能反映运营背景的情况:

数据时效性问题:例如使用过时的数据(例如1年前的数据)来分析现在的运营状态。

缺少关键因素数据:没有将运营分析涉及的主要因素所产生的数据放到抽样数据中,导致无法根据主要因素产生有效结论,模型效果差,例如抽样中没有覆盖大型促销活动带来的销售增长。

不具备业务随机性:有意/无意多抽取或覆盖特定数据场景,使得数据明显趋向于特定分布规律,例如在做社会调查时使用北京市的抽样数据来代表全国。

没有考虑业务增长性:例如在成长型公司中,公司的发展不都是呈现线性趋势,很多时候会产生指数趋势。这时需要根据这种趋势来使业务满足不同增长阶段的分析需求,而不只是集中于增长爆发区间。

没有考虑数据来源的多样性:只选择某一来源的数据做抽样,使得数据的分布受限于数据源。例如在做各分公司的销售分析时,仅将北方大区的数据纳入其中做抽样,而忽视了其他大区的数据,其结果必然有所偏颇。

业务数据可行性问题:很多时候,由于受到经费、权限、职责等方面的限制,在数据抽样方面无法按照数据工作要求来执行,此时要根据运营实际情况调整。这点往往被很多数据工作者忽视。

2.数据抽样要能满足数据分析和建模需求

数据抽样必须兼顾后续的其他数据处理工作,尤其是分析和建模需求。这时需要注意以下几个方面:

(1)抽样样本量的问题

对于大多数数据分析建模而言,数据规模越大,模型拟合结果越准确。但到底如何定义数据量的大小,笔者根据不同类型的数据应用总结为以下几个维度:

❑ 以时间为维度分布的,至少包含 一个能满足预测的完整业务周期。例如做月度销售预测的,至少包含12个月的数据;做日销售预测的,至少包含30天的数据,如果一天中包含特定周期,则需要重复多个周期。同时,时间性特征的要充分考虑季节性、波动性、节假日等特殊规律,这些都要尽量包含在抽样数据中。

❑ 做预测(包含分类和回归)分析建模的,需要考虑特征数量和特征值域(非数值型)的分布,通常数据记录数要同时是特征数量和特征值域的100倍以上。例如数据集有5个特征,假如每个特征有2个值域,那么数据记录数需要至少在1000(100× 5× 2)条以上。

❑ 做关联规则分析建模的,根据关联前后项的数量(每个前项或后项可包含多个要关联的主体,例如品牌+商品+价格关联),每个主体需要至少1000条数据。例如只做单品销售关联,那么单品的销售记录需要在1000条以上;如果要同时做单品+品牌的关联,那么需要至少2000条数据。

❑ 对于异常检测类分析建模的,无论是监督式还是非监督式建模,由于异常数据本来就是小概率分布的,因此异常数据记录一般越多越好。

以上的数据记录数不是固定的,在实际工作时,如果没有特定时间要求,笔者一般会选择一个适中的样本量做分析,此时应综合考虑特征数、特征值域分布数、模型算法适应性、建模需求等;如果是面向机器计算的工作项目,一般会选择尽量多的数据参与计算,而有关算法实时性和效率的问题会让技术和运维人员配合实现,例如提高服务器配置、扩大分布式集群规模、优化底层程序代码、使用实施计算的引擎和机制等。

(2)抽样样本在不同类别中的分布问题

做分类分析建模问题时,不同类别下的数据样本需要均衡分布,有关数据样本均衡分布的更多话题,参见3.4节。

抽样样本能准确代表全部整体特征:

❑ 非数值型的特征值域(例如各值频数相对比例、值域范围等)分布需要与总体一致。

❑ 数值型特征的数据分布区间和各个统计量(如均值、方差、偏度等)需要与整体数据分布区间一致。

❑ 缺失值、异常值、重复值等特殊数据的分布要与整体数据分布一致。

异常检测类数据的处理:

❑ 对于异常检测类的应用要包含全部异常样本。对于异常检测类的分析建模,本来异常数据就非常稀少,因此抽样时要优先将异常数据包含进去。

❑ 对于需要去除非业务因素的数据异常,如果有类别特征需要跟类别特征分布一致;如果没有类别特征,属于非监督式的学习,则需要与整体分布一致。

3.6.4 代码实操:Python数据抽样

本示例中,将使用random包及自定义代码实现抽样处理。数据源文件data2.txt、data3. txt和data4.txt位于“附件-chapter3”中,默认工作目录为“附件-chapter3”(如果不是,请切换到该目录下,否则会报类似“IOError: File data3.txt does not exist”的错误)。完整代码如下:

        import random  # 导入标准库
        import numpy as np  # 导入第三方库
        # 简单随机抽样
        data = np.loadtxt('data3.txt')  # 导入普通数据文件
        data_sample = random.sample(data, 2000)  # 随机抽取2000个样本
        print (data_sample[:2])  # 打印输出前2条数据
        print (len(data_sample))  # 打印输出抽样样本量
        # 等距抽样
        data = np.loadtxt('data3.txt')  # 导入普通数据文件
        sample_count = 2000  # 指定抽样数量
        record_count = data.shape[0]  # 获取最大样本量
        width = record_count / sample_count  # 计算抽样间距
        data_sample = []  # 初始化空白列表,用来存放抽样结果数据
        i = 0  # 自增计数以得到对应索引值
        while len(data_sample) <= sample_count and i * width <= record_count -1:  # 当样
            本量小于等于指定抽样数量并且矩阵索引在有效范围内时
            data_sample.append(data[i * width])  # 新增样本
            i += 1  # 自增长
        print (data_sample[:2])  # 打印输出前2条数据
        print (len(data_sample))  # 打印输出样本数量
        # 分层抽样
        # 导入有标签的数据文件
        data2 = np.loadtxt('data2.txt')  # 导入带有分层逻辑的数据
        each_sample_count = 200  # 定义每个分层的抽样数量
        label_data_unique = np.unique(data2[:, -1])  # 定义分层值域
        sample_list = []  # 定义空列表,用于存放临时分层数据
        sample_data = []  # 定义空列表,用于存放最终抽样数据
        sample_dict = {}  # 定义空字典,用来显示各分层样本数量
        for label_data in label_data_unique:  # 遍历每个分层标签
            for data_tmp in data2:  # 读取每条数据
                if data_tmp[-1] == label_data:  # 如果数据最后一列等于标签
                    sample_list.append(data_tmp)  # 将数据加入到分层数据中
            each_sample_data = random.sample(sample_list, each_sample_count)  # 对每层数据
                都随机抽样
            sample_data.extend(each_sample_data)  # 将抽样数据追加到总体样本集
            sample_dict[label_data] = len(each_sample_data)  # 样本集统计结果
        print (sample_dict)  # 打印输出样本集统计结果
        # 整群抽样
        data3 = np.loadtxt('data4.txt')  # 导入已经划分好整群的数据集
        label_data_unique = np.unique(data3[:, -1])  # 定义整群标签值域
        print (label_data_unique)  # 打印输出所有整群标签
        sample_label = random.sample(label_data_unique, 2)  # 随机抽取2个整群
        sample_data = []  # 定义空列表,用来存储最终抽样数据
        for each_label in sample_label:  # 遍历每个整群标签值域
            for data_tmp in data3:  # 遍历每个样本
                if data_tmp[-1] == each_label:  # 判断样本是否属于抽样整群
                    sample_data.append(data_tmp)  # 样本添加到最终抽样数据集
        print (sample_label)  # 打印输出样本整群标签
        print (len(sample_data))  # 打印输出总抽样数据记录条数

整个示例代码用空行分为5部分。

第一部分导入需要的库,这里用到了Python内置标准库random以及第三方库Numpy,前者用于做随机抽样,后者用于读取文件并作数据切片使用。

第二部分实现了简单随机抽样。首先通过Numpy的loadtxt方法读取数据文件;然后使用Random库中的sample方法做数据抽样,指定抽样总体数据为data,抽样数量为2000;最后打印输出前2条数据和总抽样样本量。返回结果如下:

        [array([ -7.66339337, -10.13735724,    5.57419663,    3.27726596,    5.06304001]),
            array([ -4.24826325,  10.1271918 ,   3.83244343,   1.82416448,  -5.94805592])]
        2000

第三部分实现了等距抽样。

首先使用Numpy的loadtxt方法读取数据文件;然后指定抽样样本量为2000,并通过读取原始数据的形状找到最大样本量边界,这可以用来作为循环的终止条件之一;接着通过最大样本量除以抽样样本量得到抽样间距;

建立一个空列表用于存储最终抽样结果数据,通过一个变量i做循环增长并用来做索引递增,然后进入到抽样条件判断过程:

❑ 当样本量小于等于指定抽样数量并且矩阵索引在有效范围内时,若要做处理,需要注意的是索引从0开始,因此需要用最大数量值减去1作为循环边界,否则会报索引溢出错误。

❑ 通过列表的append方法不断追加通过间距得到的新增样本,在本节后面的方法中还会提到用于列表追加的extend方法,前者用于每次追加1个元素,后者用于批量追加多个元素。

❑ i += 1指的是每次循环都增加1,可以写成i = i + 1。

❑ 最后打印输出前2条数据和抽样样本量。返回结果如下:

        [arra y([-3.08057779,   8.09020329,   2.02732982,   2.92353937,  -6.06318211]),
            array([-2.11984871,  7.74916701,  5.7318711 ,  4.75148273, -5.68598747])]
        2000

第四部分实现了分层抽样。

首先使用Numpy的loadtxt方法导入带有分层逻辑的数据。在该示例中,读取的数据文件中包含了分类标签,放在最后一列。该列分类标签用于做分层抽样的标志。

接着通过unique方法获取分层(分类标签)的值域,用于后续做循环处理。

然后分别定义了用于存放临时分层数据、最终抽样数据、显示各分层样本数量的空列表和空字典。

下面进入正式的主循环过程,实现分层抽样:

❑ 遍历每个分层标签,用来做数据的分层划分,数据一共分为2类标签(0和1);

❑ 读取每条数据并判断其标签是否属于特定的分层标签,如果是则将数据加入各分层数据列表中。

❑ 当每个分层标签处理完成后会得到该分层标签下的所有数据,此时使用Python内置的Random库的sample方法进行抽样。由于抽样结果是一个列表,因此这里使用extend(而不是append)批量追加到最终抽样数据列表中。然后对每个分层标签得到的样本数量,通过len方法对列表长度进行统计,并打印输出与各个分层对应的样本数量。结果是每个分层都按照指定数量抽取样本,具体如下:

        {0.0: 200, 1.0: 200}

第五部分实现了整群抽样。

首先使用Numpy的loadtxt方法导入已经划分好整群的数据集。在该示例中,读取的数据文件中的最后一列存放了不同整群的标识,整群一共被划分为4个群组,标识分别为0/1/2/3。

接着通过unique方法获取整群标签的值域,用于基于整群的抽样。打印输出结果如下:

        [ 0.  1.  2.  3.]

然后使用Random的sample方法从整群标签中进行抽样,这里定义抽取2个整群。

最后对所有属于抽取到的整群下的数据进行读取和追加并得到最终样本集,打印输出样本集的整群标签和总样本数量的结果如下:

        [3.0, 1.0]
        502

提示

由于是随机概率抽样,因此读者使用代码抽取到的样本很可能跟书中示例的不一致,这属于正常现象;另外,读者多次随机抽样程序,也可能得到不一样的结果。

上述过程中,需要考虑的关键点是:如何根据不同的数据特点、建模需求、业务背景综合考虑抽样方法,得到最适合的结果。

本小节示例中,主要用了如下几个知识点:

❑ 使用Numpy的loadtxt方法读取数据文件;

❑ 使用内置标准库Random库中的sample方法做数据抽样;

❑ 对列表通过索引做截取、通过len方法做长度统计、通过append和extend做追加等操作;

❑ 字典赋值操作;

❑ 使用Numpy的unique方法获得唯一值;

❑ 通过for和while循环,遍历一个可迭代的对象;

❑ if条件语句的使用,尤其是单条件和多条件判断。