我正在尝试使用Spark MLlib(带Scala)对包含分类变量的数据集执行逻辑回归(LogisticRegressionWithLBFGS)。我发现Spark无法使用这种变量。
在R中有一种处理这类问题的简单方法:我在因子(类别)中转换变量,因此R创建一组编码为{0,1}指标变量的列。
如何使用Spark执行此操作?
答案 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
中使用答案 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|
+---+--------+-------------+---+---+---+---+