6

Sto tentando di eseguire una regressione logistica (LogisticRegressionWithLBFGS) con Spark MLlib (con Scala) su un set di dati che contiene variabili categoriali. Scopro che Spark non è stato in grado di lavorare con quel tipo di variabile.Come trasformare una variabile categoriale in Spark in un insieme di colonne codificate come {0,1}?

In R c'è un modo semplice per gestire questo tipo di problema: trasformo la variabile in fattore (categorie), quindi R crea un insieme di colonne codificate come variabili dell'indicatore {0,1}.

Come posso eseguire questo con Spark?

+0

Cosa intendi con "non può funzionare con quel tipo di variabile"? Non sono esperto in R, ma non è una variabile categoriale solo un'enumerazione? –

+0

Voglio dire se non dico a R che la mia variabile è categoriale, R la considera come una variabile continua (ad esempio una variabile che è uguale a "'1'" per la presenza di una caratteristica specifica, "'2'" se non e "'3" "se mancano le informazioni). Per distinguere questa variabile da una variabile continua, dico a R di trasformare la variabile in fattore con il comando "as.factor". In Spark, la variabile viene automaticamente considerata come continua e il comando automatico "as.factor" non esiste, quindi devo crearlo da solo. – SparkUser

risposta

2

Se ho capito correttamente, non si desidera convertire 1 colonna categoriale in diverse colonne fittizie. Vuoi che la scintilla capisca che la colonna è categorica e non numerica.

Penso che dipenda dall'algoritmo che si desidera utilizzare in questo momento. Per esempio Foresta casuale e GBT avere sia categoricalFeaturesInfo come parametro verificarlo qui:

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

così per esempio:

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

è in realtà dicendo che seconda colonna delle vostre caratteristiche (indice inizia in 0, quindi 1 è la seconda colonna) è una categoriale con 2 livelli e la terza è anche una funzione categoriale con 5 livelli. È possibile specificare questi parametri quando si allena il randomForest o il GBT.

È necessario assicurarsi che i livelli siano mappati su 0,1,2 ... quindi se si dispone di qualcosa di simile ("buono", "medio", "cattivo") mapparlo in (0,1,2).

Ora nel tuo caso si desidera utilizzare LogisticRegressionWithLBFGS. In questo caso il mio suggerimento è di trasformare effettivamente le colonne categoriali in colonne fittizie. Ad esempio una singola colonna con 3 livelli ("buono", "medio", "cattivo") in 3 colonne con 1/0 a seconda di quale colpisce. Non ho un esempio di lavorare con ecco un codice di esempio in scala che dovrebbe funzionare:

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

che si può chiamare piace:

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

Qui lo faccio per un elenco di Colonne categoriali (nel caso in cui ne abbiate più di 1 nell'elenco delle caratteristiche). Il primo "ciclo for" passa per ciascuna colonna categoriale, il secondo "ciclo per" passa attraverso ogni livello della colonna e crea un numero di colonne uguale al numero di livelli per ciascuna colonna.

Importante !!! che si presuppone che li abbia prima mappati a 0,1,2 ...

È quindi possibile eseguire LogisticRegressionWithLBFGS utilizzando questo nuovo set di funzionalità. Questo approccio aiuta anche con SVM.

0

Se le categorie possono rientrare nella memoria del driver, qui è il mio suggerimento:

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| 
+---+--------+-------------+---+---+---+---+ 
3

Utilizzando VectorIndexer, si può dire l'indicizzatore il numero di valori diversi (cardinalità) che un campo può avere al fine di essere considerato categoriale con il metodo setMaxCategories().

val indexer = new VectorIndexer() 
.setInputCol("features") 
.setOutputCol("indexed") 
.setMaxCategories(10) 

Da Scaladocs:

Class per indicizzazione categorici caratteristica colonne in un set di dati di vettore.

Questo ha 2 modalità di utilizzo:

identificare automaticamente funzioni categoriali (comportamento di default)

Questo aiuta processo di un set di dati di vettori sconosciuti in un insieme di dati con alcune caratteristiche continue e alcune caratteristiche categoriali. La scelta tra continuo e categoriale si basa su un parametro maxCategories.

Impostare maxCategories sul numero massimo di categoriali che ogni caratteristica categoriale dovrebbe avere.

E.g .: La caratteristica 0 ha valori univoci {-1.0, 0.0} e valori di caratteristica 1 {1.0, 3.0, 5.0}. Se maxCategories = 2, la caratteristica 0 verrà dichiarata categorica e utilizzerà gli indici {0, 1} e la caratteristica 1 sarà dichiarata continua.

Trovo questo modo una vantaggiosa (anche se a grana grossa) per estrarre i valori categoriali, ma attenzione se in ogni caso si dispone di un campo con arity inferiore che si desidera essere continua (ad esempio l'età in un college studenti vs paese di origine o stato USA).

Problemi correlati