Skip to content
This repository was archived by the owner on Jan 9, 2020. It is now read-only.

Commit 2eb6764

Browse files
wangmiao1981yanboliang
authored andcommitted
[SPARK-18476][SPARKR][ML] SparkR Logistic Regression should should support output original label.
## What changes were proposed in this pull request? Similar to SPARK-18401, as a classification algorithm, logistic regression should support output original label instead of supporting index label. In this PR, original label output is supported and test cases are modified and added. Document is also modified. ## How was this patch tested? Unit tests. Author: wm624@hotmail.com <wm624@hotmail.com> Closes apache#15910 from wangmiao1981/audit.
1 parent 0a81121 commit 2eb6764

4 files changed

Lines changed: 54 additions & 30 deletions

File tree

R/pkg/R/mllib.R

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,6 @@ setMethod("predict", signature(object = "KMeansModel"),
712712
#' of L1 and L2. Default is 0.0 which is an L2 penalty.
713713
#' @param maxIter maximum iteration number.
714714
#' @param tol convergence tolerance of iterations.
715-
#' @param fitIntercept whether to fit an intercept term.
716715
#' @param family the name of family which is a description of the label distribution to be used in the model.
717716
#' Supported options:
718717
#' \itemize{
@@ -747,11 +746,11 @@ setMethod("predict", signature(object = "KMeansModel"),
747746
#' \dontrun{
748747
#' sparkR.session()
749748
#' # binary logistic regression
750-
#' label <- c(1.0, 1.0, 1.0, 0.0, 0.0)
751-
#' feature <- c(1.1419053, 0.9194079, -0.9498666, -1.1069903, 0.2809776)
752-
#' binary_data <- as.data.frame(cbind(label, feature))
749+
#' label <- c(0.0, 0.0, 0.0, 1.0, 1.0)
750+
#' features <- c(1.1419053, 0.9194079, -0.9498666, -1.1069903, 0.2809776)
751+
#' binary_data <- as.data.frame(cbind(label, features))
753752
#' binary_df <- createDataFrame(binary_data)
754-
#' blr_model <- spark.logit(binary_df, label ~ feature, thresholds = 1.0)
753+
#' blr_model <- spark.logit(binary_df, label ~ features, thresholds = 1.0)
755754
#' blr_predict <- collect(select(predict(blr_model, binary_df), "prediction"))
756755
#'
757756
#' # summary of binary logistic regression
@@ -783,7 +782,7 @@ setMethod("predict", signature(object = "KMeansModel"),
783782
#' @note spark.logit since 2.1.0
784783
setMethod("spark.logit", signature(data = "SparkDataFrame", formula = "formula"),
785784
function(data, formula, regParam = 0.0, elasticNetParam = 0.0, maxIter = 100,
786-
tol = 1E-6, fitIntercept = TRUE, family = "auto", standardization = TRUE,
785+
tol = 1E-6, family = "auto", standardization = TRUE,
787786
thresholds = 0.5, weightCol = NULL, aggregationDepth = 2,
788787
probabilityCol = "probability") {
789788
formula <- paste(deparse(formula), collapse = "")
@@ -795,10 +794,10 @@ setMethod("spark.logit", signature(data = "SparkDataFrame", formula = "formula")
795794
jobj <- callJStatic("org.apache.spark.ml.r.LogisticRegressionWrapper", "fit",
796795
data@sdf, formula, as.numeric(regParam),
797796
as.numeric(elasticNetParam), as.integer(maxIter),
798-
as.numeric(tol), as.logical(fitIntercept),
799-
as.character(family), as.logical(standardization),
800-
as.array(thresholds), as.character(weightCol),
801-
as.integer(aggregationDepth), as.character(probabilityCol))
797+
as.numeric(tol), as.character(family),
798+
as.logical(standardization), as.array(thresholds),
799+
as.character(weightCol), as.integer(aggregationDepth),
800+
as.character(probabilityCol))
802801
new("LogisticRegressionModel", jobj = jobj)
803802
})
804803

R/pkg/inst/tests/testthat/test_mllib.R

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -646,30 +646,30 @@ test_that("spark.isotonicRegression", {
646646

647647
test_that("spark.logit", {
648648
# test binary logistic regression
649-
label <- c(1.0, 1.0, 1.0, 0.0, 0.0)
649+
label <- c(0.0, 0.0, 0.0, 1.0, 1.0)
650650
feature <- c(1.1419053, 0.9194079, -0.9498666, -1.1069903, 0.2809776)
651651
binary_data <- as.data.frame(cbind(label, feature))
652652
binary_df <- createDataFrame(binary_data)
653653

654654
blr_model <- spark.logit(binary_df, label ~ feature, thresholds = 1.0)
655655
blr_predict <- collect(select(predict(blr_model, binary_df), "prediction"))
656-
expect_equal(blr_predict$prediction, c(0, 0, 0, 0, 0))
656+
expect_equal(blr_predict$prediction, c("0.0", "0.0", "0.0", "0.0", "0.0"))
657657
blr_model1 <- spark.logit(binary_df, label ~ feature, thresholds = 0.0)
658658
blr_predict1 <- collect(select(predict(blr_model1, binary_df), "prediction"))
659-
expect_equal(blr_predict1$prediction, c(1, 1, 1, 1, 1))
659+
expect_equal(blr_predict1$prediction, c("1.0", "1.0", "1.0", "1.0", "1.0"))
660660

661661
# test summary of binary logistic regression
662662
blr_summary <- summary(blr_model)
663663
blr_fmeasure <- collect(select(blr_summary$fMeasureByThreshold, "threshold", "F-Measure"))
664-
expect_equal(blr_fmeasure$threshold, c(0.8221347, 0.7884005, 0.6674709, 0.3785437, 0.3434487),
664+
expect_equal(blr_fmeasure$threshold, c(0.6565513, 0.6214563, 0.3325291, 0.2115995, 0.1778653),
665665
tolerance = 1e-4)
666-
expect_equal(blr_fmeasure$"F-Measure", c(0.5000000, 0.8000000, 0.6666667, 0.8571429, 0.7500000),
666+
expect_equal(blr_fmeasure$"F-Measure", c(0.6666667, 0.5000000, 0.8000000, 0.6666667, 0.5714286),
667667
tolerance = 1e-4)
668668
blr_precision <- collect(select(blr_summary$precisionByThreshold, "threshold", "precision"))
669-
expect_equal(blr_precision$precision, c(1.0000000, 1.0000000, 0.6666667, 0.7500000, 0.6000000),
669+
expect_equal(blr_precision$precision, c(1.0000000, 0.5000000, 0.6666667, 0.5000000, 0.4000000),
670670
tolerance = 1e-4)
671671
blr_recall <- collect(select(blr_summary$recallByThreshold, "threshold", "recall"))
672-
expect_equal(blr_recall$recall, c(0.3333333, 0.6666667, 0.6666667, 1.0000000, 1.0000000),
672+
expect_equal(blr_recall$recall, c(0.5000000, 0.5000000, 1.0000000, 1.0000000, 1.0000000),
673673
tolerance = 1e-4)
674674

675675
# test model save and read
@@ -683,6 +683,16 @@ test_that("spark.logit", {
683683
expect_error(summary(blr_model2))
684684
unlink(modelPath)
685685

686+
# test prediction label as text
687+
training <- suppressWarnings(createDataFrame(iris))
688+
binomial_training <- training[training$Species %in% c("versicolor", "virginica"), ]
689+
binomial_model <- spark.logit(binomial_training, Species ~ Sepal_Length + Sepal_Width)
690+
prediction <- predict(binomial_model, binomial_training)
691+
expect_equal(typeof(take(select(prediction, "prediction"), 1)$prediction), "character")
692+
expected <- c("virginica", "virginica", "virginica", "versicolor", "virginica",
693+
"versicolor", "virginica", "versicolor", "virginica", "versicolor")
694+
expect_equal(as.list(take(select(prediction, "prediction"), 10))[[1]], expected)
695+
686696
# test multinomial logistic regression
687697
label <- c(0.0, 1.0, 2.0, 0.0, 0.0)
688698
feature1 <- c(4.845940, 5.64480, 7.430381, 6.464263, 5.555667)
@@ -694,7 +704,7 @@ test_that("spark.logit", {
694704

695705
model <- spark.logit(df, label ~., family = "multinomial", thresholds = c(0, 1, 1))
696706
predict1 <- collect(select(predict(model, df), "prediction"))
697-
expect_equal(predict1$prediction, c(0, 0, 0, 0, 0))
707+
expect_equal(predict1$prediction, c("0.0", "0.0", "0.0", "0.0", "0.0"))
698708
# Summary of multinomial logistic regression is not implemented yet
699709
expect_error(summary(model))
700710
})

core/src/main/scala/org/apache/spark/SparkContext.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package org.apache.spark
1919

2020
import java.io._
2121
import java.lang.reflect.Constructor
22-
import java.net.{MalformedURLException, URI}
22+
import java.net.{URI}
2323
import java.util.{Arrays, Locale, Properties, ServiceLoader, UUID}
2424
import java.util.concurrent.{ConcurrentHashMap, ConcurrentMap}
2525
import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger, AtomicReference}

mllib/src/main/scala/org/apache/spark/ml/r/LogisticRegressionWrapper.scala

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import org.json4s.JsonDSL._
2323
import org.json4s.jackson.JsonMethods._
2424

2525
import org.apache.spark.ml.{Pipeline, PipelineModel}
26-
import org.apache.spark.ml.attribute.AttributeGroup
2726
import org.apache.spark.ml.classification.{BinaryLogisticRegressionSummary, LogisticRegression, LogisticRegressionModel}
28-
import org.apache.spark.ml.feature.RFormula
27+
import org.apache.spark.ml.feature.{IndexToString, RFormula}
28+
import org.apache.spark.ml.r.RWrapperUtils._
2929
import org.apache.spark.ml.util._
3030
import org.apache.spark.sql.{DataFrame, Dataset}
3131

@@ -34,6 +34,8 @@ private[r] class LogisticRegressionWrapper private (
3434
val features: Array[String],
3535
val isLoaded: Boolean = false) extends MLWritable {
3636

37+
import LogisticRegressionWrapper._
38+
3739
private val logisticRegressionModel: LogisticRegressionModel =
3840
pipeline.stages(1).asInstanceOf[LogisticRegressionModel]
3941

@@ -57,7 +59,11 @@ private[r] class LogisticRegressionWrapper private (
5759
lazy val recallByThreshold: DataFrame = blrSummary.recallByThreshold
5860

5961
def transform(dataset: Dataset[_]): DataFrame = {
60-
pipeline.transform(dataset).drop(logisticRegressionModel.getFeaturesCol)
62+
pipeline.transform(dataset)
63+
.drop(PREDICTED_LABEL_INDEX_COL)
64+
.drop(logisticRegressionModel.getFeaturesCol)
65+
.drop(logisticRegressionModel.getLabelCol)
66+
6167
}
6268

6369
override def write: MLWriter = new LogisticRegressionWrapper.LogisticRegressionWrapperWriter(this)
@@ -66,14 +72,16 @@ private[r] class LogisticRegressionWrapper private (
6672
private[r] object LogisticRegressionWrapper
6773
extends MLReadable[LogisticRegressionWrapper] {
6874

75+
val PREDICTED_LABEL_INDEX_COL = "pred_label_idx"
76+
val PREDICTED_LABEL_COL = "prediction"
77+
6978
def fit( // scalastyle:ignore
7079
data: DataFrame,
7180
formula: String,
7281
regParam: Double,
7382
elasticNetParam: Double,
7483
maxIter: Int,
7584
tol: Double,
76-
fitIntercept: Boolean,
7785
family: String,
7886
standardization: Boolean,
7987
thresholds: Array[Double],
@@ -84,14 +92,14 @@ private[r] object LogisticRegressionWrapper
8492

8593
val rFormula = new RFormula()
8694
.setFormula(formula)
87-
RWrapperUtils.checkDataColumns(rFormula, data)
95+
.setForceIndexLabel(true)
96+
checkDataColumns(rFormula, data)
8897
val rFormulaModel = rFormula.fit(data)
8998

90-
// get feature names from output schema
91-
val schema = rFormulaModel.transform(data).schema
92-
val featureAttrs = AttributeGroup.fromStructField(schema(rFormulaModel.getFeaturesCol))
93-
.attributes.get
94-
val features = featureAttrs.map(_.name.get)
99+
val fitIntercept = rFormula.hasIntercept
100+
101+
// get labels and feature names from output schema
102+
val (features, labels) = getFeaturesAndLabels(rFormulaModel, data)
95103

96104
// assemble and fit the pipeline
97105
val logisticRegression = new LogisticRegression()
@@ -105,16 +113,23 @@ private[r] object LogisticRegressionWrapper
105113
.setWeightCol(weightCol)
106114
.setAggregationDepth(aggregationDepth)
107115
.setFeaturesCol(rFormula.getFeaturesCol)
116+
.setLabelCol(rFormula.getLabelCol)
108117
.setProbabilityCol(probability)
118+
.setPredictionCol(PREDICTED_LABEL_INDEX_COL)
109119

110120
if (thresholds.length > 1) {
111121
logisticRegression.setThresholds(thresholds)
112122
} else {
113123
logisticRegression.setThreshold(thresholds(0))
114124
}
115125

126+
val idxToStr = new IndexToString()
127+
.setInputCol(PREDICTED_LABEL_INDEX_COL)
128+
.setOutputCol(PREDICTED_LABEL_COL)
129+
.setLabels(labels)
130+
116131
val pipeline = new Pipeline()
117-
.setStages(Array(rFormulaModel, logisticRegression))
132+
.setStages(Array(rFormulaModel, logisticRegression, idxToStr))
118133
.fit(data)
119134

120135
new LogisticRegressionWrapper(pipeline, features)

0 commit comments

Comments
 (0)