避免在自定义 crossfilter reduce 函数中进行多次求和
Avoid multiple sums in custom crossfilter reduce functions
这个问题源于创建 crossfilter
数据集的一些困难,特别是关于如何对不同的维度进行分组和计算派生值。最终目标是使用维度和组创建多个 dc.js
图表。
(Fiddle 示例 https://jsfiddle.net/raino01r/0vjtqsjL/)
问题
在继续解释设置之前,关键问题如下:
如何创建自定义 add
、remove
、init
函数以传入 .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(数据);
// ndx.dimension(函数(d){return d.month;})
// ndx.dimension(函数(d){return d.room;})
// ndx.dimension(函数(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/) 的实现。数字不同,但数据结构相同。请注意,在 fiddle 中,您希望 Machine count
为 18(在两个月中),但是您总是得到双倍的(因为 2 个不同的位置)。
编辑
减少 + dc.js
根据 Ethan Jewett 的回答,我使用 reductio
来处理分组。更新后的 fiddle 在这里 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; })
这个解决方案似乎有效。但是我不确定这样做是否明智:如果数据集很大,添加新功能可能会大大降低速度!
几件事:
不要计算您的 Crossfilter 减速器中的速率。计算利率的组成部分。这将保持更简单和更快。在您的值访问器中进行实际除法。
基本上你的想法是对的。我认为我立即看到了两个问题:
在您的 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 reducer 很难正确有效地执行,因此使用库来提供帮助可能是有意义的。使用 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)
这个问题源于创建 crossfilter
数据集的一些困难,特别是关于如何对不同的维度进行分组和计算派生值。最终目标是使用维度和组创建多个 dc.js
图表。
(Fiddle 示例 https://jsfiddle.net/raino01r/0vjtqsjL/)
问题
在继续解释设置之前,关键问题如下:
如何创建自定义 add
、remove
、init
函数以传入 .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(数据); // ndx.dimension(函数(d){return d.month;}) // ndx.dimension(函数(d){return d.room;}) // ndx.dimension(函数(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/) 的实现。数字不同,但数据结构相同。请注意,在 fiddle 中,您希望 Machine count
为 18(在两个月中),但是您总是得到双倍的(因为 2 个不同的位置)。
编辑
减少 + dc.js
根据 Ethan Jewett 的回答,我使用 reductio
来处理分组。更新后的 fiddle 在这里 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; })
这个解决方案似乎有效。但是我不确定这样做是否明智:如果数据集很大,添加新功能可能会大大降低速度!
几件事:
不要计算您的 Crossfilter 减速器中的速率。计算利率的组成部分。这将保持更简单和更快。在您的值访问器中进行实际除法。
基本上你的想法是对的。我认为我立即看到了两个问题:
在您的
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 reducer 很难正确有效地执行,因此使用库来提供帮助可能是有意义的。使用 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)