我想在Crossfilter-DC驱动的仪表板中提取逐年统计数据
2017年同比是2017年的总单位数除以2016年的总单位数。
我正在使用DC.js
(因此是D3.js
和Crossfilter
)创建一个交互式仪表板,该仪表板也可用于更改其呈现的数据。
我有数据,尽管数据更宽(除日期和数量外还有〜6个其他属性:大小,颜色等...销售数据),但归结为以下对象:
[
{ date: 2017-12-7, quantity: 56, color: blue ...},
{ date: 2017-2-17, quantity: 104, color: red ...},
{ date: 2016-12-7, quantity: 60, color: red ...},
{ date: 2016-4-15, quantity: 6, color: blue ...},
{ date: 2017-2-17, quantity: 10, color: green ...},
{ date: 2016-12-7, quantity: 12, color: green ...}
...
]
我正在为每个属性显示一个行图,以便您可以按颜色,大小等查看总计。人们将使用这些图表中的每个图表,以查看该属性的总计,并通过按只是一种颜色,或者是颜色和大小,或者是大小等。此设置(相对)简单明了,是DC的用途。
但是,现在,我想添加一些同比数据,以便可以显示以年为x轴,以年值为y轴的条形图(例如,Yo-2019 = Units-2019 /单位-2018)。我还想按季度和月份进行相同操作,以便可以看到2019年3月同比= 2019年3月单位/ 2018年3月单位(季度也是如此)。
我有一个年份维度和总数
var yearDim = crossfilterObject.dimension(_ => _.date.getFullYear());
var quantityGroup = yearDim.group.reduceSum(_ => _.quantity);
尽管无法通过漂亮的漂亮DC.js方式计算出年复一年的计算量。
年份+1
添加另一个维度,即年份+1。尽管如此,我并没有真正得到进一步的帮助,因为我得到的只是两个维度,我想对其年份组进行划分……但不确定如何。
var yearPlusOneDim = crossfilterObject.dimension(_ => _.date.getFullYear() + 1);
在视觉上,我可以分别绘制两个图形,从概念上我知道我想做什么:将yearDim中的2017年数字除以YearPlusOneDim中的2017年数字(实际上是2016年数字)。但是“据我所知,这只是一个概念。
放弃DC绘图
我总是可以使用yearDim的数量组来获取值的数组,然后可以将其输入到普通的D3.js图中。
var annualValues = quantityGroup.all();
console.log(annualValues);
// output = [{key: 2016, value: 78}, {key: 2017, value: 170}]
// example data from the limited rows listed above
但是,这感觉像是一个骇人听闻的解决方案,注定会失败,并且无法从所有快速而动态的DC更新中受益。
答案 0 :(得分:1)
我会使用一个伪造的小组,以便一次性解决这个问题。
正如@Ethan所说,您也可以使用值访问器,但是每次访问值时,您都必须查阅前一年-因此您可能必须保留一个额外的表。对于伪造的组,您只需要在.all()
函数主体中使用此表。
下面是假组的外观的简短概述:
function yoy_group(group) {
return {
all: function() {
// index all values by date
var bydate = group.all().reduce(function(p, kv) {
p[kv.key.getTime()] = kv.value;
return p;
}, {});
// for any key/value pair which had a value one year earlier,
// produce a new pair with the ratio between this year and last
return group.all().reduce(function(p, kv) {
var date = d3.timeYear.offset(kv.key, -1);
if(bydate[date.getTime()])
p.push({key: kv.key, value: kv.value / bydate[date.getTime()]});
return p;
}, []);
}
};
}
这个想法很简单:首先按日期索引所有值。然后,在生成键/值对数组时,向上查找每个键/值对是否一年前有值。如果是这样,将一对插入结果(否则将其放下)。
这应适用于日期已四舍五入的任何带日期键的组。
请注意在几个地方使用Array.reduce。这是crossfilter group.reduce
的精神祖先-它采用了具有与reduce-add函数相同的签名的函数,以及一个初始值(而不是函数)并产生单个值。它没有像交叉过滤器那样对更改做出反应,而是仅循环遍历该数组一次。当您要从数组中生成对象或生成与原始对象大小不同的数组时,这很有用。
此外,当按日期索引对象时,我使用Date.getTime()来获取日期的数字表示形式。否则,日期将强制为可能不准确的字符串表示形式。也许对于此应用程序,可以跳过.getTime()
,但我习惯于始终精确比较日期。
Demo fiddle同比交易量。
答案 1 :(得分:0)
我在下面重写了@Gordon的代码。所有的功劳归功于他的解决方案(以上回答),我只是写下了自己的代码版本(时间更长,并且可能仅对像我这样的初学者有用)的代码(更加冗长!)和解释(还包括更多内容)冗长)来复制我的想法,将我几乎没有的起点桥接到@戈登的真正聪明的答案。
yoyGroup = function(group) {
return { all: function() {
// For every key-value pair in the group, iterate across it, indexing it by it's time-value
var valuesByDate = group.all().reduce(function(outputArray, thisKeyValuePair) {
outputArray[thisKeyValuePair.key.getTime()] = thisKeyValuePair.value;
return outputArray;
}, []);
return group.all().reduce(function(newAllArray, thisKeyValuePair) {
var dateLastYear = d3.timeYear.offset(thisKeyValuePair.key, -1);
if (valuesByDate[dateLastYear.getTime()]) {
newAllArray.push({
key: thisKeyValuePair.key,
value: thisKeyValuePair.value / valuesByDate[dateLastYear.getTime()] - 1
});
}
return newAllArray;
}, []); // closing reduce() and a function(...)
}}; // closing the return object & a function
};
?为什么我们要覆盖all()
函数?
当DC.js根据分组创建图时,Crossfilter唯一使用的功能是all()
函数。因此,如果我们想对分组进行自定义操作以影响DC图,则只需覆盖一个函数:all()
。
¿all()
函数需要返回什么?
组的all
函数必须返回array
个object
,每个object
必须具有两个属性:key
和value
。
¿那么我们到底在这里做什么? 我们从一个现有的组开始,该组显示一段时间内的一些值(重要假设:键是日期对象),然后围绕它创建一个包装器,以便我们可以利用交叉过滤器已经拥有的工作完成以某个级别(例如年,月等)进行汇总。
我们首先使用reduce
将对象数组操纵为更简单的数组,其中对象中的键和值现在直接在数组中。我们这样做是为了更轻松地通过键查找值。
before / output structure of group.all()
[ {key: k1, value: v1},
{key: k2, value: v2},
{key: k3, value: v3}
]
after
[ k1: v1,
k2: v2,
k3: v3
]
然后,我们再次继续创建正确的all()
结构:array
中的objects
每个都具有key
和value
属性。我们从现有组的all()
数组开始(再一次)开始,但是这次我们有了valuesByDate
数组的优势,它将使查找其他日期变得容易。
因此,如果存在一年前的条目({{},我们(通过reduce
)对原始group.all()
的输出进行迭代,并在我们之前生成的数组(valuesByDate
)中进行查找。 1}})。 (我们使用valuesByDate[dateLastYear.getTime()]
,所以它是简单的整数,而不是我们要为其编制索引的对象。)如果一年前存在数组的元素,那么我们会在不久的将来为我们添加键值对象对,包含当前键(日期)的待返回数组,对于该值,我们将“现在”值(getTime()
)除以1年前的值:thisKeyValuePair.value
。最后,我们减去1,以使其为YoY(最传统的定义)。例如今年= 110,去年= 100 ...同比= + 10%= 110/100-1。