使用 summary 函数生成大规模数据统计信息

1. 业务场景

对数据集深入分析前,可以通过 summary 函数快速了解数值型列的概括信息,例如,平均值、标准差、最小值、最大值和百分位数,这有助于初步了解数据的分布情况和特征。在探索性数据分析(EDA)过程中,summary 函数有助于识别异常值、离群值和数据分析的偏态。通过观察统计信息,可以进一步决定数据清洗和数据转换的策略。例如,如果某个数值型列的最大值或最小值远离其平均值,则可能存在异常值或离群值;如果某个数值型列的标准差较大,则表示可能存在数据分布的偏态。

2. 函数简介

2.1. 语法

summary(X,[interpolation='linear'],[characteristic=`avg`std],[percentile=[25,50,75]],[precision=0.01],[partitionSampling=1.0])

2.2. 参数介绍

  • X:内存表、DFS 表或由 sqlDS 函数生成的数据源。
  • interpolation:计算百分位采用的插值方法,可选:linear、nearest、lower、higher、midpoint。
  • characteristic:需要输出的统计特征,可选:avg 和 std。
  • percentile:需要输出的百分位数。
  • precision:百分位计算精度。
  • partitionSampling:针对分区表设计的参数,表示参与进行数据统计的分区数量。

有关 summary 函数的用法、示例及注意事项,参考 DolphinDB 用户手册中该函数的详细介绍。

3. 使用示例

3.1. 生成内存表统计信息

n=2022
data=1..2022
value=take(1..3,n)
name=take(`APPLE`IBM`INTEL,n)
t=table(data,value,name);
res = summary(t, precision=0.001);

3.2. 生成 DFS 表统计信息

db = database(dbName,VALUE,`A`B`C`D)
...
pt = loadTable(dbName, tableName)
// 生成 DFS 表统计信息,同时指定分区采样率为 0.5
re = summary(pt,`linear,`avg`std, [25,50], 0.001, 0.5)

示例中的 DFS 表采用值分区方式,拥有 A、B、C 和 D 共 4 个分区。分区采样率为 0.5,结果将随机输出 2 个分区的数据统计信息。

3.3. 使用 SQL 语句作为输入数据源

ds = sqlDS(<select * from table>)
re = summary(ds, type)

3.4. 大规模数据计算

pandas 的 describe 函数要求所有的计算过程都在内存中完成,因此其无法处理数据规模超出内存限制的情况。为解决大规模数据的计算问题,summary 对数据分批计算,即每次只读取部分数据到内存。对于百分位的计算,summary 每次只计算一个数值列而非全量数据。同时,summary 还提供了分区采样的功能,即不统计全量数据,而是通过统计部分分区数据来反映总体数据的特征。

以下是用 summary 函数计算大规模数据统计信息的示例:

基本信息

  • 数据集:hits.csv.gz (约 76G)
  • 内存:32G
  • DolphinDB 版本:2.00.10
  • 分区采样数:总分区数的一半
  • 耗时:43s
  • 脚本:见附件

4. 与 Python 性能对比

使用 DolphinDB 的 summary 与 pandas 中的 describe 函数在 4G 数据集下进行性能对比。

基本信息

  • 数据集:TradesData.csv(见附件)
  • DolphinDB 版本:2.00.10 社区版
  • pandas 版本:v1.5.3

DolphinDB 脚本:

login('admin','123456')
dataDir = "/path/to/TradesData.csv"
dbName = "dfs://summary"
schematable = table(`symbol`date`second`price`size`g127`corr`cone`ex as name, `SYMBOL`DATE`SECOND`DOUBLE`INT`INT`INT`STRING`CHAR as type)
if(existsDatabase(dbName)){
		dropDatabase(dbName)
	}
db = database(directory=dbName, partitionType=HASH, partitionScheme=[STRING, 10], engine="TSDB", chunkGranularity="DATABASE")
loadTextEx(db, "pt", "symbol", dataDir, sortColumns = `symbol`size, schema=schematable)
pt = loadTable(dbName, "pt")
timer{
	summary(pt)
}

summary 统计结果:

nameminmaxnonNullCountcountavgstdpercentile
price0.000119999.99099,999,99999,999,99943.9381255.7168[20.430, 34.979, 53.310]
size1.00009000000.00099,999,99999,999,999285.84524622.1386[100.000, 100.000, 200.000]
g1270.00.099,999,99999,999,9990.00.0[0.000, 0.000, 0.000]
corr0.012.00099,999,99999,999,9990.0010.097[0.000, 0.000, 0.000]
ex65.00088.00099,999,99999,999,99978.7994.792[78.000, 79.999, 81.000]

Python 代码:

import pandas as pd
import numpy as np
import time

def to_int(s):
    return int(s[0])

data = np.loadtxt('/path/to/TradesData.csv', delimiter=',', skiprows=1, usecols=(3, 4, 5, 6, 8), converters={8: to_int})
df = pd.DataFrame(data, columns=['price', 'size', 'g127', 'corr', 'ex'])
startTime = time.time()
df.describe()
endTime = time.time()
print("duration", endTime - startTime)

4.1. 对比结果

summarydescribe
8.053s13.409s

在示例中,summary 函数相比于 describe 函数性能提升了 40%。对于分区表,summary 函数采用 MapReduce 框架以分区为粒度分批并行地进行计算,对比 pandas 中 describe 函数的串行计算,在保障计算结果精确的同时,summary 函数具有更快的计算速度。

5. 实现原理

5.1. min、max、count、avg、std 计算

对于数据量相对较小的内存表,直接计算 min、max、count、avg、std 值。

对于分区表,以分区为单位使用 MapReduce 架构进行计算:

  • 对于 min、max 和 count 的计算,先用 Map 计算每个分区的值,然后根据 Map 的计算结果在 Reduce 时计算 min,max 和 count;
  • 对于 avg 的计算,需要在 MapReduce 中计算数据的和 sum,然后根据 sum 和 count 值计算 avg;
  • 对于 std 的计算,还需要在 MapReduce 中计算数据的和 sum 以及平方和 sum²,然后根据 sum、sum² 和 count 计算 std。

5.2. 百分位计算

百分位数表述数据从小到大时,某个值在数据中的相对位置。第 K 个百分位表示位于第 K% 的值。百分位数以输入分数相同的度量单位表示:例如,如果分数指的是物品的价格,相应的百分位数将以相应的货币单位表示。

summary 的百分位计算取值方式包括:linear、lower、higher、nearest、midpoint。

summary 以数据的最小值和最大值作为初始范围,并将该范围划分为数个大小相等的区间,统计数据落在各个区间的数量,再根据修改结果调整区间范围,迭代直至精度满足要求。

summary 采用 Iterate-MapReduce 框架实现对百分位的计算,计算过程如下:

  1. Map:根据数据范围均等划分区间,统计各个区间内元素的个数。
  2. Reduce:将 Map 过程的结果相加。
  3. MapFinal:确定每个查询点所在的区间。其中每个查询点赋一个 askRange,初始为全局最大最小值。对于 askRange 和当前 range 相同的查询点更新其 askRange。迭代计算直至 askRange 上界与下界之差达到精度要求。

summary

上图为算法示例,数据总共有 6 个元素,初始范围为 [1, 6],要求以 linear 的方式计算第 50 百分位数。第 50 百分位的分位点刚好为数据正序排序后的第 3 个元素,因此,数据正序排序后的第 3 个元素即为 linear 的结果。图中每次迭代将会把 askRange 均等划分为 2 个区间,并统计区间中元素的数量,取第 3 个元素所在区间为新的 askRange,如果此时 askRange 还没达到精度要求则会继续进行划分。当精度达到要求时退出迭代,取所求结果为 askRange 的上界 Xi 和下界 Xi+1 的平均值(图中示例为 3)。

6. 小结

summary 函数可用于:

  • 生成内存表、内存分区表以及 DFS 表的描述性统计数据,统计数据集的集中趋势、分散和行列的分布情况。
  • 快速提供有关数据中数值列的汇总统计信息,包括:最小值 min、最大值 max、数值列非空值总数 nonNullCount、数值列总数 count、平均值 avg、标准差 std、百分位数 percentile。
  • 计算超出内存限制的数据的统计信息,并在同等数据规模下,表现出比 pandas 的 describe 函数更高的性能。