根据日期字段的年份对数据进行分组和计数

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())

这为您提供了嵌套的 Maps,如果您出于某种原因需要对象,请像这样转换地图:

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>