饼图与柱状图的topN处理

来源:互联网 发布:新媒体沟通软件 编辑:程序博客网 时间:2024/05/01 04:54

做数据分析的时候,很多业务都需要展示数据的前N个数值、剩余的汇总为“其他”,比如全国人口分布top 10。一般的业务场景都需要支持单一维度的饼图和可以多维度的柱状图。
这样的数据处理可以放在DB层,用order by加上limit的sql来完成,但是这样就依赖于底层DB的类型,一旦有新增的数据底层则必须为此再写一套处理逻辑;另一个选择就是独立成模块,规定统一的输入格式,为这套数据格式写算法做topN的统计。我选择了后者,这样可以应对DB、api等不同来源的数据。
单一维度的饼图很好处理,最简单的方式就是排序后取出前N个数据、剩下的求和就行:

饼图的算法核心部分(伪代码):

   private Array getTopNData(quota, dimensions, data, n) {        sortedData = getSortPieData(quota, data);        allTotal = topTotal = 0;        foreach (sortedData as key => value) {            allTotal += value;            if (count(topDataResult) < n) {                topTotal += value;                topDataResult[key] = value;            }        }        //topN之外的其他值归并为图中第N+1个元素        other = getOtherPieData(quota, dimensions, allTotal-topTotal);        topDataResult[“other”] = other;        return topDataResult;    }

而柱状图的多维度数据就比较麻烦。原始数据的维度数量、维度名称、指标都是不确定的,而且还有未经分组汇总的情况(也就是数据没有group by的情况),所以在编码时不能限定维度数量。
原始数据格式example:
{“科目”=>”科目一”, “成绩”=>”合格”, “驾校”=>”x”, “人数”=>”26”},
{“科目”=>”科目一”, “成绩”=>”合格”, “驾校”=>”x”, “人数”=>”49”},
{“科目”=>”科目二”, “成绩”=>”合格”, “驾校”=>”y”, “人数”=>”13”}…
x轴是“驾校”,指标是“人数”,维度列是2个:“科目”、“成绩”
核心算法伪代码:

private Array getTopData(horizontal, quota, data, n) {    if(hasManyDimensions()) {//多维度单指标的情况            validDimensions = getDimensionalityName(horizontal, quota, data);//维度列的列名:“科目”、“成绩”            validDimensionsValues;//维度列的值的组合:“科目一_合格”、“科目二_合格”等等            valuesOfValidDimensions;//维度列的值与列名的映射关系:“科目一”=>”科目”、“合格”=>”成绩"            detail4Horizontal;//维度列值_x轴列值 为键的数据:”科目一_合格_x”=>75、”科目二_合格_y”=>13            total4Horizontal;//x轴列值 为键的数据:“x”=>249、”y=>54"            for (eachData : data) {                nowDimensionalityValues = "";                for (key : validDimensions) {                    value = eachData.get(key);                    valuesOfValidDimensions.put(value, key);                    nowDimensionalityValues = nowDimensionalityValues + "_" + value;                }                nowDimensionalityValues = nowDimensionalityValues.substring(1, nowDimensionalityValues.length());//维度列的值的拼接                validDimensionsValues.add(nowDimensionalityValues);                nowHorizontalData = eachData.get(horizontal);//x轴列的值                if (!total4Horizontal.containsKey(nowHorizontalData)) {                    total4Horizontal.put(nowHorizontalData, 0);                }                quotaValue = getValidIndicatorValue(eachData, quota);                total4Horizontal.put(nowXData, total4Horizontal.get(nowHorizontalData) + quotaValue);                detail4Horizontal.put(nowDimensionalityValues + "_" + nowHorizontalData, quotaValue);            }            topAndOtherList = getTopAndOtherList(total4Horizontal, n);//获取排序后的数据            topXDataNameList = topAndOtherList.get("top");            otherXDataNameList = topAndOtherList.get("other");            topDataResult = getTopResult(validDimensionsValues, quota, horizontal, otherXDataNameList, topXDataNameList, detail4Horizontal, valuesOfValidDimensions);            return topDataResult;    }}

之所以制造下面这5个数组或List,是为了排序和对汇总后的数据按维度来拆分:

validDimensions//维度列的列名
validDimensionsValues//维度列的值的组合
valuesOfValidDimensions//维度列的值与列名的映射关系
detail4Horizontal//维度列值_x轴列值 为键的数据
total4Horizontal//x轴列值 为键的数据

getTopResult()实现了对“其他”数据的按维度拆分,算法如下:

for (value : validDimensionsValues) {            other4EachValidDimensionsKey.put(value, 0L);//初始化            for (xDataValue : otherXDataNameList) {                if (detail4Horizontal.containsKey(value + "_" + horizontal)) {                    other4EachValidDimensionsKey.put(value, other4EachValidDimensionsKey.get(value) + detail4Horizontal.get(value + "_" + horizontal));                }            }        }//装载topN之外的"其他"汇总数据        for (entry : other4EachValidDimensionsKey.entrySet()) {            nowValues = entry.getKey().split("_");//将维度列的值的组合分解成数组            for (value : nowValues) {                key = valuesOfValidDimensions.get(value);//从映射关系中找到维度列的列名                other.put(key, value);            }            other.put(horizontal, "其他");            other.put(quota, entry.getValue());            topDataResult.add(other);        }

不过如果数据的值中包含了“_”,这个算法的分隔符就要更换了。

0 0
原创粉丝点击