在Java/Scala中,内存中是否有olap/pivot表的数据结构/库? 相关问题
这个问题非常相关,但已经2年了: 背景 我想从给定的表格数据集在内存中创建一个类似透视表的矩阵 e、 g.按婚姻状况统计的年龄(行为年龄,列为婚姻状况)在Java/Scala中,内存中是否有olap/pivot表的数据结构/库? 相关问题,scala,data-structures,olap,Scala,Data Structures,Olap,这个问题非常相关,但已经2年了: 背景 我想从给定的表格数据集在内存中创建一个类似透视表的矩阵 e、 g.按婚姻状况统计的年龄(行为年龄,列为婚姻状况) 输入:具有年龄和某些布尔属性(例如已婚)的人员列表 所需输出:按年龄(行)和已婚(列)统计人数 我所尝试的(Scala) 我尝试了一种简单的方法,首先按年龄分组,然后按婚姻状况进行计数,然后输出结果,然后我按年龄进行聚合 TreeMap(peopleByAge.toSeq: _*).map(x => { val age = x
- 输入:具有年龄和某些布尔属性(例如已婚)的人员列表
- 所需输出:按年龄(行)和已婚(列)统计人数
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已婚
- 没有结婚
- 全速前进
- 跑步总不结婚
不仅要回答那些特定的问题,还要生成一份能够回答所有此类问题的报告。我认为最好直接使用
列表上的计数方法
关于问题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 }
您可以通过只执行一次遍历来优化这些,但这是一项要求吗?您可以
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
您可以使用元组进行分组:
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
您可以使用列表中的方法计数来回答这两个问题:
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
这里有一个更详细一点的选项,但是它是以一种通用的方式来实现的,而不是使用严格的数据类型。当然,你可以使用泛型来让它变得更好,但我想你明白了
/** 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}))
}
我的轴心类型非常基本:
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透视表中一样,使用currying为值传递任何函数
更多信息可以在这里找到:它不应该是filterKeys(u.1>30&&uu.2)
?
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