如何将Spark中的分类变量转换为一组编码为{0,1}的列?

时间:2015-05-07 14:56:14

标签: scala apache-spark bigdata apache-spark-mllib categorical-data

我正在尝试使用Spark MLlib(带Scala)对包含分类变量的数据集执行逻辑回归(LogisticRegressionWithLBFGS)。我发现Spark无法使用这种变量。

在R中有一种处理这类问题的简单方法:我在因子(类别)中转换变量,因此R创建一组编码为{0,1}指标变量的列。

如何使用Spark执行此操作?

4 个答案:

答案 0 :(得分:4)

使用VectorIndexer,您可以告诉索引器字段可能具有的不同值(基数)的数量,以便通过setMaxCategories()方法被视为分类。

ThisIsAVariable

来自Scaladocs

  

用于索引Vector的数据集中的分类要素列的类。

     

这有两种使用模式:

     

自动识别分类功能(默认行为)

     
    

这有助于将未知矢量的数据集处理成一些数据集     连续特征和一些分类特征。     连续和分类之间的选择基于maxCategories参数。

         

将maxCategories设置为任何分类功能应具有的最大分类数。

         

例如:特征0具有唯一值{-1.0,0.0},特征1值{1.0,3.0,5.0}。如果maxCategories = 2,则特征0将被声明为分类并使用索引{0,1},并且特征1将被声明为连续。

  

我发现这是一种方便的(虽然粗粒度)提取分类值的方法,但要注意,如果在任何情况下你有一个较低的arity你希望连续的字段(例如大学生与原籍国的年龄或美国国家)。

答案 1 :(得分:2)

如果我理解正确,您不想在几个虚拟列中转换1个分类列。您希望spark知道列是分类的而不是数字。

我认为这取决于你现在想要使用的算法。例如,随机的Forest和GBT都将categoricalFeaturesInfo作为参数进行检查:

https://spark.apache.org/docs/1.4.0/api/scala/index.html#org.apache.spark.mllib.tree.RandomForest $

所以例如:

categoricalFeaturesInfo = Map[Int, Int]((1,2),(2,5))

实际上是说你的特征的第二列(索引从0开始,因此1是第二列)是具有2个级别的分类,而第3也是具有5个级别的分类特征。您可以在训练randomForest或GBT时指定这些参数。

你需要确保你的关卡被映射到0,1,2 ...所以如果你有类似的东西("好","中","坏")将其映射到(0,1,2)。

现在您要使用LogisticRegressionWithLBFGS。在这种情况下,我的建议是将分类列实际转换为虚拟列。例如,具有3个级别("良好","中""坏")的单个列分为3列,其中1/0取决于哪一个点击。我没有一个可以使用的示例,所以这里是一个scala中的示例代码应该可以工作:

val dummygen = (data : DataFrame, col:Array[String]) => {
    var temp = data
    for(i <- 0 until col.length) {
      val N = data.select(col(i)).distinct.count.toInt
      for (j<- 0 until N)
      temp = temp.withColumn(col(i) + "_" + j.toString, callUDF(index(j), DoubleType, data(col(i))))
    }
  temp
  }
  val index = (value:Double) => {(a:Double) => {
    if (value==a) {
      1
    } else{
      0
    }
  }}

你可以这样称呼:

val results = dummygen(data, Array("CategoricalColumn1","CategoricalColumn2"))

在这里,我为分类列列表执行此操作(以防万一您的功能列表中有多个列)。第一个&#34; for循环&#34;遍历每个分类列,第二个&#34; for循环&#34;遍历列中的每个级别,并创建一些列等于每列的级别数。

重要!!!假设您首先将它们映射到0,1,2 ......

然后,您可以使用此新功能集运行LogisticRegressionWithLBFGS。这种方法也有助于SVM。

答案 2 :(得分:1)

VectorIndexer即将推出Spark 1.4,它可以帮助您进行此类功能转换:http://people.apache.org/~pwendell/spark-1.4.0-rc1-docs/api/scala/index.html#org.apache.spark.ml.feature.VectorIndexer

然而,看起来这只能在spark.ml而不是mllib

中使用

https://issues.apache.org/jira/browse/SPARK-4081

答案 3 :(得分:0)

如果类别可以适合驱动程序内存,我的建议如下:

import org.apache.spark.ml.feature.StringIndexer
import org.apache.spark.sql.functions._
import org.apache.spark.sql._


val df = Seq((0, "a"),(1, "b"),(2, "c"),(3, "a"),(4, "a"),(5, "c"),(6,"c"),(7,"d"),(8,"b"))
            .toDF("id", "category")
val indexer = new StringIndexer()
                   .setInputCol("category")
                   .setOutputCol("categoryIndex")
                   .fit(df)

val indexed = indexer.transform(df)

val categoriesIndecies = indexed.select("category","categoryIndex").distinct
val categoriesMap: scala.collection.Map[String,Double] = categoriesIndecies.map(x=>(x(0).toString,x(1).toString.toDouble)).collectAsMap()

def getCategoryIndex(catMap: scala.collection.Map[String,Double], expectedValue: Double) = udf((columnValue: String) =>
if (catMap(columnValue) == expectedValue) 1 else 0)


val newDf:DataFrame =categoriesMap.keySet.toSeq.foldLeft[DataFrame](indexed)(
     (acc,c) => 
          acc.withColumn(c,getCategoryIndex(categoriesMap,categoriesMap(c))($"category"))
     )

newDf.show


+---+--------+-------------+---+---+---+---+
| id|category|categoryIndex|  b|  d|  a|  c|
+---+--------+-------------+---+---+---+---+
|  0|       a|          0.0|  0|  0|  1|  0|
|  1|       b|          2.0|  1|  0|  0|  0|
|  2|       c|          1.0|  0|  0|  0|  1|
|  3|       a|          0.0|  0|  0|  1|  0|
|  4|       a|          0.0|  0|  0|  1|  0|
|  5|       c|          1.0|  0|  0|  0|  1|
|  6|       c|          1.0|  0|  0|  0|  1|
|  7|       d|          3.0|  0|  1|  0|  0|
|  8|       b|          2.0|  1|  0|  0|  0|
+---+--------+-------------+---+---+---+---+