【问题标题】:Histogram based on "reduceSummed" groups基于“reduceSummed”组的直方图
【发布时间】:2021-05-31 14:20:28
【问题描述】:

我有以下模式的 CSV 数据:

Quarter,productCategory,unitsSold
2018-01-01,A,21766
2018-01-01,B,10076
2018-01-01,C,4060
2018-04-01,A,27014
2018-04-01,B,12219
2018-04-01,C,4740
2018-07-01,A,29503
2018-07-01,B,13020
2018-07-01,C,5549
2018-10-01,A,3796
2018-10-01,B,15110
2018-10-01,C,6137
2019-01-01,A,25008
2019-01-01,B,11655
2019-01-01,C,4630
2019-04-01,A,31633
2019-04-01,B,14837
2019-04-01,C,5863
2019-07-01,A,33813
2019-07-01,B,15442
2019-07-01,C,6293
2019-10-01,A,35732
2019-10-01,B,19482
2019-10-01,C,6841

如您所见,每天销售 3 个产品类别。我可以制作一个直方图,并计算每箱 unitSold 涉及多少个 Quarters。这里的问题是每个季度都是单独计算的。我想要的是一个直方图,其中 unitsSold 的 bin 已经与 Quarter 上的 reduceSum 分组。

这将导致如下结果:

Quarter, unitsSold
2018-01-01,35902
2018-04-01,43973
2018-07-01,48072
2018-10-01,25043
2019-01-01,41293
2019-04-01,52333
2019-07-01,55548
2019-10-01,62055

根据unitsSold 的分类,有多少Quarters 会落入其中。例如,50.000 - 70.000 的 bin 将计算 3 个季度(2019-04-01、2019-07-01 和 2019-10-01)

通常我会这样做:

const histogramChart = new dc.BarChart('#histogram');
const histogramDim = ndx.dimension(d => Math.round(d.unitsSold / binSize) * binSize);
const histogramGroup = histogramDim.group().reduceCount();

但在理想情况下,直方图是在已经“reducedSummed”的东西上创建的。以这样的条形图直方图结束(数据与本示例不匹配):

如何使用 dc.js/crossfilter.js 来做到这一点?

【问题讨论】:

  • 我用另一个例子试过了,但我就是做错了。

标签: dc.js crossfilter


【解决方案1】:

按值重新分组数据

我认为您的问题与this previous question 之间的主要区别在于您希望在“重新组合”数据时对数据进行分箱。 (有时这被称为“双重减少”......这些东西没有明确的名称。)

这是一种方法,使用偏移量和宽度:

function regroup(group, width, offset = 0) {
  return {
    all: function() {
      const bins = {};
      group.all().forEach(({key, value}) => {
        const bin = Math.floor((value - offset) / width);
        bins[bin] = (bins[bin] || 0) + 1;
      });
      return Object.entries(bins).map(
        ([bin, count]) => ({key: bin*width + offset, value: count}));
    }
  }
}

我们在这里做的是遍历原始组和

  1. 将每个值映射到其 bin 编号
  2. 增加该 bin 编号的计数,或从 1 开始
  3. 将垃圾箱映射回带有计数的原始数字

测试一下

我用下面的图表展示了你的原始数据(懒得计算季度,虽然我认为最近的 D3 并不难):

const quarterDim = cf.dimension(({Quarter}) => Quarter),
    unitsGroup = quarterDim.group().reduceSum(({unitsSold}) => unitsSold);

quarterChart.width(300)
    .height(200)
  .margins({left: 50, top: 0, right: 0, bottom: 20})
    .dimension(quarterDim)
  .group(unitsGroup)
  .x(d3.scaleTime().domain([d3.min(data, d => d.Quarter), d3.timeMonth.offset(d3.max(data, d => d.Quarter), 3)]))
  .elasticY(true)
  .xUnits(d3.timeMonths);

和新的图表

const rg = regroup(unitsGroup, 10000);
countQuartersChart.width(500)
  .height(200)
  .dimension({})
  .group(rg)
  .x(d3.scaleLinear())
  .xUnits(dc.units.fp.precision(10000))
  .elasticX(true)
  .elasticY(true);

(注意空维度,它禁用过滤。过滤可能是可能的,但您必须映射回原始维度键,所以我现在跳过它。)

这是我得到的图表,一看就知道是正确的:

Demo fiddle.

为图表添加过滤

要对这个“按值计算的季度数”直方图进行过滤,首先让我们通过将按值图表放在自己的维度上来启用按值图表和季度图表之间的过滤:

const quarterDim2 = cf.dimension(({Quarter}) => Quarter),
    unitsGroup2 = quarterDim2.group().reduceSum(({unitsSold}) => unitsSold);
const byvaluesGroup = regroup(unitsGroup2, 10000);
countQuartersChart.width(500)
    .height(200)
  .dimension(quarterDim2)
  .group(byvaluesGroup)
  .x(d3.scaleLinear())
  .xUnits(dc.units.fp.precision(10000))
  .elasticX(true)
  .elasticY(true);

然后,我们实现过滤

countQuartersChart.filterHandler((dimension, filters) => {
  if(filters.length === 0)
    dimension.filter(null);
  else {
    console.assert(filters.length === 1 && filters[0].filterType === 'RangedFilter');
    const range = filters[0];
    const included_quarters = unitsGroup2.all()
        .filter(({value}) => range[0] <= value && value < range[1])
        .map(({key}) => key.getTime());
    dimension.filterFunction(k => included_quarters.includes(k.getTime()));
  }
  return filters;
});

这会在unitsGroup2 中找到值在该范围内的所有季度。然后它将维度的过滤器设置为仅接受这些季度的日期。

零碎物品

季度

D3 支持带有interval.every 的宿舍:

const quarterInterval = d3.timeMonth.every(3);

chart.xUnits(quarterInterval.range);

消除第零个 bin

正如 cmets 中所讨论的,当其他图表启用过滤器时,最终可能会有许多季度的销量少于 10000 件,从而导致非常高的零柱扭曲图表。

第零个 bin 可以使用

  delete bins[0];

regroup()返回之前

四舍五入按值画笔

如果需要捕捉到条形,您可以启用它

.round(x => Math.round(x/10000)*10000)

否则,过滤的范围可以在条形内开始或结束, the way the bars are colored when brushed is somewhat inaccurate 如下所示。

Here's the new fiddle.

【讨论】:

  • 谢谢!它像瑞士时钟一样工作。就像您已经提到的那样,过滤被禁用。
  • 它实际上在过滤产品类别时有效,但在其余部分(时间组)上无效。您能否添加过滤解决方案,使其实际上表现得像一个正常/单一减少的直方图?
  • 更新:productCategory 的过滤也会产生奇怪的行为。所以确实,过滤还没有实现:-)
  • 它应该根据其他图表过滤好。只要确保您在这些图表上使用不同的维度,因为a group does not observe its own dimension’s filters。所以你可能需要两个时间维度。我上面的评论是,很难根据 this 图表中的选择来实现过滤。 “奇怪的行为”不是描述性的……很确定您会发现它正在按照您的维度/组定义进行操作。 ;-)
  • 我从您的示例中获取了确切的代码,并为重组图表使用了不同的维度。当我通过另一个图表过滤时,例如在一年中,会发生这种情况:1. 0 之后的 bin 被所有季度填充。还有一些垃圾箱,您可以在其中看到一个非常垂直的小条。在过渡中,最初似乎条形图正朝着正确的方向发展,但随后就变成了这种情况。
猜你喜欢
  • 1970-01-01
  • 2021-08-21
  • 1970-01-01
  • 1970-01-01
  • 2020-01-04
  • 1970-01-01
  • 2012-01-18
  • 2019-11-29
  • 1970-01-01
相关资源
最近更新 更多