【问题标题】:Avoid multiple sums in custom crossfilter reduce functions避免自定义交叉过滤器减少函数中的多个总和
【发布时间】:2017-02-22 12:19:06
【问题描述】:

这个问题源于创建crossfilter 数据集的一些困难,特别是关于如何对不同维度进行分组和计算派生值。最终目标是使用维度和组来创建多个dc.js 图表。

(小提琴示例https://jsfiddle.net/raino01r/0vjtqsjL/

问题

在继续解释设置之前,关键问题如下:

如何创建自定义addremoveinit,传入.reduce的函数,这样前两个就不会多次求和相同的特征?

数据

假设我想监控多台机器的故障率(只是一个例子)。我使用不同的维度来执行此操作:月份、机器的位置和故障类型。

例如我有以下形式的数据:

| month   | room | failureType | failCount | machineCount |
|---------|------|-------------|-----------|--------------|
| 2015-01 |  1   |  A          |  10       |  5           |
| 2015-01 |  1   |  B          |   2       |  5           |
| 2015-01 |  2   |  A          |   0       |  3           |
| 2015-01 |  2   |  B          |   1       |  3           |
| 2015-02 |  .   |  .          |   .       |  .           |

预期

对于三个给定的维度,我应该:

  • month_1_rate = $\frac{10+2+0+1}{5+3}$;
  • room_1_rate = $\frac{10+2}{5}$;
  • type_A_rate = $\frac{10+0}{5+3}$.

想法

本质上,在这个设置中重要的是这对夫妇(day, room)。 IE。给定一天和一个房间,它们应该附加一个费率(然后交叉过滤器应该考虑其他过滤器)。

因此,一种方法可能是存储已使用的情侣并且不为它们求和 machineCount - 但是我们仍然希望更新 failCount 值。

尝试(失败)

我的尝试是创建自定义 reduce 函数,而不是对已经考虑在内的 MachineCount 求和。

但是有一些意想不到的行为。 我确定这不是要走的路 - 所以我希望对此有一些建议。 // 维度是以下之一: // ndx = crossfilter(data); // ndx.dimension(function(d){return d.month;}) // ndx.dimension(function(d){return d.room;}) // ndx.dimension(function(d){return d.failureType;}) // 目标:有一个通用的方法来获取给定维度的组:

function get_group(dim){
    return dim.group().reduce(add_rate, remove_rate, initial_rate);
}

// month is given as datetime object
var monthNameFormat = d3.time.format("%Y-%m");
//
function check_done(p, v){
    return p.done.indexOf(v.room+'_'+monthNameFormat(v.month))==-1;
}    

// The three functions needed for the custom `.reduce` block.
function add_rate(p, v){
    var index = check_done(p, v);
    if (index) p.done.push(v.room+'_'+monthNameFormat(v.month));
    var count_to_sum = (index)? v.machineCount:0;
    p.mach_count += count_to_sum;
    p.fail_count += v.failCount;
    p.rate = (p.mach_count==0) ? 0 : p.fail_count*1000/p.mach_count;
    return p;
}
function remove_rate(p, v){
    var index = check_done(p, v);
    var count_to_subtract = (index)? v.machineCount:0;
    if (index) p.done.push(v.room+'_'+monthNameFormat(v.month));
    p.mach_count -= count_to_subtract;
    p.fail_count -= v.failCount;
    p.rate = (p.mach_count==0) ? 0 : p.fail_count*1000/p.mach_count;
    return p;
}
function initial_rate(){
    return {rate: 0, mach_count:0, fail_count:0, done: new Array()};
}

与 dc.js 的连接

如前所述,前面的代码需要创建dimension, group 以使用dc.js 在三个不同的条形图中传递。

每个图表都有.valueAccessor(function(d){return d.value.rate};)

请参阅 jsfiddle (https://jsfiddle.net/raino01r/0vjtqsjL/) 以获取实现。数字不同,但数据结构相同。请注意,在小提琴中,您希望 Machine count 为 18(两个月内),但您总是得到双倍(因为有 2 个不同的位置)。


编辑

还原 + dc.js

按照 Ethan Jewett 的回答,我使用 reductio 来处理分组问题。更新的小提琴在这里https://jsfiddle.net/raino01r/dpa3vv69/

我的reducer 对象在对machineCount 值求和时需要两个异常(month, room)。因此它是这样构建的:

var reducer = reductio()
reducer.value('mach_count')
       .exception(function(d) { return d.room; })
       .exception(function(d) { return d.month; })
       .exceptionSum(function(d) { return d.machineCount; })
reducer.value('fail_count')
       .sum(function(d) { return d.failCount; })

这似乎修复了呈现图形时的数字。

但是,我在过滤一个月并查看type 图表中的数字时确实有一种奇怪的行为。

可能的解决方案

而是双重创建两个异常,我可以在处理数据时合并两个字段。 IE。一旦定义了数据,我就可以:

data.foreach(function(x){
    x['room_month'] = x['room'] + '_' + x['month'];
})

那么上面的缩减代码应该变成:

var reducer = reductio()
reducer.value('mach_count')
       .exception(function(d) { return d.room_month; })
       .exceptionSum(function(d) { return d.machineCount; })
reducer.value('fail_count')
       .sum(function(d) { return d.failCount; })

此解决方案似乎有效。但是我不确定这样做是否明智:如果数据集很大,添加新功能可能会大大降低速度!

【问题讨论】:

    标签: dc.js crossfilter reductio


    【解决方案1】:

    一些事情:

    1. 不要在您的 Crossfilter 减速器中计算费率。计算费率的组成部分。这将保持更简单和更快。在您的值访问器中进行实际除法。

    2. 您的想法基本上是正确的。我认为我立即看到了两个问题:

      • 在您的 remove_rate 中,您没有从 p.done 数组中删除密钥。您应该执行if (index) p.done.splice(p.done.indexOf(v.room+'_'+monthNameFormat(v.month)), 1); 之类的操作来删除它。

      • 在您的 reduce 函数中,index 是一个布尔值。 (index == -1) 永远不会评估为 true,IIRC。因此,您添加的机器数将始终为 0。请改用 var count_to_sum = index ? v.machineCount:0;

    如果您想整理一个可行的示例,我相信我或其他人会很乐意为您完成它。

    您可能还想试试Reductio。 Crossfilter reducers 很难正确有效地完成,因此使用库来提供帮助可能是有意义的。使用 Reductio,创建一个计算机器计数和故障计数的组如下所示:

    var reducer = reductio()
    reducer.value('mach_count')
      .exception(function(d) { return d.room; })
      .exceptionSum(function(d) { return d.machineCount; })
    reducer.value('fail_count')
      .sum(function(d) { return d.failCount; })
    
    var dim = ndx.dimension(...)
    var grp = dim.group()
    reducer(group)
    

    【讨论】:

    • 谢谢您的回答。我已经用一个简单的小提琴链接更新了这个问题。然而,原则上的解决方案应该有效,同样在小提琴中(来自工具提示)很明显有些事情失败了。我将尝试使用Reductio,我将链接到新的小提琴。
    • 另外,我更正了(index==-1),正如您所指出的,它应该只是(index)
    • 在我看来,您的示例非常有效。您不需要在同一个 reducer 上使用 exception 2 次(它只会覆盖自己)。只需 .exception(function(d) { return d.room; }) 即可满足您的需求。
    猜你喜欢
    • 2023-03-07
    • 2015-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多