使用 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)
我有以下形式的数据:
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)