-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-9893] User guide with Java test suite for VectorSlicer #8267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
184a3cd
61092cc
3ff4b5d
d5cdabd
61985b5
e85e60f
6d066c7
f8f0d61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1389,3 +1389,145 @@ print(output.select("features", "clicked").first()) | |
|
|
||
| # Feature Selectors | ||
|
|
||
| ## VectorSlicer | ||
|
|
||
| `VectorSlicer` is a transformer that takes a feature vector and outputs a new feature vector with a sub-array of the original features. It is useful for extracting features from a vector column. | ||
|
|
||
| `VectorSlicer` accepts a vector column with a specified indices, then outputs a new vector column whose values are selected via those indices. There are two types of indices, | ||
|
|
||
| 1. Integer indices that represents the real indices in the vector, `setIndices()`; | ||
|
|
||
| 2. String indices that represents the names of features in the vector, `setNames()`. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document that this requires the vector column to have an |
||
|
|
||
| Specify by integer and string are both acceptable, moreover, you can use integer index and string name simultaneously. At least one feature must be selected. Duplicate features are not allowed, so there can be no overlap between selected indices and names. Note that if names of features are selected, an exception will be threw out when encountering with empty input attributes. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know other parts of the user guides don't do this, but we should try to keep lines to 100 characters so future diffs are smaller
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: _Specification_ by integer and string are both acceptable**_. M**_oreover, |
||
|
|
||
| The output vector will order features with the selected indices first (in the order given), followed by the selected names (in the order given). | ||
|
|
||
| **Examples** | ||
|
|
||
| Suppose that we have a DataFrame with the column `userFeatures`: | ||
|
|
||
| ~~~ | ||
| userFeatures | ||
| ------------------ | ||
| [0.0, 10.0, 0.5] | ||
| ~~~ | ||
|
|
||
| `userFeatures` is a vector column that contains three user features. Assuming that the first column of `userFeatures` are all zeros, so we want to remove it and only the last two columns are selected. The `VectorSlicer` selects the last two elements with `setIndices(1, 2)` then produces a new vector column named `features`: | ||
|
|
||
| ~~~ | ||
| userFeatures | features | ||
| ------------------|----------------------------- | ||
| [0.0, 10.0, 0.5] | [10.0, 0.5] | ||
| ~~~ | ||
|
|
||
| Suppose also that we have a potential input attributes for the `userFeatures`, i.e. `["f1", "f2", "f3"]`, then we can use `setNames("f2", "f3")` to select them. | ||
|
|
||
| ~~~ | ||
| userFeatures | features | ||
| ------------------|----------------------------- | ||
| [0.0, 10.0, 0.5] | [10.0, 0.5] | ||
| ["f1", "f2", "f3"] | ["f2", "f3"] | ||
| ~~~ | ||
|
|
||
| **NOTE** | ||
|
|
||
| `VectorSlicer` of Python version does not supprt selecting by names currently. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: "The Python version of |
||
|
|
||
| <div class="codetabs"> | ||
| <div data-lang="scala" markdown="1"> | ||
|
|
||
| [`VectorSlicer`](api/scala/index.html#org.apache.spark.ml.feature.VectorSlicer) takes an input column name with specified indices or names and an output column name. | ||
|
|
||
| {% highlight scala %} | ||
| import org.apache.spark.mllib.linalg.Vectors | ||
| import org.apache.spark.ml.attribute.{Attribute, AttributeGroup, NumericAttribute} | ||
| import org.apache.spark.ml.feature.VectorSlicer | ||
| import org.apache.spark.sql.types.StructType | ||
| import org.apache.spark.sql.{DataFrame, Row, SQLContext} | ||
|
|
||
| val data = Array( | ||
| Vectors.sparse(3, Seq((0, -2.0), (1, 2.3))), | ||
| Vectors.dense(-2.0, 2.3, 0.0) | ||
| ) | ||
|
|
||
| val defaultAttr = NumericAttribute.defaultAttr | ||
| val attrs = Array("f1", "f2", "f3").map(defaultAttr.withName) | ||
| val attrGroup = new AttributeGroup("userFeatures", attrs.asInstanceOf[Array[Attribute]]) | ||
|
|
||
| val dataRDD = sc.parallelize(data).map(Row.apply) | ||
| val dataset = sqlContext.createDataFrame(dataRDD, StructType(attrGroup.toStructField())) | ||
|
|
||
| val slicer = new VectorSlicer().setInputCol("userFeatures").setOutputCol("features") | ||
|
|
||
| slicer.setIndices(1).setNames("f3") | ||
| // or slicer.setIndices(Array(1, 2)), or slicer.setNames(Array("f2", "f3")) | ||
|
|
||
| val output = slicer.transform(dataset) | ||
| println(output.select("userFeatures", "features").first()) | ||
| {% endhighlight %} | ||
| </div> | ||
|
|
||
| <div data-lang="java" markdown="1"> | ||
|
|
||
| [`VectorSlicer`](api/java/org/apache/spark/ml/feature/VectorSlicer.html) takes an input column name with specified indices or names and an output column name. | ||
|
|
||
| {% highlight java %} | ||
| import java.util.Arrays; | ||
|
|
||
| import org.apache.spark.api.java.JavaRDD; | ||
| import org.apache.spark.mllib.linalg.Vectors; | ||
| import org.apache.spark.sql.DataFrame; | ||
| import org.apache.spark.sql.Row; | ||
| import org.apache.spark.sql.RowFactory; | ||
| import org.apache.spark.sql.types.*; | ||
| import static org.apache.spark.sql.types.DataTypes.*; | ||
|
|
||
| Attribute[] attrs = new Attribute[]{ | ||
| NumericAttribute.defaultAttr().withName("f1"), | ||
| NumericAttribute.defaultAttr().withName("f2"), | ||
| NumericAttribute.defaultAttr().withName("f3") | ||
| }; | ||
| AttributeGroup group = new AttributeGroup("userFeatures", attrs); | ||
|
|
||
| JavaRDD<Row> jrdd = jsc.parallelize(Lists.newArrayList( | ||
| RowFactory.create(Vectors.sparse(3, new int[]{0, 1}, new double[]{-2.0, 2.3})), | ||
| RowFactory.create(Vectors.dense(-2.0, 2.3, 0.0)) | ||
| )); | ||
|
|
||
| DataFrame dataset = jsql.createDataFrame(jrdd, (new StructType()).add(group.toStructField())); | ||
|
|
||
| VectorSlicer vectorSlicer = new VectorSlicer() | ||
| .setInputCol("userFeatures").setOutputCol("features"); | ||
|
|
||
| vectorSlicer.setIndices(new int[]{1}).setNames(new String[]{"f3"}); | ||
| // or slicer.setIndices(new int[]{1, 2}), or slicer.setNames(new String[]{"f2", "f3"}) | ||
|
|
||
| DataFrame output = vectorSlicer.transform(dataset); | ||
|
|
||
| System.out.println(output.select("userFeatures", "features").first()); | ||
| {% endhighlight %} | ||
| </div> | ||
|
|
||
| <div data-lang="python" markdown="1"> | ||
|
|
||
| [`VectorSlicer`](api/python/pyspark.ml.html#pyspark.ml.feature.VectorSlicer) takes an input column name with specified indices or names and an output column name. | ||
|
|
||
| {% highlight python %} | ||
| from pyspark.mllib.linalg import DenseVector | ||
| from pyspark.mllib.linalg import SparseVector | ||
| from pyspark.ml.feature import VectorSlicer | ||
|
|
||
| dataset = sqlContext.createDataFrame([(SparseVector(3, {0: -2.0, 1: 2.3}),), | ||
| (DenseVector([-2.0, 2.3, 0.0]),)], ["userFeatures"]) | ||
|
|
||
| vectorSlicer = VectorSlicer(indices=[1, 2], inputCol="userFeatures", outputCol="features") | ||
|
|
||
| output = vectorSlicer.transform(dataset) | ||
|
|
||
| print(output.select("userFeatures", "features").first()) | ||
| {% endhighlight %} | ||
| </div> | ||
| </div> | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.spark.ml.feature; | ||
|
|
||
| import com.google.common.collect.Lists; | ||
|
|
||
| import org.junit.After; | ||
| import org.junit.Assert; | ||
| import org.junit.Before; | ||
| import org.junit.Test; | ||
|
|
||
| import org.apache.spark.api.java.JavaRDD; | ||
| import org.apache.spark.api.java.JavaSparkContext; | ||
| import org.apache.spark.ml.attribute.Attribute; | ||
| import org.apache.spark.ml.attribute.AttributeGroup; | ||
| import org.apache.spark.ml.attribute.NumericAttribute; | ||
| import org.apache.spark.mllib.linalg.Vector; | ||
| import org.apache.spark.mllib.linalg.Vectors; | ||
| import org.apache.spark.sql.DataFrame; | ||
| import org.apache.spark.sql.Row; | ||
| import org.apache.spark.sql.RowFactory; | ||
| import org.apache.spark.sql.SQLContext; | ||
| import org.apache.spark.sql.types.StructType; | ||
|
|
||
|
|
||
| public class JavaVectorSlicerSuite { | ||
| private transient JavaSparkContext jsc; | ||
| private transient SQLContext jsql; | ||
|
|
||
| @Before | ||
| public void setUp() { | ||
| jsc = new JavaSparkContext("local", "JavaVectorSlicerSuite"); | ||
| jsql = new SQLContext(jsc); | ||
| } | ||
|
|
||
| @After | ||
| public void tearDown() { | ||
| jsc.stop(); | ||
| jsc = null; | ||
| } | ||
|
|
||
| @Test | ||
| public void vectorSlice() { | ||
| Attribute[] attrs = new Attribute[]{ | ||
| NumericAttribute.defaultAttr().withName("f1"), | ||
| NumericAttribute.defaultAttr().withName("f2"), | ||
| NumericAttribute.defaultAttr().withName("f3") | ||
| }; | ||
| AttributeGroup group = new AttributeGroup("userFeatures", attrs); | ||
|
|
||
| JavaRDD<Row> jrdd = jsc.parallelize(Lists.newArrayList( | ||
| RowFactory.create(Vectors.sparse(3, new int[]{0, 1}, new double[]{-2.0, 2.3})), | ||
| RowFactory.create(Vectors.dense(-2.0, 2.3, 0.0)) | ||
| )); | ||
|
|
||
| DataFrame dataset = jsql.createDataFrame(jrdd, (new StructType()).add(group.toStructField())); | ||
|
|
||
| VectorSlicer vectorSlicer = new VectorSlicer() | ||
| .setInputCol("userFeatures").setOutputCol("features"); | ||
|
|
||
| vectorSlicer.setIndices(new int[]{1}).setNames(new String[]{"f3"}); | ||
|
|
||
| DataFrame output = vectorSlicer.transform(dataset); | ||
|
|
||
| for (Row r : output.select("userFeatures", "features").take(2)) { | ||
| Vector features = r.getAs(1); | ||
| Assert.assertEquals(features.size(), 2); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,7 +30,83 @@ | |
| 'OneHotEncoder', 'PolynomialExpansion', 'RegexTokenizer', 'StandardScaler', | ||
| 'StandardScalerModel', 'StringIndexer', 'StringIndexerModel', 'Tokenizer', | ||
| 'VectorAssembler', 'VectorIndexer', 'Word2Vec', 'Word2VecModel', 'PCA', | ||
| 'PCAModel', 'RFormula', 'RFormulaModel'] | ||
| 'PCAModel', 'RFormula', 'RFormulaModel', 'VectorSlicer'] | ||
|
|
||
|
|
||
| @inherit_doc | ||
| class VectorSlicer(JavaTransformer, HasInputCol, HasOutputCol): | ||
| """ | ||
| Slice a vector column given indices or names. | ||
|
|
||
| >>> from pyspark.mllib.linalg import DenseVector | ||
| >>> from pyspark.mllib.linalg import SparseVector | ||
| >>> df = sqlContext.createDataFrame([(SparseVector(3, {0: -2.0, 1: 2.3}),), | ||
| ... (DenseVector([-2.0, 2.3, 0.0]),)], ["userFeatures"]) | ||
| >>> vectorSlicer = VectorSlicer(indices=[1, 2], inputCol="userFeatures", outputCol="features") | ||
| >>> vectorSlicer.transform(df).head().features | ||
| SparseVector(2, {0: 2.3}) | ||
| """ | ||
|
|
||
| # a placeholder to make it appear in the generated doc | ||
| indices = Param(Params._dummy(), "indices", | ||
| "An array of indices to select features from a vector column." + | ||
| " There can be no overlap with names.") | ||
| names = Param(Params._dummy(), "names", | ||
| "An array of feature names to select features from a vector column." + | ||
| " There can be no overlap with indices.") | ||
|
|
||
| @keyword_only | ||
| def __init__(self, indices=[], names=[], inputCol=None, outputCol=None): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not use |
||
| """ | ||
| __init__(self, indices=[], names=[], inputCol=None, outputCol=None) | ||
| """ | ||
| super(VectorSlicer, self).__init__() | ||
| self._java_obj = self._new_java_obj("org.apache.spark.ml.feature.VectorSlicer", self.uid) | ||
| self.indices = Param(self, "indices", | ||
| "An array of indices to select features from a vector column." + | ||
| " There can be no overlap with names.") | ||
| self._setDefault(indices=[]) | ||
| self.names = Param(self, "names", | ||
| "An array of feature names to select features from a vector column." + | ||
| " There can be no overlap with indices.") | ||
| self._setDefault(names=[]) | ||
| kwargs = self.__init__._input_kwargs | ||
| self.setParams(**kwargs) | ||
|
|
||
| @keyword_only | ||
| def setParams(self, indices=[], names=[], inputCol=None, outputCol=None): | ||
| """ | ||
| setParams(self, indices=[], names=[], inputCol=None, outputCol=None) | ||
| Sets params for this VectorSlicer. | ||
| """ | ||
| kwargs = self.setParams._input_kwargs | ||
| return self._set(**kwargs) | ||
|
|
||
| def setIndices(self, value): | ||
| """ | ||
| Sets the value of :py:attr:`indices`. | ||
| """ | ||
| self._paramMap[self.indices] = value | ||
| return self | ||
|
|
||
| def getIndices(self): | ||
| """ | ||
| Gets the value of indices or its default value. | ||
| """ | ||
| return self.getOrDefault(self.indices) | ||
|
|
||
| def setNames(self, value): | ||
| """ | ||
| Sets the value of :py:attr:`names`. | ||
| """ | ||
| self._paramMap[self.names] = value | ||
| return self | ||
|
|
||
| def getNames(self): | ||
| """ | ||
| Gets the value of names or its default value. | ||
| """ | ||
| return self.getOrDefault(self.names) | ||
|
|
||
|
|
||
| @inherit_doc | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove the word "real" (i.e. "...that represent the indices into the vector") since it could be confused for real numbers (i.e. real-valued indices, which don't really make sense)