Skip to content

Commit 4cd4ef9

Browse files
committed
[WIP][ML] Update vector posexplode semantics
1 parent aabcc12 commit 4cd4ef9

3 files changed

Lines changed: 84 additions & 135 deletions

File tree

mllib/src/main/scala/org/apache/spark/ml/functions.scala

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,17 @@ object functions {
4646

4747
/**
4848
* Creates a new row for each index-value pair in the given vector column. This expression is
49-
* dedicated only for Spark ML.
49+
* dedicated only for Spark ML. It always emits a marker row with index `-1 - vector.size` and
50+
* value `Double.NaN` before each non-null vector.
5051
* @param v: the column of MLlib sparse/dense vectors
51-
* @param mode: `dense` emits all elements, `active` emits active elements, and `nonzero` emits
52-
* active elements whose values are not zero
53-
* @param withMarker: whether to emit `(-1, Double.NaN)` before each vector's values
52+
* @param mode: `dense` emits all elements, and `sparse` emits nonzero elements
5453
* @return the index and value columns of the vector elements
5554
* @since 4.4.0
5655
*/
5756
private[ml] def vector_posexplode(
5857
v: Column,
59-
mode: String = "active",
60-
withMarker: Boolean = false): Column = {
61-
Column.internalFn("vector_posexplode", v, sf.lit(mode), sf.lit(withMarker))
58+
mode: String = "sparse"): Column = {
59+
Column.internalFn("vector_posexplode", sf.unwrap_udt(v), sf.lit(mode))
6260
}
6361

6462
private[ml] def array_binary_search(a: Column, v: Column): Column =

mllib/src/test/scala/org/apache/spark/ml/FunctionsSuite.scala

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import org.apache.spark.ml.functions._
2222
import org.apache.spark.ml.linalg.{Vector, Vectors}
2323
import org.apache.spark.ml.util.MLTest
2424
import org.apache.spark.mllib.linalg.{Vector => OldVector, Vectors => OldVectors}
25-
import org.apache.spark.sql.{AnalysisException, Row}
26-
import org.apache.spark.sql.functions.col
25+
import org.apache.spark.sql.{AnalysisException, Column, Row}
26+
import org.apache.spark.sql.functions.{col, lit}
2727
import org.apache.spark.sql.types._
2828

2929
class FunctionsSuite extends MLTest {
@@ -125,61 +125,53 @@ class FunctionsSuite extends MLTest {
125125
.as[(Int, Int, Double)]
126126
.collect()
127127
.toSeq
128-
assert(result === Seq(
129-
(0, 0, 1.0),
130-
(0, 1, 0.0),
131-
(0, 2, 3.0),
132-
(1, 1, 2.0),
133-
(1, 2, 0.0),
134-
(1, 3, 4.0)))
128+
assert(normalizeNaN(result) === Seq(
129+
(0, -4, "NaN"),
130+
(0, 0, "1.0"),
131+
(0, 2, "3.0"),
132+
(1, -5, "NaN"),
133+
(1, 1, "2.0"),
134+
(1, 3, "4.0"),
135+
(3, -11, "NaN")))
135136

136137
val oldResult = df.select($"id", vector_posexplode($"oldVec"))
137138
.as[(Int, Int, Double)]
138139
.collect()
139140
.toSeq
140-
assert(oldResult === Seq(
141-
(0, 0, 10.0),
142-
(0, 1, 0.0),
143-
(0, 2, 30.0),
144-
(1, 0, 20.0),
145-
(1, 1, 0.0),
146-
(1, 2, 30.0)))
141+
assert(normalizeNaN(oldResult) === Seq(
142+
(0, -4, "NaN"),
143+
(0, 0, "10.0"),
144+
(0, 2, "30.0"),
145+
(1, -5, "NaN"),
146+
(1, 0, "20.0"),
147+
(1, 2, "30.0"),
148+
(3, -11, "NaN")))
147149

148150
val denseResult = df
149151
.where($"id" === 1)
150152
.select($"id", vector_posexplode($"vec", mode = "dense"))
151153
.as[(Int, Int, Double)]
152154
.collect()
153155
.toSeq
154-
assert(denseResult === Seq(
155-
(1, 0, 0.0),
156-
(1, 1, 2.0),
157-
(1, 2, 0.0),
158-
(1, 3, 4.0)))
156+
assert(normalizeNaN(denseResult) === Seq(
157+
(1, -5, "NaN"),
158+
(1, 0, "0.0"),
159+
(1, 1, "2.0"),
160+
(1, 2, "0.0"),
161+
(1, 3, "4.0")))
159162

160-
val nonzeroResult = df.select($"id", vector_posexplode($"vec", mode = "nonzero"))
163+
val sparseResult = df.select($"id", vector_posexplode($"vec", mode = "sparse"))
161164
.as[(Int, Int, Double)]
162165
.collect()
163166
.toSeq
164-
assert(nonzeroResult === Seq(
165-
(0, 0, 1.0),
166-
(0, 2, 3.0),
167-
(1, 1, 2.0),
168-
(1, 3, 4.0)))
169-
170-
val markerResult = df.select($"id",
171-
vector_posexplode($"vec", mode = "nonzero", withMarker = true))
172-
.as[(Int, Int, Double)]
173-
.collect()
174-
.toSeq
175-
assert(normalizeNaN(markerResult) === Seq(
176-
(0, -1, "NaN"),
167+
assert(normalizeNaN(sparseResult) === Seq(
168+
(0, -4, "NaN"),
177169
(0, 0, "1.0"),
178170
(0, 2, "3.0"),
179-
(1, -1, "NaN"),
171+
(1, -5, "NaN"),
180172
(1, 1, "2.0"),
181173
(1, 3, "4.0"),
182-
(3, -1, "NaN")))
174+
(3, -11, "NaN")))
183175

184176
val schema = df.select(vector_posexplode($"vec")).schema
185177
assert(schema.simpleString === "struct<index:int,value:double>")
@@ -197,20 +189,12 @@ class FunctionsSuite extends MLTest {
197189
StructType(Seq(
198190
StructField("id", IntegerType, nullable = false),
199191
StructField("vec", vectorSqlType, nullable = true))))
200-
val structResult = structDf.select($"id", vector_posexplode($"vec"))
201-
.as[(Int, Int, Double)]
202-
.collect()
203-
.toSeq
204-
assert(structResult === Seq((0, 0, 5.0), (0, 2, 0.0)))
205-
val structMarkerResult = structDf.select($"id",
206-
vector_posexplode($"vec", withMarker = true))
192+
val result = structDf
193+
.select($"id", Column.internalFn("vector_posexplode", $"vec", lit("sparse")))
207194
.as[(Int, Int, Double)]
208195
.collect()
209196
.toSeq
210-
assert(normalizeNaN(structMarkerResult) === Seq(
211-
(0, -1, "NaN"),
212-
(0, 0, "5.0"),
213-
(0, 2, "0.0")))
197+
assert(normalizeNaN(result) === Seq((0, -4, "NaN"), (0, 0, "5.0")))
214198
}
215199

216200
test("test get_vector") {

0 commit comments

Comments
 (0)