17、D3.js 数组、统计与时间戳处理实用指南

D3.js 数组、统计与时间戳处理实用指南

1. 数组操作函数

D3.js 提供了一系列强大的数组操作函数,以下是一些常用函数的介绍:
| 函数 | 描述 |
| ---- | ---- |
| d3.shuffle( array, low, high ) | 对数组的子数组进行原地随机洗牌,子数组由索引 low (包含)和 high (不包含)界定。若省略边界,则对整个数组洗牌,返回数组。 |
| d3.cross(a, b, reducer) | 返回数组 a b 的笛卡尔积,结果为一维数组。 reducer 函数会为每对输入元素调用,其返回值会进入笛卡尔积。默认 reducer (u, v) => [u, v] 。 |
| d3.merge( [array] ) | 将数组的数组元素连接成一个单个数组,不处理嵌套数据结构。 |
| d3.pairs(array, reducer) | 对数组中相邻元素对调用 reducer 函数,并将返回值收集到一维数组中。返回数组比输入数组少一项。默认 reducer (u, v) => [u, v] 。若输入数组少于两个元素,则返回空数组。 |
| d3.transpose(matrix) | 对二维数组进行转置,返回转置后的二维数组。 |
| d3.zip( array1, array2, … ) | 接受任意数量的数组,返回一个数组的数组。第一个数组包含所有参数的第一个元素,第二个数组包含第二个元素,依此类推。返回数组的长度截断为最短参数的长度。若只提供一个数组,则返回单参数数组的数组。 |

2. 数值数组的描述性统计

D3.js 提供了一些计算数值数组基本描述性统计量的函数,这些函数主要用于数值,会忽略未定义的值( null undefined NaN ),在计算时也不会将其计入数组元素数量。

函数 描述
d3.min( array, accessor ) , d3.max( array, accessor ) 返回数组中最小或最大元素,若数组为空则返回 undefined
d3.extent( array, accessor ) 以二维数组 [min, max] 的形式返回数组中最小和最大元素。若输入为空,则返回 [undefined, undefined]
d3.sum( array, accessor ) 返回数组元素的总和,若数组为空则返回 0。
d3.mean( array, accessor ) 返回数组元素的平均值,若数组为空则返回 undefined
d3.variance( array, accessor ) , d3.deviation( array, accessor ) 分别返回样本方差 $s_{n - 1}^2$ 及其平方根。若输入少于两个值,则返回 undefined
d3.median( array, accessor ) 返回中位数,若输入为空则返回 0。数组无需排序。
d3.quantile( array, p, accessor ) 返回 p 分位数,其中 $0 \leq p \leq 1$。输入数组必须排序。

中位数和分位数使用 R - 7 方法计算:https://en.wikipedia.org/wiki/Quantile 。

3. 直方图生成

D3.js 提供了生成数值数据直方图的功能,直方图工具是一种布局,它返回一个箱(bin)数组。每个箱是一个包含与该箱关联的原始数据点的数组,其 length 属性表示每个箱中的元素数量。每个箱还暴露了 x0 x1 属性,分别包含箱的下限和上限。

函数 描述
d3.histogram() 返回一个新的直方图布局操作符。
h( array ) 为提供的数值数组计算直方图。
h.value( accessor ) 设置值访问器。访问器将为输入数组中的每个元素调用,传递元素 d 、索引 i 和数组本身作为三个参数。默认访问器假设输入值是可排序的;若不是,则访问器应为数据集中的每个元素返回相应的可排序值。
h.domain( [min, max] ) 设置构建直方图时要考虑的最小值和最大值;严格超出此区间的值将被忽略。区间可以指定为数组或访问器函数,该函数将在数值数组上调用。若输入数据不可排序,则应在相应的可排序值上指定区间。
h.thresholds( count ) , h.thresholds( [boundary] ) , h.thresholds( fct ) 若参数为整数,则解释为要创建的等大小箱的所需数量。若为数组,则其元素视为箱边界;若有 n 个边界,则结果直方图将有 n + 1 个箱。若参数为函数,则期望它生成一个箱边界数组。默认是 d3.thresholdSturges
d3.thresholdSturges() 计算箱的数量为 $1 + \log_2 n$,其中 n 是数据点的数量。
d3.thresholdScott() 计算箱宽度为 $3.5\sigma / \sqrt[3]{n}$,其中 $\sigma$ 是样本标准差, n 是数据点的数量。
d3.thresholdFreedmanDiaconis() 计算箱宽度为 $2IQR / \sqrt[3]{n}$,其中 IQR 是样本四分位距, n 是数据点的数量。

以下是一个生成直方图的示例代码:

function makeHisto() {
    d3.csv( "dense.csv" ).then( function( data ) {
        var histo = d3.histogram().value( d=>+d.A )( data );
        var scX = d3.scaleBand().padding( 0.2 ).round( true )
            .range( [15, 515] ).domain( histo );
        var scY = d3.scaleLinear().range( [200, 0] )
            .domain( [0, d3.max( histo, d=>d.length ) ] ).nice();
        var g = d3.select( "#histo" )
            .append( "g" ).attr( "transform", "translate( 40,50 )" )
        g.selectAll( "rect" ).data( histo ).enter()
            .append( "rect" ).attr( "width", scX.bandwidth() )
            .attr( "x", scX ).attr( "y", d=>scY(d.length) )
            .attr( "height", d => 200-scY(d.length) )
            .attr( "fill", "red" ).attr( "fill-opacity", 0.2 )
            .attr( "stroke", "red" ).attr( "stroke-width", 2 )
        g.selectAll( "text" ).data( histo ).enter().append( "text" )
            .attr( "text-anchor", "middle" )
            .attr( "font-family", "sans-serif" )
            .attr( "font-size", 14 )
            .attr( "x", d => scX(d)+0.5*scX.bandwidth() )
            .attr( "y", 225 )
            .text( d=>(d.x0+d.x1)/2 );
        g.append( "g" ).call( d3.axisLeft(scY) );
    } );
}
4. 日期和时间戳处理

D3.js 提供了一些对日期进行算术运算的功能,特别是可以生成等间隔的时间区间集合,例如为坐标轴生成刻度线等。D3.js 没有引入自己的日期/时间抽象,所有相关函数都基于原生 JavaScript Date 数据类型,因此也受其限制(特别是在高精度日期计算方面)。

4.1 JavaScript Date 类型

JavaScript Date 内部将时间表示为自 Unix 纪元以来的毫秒数(不是秒),忽略闰秒。
要获取 Date 对象,必须使用 new 关键字调用构造函数;仅调用 Date() 函数将生成人类可读的字符串表示:

var date   = new Date();    // a Date object
var string = Date();        // a human-readable string
var millis = Date.now();    // milliseconds since the epoch

Date 构造函数可以使用不同的参数调用:

new Date();
new Date( millis );
new Date( isostring );
new Date( year,monthIndex,day,hour,minute,second,millis );

可以从一个日期中减去另一个日期或数字,日期会隐式转换为毫秒数。但尝试将数字添加到日期时,两个参数会转换为字符串并连接。可以使用一元前缀 + Date 强制转换为数字,然后使用构造函数从结果(毫秒数)中获取新的 Date 对象:

var now = new Date();
var then = now - 1000;
var later = new Date( +now + 10000 );

非相等比较按预期工作,但相等比较需要特别注意,因为 == === 运算符都不会将其参数转换为数字,这会导致比较对象标识而不是值相等。要确保值比较,需将两个参数都强制转换为数字:

if( later > now ) { ... } ;                 // true
var now2 = new Date( +now );
if( now == now2 ) { ... }                   // false
if( +now == +now2 ) { ... }                 // true
4.2 时间间隔计算

D3.js 处理日期/时间计算的困难时,采用以下方式:
- 在计算开始时,必须选择所需的时间间隔(即当前计算的粒度),并在整个计算过程中保持固定。
- 时间戳通常会截断到所选的间隔。
- 计算涉及对所选时间间隔对应的毫秒数的倍数进行加减。

以下是一些典型的时间间隔计算函数:
| 函数 | 返回类型 | 描述 |
| ---- | ---- | ---- |
| itvl( date ) | Date | 等同于 itvl.floor( date ) 。 |
| itvl.floor( date ) | Date | 返回一个新的 Date ,等于参数之前或等于参数的最新间隔边界。 |
| itvl.ceil( date ) | Date | 返回一个新的 Date ,等于参数之后或等于参数的最早间隔边界。 |
| itvl.round( date ) | Date | 返回一个新的 Date ,等于最接近参数的间隔边界。 |
| itvl.offset( date, n ) | Date | 返回一个新的 Date ,等于 date 之后(若 n 为正)或之前(若 n 为负) n 个间隔的时间。日期不截断。若 n 不是整数,则替换为 Math.floor(n) 。若省略 n ,则默认为 1。 |
| itvl.count( date1, date2 ) | Number | 返回严格晚于 date1 且早于或等于 date2 的间隔边界的数量。 |
| itvl.range( start, stop, step ) | [ Date ] | 返回一个 Date 对象数组,表示等于或晚于 start 且严格早于 stop 的每个间隔边界。若指定 step ,则只包含每 step 个边界。若 step 不是整数,则替换为 Math.floor(n) 。 |
| itvl.filter( fct ) | Interval | 返回一个新的间隔,是接收者的过滤子集。提供的函数将接收一个 Date ,只有当该日期要保留在新间隔中时才应返回 true 。 |
| itvl.every( step ) | Interval | 一个过滤间隔,只保留接收者间隔的每 step 个。例如, d3.timeMinute.every(15) 返回一个表示从整点开始的一刻钟的间隔。 |

D3.js 定义了一组内置间隔,如下表所示:
| 本地时间 | UTC 时间 |
| ---- | ---- |
| d3.timeMillisecond | d3.utcMillisecond |
| d3.timeSecond | d3.utcSecond |
| d3.timeMinute | d3.utcMinute |
| d3.timeHour | d3.utcHour |
| d3.timeDay | d3.utcDay |
| d3.timeWeek | d3.utcWeek |
| d3.timeMonth | d3.utcMonth |
| d3.timeYear | d3.utcYear |

4.3 时间戳解析和格式化

格式化时间戳(即 JavaScript Date 对象的实例)遵循与数字相同的工作流程:
1. 获取一个区域设置对象(或使用当前默认区域设置)。
2. 给定区域设置对象,通过提供格式规范实例化一个格式化器。
3. 使用 Date 实例作为参数调用格式化器,以获得格式化的字符串。

以下是获取区域设置对象的方法:
| 函数 | 描述 |
| ---- | ---- |
| d3.timeFormatLocale( def ) | 接受区域设置定义并返回适合时间戳转换的区域设置对象。 |
| d3.timeFormatDefaultLocale( def ) | 接受区域设置定义并返回区域设置对象,同时设置时间戳转换的默认区域设置。 |

以下是格式化时间戳的方法:
| 函数 | 描述 |
| ---- | ---- |
| d3.timeFormat( fmt ) | 使用字符串 fmt 中的格式规范,为当前默认区域设置返回一个时间戳格式化器实例。返回的格式化器将其参数解释为本地时间。 |
| d3.utcFormat( fmt ) | 使用字符串 fmt 中的格式规范,为当前默认区域设置返回一个时间戳格式化器实例。返回的格式化器将其参数解释为 UTC 时间。 |
| loc.format( fmt ) | 使用字符串 fmt 中的格式规范,为接收者区域设置返回一个时间戳格式化器实例。返回的格式化器将其参数解释为本地时间。 |
| loc.utcFormat( fmt ) | 使用字符串 fmt 中的格式规范,为接收者区域设置返回一个时间戳格式化器实例。返回的格式化器将其参数解释为 UTC 时间。 |
| d3.isoFormat( date ) | 用于 ISO 8601 格式 %Y-%m-%dT%H:%M:%S.%LZ 的格式化器实例。接受日期对象并返回格式化的字符串。 |

以下是解析时间戳的方法:
| 函数 | 描述 |
| ---- | ---- |
| d3.timeParse( fmt ) | 使用字符串 fmt 中的格式规范,为当前默认区域设置返回一个时间戳解析器实例。返回的解析器将其参数解释为本地时间。 |
| d3.utcParse( fmt ) | 使用字符串 fmt 中的格式规范,为当前默认区域设置返回一个时间戳解析器实例。返回的解析器将其参数解释为 UTC 时间。 |
| loc.parse( fmt ) | 使用字符串 fmt 中的格式规范,为接收者区域设置返回一个时间戳解析器实例。返回的解析器将其参数解释为本地时间。 |
| loc.utcParse( fmt ) | 使用字符串 fmt 中的格式规范,为接收者区域设置返回一个时间戳解析器实例。返回的解析器将其参数解释为 UTC 时间。 |
| d3.isoParse( string ) | 用于 ISO 8601 格式 %Y-%m-%dT%H:%M:%S.%LZ 的解析器实例。接受字符串并返回 Date 实例。 |

格式说明符的语法基于标准 C 库中的 strftime() strptime() 函数家族,以下是一些常用的转换说明符:
| 说明符 | 描述 |
| ---- | ---- |
| %a | 缩写的工作日名称 |
| %A | 完整的工作日名称 |
| %b | 缩写的月份名称 |
| %B | 完整的月份名称 |
| %c | 区域设置的日期和时间,如 %x, %X |
| %d | 零填充的月份中的日期,十进制数 [01,31] |
| %e | 空格填充的月份中的日期,十进制数 [ 1,31] ,等同于 %_d |
| %f | 微秒,十进制数 [000000, 999999] |
| %H | 小时(24 小时制),十进制数 [00,23] |
| %I | 小时(12 小时制),十进制数 [01,12] |
| %j | 一年中的日期,十进制数 [001,366] |
| %m | 月份,十进制数 [01,12] |
| %M | 分钟,十进制数 [00,59] |
| %L | 毫秒,十进制数 [000, 999] |
| %p | AM PM |
| %Q | 自 Unix 纪元以来的毫秒数 |
| %s | 自 Unix 纪元以来的秒数 |
| %S | 秒,十进制数 [00,61] |
| %u | 基于星期一(ISO 8601)的工作日,十进制数 [1,7] |
| %U | 基于星期日的一年中的周数,十进制数 [00,53] |
| %V | 基于星期一(ISO 8601)的一年中的周数,十进制数 [01, 53] |
| %w | 基于星期日的工作日,十进制数 [0,6] |
| %W | 基于星期一的一年中的周数,十进制数 [00,53] |
| %x | 区域设置的日期,如 %-m/%-d/%Y |
| %X | 区域设置的时间,如 %-I:%M:%S %p |
| %y | 无世纪的年份,十进制数 [00,99] |
| %Y | 带世纪的年份,十进制数 |
| %Z | 时区偏移,如 -0700 , -07:00 , -07 , 或 Z |
| %% | 文字百分号 % |

部分输出可能受区域设置选择的影响。

通过以上介绍,我们可以看到 D3.js 在数组操作、数值统计、直方图生成以及日期和时间戳处理方面提供了丰富的功能,能够帮助开发者更方便地处理各种数据和可视化任务。

5. 时间处理示例

为了更好地理解 D3.js 中的时间处理,下面给出一些示例代码,展示如何使用前面提到的时间函数。

// 获取当前时间
var now = new Date();

// 计算一个月后的时间
var nextMonth = d3.timeMonth.offset(now, 1);

// 计算今天到下个月对应日期的天数
var daysBetween = d3.timeDay.count(now, nextMonth);

// 计算从现在到下个月开始的每周起始时间
var weekStarts = d3.timeWeek.range(now, nextMonth);

// 格式化当前时间
var formattedNow = d3.timeFormat("%Y-%m-%d %H:%M:%S")(now);

// 解析特定格式的日期字符串
var parsedDate = d3.timeParse("%Y-%m-%d")("2024-01-01");

console.log("当前时间: ", formattedNow);
console.log("一个月后的时间: ", nextMonth);
console.log("今天到下个月对应日期的天数: ", daysBetween);
console.log("从现在到下个月开始的每周起始时间: ", weekStarts);
console.log("解析后的日期: ", parsedDate);
6. 综合应用示例

下面是一个综合应用示例,结合数组操作、统计和时间处理,生成一个包含时间序列数据的直方图。

// 模拟时间序列数据
var data = [];
for (var i = 0; i < 100; i++) {
    var date = new Date(2024, 0, i);
    var value = Math.random() * 100;
    data.push({ date: date, value: value });
}

// 创建直方图
var histo = d3.histogram()
   .value(d => +d.value)
   .domain([0, 100])
   .thresholds(10)(data);

// 创建时间尺度
var scX = d3.scaleTime()
   .range([15, 515])
   .domain(d3.extent(data, d => d.date));

// 创建数值尺度
var scY = d3.scaleLinear()
   .range([200, 0])
   .domain([0, d3.max(histo, d => d.length)]).nice();

// 创建 SVG 容器
var svg = d3.select("body")
   .append("svg")
   .attr("width", 600)
   .attr("height", 300);

// 添加直方图矩形
svg.selectAll("rect")
   .data(histo)
   .enter()
   .append("rect")
   .attr("width", (d, i) => {
        var nextDate = i < histo.length - 1 ? histo[i + 1].x0 : 100;
        return scX(nextDate) - scX(d.x0);
    })
   .attr("x", d => scX(d.x0))
   .attr("y", d => scY(d.length))
   .attr("height", d => 200 - scY(d.length))
   .attr("fill", "blue")
   .attr("fill-opacity", 0.2)
   .attr("stroke", "blue")
   .attr("stroke-width", 2);

// 添加坐标轴
svg.append("g")
   .attr("transform", "translate(0, 200)")
   .call(d3.axisBottom(scX));

svg.append("g")
   .attr("transform", "translate(15, 0)")
   .call(d3.axisLeft(scY));
7. 总结

D3.js 提供了一套全面且强大的工具,用于处理数组、统计和时间戳。通过这些工具,开发者可以轻松地完成各种数据处理和可视化任务。

  • 数组操作 :提供了如洗牌、计算笛卡尔积、合并数组等多种操作,方便对数组进行处理。
  • 数值统计 :可以计算最小值、最大值、平均值、方差等基本统计量,为数据分析提供支持。
  • 直方图生成 :能够根据数值数据生成直方图,直观展示数据分布。
  • 时间处理 :基于 JavaScript 的 Date 类型,提供了时间间隔计算、时间戳解析和格式化等功能,满足时间相关数据的处理需求。

在实际应用中,开发者可以根据具体需求灵活组合这些功能,实现复杂的数据可视化和分析任务。例如,在金融领域可以使用时间处理功能分析股票价格的时间序列数据,使用统计功能计算收益率的均值和方差;在医疗领域可以使用数组操作和直方图生成功能分析患者的健康数据分布。

通过不断学习和实践 D3.js 的这些功能,开发者可以提升自己的数据处理和可视化能力,为项目带来更丰富和准确的展示效果。

8. 流程图示例

下面是一个简单的使用 D3.js 处理时间序列数据的流程图:

graph TD;
    A[获取时间序列数据] --> B[数据预处理];
    B --> C[创建时间尺度和数值尺度];
    C --> D[生成直方图];
    D --> E[添加坐标轴和图形元素];
    E --> F[展示可视化结果];

这个流程图展示了一个典型的使用 D3.js 处理时间序列数据并进行可视化的流程,从数据获取开始,经过预处理、尺度创建、直方图生成等步骤,最终展示可视化结果。

通过不断探索和实践 D3.js 的各种功能,开发者可以更好地应对不同场景下的数据处理和可视化需求,为项目带来更出色的效果。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值