千量数据级别的数据统计分析渲染

千量数据级别的数据统计分析渲染

1.前言

噔楞,一个消息,这个数据按照以下要求统计渲染下,跟老板开会汇报用。不知道领导从哪里搞来的这么多数据,要的还挺急,咋办,整呗!

2.需求

先看下领导给的数据:

  • 数据结构:
css 复制代码
{
    nodes: [
        ...
        {
            id: "44",
            sim_name: "BHS",
            name: "Bahamas",
            region: "Caribbean and Central America",
            resource: "Cereals",
            year: 2012,
            value: 427.395,
            weight: 969.33
        }
        ...
    ],
    edges:[
        ...
        {
            "eid": "e16",
            "source": "4",
            "sourceSimName": "AFG",
            "sourceRegion": "Afghanistan",
            "sourceName": "South Asia",
            "target": "56",
            "targetSimName": "BEL",
            "targetName": "Belgium",
            "targetRegion": "Europe",
            "resource": "Maize",
            "year": "2015",
            "value": "9.049",
            "weight": "11.52",
            "embodiedLand": "4.765",
            "embodiedWater": "348.756"
        },
        ......
    ]
}
  • 统计要求:
    • 在 nodes 数据组中,按 region 分组,计算出每一类 region 下 value 平均值、最大值、最小值、中位值、总和
    • 在 nodes 数据组中,按 region 分组,计算每一年,每一类 region 下 value 平均值、最大值、最小值、中位值、总和
    • 在 edges 数据组中,按 resource 分类,计算每一类 resource 下 value 平均值、最大值、最小值、中位值、总和
    • 在整个数据集中,找到weight的最大值、最小值、中位值

3.分析需求

  • 按照要求可以使用Map进行分类,易于查找
  • 需要统计出数据的平均值、最大值、最小值、中位值、总和,需要初始化一个存储结构
  • 然后循环遍历取值求结果,尽量时间复杂度控制在 O(n)

统计操作数据应该不是很难的,主要是意识到千级别的数据量咔咔处理完数据,再去进行一下性能分析,看看有没有需要优化的点,开干!

4.码代码

4.1 按组统计的,定义好参数和返回值

对于前三个统计信息,可以封装成一个方法实现,

  • 参数:数据源、按照哪个字段进行统计、需要的分类字段
  • 返回值: 分类好的 map
markdown 复制代码
/*
* 数据进行分类完进行统计
* @params 
* data  json对象数组原数据
* static_field 字符串 对某一个字段全部统计
* args: 各类分类字段 一级分类字段 region、resource.....、二级分类字段 year、month.....、三级....
* @return map
*/
const static_classify_data = (data,static_field,...args) => {
    //Todo...
}

4.2 ⭐️重点动态实现多层级分组

核心思想就是若一级分类下没有数据,则创建一个新的 map,若二级分类下没有数据,则继续创建...直到最后一个分类数据下存储统计数据的最大值、最小值...等

ini 复制代码
const static_classify_data = (data,static_field,...args) => {

const map = new Map()

for(let i = 0; i < data.length; i++) {


    let j = 0,temp_map = map,level = j + 1 //读取到分类的层级

    //动态遍历分类字段,确保每层分类上都具备数据
    while(j < args.length && (!temp_map.get(data[i][args[j]]) || level < args.length)){

        // 查找每一分类层级是不是具备数据,有的话继续查找并更新 temp_map
        if(temp_map.get(data[i][args[j]]) && level < args.length){

            temp_map = temp_map.get(data[i][args[j]])

            j = level++

            continue
        }

        // 最后一个分类层级需要初始统计数据结构result_data()
        if(j === args.length - 1) {

            temp_map.set(data[i][args[j]],result_data())

            break
        }

        // 每一分类层级不具备数据,则需要初始一个 map
        temp_map.set(data[i][args[j]],new Map())

        temp_map = temp_map.get(data[i][args[j]])

        j++
    }

    const last_param = args[args.length - 1]

    //获取最后一个分类层级下的数据,是result_data()结构
    const cur_map = temp_map.get(data[i][last_param])

    //对数据进行数字转型
    const value = data[i][static_field]? parseFloat(data[i][static_field]):0

    //对数据进行保留 3 位小数处理
    const value_float3 =  keep_float(value,3)

    //存储数据
    cur_map.values.push(value_float3)

    // 最大值
    cur_map.max = Math.max(cur_map.max,value_float3)

    // 最小值
    cur_map.min = Math.min(cur_map.min, value_float3)

    // 总和
    cur_map.sum += value_float3

    cur_map.sum = keep_float(cur_map.sum,3)

    // 中位数
    cur_map.center = keep_float(get_center(cur_map.values),3)

    // 平均值
    cur_map.average = keep_float(cur_map.sum / cur_map.values.length,3)

    cur_map.static_field = static_field

    args.forEach((item,index) => {
        cur_map[item] = data[i][item]
    })

}

return map

}

4.3 整个数据集按照字段进行统计

实现时注意时间复杂度尽量降级到 O(n)

ini 复制代码
/*
* 全部数据进行统计
* @params 
* data  json对象数组原数据
* static_field 字符串 对某一个字段全部统计
* @return map
*/

const static_data = (data,static_field) => {

    // 判断数据类型
    if(!Array.isArray(data)) {

        console.error('JSON文件内容必须是一个对象数组');

        return;
    }

    const result = result_data()

    for(let i = 0; i < data.length; i++) {

        const value = data[i][static_field]? parseFloat(data[i][static_field]):0

        const value_float3 =  keep_float(value,3)

        result.values.push(value_float3)

        // 最大值
        result.max = Math.max(result.max,value_float3)

        // 最小值
        result.min = Math.min(result.min,value_float3)

        // 总和
        result.sum += value_float3

        result.sum = keep_float(result.sum,3)
    }
    // 中位数
    result.center = keep_float(get_center(result.values),3)

    // 平均值
    result.average = keep_float(result.sum / result.values.length,3)

    result.static_field = static_field

    return result
}

4.4 完整代码

以下是 js的端代码,完整代码在仓库可自行下载,代码仓库还具备 node端的实现方式

ini 复制代码
/*
* 数据进行分类完进行统计
* @params 
* data  json对象数组原数据
* static_field 字符串 对某一个字段全部统计
* args: 各类分类字段 一级分类字段 region、resource.....、二级分类字段 year、month.....、三级....
* @return map
*/
const static_classify_data = (data,static_field,...args) => {

    // 判断数据类型
    if(!Array.isArray(data)) {
        console.error('JSON文件内容必须是一个对象数组');
        return;
    }
    const map = new Map()

    for(let i = 0; i < data.length; i++) {

        let j = 0,temp_map = map,level = j + 1

        while(j < args.length && (!temp_map.get(data[i][args[j]]) || level < args.length)){

            if(temp_map.get(data[i][args[j]]) && level < args.length){

                temp_map = temp_map.get(data[i][args[j]])

                j = level++

                continue
            }

            if(j === args.length - 1) {

                temp_map.set(data[i][args[j]],result_data())

                break
            }

            temp_map.set(data[i][args[j]],new Map())

            temp_map = temp_map.get(data[i][args[j]])

            j++
        }

        const last_param = args[args.length - 1]

        const cur_map = temp_map.get(data[i][last_param])

        const value = data[i][static_field]? parseFloat(data[i][static_field]):0

        const value_float3 =  keep_float(value,3)
    
        cur_map.values.push(value_float3)
    
        // 最大值
        cur_map.max = Math.max(cur_map.max,value_float3)
    
        // 最小值
        cur_map.min = Math.min(cur_map.min, value_float3)
    
        // 总和
        cur_map.sum += value_float3

        cur_map.sum = keep_float(cur_map.sum,3)

        // 中位数
        cur_map.center = keep_float(get_center(cur_map.values),3)

        // 平均值
        cur_map.average = keep_float(cur_map.sum / cur_map.values.length,3)

        cur_map.static_field = static_field

        args.forEach((item,index) => {
            cur_map[item] = data[i][item]
        })
    
    }
    
    return map

}
/*
* 全部数据进行统计
* @params 
* data  json对象数组原数据
* static_field 字符串 对某一个字段全部统计
* @return map
*/

const static_data = (data,static_field) => {

    // 判断数据类型
    if(!Array.isArray(data)) {

        console.error('JSON文件内容必须是一个对象数组');

        return;
    }

    const result = result_data()

    for(let i = 0; i < data.length; i++) {

        const value = data[i][static_field]? parseFloat(data[i][static_field]):0

        const value_float3 =  keep_float(value,3)

        result.values.push(value_float3)

        // 最大值
        result.max = Math.max(result.max,value_float3)

        // 最小值
        result.min = Math.min(result.min,value_float3)

        // 总和
        result.sum += value_float3

        result.sum = keep_float(result.sum,3)
    }
    // 中位数
    result.center = keep_float(get_center(result.values),3)

    // 平均值
    result.average = keep_float(result.sum / result.values.length,3)

    result.static_field = static_field

    return result
}

const result_data = () => {

    return  {
        static_field: '',
        average:0,
        max: 0,
        min: Number.MAX_VALUE,
        center: 0,
        sum: 0,
        values: []
    }

}

// 保留 f 位小数
const keep_float = (value,f)=> {
    return Math.round((value + Number.EPSILON)* Math.pow(10,f)) / (Math.pow(10,f))
}

// 求取中位数
const get_center = (values,f)=> {

    const count = values.length;

    const sorted = [...values].sort((a,b) => a-b)

    let res = null

    if (count % 2 === 0) {
        res = (sorted[count / 2 - 1] + sorted[count / 2]) / 2;
    } else {
        res = sorted[Math.floor(count / 2)];
    }

    return res
}

5.性能分析

  • 在 web 端进行性能分析,我一般会使用 chrome面板的 performance进行性能分析,如下图,js 运行时间 100ms 以内,在接受范围内吧
  • 在 node端性能分析,如下是分析执行代码的时间
javascript 复制代码
// 1. 使用 console.time 标记关键步骤
console.time('数据处理');
// 你的处理逻辑
console.timeEnd('数据处理');

// 2. 更详细的性能分析(Node.js)
const { performance } = require('perf_hooks');
const start = performance.now();
// 你的处理逻辑
console.log(`耗时: ${(performance.now() - start).toFixed(2)}ms`);

也是在 100ms 以内,OK!

6.总结

整个流程分析需求,拆分代码思路,码代码,性能分析,一套组合拳下来,写出更优雅的代码!

相关推荐
爱喝白开水a19 分钟前
前端AI自动化测试:brower-use调研让大模型帮你做网页交互与测试
前端·人工智能·大模型·prompt·交互·agent·rag
Never_Satisfied19 分钟前
在JavaScript / HTML中,关于querySelectorAll方法
开发语言·javascript·html
董世昌4119 分钟前
深度解析ES6 Set与Map:相同点、核心差异及实战选型
前端·javascript·es6
WeiXiao_Hyy1 小时前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡1 小时前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone1 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09012 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农2 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king2 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳3 小时前
JavaScript 的宏任务和微任务
javascript