【问题标题】:Is there a data structure / library to do in memory olap / pivot tables in Java / Scala?Java / Scala中的内存olap /数据透视表中是否有数据结构/库?
【发布时间】:2012-10-10 09:50:27
【问题描述】:

相关问题

这个问题很相关,但已经2岁了:In memory OLAP engine in Java

背景

我想在内存中从给定的表格数据集创建一个类似于数据透视表的矩阵

例如按婚姻状况计数的年龄(行是年龄,列是婚姻状况)。

  • 输入:人员列表,包括年龄和一些布尔属性(例如已婚),

  • 期望的输出:人数,按年龄(行)和已婚(列)

我尝试过的 (Scala)

case class Person(val age:Int, val isMarried:Boolean)

...
val people:List[Person] = ... //

val peopleByAge = people.groupBy(_.age)  //only by age
val peopleByMaritalStatus = people.groupBy(_.isMarried) //only by marital status

我设法以幼稚的方式做到这一点,首先按年龄分组,然后map 按婚姻状况进行count,并输出结果,然后我将foldRight 汇总

TreeMap(peopleByAge.toSeq: _*).map(x => {
    val age = x._1
    val rows = x._2
    val numMarried = rows.count(_.isMarried())
    val numNotMarried = rows.length - numMarried
    (age, numMarried, numNotMarried)
}).foldRight(List[FinalResult]())(row,list) => {
     val cumMarried = row._2+ 
        (if (list.isEmpty) 0 else list.last.cumMarried) 
     val cumNotMarried = row._3 + 
        (if (list.isEmpty) 0 else l.last.cumNotMarried) 
     list :+ new FinalResult(row._1, row._2, row._3, cumMarried,cumNotMarried) 
}.reverse

我不喜欢上面的代码,效率不高,难以阅读,我相信还有更好的方法。

问题

如何按“两者”分组?以及如何对每个子组进行计数,例如

有多少人正好 30 岁结婚?

还有一个问题,我怎么做一个累计,来回答这个问题:

30岁以上结婚的人有多少?


编辑:

感谢您提供的所有精彩答案。

为了澄清,我希望输出包含一个带有以下列的“表格”

  • 年龄(升序)
  • Num 结婚了
  • Num 未婚
  • 跑步总结婚
  • 跑步总未婚

不仅要回答这些特定的问题,还要生成一份报告,以便回答所有此类问题。

【问题讨论】:

    标签: scala data-structures olap


    【解决方案1】:

    我觉得直接在Lists上使用count方法会更好

    对于问题 1

    people.count { p => p.age == 30 && p.isMarried }
    

    关于问题 2

    people.count { p => p.age > 30 && p.isMarried }
    

    如果您还希望符合这些谓词的实际人群使用过滤器。

    people.filter { p => p.age > 30 && p.isMarried }
    

    您可以通过只进行一次遍历来优化这些,但这是一个要求吗?

    【讨论】:

      【解决方案2】:

      你可以

      val groups = people.groupBy(p => (p.age, p.isMarried))
      

      然后

      val thirty_and_married = groups((30, true))._2
      val over_thirty_and_married_count = 
        groups.filterKeys(k => k._1 > 30 && k._2).map(_._2.length).sum
      

      【讨论】:

      • 不应该是filterKeys(_._1 > 30 && _.2)吗?
      【解决方案3】:

      您可以使用元组进行分组:

      val res1 = people.groupBy(p => (p.age, p.isMarried)) //or
      val res2 = people.groupBy(p => (p.age, p.isMarried)).mapValues(_.size) //if you dont care about People instances
      

      你可以这样回答这两个问题:

      res2.getOrElse((30, true), 0)
      res2.filter{case (k, _) => k._1 > 30 && k._2}.values.sum
      res2.filterKeys(k => k._1 > 30 && k._2).values.sum // nicer with filterKeys from Rex Kerr's answer
      

      您可以使用 List 上的方法数来回答这两个问题:

      people.count(p => p.age == 30 && p.isMarried)
      people.count(p => p.age > 30 && p.isMarried)
      

      或者使用过滤器和大小:

      people.filter(p => p.age == 30 && p.isMarried).size
      people.filter(p => p.age > 30 && p.isMarried).size
      

      编辑: 你的代码稍微干净一点的版本:

      TreeMap(peopleByAge.toSeq: _*).map {case (age, ps) =>
          val (married, notMarried) = ps.span(_.isMarried)
          (age, married.size, notMarried.size)
        }.foldLeft(List[FinalResult]()) { case (acc, (age, married, notMarried)) =>
          def prevValue(f: (FinalResult) => Int) = acc.headOption.map(f).getOrElse(0)
          new FinalResult(age, married, notMarried, prevValue(_.cumMarried) + married, prevValue(_.cumNotMarried) + notMarried) :: acc
        }.reverse 
      

      【讨论】:

        【解决方案4】:

        这里有一个更冗长的选项,但是以通用方式而不是使用严格的数据类型来执行此操作。你当然可以使用泛型来使它更好,但我想你明白了。

        /** Creates a new pivot structure by finding correlated values 
          * and performing an operation on these values
          *
          * @param accuOp the accumulator function (e.g. sum, max, etc)
          * @param xCol the "x" axis column
          * @param yCol the "y" axis column
          * @param accuCol the column to collect and perform accuOp on
          * @return a new Pivot instance that has been transformed with the accuOp function
          */
        def doPivot(accuOp: List[String] => String)(xCol: String, yCol: String, accuCol: String) = {
          // create list of indexes that correlate to x, y, accuCol
          val colsIdx = List(xCol, yCol, accuCol).map(headers.getOrElse(_, 1))
        
          // group by x and y, sending the resulting collection of
          // accumulated values to the accuOp function for post-processing
          val data = body.groupBy(row => {
            (row(colsIdx(0)), row(colsIdx(1)))
          }).map(g => {
            (g._1, accuOp(g._2.map(_(colsIdx(2)))))
          }).toMap
        
          // get distinct axis values
          val xAxis = data.map(g => {g._1._1}).toList.distinct
          val yAxis = data.map(g => {g._1._2}).toList.distinct
        
          // create result matrix
          val newRows = yAxis.map(y => {
            xAxis.map(x => {
              data.getOrElse((x,y), "")
            })
          })
        
         // collect it with axis labels for results
         Pivot(List((yCol + "/" + xCol) +: xAxis) :::
           newRows.zip(yAxis).map(x=> {x._2 +: x._1}))
        }
        

        我的 Pivot 类型非常基本:

        class Pivot(val rows: List[List[String]]) {
        
          val headers = rows.head.zipWithIndex.toMap
          val body    = rows.tail
          ...
        }
        

        为了测试它,你可以这样做:

        val marriedP = Pivot(
          List(
            List("Name", "Age", "Married"),
            List("Bill", "42", "TRUE"),
            List("Heloise", "47", "TRUE"),
            List("Thelma", "34", "FALSE"),
            List("Bridget", "47", "TRUE"),
            List("Robert", "42", "FALSE"),
            List("Eddie", "42", "TRUE")
        
          )
        )
        
        def accum(values: List[String]) = {
            values.map(x => {1}).sum.toString
        }
        println(marriedP + "\n")
        println(marriedP.doPivot(accum)("Age", "Married", "Married"))
        

        产量:

        Name     Age      Married  
        Bill     42       TRUE     
        Heloise  47       TRUE     
        Thelma   34       FALSE    
        Bridget  47       TRUE     
        Robert   42       FALSE    
        Eddie    42       TRUE     
        
        Married/Age  47           42           34           
        TRUE         2            2                         
        FALSE                     1            1 
        

        好消息是您可以像在传统的 excel 数据透视表中一样使用柯里化为值传递任何函数。

        更多信息可以在这里找到:https://github.com/vinsonizer/pivotfun

        【讨论】:

          猜你喜欢
          • 2021-03-24
          • 2022-11-29
          • 2013-10-10
          • 2018-11-24
          • 2020-11-22
          • 1970-01-01
          • 2018-09-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多