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 e1a4f6077104 [SPARK-58964][ML] Consolidate vector and matrix UDT 
column conversion
e1a4f6077104 is described below

commit e1a4f607710468e77670e118c05f25956fa2af98
Author: Ruifeng Zheng <[email protected]>
AuthorDate: Wed Aug 26 11:27:26 2026 +0800

    [SPARK-58964][ML] Consolidate vector and matrix UDT column conversion
    
    ### What changes were proposed in this pull request?
    
    This PR consolidates UDT column selection in these `MLUtils` conversion 
helpers:
    
    - `MLUtils.convertVectorColumnsToML`
    - `MLUtils.convertVectorColumnsFromML`
    - `MLUtils.convertMatrixColumnsToML`
    - `MLUtils.convertMatrixColumnsFromML`
    
    When columns are not explicitly specified, each helper preserves the 
previous automatic selection
    behavior: `ToML` helpers select only old `mllib` UDT columns, and `FromML` 
helpers select only new
    `ml` UDT columns. For explicit columns, the helpers validate that the named 
columns exist and rely
    on the existing `wrap_udt(unwrap_udt(...))` expression analysis to reject 
non-UDT inputs.
    Converted columns are replaced with the metadata-preserving `withColumns` 
overload instead of
    rebuilding the whole projection manually.
    
    ### Why are the changes needed?
    
    After vector and matrix conversion moved to `wrap_udt` / `unwrap_udt`, the 
explicit-column paths no
    longer need separate source-type and target-type branches when deciding 
whether a column can be
    converted.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Ran:
    
    ```
    JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 build/sbt 'mllib/testOnly 
org.apache.spark.mllib.util.MLUtilsSuite -- -z "convertVectorColumnsToML" -z 
"convertVectorColumnsFromML" -z "convert nullable vector columns" -z 
"convertMatrixColumnsToML" -z "convertMatrixColumnsFromML" -z "convert nullable 
matrix columns"'
    ```
    
    Also ran:
    
    - `git diff --check`
    - line-length scan for the changed Scala files
    - non-ASCII scan for the changed Scala files
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: OpenAI Codex
    
    Closes #58241 from zhengruifeng/ml-consolidate-matrix-udt-conversion-dev-3.
    
    Authored-by: Ruifeng Zheng <[email protected]>
    Signed-off-by: Ruifeng Zheng <[email protected]>
    (cherry picked from commit e61acc6a8f4b7d15940ba965e09251f7b37e0a4a)
    Signed-off-by: Ruifeng Zheng <[email protected]>
---
 .../org/apache/spark/mllib/util/MLUtils.scala      | 110 +++++++++------------
 1 file changed, 45 insertions(+), 65 deletions(-)

diff --git a/mllib/src/main/scala/org/apache/spark/mllib/util/MLUtils.scala 
b/mllib/src/main/scala/org/apache/spark/mllib/util/MLUtils.scala
index bbfb529bdae6..31ddf5b5a36b 100644
--- a/mllib/src/main/scala/org/apache/spark/mllib/util/MLUtils.scala
+++ b/mllib/src/main/scala/org/apache/spark/mllib/util/MLUtils.scala
@@ -36,6 +36,7 @@ import 
org.apache.spark.sql.execution.datasources.text.TextFileFormat
 import org.apache.spark.sql.functions.{col, length, lit, not, printf, 
raise_error, trim,
   unwrap_udt, when, wrap_udt}
 import org.apache.spark.storage.StorageLevel
+import org.apache.spark.util.ArrayImplicits._
 import org.apache.spark.util.random.BernoulliCellSampler
 
 /**
@@ -320,15 +321,15 @@ object MLUtils extends Logging {
    * Converts vector columns in an input Dataset from the 
[[org.apache.spark.mllib.linalg.Vector]]
    * type to the new [[org.apache.spark.ml.linalg.Vector]] type under the 
`spark.ml` package.
    * @param dataset input dataset
-   * @param cols a list of vector columns to be converted. New vector columns 
will be ignored. If
-   *             unspecified, all old vector columns will be converted except 
nested ones.
+   * @param cols a list of vector columns to be converted. If unspecified, all 
old vector columns
+   *             will be converted except nested ones.
    * @return the input `DataFrame` with old vector columns converted to the 
new vector type
    */
   @Since("2.0.0")
   @varargs
   def convertVectorColumnsToML(dataset: Dataset[_], cols: String*): DataFrame 
= {
     val schema = dataset.schema
-    val colSet = if (cols.nonEmpty) {
+    val colNames = if (cols.nonEmpty) {
       cols.flatMap { c =>
         val dataType = schema(c).dataType
         if (dataType.getClass == classOf[VectorUDT]) {
@@ -339,43 +340,38 @@ object MLUtils extends Logging {
             s"Column $c must be old Vector type to be converted to new type 
but got $dataType.")
           None
         }
-      }.toSet
+      }
     } else {
       schema.fields
         .filter(_.dataType.getClass == classOf[VectorUDT])
         .map(_.name)
-        .toSet
+        .toImmutableArraySeq
     }
 
-    if (colSet.isEmpty) {
+    if (colNames.isEmpty) {
       return dataset.toDF()
     }
 
-    val exprs = schema.fields.map { field =>
-      val c = field.name
-      if (colSet.contains(c)) {
-        wrap_udt(unwrap_udt(col(c)), new MLVectorUDT).as(c, field.metadata)
-      } else {
-        col(c)
-      }
-    }
-    import org.apache.spark.util.ArrayImplicits._
-    dataset.select(exprs.toImmutableArraySeq: _*)
+    val fields = colNames.map(schema(_))
+    dataset.withColumns(
+      fields.map(_.name),
+      fields.map(field => wrap_udt(unwrap_udt(col(field.name)), new 
MLVectorUDT)),
+      fields.map(_.metadata))
   }
 
   /**
    * Converts vector columns in an input Dataset to the 
[[org.apache.spark.mllib.linalg.Vector]]
    * type from the new [[org.apache.spark.ml.linalg.Vector]] type under the 
`spark.ml` package.
    * @param dataset input dataset
-   * @param cols a list of vector columns to be converted. Old vector columns 
will be ignored. If
-   *             unspecified, all new vector columns will be converted except 
nested ones.
+   * @param cols a list of vector columns to be converted. If unspecified, all 
new vector columns
+   *             will be converted except nested ones.
    * @return the input `DataFrame` with new vector columns converted to the 
old vector type
    */
   @Since("2.0.0")
   @varargs
   def convertVectorColumnsFromML(dataset: Dataset[_], cols: String*): 
DataFrame = {
     val schema = dataset.schema
-    val colSet = if (cols.nonEmpty) {
+    val colNames = if (cols.nonEmpty) {
       cols.flatMap { c =>
         val dataType = schema(c).dataType
         if (dataType.getClass == classOf[MLVectorUDT]) {
@@ -386,43 +382,38 @@ object MLUtils extends Logging {
             s"Column $c must be new Vector type to be converted to old type 
but got $dataType.")
           None
         }
-      }.toSet
+      }
     } else {
       schema.fields
         .filter(_.dataType.getClass == classOf[MLVectorUDT])
         .map(_.name)
-        .toSet
+        .toImmutableArraySeq
     }
 
-    if (colSet.isEmpty) {
+    if (colNames.isEmpty) {
       return dataset.toDF()
     }
 
-    val exprs = schema.fields.map { field =>
-      val c = field.name
-      if (colSet.contains(c)) {
-        wrap_udt(unwrap_udt(col(c)), new VectorUDT).as(c, field.metadata)
-      } else {
-        col(c)
-      }
-    }
-    import org.apache.spark.util.ArrayImplicits._
-    dataset.select(exprs.toImmutableArraySeq: _*)
+    val fields = colNames.map(schema(_))
+    dataset.withColumns(
+      fields.map(_.name),
+      fields.map(field => wrap_udt(unwrap_udt(col(field.name)), new 
VectorUDT)),
+      fields.map(_.metadata))
   }
 
   /**
    * Converts Matrix columns in an input Dataset from the 
[[org.apache.spark.mllib.linalg.Matrix]]
    * type to the new [[org.apache.spark.ml.linalg.Matrix]] type under the 
`spark.ml` package.
    * @param dataset input dataset
-   * @param cols a list of matrix columns to be converted. New matrix columns 
will be ignored. If
-   *             unspecified, all old matrix columns will be converted except 
nested ones.
+   * @param cols a list of matrix columns to be converted. If unspecified, all 
old matrix columns
+   *             will be converted except nested ones.
    * @return the input `DataFrame` with old matrix columns converted to the 
new matrix type
    */
   @Since("2.0.0")
   @varargs
   def convertMatrixColumnsToML(dataset: Dataset[_], cols: String*): DataFrame 
= {
     val schema = dataset.schema
-    val colSet = if (cols.nonEmpty) {
+    val colNames = if (cols.nonEmpty) {
       cols.flatMap { c =>
         val dataType = schema(c).dataType
         if (dataType.getClass == classOf[MatrixUDT]) {
@@ -433,43 +424,38 @@ object MLUtils extends Logging {
             s"Column $c must be old Matrix type to be converted to new type 
but got $dataType.")
           None
         }
-      }.toSet
+      }
     } else {
       schema.fields
         .filter(_.dataType.getClass == classOf[MatrixUDT])
         .map(_.name)
-        .toSet
+        .toImmutableArraySeq
     }
 
-    if (colSet.isEmpty) {
+    if (colNames.isEmpty) {
       return dataset.toDF()
     }
 
-    val exprs = schema.fields.map { field =>
-      val c = field.name
-      if (colSet.contains(c)) {
-        wrap_udt(unwrap_udt(col(c)), new MLMatrixUDT).as(c, field.metadata)
-      } else {
-        col(c)
-      }
-    }
-    import org.apache.spark.util.ArrayImplicits._
-    dataset.select(exprs.toImmutableArraySeq: _*)
+    val fields = colNames.map(schema(_))
+    dataset.withColumns(
+      fields.map(_.name),
+      fields.map(field => wrap_udt(unwrap_udt(col(field.name)), new 
MLMatrixUDT)),
+      fields.map(_.metadata))
   }
 
   /**
    * Converts matrix columns in an input Dataset to the 
[[org.apache.spark.mllib.linalg.Matrix]]
    * type from the new [[org.apache.spark.ml.linalg.Matrix]] type under the 
`spark.ml` package.
    * @param dataset input dataset
-   * @param cols a list of matrix columns to be converted. Old matrix columns 
will be ignored. If
-   *             unspecified, all new matrix columns will be converted except 
nested ones.
+   * @param cols a list of matrix columns to be converted. If unspecified, all 
new matrix columns
+   *             will be converted except nested ones.
    * @return the input `DataFrame` with new matrix columns converted to the 
old matrix type
    */
   @Since("2.0.0")
   @varargs
   def convertMatrixColumnsFromML(dataset: Dataset[_], cols: String*): 
DataFrame = {
     val schema = dataset.schema
-    val colSet = if (cols.nonEmpty) {
+    val colNames = if (cols.nonEmpty) {
       cols.flatMap { c =>
         val dataType = schema(c).dataType
         if (dataType.getClass == classOf[MLMatrixUDT]) {
@@ -480,31 +466,25 @@ object MLUtils extends Logging {
             s"Column $c must be new Matrix type to be converted to old type 
but got $dataType.")
           None
         }
-      }.toSet
+      }
     } else {
       schema.fields
         .filter(_.dataType.getClass == classOf[MLMatrixUDT])
         .map(_.name)
-        .toSet
+        .toImmutableArraySeq
     }
 
-    if (colSet.isEmpty) {
+    if (colNames.isEmpty) {
       return dataset.toDF()
     }
 
-    val exprs = schema.fields.map { field =>
-      val c = field.name
-      if (colSet.contains(c)) {
-        wrap_udt(unwrap_udt(col(c)), new MatrixUDT).as(c, field.metadata)
-      } else {
-        col(c)
-      }
-    }
-    import org.apache.spark.util.ArrayImplicits._
-    dataset.select(exprs.toImmutableArraySeq: _*)
+    val fields = colNames.map(schema(_))
+    dataset.withColumns(
+      fields.map(_.name),
+      fields.map(field => wrap_udt(unwrap_udt(col(field.name)), new 
MatrixUDT)),
+      fields.map(_.metadata))
   }
 
-
   /**
    * Returns the squared Euclidean distance between two vectors. The following 
formula will be used
    * if it does not introduce too much numerical error:


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

Reply via email to