Я использую Spark 1.5.2 и Java API. Есть ли способ создать DataFrame, содержащий количество слов для каждого документа со всеми словами и счетчиками в одной строке для каждого документа?

Пока что я смог использовать «org.apache.spark.sql.functions.explode», чтобы преобразовать каждое слово в текстовой документе в новую строку.

Затем я могу создать новый DataFrame, содержащий каждый документ, слово и количество слов в нескольких строках, используя следующий код:

df = df.orderBy("doc_id").groupBy(df.col("doc_id"), df.col("word")).count(); 

Выход:

+------+-----------+-----+
|doc_id|       word|count|
+------+-----------+-----+
|doc_1 |       game|    2|
|doc_1 |       life|    1|
|doc_1 |everlasting|    1|
|doc_1 |      learn|    1|
|doc_2 |    special|    1|
|doc_2 |     moment|    1|
|doc_2 |       time|    1|
|doc_3 | unexamined|    1|
|doc_3 |       life|    1|
|doc_3 |      worth|    1|
|doc_3 |       live|    1|
+------+-----------+-----+

Как создать dataframe в следующем формате:

 +------+-----------+---------------------------------+
 |doc_id|      word_counts|
 +------+-----------+------------------------------+
 |doc_1 |{game=1, learn=2, everlating=1, life=1}
 |doc_2 |{special=1, moment=2, everlating=1, time=1}

Спасибо. Любые идеи приветствуются

2
tullm 22 Дек 2015 в 22:40

2 ответа

Лучший ответ

Я бы не использовал {{x0}} в первую очередь. Если вы начнете с документа в строке, вы можете использовать Compute Counts напрямую, например, с использованием ML трансформаторов. Очень простой пример может посмотреть это:

import org.apache.spark.ml.feature.Tokenizer
import org.apache.spark.ml.feature.CountVectorizer

val df = sc.parallelize(Seq(
  ("doc_1", "game game life everlasting learn"),
  ("doc_2", "special moment time unexamined"),
  ("doc_3", "life worth live")
)).toDF("doc_id", "text")

val tokenizer = new Tokenizer()
  .setInputCol("text")
  .setOutputCol("words")

val tokenized = tokenizer.transform(df)

val cvModel = new CountVectorizer()
  .setInputCol("words")
  .setOutputCol("features")
  .fit(tokenized)

val counted = cvModel.transform(tokenized)

На данный момент у вас уже есть счетчики на документ. Хранение токенов явно в каждой строке довольно расточительно, но это можно сделать с помощью небольшого UDF:

import org.apache.spark.mllib.linalg.{SparseVector, Vector} 

def vectorsToMaps(vocabulary: Array[String]) = {
  udf((v: Vector) => {
    val sv = v.toSparse
    sv.indices.map(i => (vocabulary(i) -> sv(i))).toMap
  })
}

counted.select(vectorsToMaps(cvModel.vocabulary)($"features")
  .alias("freqs"))
  .show(3, false)

// +------------------------------------------------------------------+
// |freqs                                                             |
// +------------------------------------------------------------------+
// |Map(game -> 2.0, life -> 1.0, learn -> 1.0, everlasting -> 1.0)   |
// |Map(moment -> 1.0, special -> 1.0, unexamined -> 1.0, time -> 1.0)|
// |Map(life -> 1.0, live -> 1.0, worth -> 1.0)                       |
// +------------------------------------------------------------------+
0
zero323 23 Дек 2015 в 06:01

Вы можете раскрыть RDD и использовать aggregateByKey:

df.rdd
  .aggregateByKey(Map[String,Int]())
  (
    (wordMap, word) => wordMap + (word -> (1 + wordMap.getOrElse(word, 0))), 
    (wordMap1, wordMap2) => wordMap1 ++ wordMap2.map{ case(k,v) => (k -> (v + wordMap1.getOrElse(k,0))) }
  )
0
Justin Pihony 22 Дек 2015 в 21:09