This is an automated email from the ASF dual-hosted git repository.

zhengruifeng pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new 9f027a981f20 [SPARK-58546][ML] Avoid redundant StringIndexer label 
lookups
9f027a981f20 is described below

commit 9f027a981f20283bdc493ee3ed550fdcafceeb3b
Author: Ruifeng Zheng <[email protected]>
AuthorDate: Tue Aug 4 20:24:53 2026 +0800

    [SPARK-58546][ML] Avoid redundant StringIndexer label lookups
    
    ### What changes were proposed in this pull request?
    
    This PR passes `keepInvalid` into `StringIndexerModel.getIndexer`, computes 
it once during transformation, and uses one `OpenHashMap.get` lookup for 
non-null labels.
    
    ### Why are the changes needed?
    
    The previous UDF performed `contains` followed by map access for known 
labels, causing two lookups per value. This removes the redundant lookup while 
preserving invalid-label behavior.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Not run (not requested). Verified with `git diff --check`, ASCII scan, and 
changed-line length scan.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Codex (GPT-5)
    
    Closes #57745 from zhengruifeng/single-lookup-string-indexer.
    
    Authored-by: Ruifeng Zheng <[email protected]>
    Signed-off-by: Ruifeng Zheng <[email protected]>
    (cherry picked from commit 1f2b47d48843e955f71ff006598b327182576b93)
    Signed-off-by: Ruifeng Zheng <[email protected]>
---
 .../apache/spark/ml/feature/StringIndexer.scala    | 46 +++++++++++-----------
 1 file changed, 24 insertions(+), 22 deletions(-)

diff --git 
a/mllib/src/main/scala/org/apache/spark/ml/feature/StringIndexer.scala 
b/mllib/src/main/scala/org/apache/spark/ml/feature/StringIndexer.scala
index c0f7e312dcbb..bfb78adfd42d 100644
--- a/mllib/src/main/scala/org/apache/spark/ml/feature/StringIndexer.scala
+++ b/mllib/src/main/scala/org/apache/spark/ml/feature/StringIndexer.scala
@@ -363,28 +363,32 @@ class StringIndexerModel (
       .where(conditions.reduce(_ and _))
   }
 
-  private def getIndexer(labels: Seq[String], labelToIndex: 
OpenHashMap[String, Double]) = {
-    val keepInvalid = (getHandleInvalid == StringIndexer.KEEP_INVALID)
-
-    udf { label: String =>
-      if (label == null) {
-        if (keepInvalid) {
-          labels.length
+  private def getIndexer(
+      labels: Seq[String],
+      labelToIndex: OpenHashMap[String, Double],
+      keepInvalid: Boolean) = {
+    val unknownIndex = labels.length.toDouble
+    if (keepInvalid) {
+      udf { label: String =>
+        if (label == null) {
+          unknownIndex
         } else {
+          labelToIndex.get(label).getOrElse(unknownIndex)
+        }
+      }.asNondeterministic()
+    } else {
+      udf { label: String =>
+        if (label == null) {
           throw new SparkException("StringIndexer encountered NULL value. To 
handle or skip " +
             "NULLS, try setting StringIndexer.handleInvalid.")
-        }
-      } else {
-        if (labelToIndex.contains(label)) {
-          labelToIndex(label)
-        } else if (keepInvalid) {
-          labels.length
         } else {
-          throw new SparkException(s"Unseen label: $label. To handle unseen 
labels, " +
-            s"set Param handleInvalid to ${StringIndexer.KEEP_INVALID}.")
+          labelToIndex.get(label).getOrElse {
+            throw new SparkException(s"Unseen label: $label. To handle unseen 
labels, " +
+              s"set Param handleInvalid to ${StringIndexer.KEEP_INVALID}.")
+          }
         }
-      }
-    }.asNondeterministic()
+      }.asNondeterministic()
+    }
   }
 
   @Since("2.0.0")
@@ -400,6 +404,7 @@ class StringIndexerModel (
       map
     }
     val outputColumns = new Array[Column](outputColNames.length)
+    val keepInvalid = getHandleInvalid == StringIndexer.KEEP_INVALID
 
     // Skips invalid rows if `handleInvalid` is set to 
`StringIndexer.SKIP_INVALID`.
     val filteredDataset = if (getHandleInvalid == StringIndexer.SKIP_INVALID) {
@@ -416,16 +421,13 @@ class StringIndexerModel (
 
       try {
         dataset.col(inputColName)
-        val filteredLabels = getHandleInvalid match {
-          case StringIndexer.KEEP_INVALID => labels :+ "__unknown"
-          case _ => labels
-        }
+        val filteredLabels = if (keepInvalid) labels :+ "__unknown" else labels
         val metadata = NominalAttribute.defaultAttr
           .withName(outputColName)
           .withValues(filteredLabels)
           .toMetadata()
 
-        val indexer = getIndexer(labels.toImmutableArraySeq, labelToIndex)
+        val indexer = getIndexer(labels.toImmutableArraySeq, labelToIndex, 
keepInvalid)
 
         outputColumns(i) = indexer(dataset(inputColName).cast(StringType))
           .as(outputColName, metadata)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to