【问题标题】:Groups and counts data based on year of a Date field根据日期字段的年份对数据进行分组和计数
【发布时间】:2021-08-10 14:03:49
【问题描述】:

我有这个数据集:

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。

非常感谢任何帮助!

【问题讨论】:

    标签: javascript d3.js lodash


    【解决方案1】:

    使用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]++
    }
    

    【讨论】:

      【解决方案2】:

      您可以使用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);

      【讨论】:

        【解决方案3】:

        您可以使用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);

        【讨论】:

          【解决方案4】:

          感谢@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>

          【讨论】:

            【解决方案5】:

            使用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>

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-03-03
              • 1970-01-01
              • 1970-01-01
              • 2012-03-05
              • 1970-01-01
              相关资源
              最近更新 更多