如何从减少过滤空组?

时间:2014-01-25 15:13:59

标签: crossfilter

在我看来,即使应用的过滤器已排除该组中的所有行,Crossfilter也不会从减少的结果中排除一个组。已过滤掉所有行的组只返回聚合值0(或任何reduceInitial返回)。

这样做的问题在于它无法区分不包含行的组和包含行的组,而只是合法地聚合到值0.基本上,没有办法(我可以看到)区分在空值和0聚合之间。

有人知道内置的Crossfilter技术可以实现这一目标吗?我确实想出了一种方法来使用我自己的自定义reduceInitial/reduceAdd/reduceRemove方法做到这一点,但它并不完全是直接的,在我看来,这是可能/应该更加原生于Crossfilter的过滤语义的行为。所以我想知道是否有一种规范的方法来实现这一点。

如果事实证明没有内置方法可以做到这一点,我会发布我的技巧。

2 个答案:

答案 0 :(得分:6)

实现此目的的一种简单方法是使count和total都属于reduce属性:

var dimGroup = dim.group().reduce(reduceAdd, reduceRemove, reduceInitial);

function reduceAdd(p, v) {
  ++p.count;
  p.total += v.value;
  return p;
}

function reduceRemove(p, v) {
  --p.count;
  p.total -= v.value;
  return p;
}

function reduceInitial() {
  return {count: 0, total: 0};
}

空组的计数为零,因此只检索非空组很容易:

dimGroup.top(Infinity).filter(function(d) { return d.value.count > 0; });

答案 1 :(得分:0)

好的,似乎没有任何明显的答案跳出来,所以我会回答我自己的问题并发布我用来解决这个问题的技术。

此示例假定我已经创建了维度和分组,并以groupDim的形式传递。因为我希望能够总结任意数字字段,所以我也传入fieldName,以便它可以在缩减函数的闭包范围内使用。

这种技术的一个重要特征是它依赖于有一种方法来唯一地识别每一行属于哪个组。从OLAP的角度思考,这本质上是定义特定聚合上下文的“元组”。但它可以是您想要的任何东西,只要它确定性地为属于给定组的所有数据行返回相同的值。

最终结果是空组的聚合值为“null”,可以很容易地检测到并在事后过滤掉。具有至少一行的任何组都将具有数值(即使它恰好为零)。

对此的改进或建议更受欢迎。这是带内联注释的代码:

function configureAggregateSum(groupDim, fieldName) {

    function getGroupKey(datum) {
        // Given datum return key corresponding to the group to which the datum belongs
    }

    // This object will keep track of the number of times each group had reduceAdd
    // versus reduceRemove called. It is used to revert the running aggregate value
    // back to "null" if the count hits zero. This is unfortunately necessary because
    // Crossfilter filters as it is aggregating so reduceAdd can be called even if, in
    // the end, all records in a group end up being filtered out.
    //
    var groupCount = {};

    function reduceAdd(p, v) {
        // Here's the code that keeps track of the invocation count per group
        var groupKey = getGroupKey(v);
        if (groupCount[groupKey] === undefined) { groupCount[groupKey] = 0; }
        groupCount[groupKey]++;

        // And here's the implementation of the add reduction (sum in my case)
        // Note the check for null (our initial value)
        var value = +v[fieldName];
        return p === null ? value : p + value;
    }

    function reduceRemove(p, v) {
        // This code keeps track of invocations of invocation count per group and, importantly,
        // reverts value back to "null" if it hits 0 for the group. Essentially, if we detect
        // that group has no records again we revert to the initial value.
        var groupKey = getGroupKey(v);
        groupCount[groupKey]--;
        if (groupCount[groupKey] === 0) {
            return null;
        }

        // And here's the code for the remove reduction (sum in my case)
        var value = +v[fieldName];
        return p - value;
    }

    function reduceInitial() {
        return null;
    }

    // Once returned, can invoke all() or top() to get the values, which can then be filtered
    // using a native Array.filter to remove the groups with null value.
    return groupedDim.reduce(reduceAdd, reduceRemove, reduceInitial);
}