使用交叉过滤器减少数据点的数量
Reduce number of datapoints using crossfilter
假设我有 100 年的月度数据,总共 1200 个数据点,见底部。
要绘制一个微小的概览折线图(例如只有 100 个数据点),我必须通过分组手动完成。比如将数据按年分组,然后取12个月的平均值,遍历每组,最后将数据点减少到100个。
有没有使用 crossfilter 或任何其他库的便捷方法来代替这种方法?
[
{ date: 1900-01, value: 72000000000},
{ date: 1900-02, value: 58000000000},
{ date: 1900-03, value: 2900000000},
{ date: 1900-04, value: 31000000000},
{ date: 1900-05, value: 33000000000},
...
{ date: 1999-11, value: 30000000000},
{ date: 1999-12, value: 10000000000},
]
假设你的问题只涉及生成数据,你可以使用 d3-nest,不带交叉过滤器,每年取平均值:
解析日期值,然后您可以将日期格式化为年份以创建键。这按键对值进行分组,然后我们使用函数汇总这些值以计算给定年份的平均值:
var parse = d3.timeParse("%Y-%m"); // takes: "1900-01"
var format = d3.timeFormat("%Y"); // gives: "1900"
var means = d3.nest()
.key(function(d) { return format(parse(d.date)); })
.rollup(function(values) { return d3.mean(values, function(d) {return d.value; }) })
.entries(data);
这给了我们以下结构:
[
{
"key": "1900",
"value": 39380000000
},
{
"key": "1999",
"value": 20000000000
}
]
var data = [
{ date: "1900-01", value: 72000000000},
{ date: "1900-02", value: 58000000000},
{ date: "1900-03", value: 2900000000},
{ date: "1900-04", value: 31000000000},
{ date: "1900-05", value: 33000000000},
{ date: "1999-11", value: 30000000000},
{ date: "1999-12", value: 10000000000},
];
var parse = d3.timeParse("%Y-%m");
var format = d3.timeFormat("%Y");
var means = d3.nest()
.key(function(d) { return format(parse(d.date)); })
.rollup(function(values) { return d3.mean(values, function(d) {return d.value; }) })
.entries(data);
console.log(means);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
无论您使用哪个库,算法都是相同的,只是指定方式不同。在这种情况下,d3.nest 可能是最简单的方法,但如果您想要快速过滤,交叉过滤方法也不错。
使用 d3.nest 和 crossfilter 的区别在于我们不是在构建值数组,而是构建单个值。所以我们将同时维护 sum 和 count。
我们还需要指定从容器中删除一行时发生的情况。
var parse = d3.timeParse("%Y-%m");
data.forEach(function(d) {
// it's best to convert fields before passing to crossfilter
// because crossfilter will look at them many times
d.date = parse(d.key);
});
var cf = crossfilter(data);
var yearDim = cf.dimension(d => d3.timeYear(d.date));
var yearAvgGroup = yearDim.group().reduce(
function(p, v) { // add
p.sum += v.value;
++p.count;
p.avg = p.sum/p.count;
return p;
},
function(p, v) { // remove
p.sum -= v.value;
--p.count;
p.avg = p.count ? p.sum/p.count : 0;
return p;
},
function() { // init
return {sum: 0, count: 0, avg: 0};
}
);
现在yearAvgGroup.all()
会return一个key/value对的数组,key是年份,value包含sum
,count
,和 avg
.
Crossfilter并没有使这个问题特别方便解决,但是reductio有一个辅助函数:
var yearAvgGroup = yearDim.group();
reductio().avg(d => d.value);
注意:除非你有大量的数据,否则这并不重要,但只计算组中的总和和计数,并在需要时计算平均值会更有效。
如果您使用 dc.js,您可以为此使用 valueAccessor
:
// remove avg lines from the above, and
chart.dimension(yearDim)
.group(yearAvgGroup)
.valueAccessor(kv => kv.value.sum / kv.value.count);
假设我有 100 年的月度数据,总共 1200 个数据点,见底部。
要绘制一个微小的概览折线图(例如只有 100 个数据点),我必须通过分组手动完成。比如将数据按年分组,然后取12个月的平均值,遍历每组,最后将数据点减少到100个。
有没有使用 crossfilter 或任何其他库的便捷方法来代替这种方法?
[
{ date: 1900-01, value: 72000000000},
{ date: 1900-02, value: 58000000000},
{ date: 1900-03, value: 2900000000},
{ date: 1900-04, value: 31000000000},
{ date: 1900-05, value: 33000000000},
...
{ date: 1999-11, value: 30000000000},
{ date: 1999-12, value: 10000000000},
]
假设你的问题只涉及生成数据,你可以使用 d3-nest,不带交叉过滤器,每年取平均值:
解析日期值,然后您可以将日期格式化为年份以创建键。这按键对值进行分组,然后我们使用函数汇总这些值以计算给定年份的平均值:
var parse = d3.timeParse("%Y-%m"); // takes: "1900-01"
var format = d3.timeFormat("%Y"); // gives: "1900"
var means = d3.nest()
.key(function(d) { return format(parse(d.date)); })
.rollup(function(values) { return d3.mean(values, function(d) {return d.value; }) })
.entries(data);
这给了我们以下结构:
[
{
"key": "1900",
"value": 39380000000
},
{
"key": "1999",
"value": 20000000000
}
]
var data = [
{ date: "1900-01", value: 72000000000},
{ date: "1900-02", value: 58000000000},
{ date: "1900-03", value: 2900000000},
{ date: "1900-04", value: 31000000000},
{ date: "1900-05", value: 33000000000},
{ date: "1999-11", value: 30000000000},
{ date: "1999-12", value: 10000000000},
];
var parse = d3.timeParse("%Y-%m");
var format = d3.timeFormat("%Y");
var means = d3.nest()
.key(function(d) { return format(parse(d.date)); })
.rollup(function(values) { return d3.mean(values, function(d) {return d.value; }) })
.entries(data);
console.log(means);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
无论您使用哪个库,算法都是相同的,只是指定方式不同。在这种情况下,d3.nest 可能是最简单的方法,但如果您想要快速过滤,交叉过滤方法也不错。
使用 d3.nest 和 crossfilter 的区别在于我们不是在构建值数组,而是构建单个值。所以我们将同时维护 sum 和 count。
我们还需要指定从容器中删除一行时发生的情况。
var parse = d3.timeParse("%Y-%m");
data.forEach(function(d) {
// it's best to convert fields before passing to crossfilter
// because crossfilter will look at them many times
d.date = parse(d.key);
});
var cf = crossfilter(data);
var yearDim = cf.dimension(d => d3.timeYear(d.date));
var yearAvgGroup = yearDim.group().reduce(
function(p, v) { // add
p.sum += v.value;
++p.count;
p.avg = p.sum/p.count;
return p;
},
function(p, v) { // remove
p.sum -= v.value;
--p.count;
p.avg = p.count ? p.sum/p.count : 0;
return p;
},
function() { // init
return {sum: 0, count: 0, avg: 0};
}
);
现在yearAvgGroup.all()
会return一个key/value对的数组,key是年份,value包含sum
,count
,和 avg
.
Crossfilter并没有使这个问题特别方便解决,但是reductio有一个辅助函数:
var yearAvgGroup = yearDim.group();
reductio().avg(d => d.value);
注意:除非你有大量的数据,否则这并不重要,但只计算组中的总和和计数,并在需要时计算平均值会更有效。
如果您使用 dc.js,您可以为此使用 valueAccessor
:
// remove avg lines from the above, and
chart.dimension(yearDim)
.group(yearAvgGroup)
.valueAccessor(kv => kv.value.sum / kv.value.count);