根据日期字段的年份对数据进行分组和计数
Groups and counts data based on year of a Date field
我有这个数据集:
const data = [
{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},
{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},
{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},
{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)},
{animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)},
{animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)},
{animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},
{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},
{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},
{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)},
{animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)},
]
它基本上是一个对象数组。每个对象都包含一个带有日期对象的字段 date
。
我想要的是:
const result = {
cat: {2020: 1, 2021: 2},
dog: {2020: 1, 2021: 2},
hamster: {2019: 1},
't-rex': {2019: 1, 2020: 1},
sheep: {2019: 2},
}
所以按 animal
分组的对象和每个动物值是一个对象,其包含每年的键,值是该年的记录数。
如您所见,cat
在 2021 年有 2 个日期,在 2020 年有 1 个日期,所以 cat: {2020: 1, 2021: 2}
.
我想我可以使用 d3.rollup 并执行以下操作:
const result = d3.rollup(data, v => v.?, d => d.animal)
我不知道怎么用,或者有更聪明的解决方案,我也可以用Lodash。
非常感谢任何帮助!
您可以使用 reduce 轻松实现此结果。
const data = [
{ animal: "cat", name: "mu", date: new Date(2020, 0, 1) },
{ animal: "cat", name: "muji", date: new Date(2021, 0, 1) },
{ animal: "cat", name: "mine", date: new Date(2021, 0, 1) },
{ animal: "dog", name: "fido", date: new Date(2021, 0, 1) },
{ animal: "dog", name: "fido2", date: new Date(2020, 0, 1) },
{ animal: "dog", name: "fido3", date: new Date(2021, 0, 1) },
{ animal: "hamster", name: "gerry", date: new Date(2019, 0, 1) },
{ animal: "t-rex", name: "dino", date: new Date(2020, 0, 1) },
{ animal: "t-rex", name: "sauro", date: new Date(2019, 0, 1) },
{ animal: "sheep", name: "s", date: new Date(2019, 0, 1) },
{ animal: "sheep", name: "sss", date: new Date(2019, 0, 1) },
];
const result = data.reduce((acc, curr) => {
const { animal, date } = curr;
const year = date.getFullYear();
if (acc[animal]) {
acc[animal][year] ? ++acc[animal][year] : (acc[animal][year] = 1);
} else {
acc[animal] = {
[year]: 1,
};
}
return acc;
}, {});
console.log(result);
您可以使用 Object.fromEntries
创建结果对象,因此它具有键,但每个键都有一个空对象。然后在迭代数据时填充这些对象:
const data = [{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)}, {animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)}, {animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)}, {animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)}, {animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)}, ];
let res = Object.fromEntries(data.map(({animal}) => [animal, {}]));
for (let {animal, date} of data) {
let year = date.getFullYear();
res[animal][year] = (res[animal][year]??0) + 1;
}
console.log(res);
d3.rollup
确实很容易:
result = d3.rollup(data, v => v.length, v => v.animal, v => v.date.getFullYear())
这为您提供了嵌套的 Map
s,如果您出于某种原因需要对象,请像这样转换地图:
objects = Object.fromEntries(
Array.from(result, ([k, v]) =>
[k, Object.fromEntries(v)]))
如果你不想要 d3 依赖,你可以在 vanilla JS 中重写 rollup
:
function rollup(data, reducer, grouper, ...groupers) {
let m = new Map
if (!grouper)
return reducer(data)
for (let item of data) {
let key = grouper(item)
m.has(key) ? m.get(key).push(item) : m.set(key, [item])
}
for (let [k, v] of m)
m.set(k, rollup(v, reducer, ...groupers))
return m
}
最后,如果您正在寻找简单快速的代码而不是“智能”代码,您可以简单地迭代一次并直接填充嵌套对象(这是 nullish assignment ??=
派上用场):
const result = {}
for (let obj of data) {
let k1 = obj.animal,
k2 = obj.date.getFullYear()
result[k1] ??= {}
result[k1][k2] ??= 0
result[k1][k2]++
}
感谢@georg 的回答,我想我已经解决了!
const data = [
{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},
{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},
{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},
{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)},
{animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)},
{animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)},
{animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},
{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},
{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},
{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)},
{animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)},
]
const rolled = d3.rollup(data, v => v.length, v => v.animal, v => v.date.getFullYear())
const result = Array.from(rolled).reduce((acc, r) => {
acc[r[0]] = Object.fromEntries(r[1])
return acc
}, {})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
使用 lodash 你可以按 animal
属性 分组,然后使用 _.mapValues()
映射组,并使用 _.countBy()
计算每个组的出现次数年份:
const data = [{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)}, {animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)}, {animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)}, {animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)}, {animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)}, ];
const result = _.mapValues(
_.groupBy(data, 'animal'),
group => _.countBy(group, o => o.date.getFullYear())
)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
和使用 lodash/fp 的相同想法:
const { pipe, groupBy, mapValues, countBy } = _
const fn = pipe(
groupBy('animal'),
mapValues(countBy(o => o.date.getFullYear()))
)
const data = [{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)}, {animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)}, {animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)}, {animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)}, {animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)}, ];
const result = fn(data)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>
我有这个数据集:
const data = [
{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},
{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},
{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},
{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)},
{animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)},
{animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)},
{animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},
{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},
{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},
{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)},
{animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)},
]
它基本上是一个对象数组。每个对象都包含一个带有日期对象的字段 date
。
我想要的是:
const result = {
cat: {2020: 1, 2021: 2},
dog: {2020: 1, 2021: 2},
hamster: {2019: 1},
't-rex': {2019: 1, 2020: 1},
sheep: {2019: 2},
}
所以按 animal
分组的对象和每个动物值是一个对象,其包含每年的键,值是该年的记录数。
如您所见,cat
在 2021 年有 2 个日期,在 2020 年有 1 个日期,所以 cat: {2020: 1, 2021: 2}
.
我想我可以使用 d3.rollup 并执行以下操作:
const result = d3.rollup(data, v => v.?, d => d.animal)
我不知道怎么用,或者有更聪明的解决方案,我也可以用Lodash。
非常感谢任何帮助!
您可以使用 reduce 轻松实现此结果。
const data = [
{ animal: "cat", name: "mu", date: new Date(2020, 0, 1) },
{ animal: "cat", name: "muji", date: new Date(2021, 0, 1) },
{ animal: "cat", name: "mine", date: new Date(2021, 0, 1) },
{ animal: "dog", name: "fido", date: new Date(2021, 0, 1) },
{ animal: "dog", name: "fido2", date: new Date(2020, 0, 1) },
{ animal: "dog", name: "fido3", date: new Date(2021, 0, 1) },
{ animal: "hamster", name: "gerry", date: new Date(2019, 0, 1) },
{ animal: "t-rex", name: "dino", date: new Date(2020, 0, 1) },
{ animal: "t-rex", name: "sauro", date: new Date(2019, 0, 1) },
{ animal: "sheep", name: "s", date: new Date(2019, 0, 1) },
{ animal: "sheep", name: "sss", date: new Date(2019, 0, 1) },
];
const result = data.reduce((acc, curr) => {
const { animal, date } = curr;
const year = date.getFullYear();
if (acc[animal]) {
acc[animal][year] ? ++acc[animal][year] : (acc[animal][year] = 1);
} else {
acc[animal] = {
[year]: 1,
};
}
return acc;
}, {});
console.log(result);
您可以使用 Object.fromEntries
创建结果对象,因此它具有键,但每个键都有一个空对象。然后在迭代数据时填充这些对象:
const data = [{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)}, {animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)}, {animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)}, {animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)}, {animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)}, ];
let res = Object.fromEntries(data.map(({animal}) => [animal, {}]));
for (let {animal, date} of data) {
let year = date.getFullYear();
res[animal][year] = (res[animal][year]??0) + 1;
}
console.log(res);
d3.rollup
确实很容易:
result = d3.rollup(data, v => v.length, v => v.animal, v => v.date.getFullYear())
这为您提供了嵌套的 Map
s,如果您出于某种原因需要对象,请像这样转换地图:
objects = Object.fromEntries(
Array.from(result, ([k, v]) =>
[k, Object.fromEntries(v)]))
如果你不想要 d3 依赖,你可以在 vanilla JS 中重写 rollup
:
function rollup(data, reducer, grouper, ...groupers) {
let m = new Map
if (!grouper)
return reducer(data)
for (let item of data) {
let key = grouper(item)
m.has(key) ? m.get(key).push(item) : m.set(key, [item])
}
for (let [k, v] of m)
m.set(k, rollup(v, reducer, ...groupers))
return m
}
最后,如果您正在寻找简单快速的代码而不是“智能”代码,您可以简单地迭代一次并直接填充嵌套对象(这是 nullish assignment ??=
派上用场):
const result = {}
for (let obj of data) {
let k1 = obj.animal,
k2 = obj.date.getFullYear()
result[k1] ??= {}
result[k1][k2] ??= 0
result[k1][k2]++
}
感谢@georg 的回答,我想我已经解决了!
const data = [
{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},
{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},
{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},
{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)},
{animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)},
{animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)},
{animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},
{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},
{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},
{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)},
{animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)},
]
const rolled = d3.rollup(data, v => v.length, v => v.animal, v => v.date.getFullYear())
const result = Array.from(rolled).reduce((acc, r) => {
acc[r[0]] = Object.fromEntries(r[1])
return acc
}, {})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
使用 lodash 你可以按 animal
属性 分组,然后使用 _.mapValues()
映射组,并使用 _.countBy()
计算每个组的出现次数年份:
const data = [{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)}, {animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)}, {animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)}, {animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)}, {animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)}, ];
const result = _.mapValues(
_.groupBy(data, 'animal'),
group => _.countBy(group, o => o.date.getFullYear())
)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
和使用 lodash/fp 的相同想法:
const { pipe, groupBy, mapValues, countBy } = _
const fn = pipe(
groupBy('animal'),
mapValues(countBy(o => o.date.getFullYear()))
)
const data = [{animal: 'cat', name: 'mu', date: new Date(2020, 0, 1)},{animal: 'cat', name: 'muji', date: new Date(2021, 0, 1)},{animal: 'cat', name: 'mine', date: new Date(2021, 0, 1)},{animal: 'dog', name: 'fido', date: new Date(2021, 0, 1)}, {animal: 'dog', name: 'fido2', date: new Date(2020, 0, 1)}, {animal: 'dog', name: 'fido3', date: new Date(2021, 0, 1)}, {animal: 'hamster', name: 'gerry', date: new Date(2019, 0, 1)},{animal: 't-rex', name: 'dino', date: new Date(2020, 0, 1)},{animal: 't-rex', name: 'sauro', date: new Date(2019, 0, 1)},{animal: 'sheep', name: 's', date: new Date(2019, 0, 1)}, {animal: 'sheep', name: 'sss', date: new Date(2019, 0, 1)}, ];
const result = fn(data)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>