8

関連する質問

この質問は非常に関連性がありますが、2年前のものです。Javaのメモリ内OLAPエンジン

バックグラウンド

与えられた表形式のデータセットから、メモリ内にピボットテーブルのような行列を作成したいと思います

たとえば、婚姻状況のカウントによる年齢(行は年齢、列は婚姻状況)。

  • 入力:年齢といくつかのブールプロパティ(結婚しているなど)を含む人々のリスト、

  • 必要な出力:年齢(行)およびisMarried(列)ごとの人数

私が試したこと(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

私は上記のコードが好きではありません、それは効率的ではなく、読みにくいです、そして私はより良い方法があると確信しています。

質問)

「両方」でgroupByするにはどうすればよいですか?各サブグループのカウントを行うにはどうすればよいですか。

ちょうど30歳で結婚している人は何人いますか?

もう1つの質問は、質問に答えるために、現在の合計をどのように行うかです。

30歳以上の人は何人結婚していますか?


編集:

すべての素晴らしい答えをありがとう。

明確にするために、次の列を持つ「テーブル」を出力に含めたいと思います

  • 年齢(昇順)
  • 既婚者数
  • 未婚の数
  • ランニングトータル既婚
  • 未婚の累計

これらの特定の質問に答えるだけでなく、そのようなすべてのタイプの質問に答えることができるレポートを作成します。

4

4 に答える 4

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

私のピボットタイプはかなり基本的です:

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をご覧ください。

于 2012-10-20T03:31:15.707 に答える
4

あなたはできる

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
于 2012-10-19T19:02:58.643 に答える
1

タプルを使用してグループ化できます。

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 
于 2012-10-19T19:13:27.183 に答える
1

countメソッドをLists に直接使用する方が良いと思います

質問 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 }

おそらく、トラバーサルを 1 回だけ実行することでこれらを最適化できますが、それは要件ですか?

于 2012-10-19T18:55:53.047 に答える