首页
/ D3 数据汇总统计全解:d3-array 的 22 个 summarize 函数(count、extent、quantile 等)深度指南

D3 数据汇总统计全解:d3-array 的 22 个 summarize 函数(count、extent、quantile 等)深度指南

2026-09-05 11:05:25作者:咎竹峻Karen

D3(当前仓库版本 7.9.0)不仅擅长把数据画出来,更擅长在画图之前把数据“算清楚”。本文以官方文档 Summarizing data 为主体,完整覆盖 d3-array 提供的所有汇总统计函数——从极值(min/max)、值域(extent)、集中趋势(mean/median/mode)、分位数(quantile)到离散程度(variance/deviation),逐一讲解其行为语义、缺省值处理规则与 accessor 用法,并结合本仓库源码与测试说明这些函数是如何进入 d3 总入口的。读完后你将能直接套用这些函数完成比例尺定域、分箱、分组排序等常见可视化前处理。

这些函数从哪里来:d3 伞包对 d3-array 的再导出

summarize.md 中出现的 d3.countd3.extentd3.quantile 等 API 并非定义在 d3 主包内,而是来自依赖项 d3-array。仓库 package.json 声明了 "d3-array": "^3.2.4",而 src/index.js 的第一行即为:

export * from "d3-array";

这意味着 d3 伞包(umbrella package)将 d3-array 的全部导出原样提升到顶层 d3 命名空间下,所以文档示例里才能直接写 d3.count(...)d3.extent(...)。这一契约由测试文件 test/d3-test.js 守护:它遍历 package.json 的每个依赖模块,逐个断言 “d3 exports everything from ${moduleName}”,即任何 d3-array 中的导出若未出现在 d3 上都会导致测试失败。因此在任何使用 import * as d3 from "d3" 的项目中,本文介绍的全部函数都可直接以 d3. 前缀调用;若只安装 d3-array 子包,则去掉前缀使用。

三条贯穿全文的通用约定

在逐个函数之前,先归纳 summarize.md 中反复出现的三条规则,它们决定了 d3 汇总函数与原生 Math.min/reduce 的最大差异:

  1. 输入是任意可迭代对象(iterable)。参数名普遍写作 iterable 而非 arrayd3.count(new Set([1,3,5,7])) 这类写法是合法的(见 every/some 章节示例),d3.everyd3.some 的示例直接用 Set 演示了这一点。

  2. 可选的 accessor 函数。绝大多数函数接受第二个参数 accessor,其效果“等价于先调用 Array.from 再计算”。accessor 每次接收可迭代对象的一个元素(惯写作 d)与零基索引(i),返回参与统计的值。例如:

    d3.sum(penguins, (d) => d.body_mass_g) // 1437000
    d3.mean(penguins, (d) => d.body_mass_g) // 4201.754385964912
    

    penguins 是 d3 示例生态中的经典数据集(文档中 342 条有效体质量记录,总质量 1437000 克),alphabet(英文字母频率)同样是官方示例数据。

  3. 自动忽略缺失值。对数值型统计(summeanmediancountmodecumsumvariancedeviation 等),undefinednullNaN 会被跳过,这对含缺失数据的真实数据集非常有用:

    d3.sum([1, 2, 2, 2, NaN, 3, null]) // 10
    d3.mean([1, 2, 2, 2, NaN, 3, null]) // 2
    d3.median([1, 2, 2, 2, NaN, 3, null]) // 2
    

    这一特性还有一个精巧用法:在 accessor 里对不想要的元素返回 NaN,即可在统计中“隐形”排除它们(见下文 min/max 章节)。

计数与极值:count、min、max 及其 Index 变体

d3.count

返回可迭代对象中“有效数值”的个数,即统计非 null、非 NaN、非 undefined 的元素:

d3.count(penguins, (d) => d.body_mass_g) // 342

这是处理缺失数据时的第一步:先知道有多少条记录真正可参与统计。

d3.min / d3.max

返回给定可迭代对象中按**自然序(natural order)**取的最小/最大值:

d3.min([3, 2, 1, 1, 6, 2, 4]) // 1
d3.max([3, 2, 1, 1, 6, 2, 4]) // 6

Math.min/Math.max 相比有两个关键区别:

  • 不做数值强制转换。对字符串 ["20", "3"]d3.min 的结果是 "20"d3.max 的结果是 "3"(字典序),而 Math.min(20, 3)Math.max(20, 3) 则是 3 与 20。它同样直接支持字符串与日期对象:

    d3.min(["bob", "alice", "carol"]) // "alice"
    d3.min([new Date("2018-01-01"), new Date("2011-03-09")]) // 2011-03-09
    d3.max(["bob", "alice", "carol"]) // "carol"
    d3.max([new Date("2018-01-01"), new Date("2011-03-09")]) // 2018-01-01
    
  • 忽略 undefinednullNaN

    d3.min([3, 2, 1, NaN, 4]) // 1
    d3.max([3, 2, 1, NaN, 4]) // 4
    

利用“accessor 返回 NaN 即被忽略”的规则,可以在 accessor 中过滤掉特定样本。文档给出的例子是:求字母表中除 Z 外最低频字母的频率:

d3.min(alphabet, (d) => d.frequency) // 0.00074
d3.min(alphabet, (d) => d.letter === "Z" ? NaN : d.frequency) // 0.00095
d3.max(alphabet, (d) => d.frequency) // 0.12702
d3.max(alphabet, (d) => d.letter === "E" ? NaN : d.frequency) // 0.09056

空输入(或 accessor 全部返回不可比较值)时,minmax 都返回 undefined

d3.min([]) // undefined
d3.min(alphabet, (d) => d.doesnotexist) // undefined
d3.max([]) // undefined
d3.max(alphabet, (d) => d.doesnotexist) // undefined

d3.minIndex / d3.maxIndex

min/max 行为一致,但返回最小/最大值的索引而非值本身:

d3.minIndex([3, 2, 1, 1, 6, 2, 4]) // 2
d3.maxIndex([3, 2, 1, 1, 6, 2, 4]) // 2

配合索引即可取回原始对象,等价于“按 accessor 找最小/最大元素”:

d3.minIndex(alphabet, (d) => d.frequency) // 25
alphabet[d3.minIndex(alphabet, (d) => d.frequency)] // {letter: "Z", frequency: 0.00074}
d3.maxIndex(alphabet, (d) => d.frequency) // 0
alphabet[d3.maxIndex(alphabet, (d) => d.frequency)] // {letter: "E", frequency: 0.12702}

自定义比较:least、greatest 及其 Index 变体

min/max 固定按自然序比较,而 least/greatest 允许传入比较器(comparator),从而在任意度量上取“最小/最大元素”(返回的是元素本身,而非 accessor 的值)。

d3.least / d3.greatest

d3.least(alphabet, (a, b) => a.frequency - b.frequency) // {letter: "Z", frequency: 0.00074}
d3.least(alphabet, (a, b) => b.frequency - a.frequency) // {letter: "E", frequency: 0.12702}

注意一个重要的重载规则:如果比较器只接受一个参数,它会被解释为 accessor,返回值之间再按自然序比较:

d3.least(alphabet, (d) => d.frequency) // {letter: "Z", frequency: 0.00074}
d3.least(alphabet, (d) => -d.frequency) // {letter: "E", frequency: 0.12702}

若完全不传比较器,默认使用 d3.ascending

d3.least(alphabet.map((d) => d.frequency)) // 0.00074

greatest 与其对称:

const array = [{foo: 42}, {foo: 91}];
d3.greatest(array, (a, b) => a.foo - b.foo); // {foo: 91}
d3.greatest(array, (a, b) => b.foo - a.foo); // {foo: 42}
d3.greatest(array, (d) => d.foo); // {foo: 91}

当可迭代对象中没有可比较元素(即比较器把元素与自身比较返回 NaN)时,least/greatest 返回 undefined,例如 d3.least([]) // undefined

d3.leastIndex / d3.greatestIndex

返回最小/最大元素的索引,参数规则与 least/greatest 相同;无元素可比较时返回 -1,未指定比较器时默认 ascending

const array = [{foo: 42}, {foo: 91}];
d3.leastIndex(array, (a, b) => a.foo - b.foo); // 0
d3.leastIndex(array, (a, b) => b.foo - a.foo); // 1
d3.leastIndex(array, (d) => d.foo); // 0
d3.greatestIndex(array, (a, b) => a.foo - b.foo); // 1
d3.greatestIndex(array, (a, b) => b.foo - a.foo); // 0
d3.greatestIndex(array, (d) => d.foo); // 1

四者速记:min/max 按自然序取 accessor 的最小/最大least/greatest 按自定义比较器取元素*Index 后缀则取下标

值域:extent —— 比例尺定域的标配

d3.extent([3, 2, 1, 1, 6, 2, 4]) // [1, 6]
d3.extent(alphabet, (d) => d.frequency) // [0.00074, 0.12702]

extent 一次返回 [min, max] 对,规则与 min/max 相同;无可比较值时返回 [undefined, undefined]

d3.extent(alphabet, (d) => d.doesnotexist) // [undefined, undefined]

extent 在整个 d3 生态中的存在感远超统计本身——它是最常用的比例尺 domain 来源。本仓库 docs/getting-started.md 的最小柱状图示例中就有:

const y = d3.scaleLinear(d3.extent(data), [height - marginBottom, marginTop]);

此外,d3-scale 的 nicing(如 linear 比例尺time 比例尺.nice())被明确推荐给“用 extent 从数据计算 domain”的场景,因为数据驱动得到的边界往往不规则;d3.bin 的默认 domain 访问器也是 extent。可以说 extent 是“从原始数据走到比例尺/坐标轴”的枢纽函数。

众数:d3.mode

返回出现次数最多的值,忽略 undefinednullNaN

d3.mode([1, 2, 2, 2, 3, 3]) // 2
d3.mode(penguins, (d) => d.island) // "Biscoe"

两个细节:出现次数并列时返回先出现的那个值;无可比较值时返回 undefined。与 min/max 不同,mode 对字符串、对象键等离散值同样适用(penguinsisland 分组取众岛的例子就是典型用法)。

聚合与集中趋势:sum、mean、median

d3.sum

d3.sum([1, 2, 2, 2, NaN, 3, null]) // 10
d3.sum(penguins, (d) => d.body_mass_g) // 1437000

忽略 undefined/null/NaN;若可迭代对象中没有任何数字,返回 0。若需要更高的浮点精度(例如累加大量 0.1 这类场景),文档指向 fsum——它使用 Kahan 补偿求和,d3.fsum([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) 恰好等于 1,速度较慢但可无缝替换 d3.sum

d3.mean

算术平均值,忽略缺失值;无数字时返回 undefined

d3.mean([1, 2, 2, 2, NaN, 3, null]) // 2
d3.mean(penguins, (d) => d.body_mass_g) // 4201.754385964912

d3.median / d3.medianIndex

median 使用中位数(0.5 分位数),实现采用 R-7 方法(R 语言与 Excel 的默认分位数算法),忽略缺失值;无数字时返回 undefined

d3.median([1, 2, 2, 2, NaN, 3, null]) // 2
d3.median(penguins, (d) => d.body_mass_g) // 4050

medianIndexmedian 相同,但返回中位数左侧元素的索引:

d3.medianIndex([1, 2, 2, 2, NaN, 3, null]) // 2

中位数比均值抗离群值,这在 d3 的分组统计里很常见——例如 docs/d3-array/group.md 展示用 d3.groupSort 按各组的 d3.median(D, (d) => d.body_mass_g) 对企鹅物种排序;d3.bin 也用 d3.median(values) 作为按中位数切分的分箱阈值示例。

累计和:d3.cumsum 与高精度 fcumsum

d3.cumsum([1, 1, 2, 3, 5]) // [1, 2, 4, 7, 12]
d3.cumsum(penguins, (d) => d.body_mass_g) // [3750, 7550, 10800, 10800, …]

返回与原序列等长的 Float64Array,忽略 undefinedNaN;若没有任何数字则返回全零。需要精度保障时,可用 fcumsum 替换(d3.fcumsum([1, 1e-14, -1]) // [1, 1.00000000000001, 1e-14],普通 cumsum 在此例中会丢失 1e-14)。

分位数:quantile、quantileIndex、quantileSorted

quantile(iterable, p, accessor) 返回 p-分位数,p 在 [0, 1] 区间:中位数即 p=0.5,上下四分位即 p=0.25/0.75。实现采用 R-7 方法(R 与 Excel 的默认约定):

const numbers = [0, 10, 30];
d3.quantile(numbers, 0); // 0
d3.quantile(numbers, 0.5); // 10
d3.quantile(numbers, 1); // 30
d3.quantile(numbers, 0.25); // 5
d3.quantile(numbers, 0.75); // 20
d3.quantile(numbers, 0.1); // 2

注意 0.25 分位数得到 5 而非 0 与 10 的中点——这正是 R-7 位置内插的结果。quantileIndex(array, p, accessor) 返回 p 分位数左侧的索引;quantileSorted(array, p, accessor)quantile 相同但要求输入已排序,优点是 accessor 只对被触及的元素调用(适合 accessor 昂贵的场景,可先配合 d3.sort 排序)。

quantile 的另一处重要下游是 d3.scaleQuantile:该比例尺把采样 domain 排序后视为“离散值总体”,再按 range 的数量切分分位点,其计算内核就是本文的 d3.quantile

排名:d3.rank

返回与输入等长的秩数组,即每个值在排序后可迭代对象中的零基索引:

d3.rank([{x: 1}, {}, {x: 2}, {x: 0}], d => d.x); // [1, NaN, 2, 0]
d3.rank(["b", "c", "b", "a"]); // [1, 3, 1, 0]
d3.rank([1, 2, 3], d3.descending); // [2, 1, 0]

三条规则:nullish 值(null/undefined)被排到末尾且秩为 NaN(第一例中缺少 x 的元素即如此);并列值取相同秩,定义为“该值第一次出现的位置”("b" 两次都得 1);第二参数可传 accessor(如 d => d.x)或比较器(如 d3.descending),缺省为 d3.ascending

离散程度:variance 与 deviation

variance 返回总体方差的无偏估计(即样本方差,除以 n-1),实现采用 Welford 在线算法(单次遍历、数值稳定);少于两个数字时返回 undefined,忽略 undefined/NaN

d3.variance(penguins, (d) => d.body_mass_g)

deviation 则是对应的标准差,定义为“偏差校正方差(bias-corrected variance)的平方根”,同样少于两个数字返回 undefined、同样忽略缺失值。两者都支持 accessor,效果等价于先 Array.from

谓词检验:every 与 some

d3.every(new Set([1, 3, 5, 7]), x => x & 1) // true
d3.some(new Set([0, 2, 3, 4]), x => x & 1) // true

两者与 Array.prototype.every/some 语义等价,但接受任意可迭代对象(示例直接用 Set),并且短路求值every 遇到第一个非真值即返回,some 遇到第一个真值即返回。

空输入行为速查表

d3 汇总函数对“没有可用数据”的返回值并不统一,实践中最常踩坑的正是这里。按 summarize.md 的原始约定整理如下:

函数 无可比较/无数字时返回
min / max undefined
least / greatest undefined
leastIndex / greatestIndex -1
extent [undefined, undefined]
mode undefined
sum 0
mean / median undefined
cumsum 全零数组
variance / deviation(少于 2 个数) undefined

据此,基于 mean/median/min/max 的下游逻辑(如比例尺 domain)都应当先判空,而不是假定一定拿到数字。

小结:把 summarize 放进 D3 工作流

回到 d3-array 模块索引 的视角,“Summarizing data —— Compute summary statistics” 只是 d3-array 的十个主题之一,但在可视化流水线中它出现的频率最高:extent 喂给比例尺 domain 并配合 nice() 圆整(ticks)、quantile 支撑分位数比例尺(quantile scale)、medianquantile 用于分箱阈值和分组排序(bingroup)。掌握本文的 22 个函数——尤其是“忽略 null/NaN”“accessor 与单参数比较器重载”“*Index 变体”与 R-7 分位数约定这几条通用规则,并对照 API 总览 中的 Summarize 章节索引检索具体函数,就能覆盖绝大多数“画图之前先统计”的场景;对精度敏感的场景再切换到 add.mdfsum/fcumsum 高精度版本。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384