使用 dc.js 中的子列绘制聚合数据

Plotting aggregated data with sub-columns in dc.js

我有以下形式的数据:

data = [..., {id:X,..., turnover:[[2015,2017,2018],[2000000,3000000,2800000]]}, ...];

我的目标是在 x 轴上绘制年份,在 y 轴上绘制当前通过交叉过滤器选择的所有公司的平均营业额。

每个公司记录的年份不一致,但应该始终是三年。

如果有帮助,我可以将数据重新组织为以下形式:

data = [..., {id:X,..., turnover:{2015:2000000, 2017:3000000, 2018:2800000}}, ...];

如果我能够进一步重组数据,使其看起来像:

[...{id:X, ..., year:2015, turnover:2000000},{id:X,...,year:2017,turnover:3000000},{id:X,...,year:2018,turnover:2800000}]; 

那么this question提供解决方案

但是将公司分成不同的行对我正在做的其他事情没有意义。

除非我弄错了,否则我称之为 "tag dimension",也就是带有数组键的维度。

您希望每行针对它包含的每一年记录一次,但您只希望它影响该维度。您不想在其他维度中多次观察该行,这就是您不想展平的原因。

使用您的原始数据格式,您的维度定义类似于:

var yearsDimension = cf.dimension(d => d.turnover[0], true);

标签维度的关键函数应该return一个数组,这里是年。

随着 crossfilter 的发展,这个功能仍然相当新,今年发现了几个 minor bugs。这些错误应该很容易避免。该功能已得到大量使用,未发现重大错误。

始终注意标签维度,因为任何聚合加起来都会超过 100% - 在您的情况下为 300%。但是,如果您对公司进行一年的平均计算,这应该不是问题。

标签和值对

您的问题的独特之处在于,您不仅每行有多个键,而且还有多个与这些键关联的值。

虽然crossfilter标签维度功能很方便,但它让你无法知道你在减少时正在查看哪个标签。更进一步,最强大最通用的组归约方法,group.reduce(), doesn't tell you which key you are reducing..

但是有一种更强大的方法可以同时减少整个交叉过滤器:dimension.groupAll()

一个 groupAll 对象的行为就像一个组,除了它被喂入所有行,并且它 return 只有一个箱子。如果您使用 dimension.groupAll() ,您将获得一个 groupAll 对象,该对象观察除该维度上的过滤器之外的所有过滤器。如果你想要一个观察所有过滤器的 groupAll,你也可以使用 crossfilter.groupAll

这是 groupAll.reduce() 的缩减函数的解决方案(为简洁起见,使用 ES6 语法),将所有行缩减为 year => {count, total} 的对象。

function avg_paired_tag_reduction(idTag, valTag) {
  return {
    add(p, v) {
      v[idTag].forEach((id, i) => {
        p[id] = p[id] || {count: 0, total: 0};
        ++p[id].count;
        p[id].total += v[valTag][i];
      });
      return p;
    },
    remove(p, v) {
      v[idTag].forEach((id, i) => {
        console.assert(p[id]);
        --p[id].count;
        p[id].total -= v[valTag][i];
      })
      return p;
    },
    init() {
      return {};
    }
  };
}

它会被输入每一行,它会遍历行中的键和值,为每个键生成一个计数和总数。它假定键数组和值数组的长度相同。

然后我们可以使用 "fake group" 将对象按需转换为 dc.js 图表期望的 {key,value} 对数组:

function groupall_map_to_group(groupAll) {
  return {
    all() {
      return Object.entries(groupAll.value())
        .map(([key, value]) => ({key,value}));
    }
  };
}

像这样使用这些函数:

const red = avg_paired_tag_reduction('id', 'val');
const avgPairedTagGroup = turnoverYearsDim.groupAll().reduce(
  red.add, red.remove, red.init
);
console.log(groupall_map_to_group(avgPairedTagGroup).all());

虽然可以计算 运行 平均值,但更有效的方法是计算计数和总数,如上所述,然后告诉图表如何在值访问器中计算平均值:

chart.dimension(turnoverYearsDim)
  .group(groupall_map_to_group(avgPairedTagGroup))
  .valueAccessor(kv => kv.value.total / kv.value.count)

Demo fiddle.