spark Attribute由来原理示例用法源码详解
文章目录
- spark Attribute由来原理示例用法源码详解
- 由来
- 示例
- 中文源码分析
- abstract class Attribute
- NumericAttribute
- NominalAttribute
- BinaryAttribute
- UnresolvedAttribute
由来
Spark的Attribute
类是用来表示特征属性的基类。它提供了不同类型属性的共同方法和属性。
在Spark中,属性被用于描述数据集中的特征或列。每个属性都具有名称、索引和类型等信息。Spark的Attribute
类提供了一种统一的方式来管理和操作这些属性。
Attribute
类本身是一个抽象类,它定义了以下常用方法和属性:
-
attrType: AttributeType
:返回属性的类型,包括数值、标称、二元和未解析等类型。 -
isNumeric: Boolean
:判断属性是否为数值类型。 -
isNominal: Boolean
:判断属性是否为标称类型。 -
name: Option[String]
:返回属性的名称。 -
index: Option[Int]
:返回属性的索引。 -
withName(name: String): Attribute
:设置属性的名称,并返回一个新的属性对象。 -
withoutName: Attribute
:移除属性的名称,并返回一个新的属性对象。 -
withIndex(index: Int): Attribute
:设置属性的索引,并返回一个新的属性对象。 -
withoutIndex: Attribute
:移除属性的索引,并返回一个新的属性对象。 -
toMetadata(withType: Boolean): Metadata
:将属性转换为元数据。 -
equals(other: Any): Boolean
:判断属性对象是否与另一个对象相等。 -
hashCode: Int
:计算属性对象的哈希值。
Attribute
类的子类包括NumericAttribute
、NominalAttribute
、BinaryAttribute
和UnresolvedAttribute
,分别用于表示数值属性、标称属性、二元属性和未解析属性。
Spark的Attribute
类提供了一种通用的方式来描述和操作特征属性,它在数据集的处理过程中发挥着重要的作用。通过使用属性对象,我们可以方便地获取和修改属性的信息,并进行属性类型的判断和转换。
示例
object AttributeTest extends App{
import org.apache.spark.ml.attribute._
// 创建数值属性
val numericAttr = NumericAttribute.defaultAttr.withName("numeric").withIndex(0)
// 创建标称属性
val nominalAttr = NominalAttribute.defaultAttr.withName("nominal").withIndex(1).withValues("A", "B", "C")
// 创建二元属性
val binaryAttr = BinaryAttribute.defaultAttr.withName("binary").withIndex(2).withValues("negative", "positive")
// 创建未解析属性
val unresolvedAttr = UnresolvedAttribute
// 打印属性信息
println(numericAttr.name) // Some(numeric)
println(numericAttr.index) // Some(0)
println(numericAttr.attrType) // Numeric
println(numericAttr.isNumeric) // true
println(numericAttr.isNominal) // false
println(nominalAttr.name) // Some(nominal)
println(nominalAttr.index) // Some(1)
println(nominalAttr.attrType) // Nominal
println(nominalAttr.isNumeric) // false
println(nominalAttr.isNominal) // true
println(binaryAttr.name) // Some(binary)
println(binaryAttr.index) // Some(2)
println(binaryAttr.attrType) // Binary
println(binaryAttr.isNumeric) // true
println(binaryAttr.isNominal) // true
println(unresolvedAttr.name) // None
println(unresolvedAttr.index) // None
println(unresolvedAttr.attrType) // Unresolved
println(unresolvedAttr.isNumeric) // false
println(unresolvedAttr.isNominal) // false
// 将属性转换为元数据
val metadata1 = numericAttr.toMetadata()
println(metadata1) //
val metadata2 = nominalAttr.toMetadata()
println(metadata2) //
val metadata3 = binaryAttr.toMetadata()
println(metadata3) //
val metadata4 = unresolvedAttr.toMetadata()
println(metadata4) // {}
}
Some(numeric)
Some(0)
Numeric
true
false
Some(nominal)
Some(1)
Nominal
false
true
Some(binary)
Some(2)
Binary
true
true
None
None
Unresolved
false
false
{"ml_attr":{"idx":0,"name":"numeric"}}
{"ml_attr":{"vals":["A","B","C"],"type":"nominal","idx":1,"name":"nominal"}}
{"ml_attr":{"vals":["negative","positive"],"type":"binary","idx":2,"name":"binary"}}
{"ml_attr":{}}
中文源码分析
abstract class Attribute
/**
* :: DeveloperApi ::
* ML属性的抽象类。
*/
@DeveloperApi
sealed abstract class Attribute extends Serializable {
name.foreach { n =>
require(n.nonEmpty, "名称不能为空字符串。")
}
index.foreach { i =>
require(i >= 0, s"索引不能为负数,但是得到的值为 $i")
}
/** 属性类型。 */
def attrType: AttributeType
/** 属性的名称。如果未设置,则为None。 */
def name: Option[String]
/** 使用新名称创建副本。 */
def withName(name: String): Attribute
/** 创建没有名称的副本。 */
def withoutName: Attribute
/** 属性的索引。如果未设置,则为None。 */
def index: Option[Int]
/** 使用新索引创建副本。 */
def withIndex(index: Int): Attribute
/** 创建没有索引的副本。 */
def withoutIndex: Attribute
/**
* 检查该属性是否是数值属性,对于NumericAttribute和BinaryAttribute返回true。
*/
def isNumeric: Boolean
/**
* 检查该属性是否是标称属性,对于NominalAttribute和BinaryAttribute返回true。
*/
def isNominal: Boolean
/**
* 将属性转换为Metadata。
* @param withType 是否包含类型信息
*/
private[attribute] def toMetadataImpl(withType: Boolean): Metadata
/**
* 将属性转换为Metadata。对于数值属性,排除类型信息以节省空间,因为数值类型是默认的属性类型。
* 对于标称和二进制属性,包含类型信息。
*/
private[attribute] def toMetadataImpl(): Metadata = {
if (attrType == AttributeType.Numeric) {
toMetadataImpl(withType = false)
} else {
toMetadataImpl(withType = true)
}
}
/** 转换为带有现有Metadata的ML元数据。 */
def toMetadata(existingMetadata: Metadata): Metadata = {
new MetadataBuilder()
.withMetadata(existingMetadata)
.putMetadata(AttributeKeys.ML_ATTR, toMetadataImpl())
.build()
}
/** 转换为ML元数据。 */
def toMetadata(): Metadata = toMetadata(Metadata.empty)
/**
* 转换为带有现有Metadata的`StructField`。
* @param existingMetadata 需要携带的现有元数据
*/
def toStructField(existingMetadata: Metadata): StructField = {
val newMetadata = new MetadataBuilder()
.withMetadata(existingMetadata)
.putMetadata(AttributeKeys.ML_ATTR, withoutName.withoutIndex.toMetadataImpl())
.build()
StructField(name.get, DoubleType, nullable = false, newMetadata)
}
/**
* 转换为`StructField`。
*/
def toStructField(): StructField = toStructField(Metadata.empty)
override def toString: String = toMetadataImpl(withType = true).toString
}
/** ML属性工厂的特质。 */
private[attribute] trait AttributeFactory {
/**
* 从`Metadata`实例创建[[Attribute]]。
*/
private[attribute] def fromMetadata(metadata: Metadata): Attribute
/**
* 从`StructField`实例创建[[Attribute]],可选择保留名称。
*/
private[ml] def decodeStructField(field: StructField, preserveName: Boolean): Attribute = {
require(field.dataType.isInstanceOf[NumericType])
val metadata = field.metadata
val mlAttr = AttributeKeys.ML_ATTR
if (metadata.contains(mlAttr)) {
val attr = fromMetadata(metadata.getMetadata(mlAttr))
if (preserveName) {
attr
} else {
attr.withName(field.name)
}
} else {
UnresolvedAttribute
}
}
/**
* 从`StructField`实例创建[[Attribute]]。
*/
def fromStructField(field: StructField): Attribute = decodeStructField(field, false)
}
/**
* :: DeveloperApi ::
* 属性的工厂类。
*/
@DeveloperApi
object Attribute extends AttributeFactory {
private[attribute] override def fromMetadata(metadata: Metadata): Attribute = {
import org.apache.spark.ml.attribute.AttributeKeys._
val attrType = if (metadata.contains(TYPE)) {
metadata.getString(TYPE)
} else {
AttributeType.Numeric.name
}
getFactory(attrType).fromMetadata(metadata)
}
/** 根据属性类型名称获取属性工厂。 */
private def getFactory(attrType: String): AttributeFactory = {
if (attrType == AttributeType.Numeric.name) {
NumericAttribute
} else if (attrType == AttributeType.Nominal.name) {
NominalAttribute
} else if (attrType == AttributeType.Binary.name) {
BinaryAttribute
} else {
throw new IllegalArgumentException(s"无法识别的类型 $attrType.")
}
}
}
name
、index
和attrType
:这些方法提供了获取属性名称、索引和类型的功能,可以直接调用。
-
attrType
:属性类型,可取值为Numeric、Nominal和Binary。 -
name
:属性的名称,类型为Option[String],如果未设置则为None。 -
index
:属性的索引,类型为Option[Int],如果未设置则为None。
withName(name: String)
和withIndex(index: Int)
:这些方法用于创建属性的副本,并设置新的名称或索引。可以通过调用这些方法来修改属性对象。withoutName
和withoutIndex
:这些方法创建没有名称或索引的属性副本。可以使用这些方法移除属性对象的名称或索引。isNumeric
和isNominal
:这些方法用于检查属性是否为数值型或标称型属性。可以根据返回的布尔值判断属性的类型。
-
isNumeric
:检查属性是否是数值属性,对于NumericAttribute和BinaryAttribute返回true。 -
isNominal
:检查属性是否是标称属性,对于NominalAttribute和BinaryAttribute返回true。
toMetadataImpl(withType: Boolean)
:这个方法将属性转换为Metadata对象。参数withType
表示是否包含属性类型信息。可以根据需要调用此方法来生成相应的Metadata对象。toMetadata(existingMetadata: Metadata)
和toMetadata()
:这些方法将属性转换为ML元数据对象(Metadata)。toMetadata(existingMetadata: Metadata)
方法会在现有的Metadata上添加属性相关的元数据,而toMetadata()
方法则以空的Metadata对象开始构建新的ML元数据。可以根据需求选择适当的方法来生成ML元数据。toStructField(existingMetadata: Metadata)
和toStructField()
:这些方法将属性转换为StructField
对象,用于在Spark中定义数据集的模式。toStructField(existingMetadata: Metadata)
方法会在现有的Metadata上添加属性相关的元数据,而toStructField()
方法则以空的Metadata对象开始构建新的StructField
。可以根据需求选择适当的方法来生成StructField
。
可以在DataFrame中使用。toString
:这个方法将属性对象转换为字符串形式,输出属性的元数据信息的字符串表示。可以直接调用此方法来获取属性的字符串表示形式。
Attribute
有三个子类:NumericAttribute、NominalAttribute和BinaryAttribute,分别表示数值属性、标称属性和二进制属性。这些子类提供了特定属性类型的额外方法和属性。
以NumericAttribute
为例,它包含了数值属性的一些统计信息,比如最小值、最大值、标准差和稀疏度等。NumericAttribute
提供了一系列方法来修改这些统计信息,并且继承了Attribute
的方法和属性。
Attribute
工厂类还提供了fromMetadata()
和fromStructField()
方法,用于从Metadata和StructField创建属性实例。
NumericAttribute
/**
* :: DeveloperApi ::
* 具有可选汇总统计信息的数值属性。
* @param name 可选的名称
* @param index 可选的索引
* @param min 可选的最小值
* @param max 可选的最大值
* @param std 可选的标准差
* @param sparsity 可选的稀疏度(零的比率)
*/
@DeveloperApi
class NumericAttribute private[ml] (
override val name: Option[String] = None,
override val index: Option[Int] = None,
val min: Option[Double] = None,
val max: Option[Double] = None,
val std: Option[Double] = None,
val sparsity: Option[Double] = None) extends Attribute {
std.foreach { s =>
require(s >= 0.0, s"标准差不能为负数,但得到的值为 $s.")
}
sparsity.foreach { s =>
require(s >= 0.0 && s <= 1.0, s"稀疏度必须在 [0, 1] 范围内,但得到的值为 $s.")
}
override def attrType: AttributeType = AttributeType.Numeric
override def withName(name: String): NumericAttribute = copy(name = Some(name))
override def withoutName: NumericAttribute = copy(name = None)
override def withIndex(index: Int): NumericAttribute = copy(index = Some(index))
override def withoutIndex: NumericAttribute = copy(index = None)
/** 创建具有新最小值的副本。 */
def withMin(min: Double): NumericAttribute = copy(min = Some(min))
/** 创建没有最小值的副本。 */
def withoutMin: NumericAttribute = copy(min = None)
/** 创建具有新最大值的副本。 */
def withMax(max: Double): NumericAttribute = copy(max = Some(max))
/** 创建没有最大值的副本。 */
def withoutMax: NumericAttribute = copy(max = None)
/** 创建具有新标准差的副本。 */
def withStd(std: Double): NumericAttribute = copy(std = Some(std))
/** 创建没有标准差的副本。 */
def withoutStd: NumericAttribute = copy(std = None)
/** 创建具有新稀疏度的副本。 */
def withSparsity(sparsity: Double): NumericAttribute = copy(sparsity = Some(sparsity))
/** 创建没有稀疏度的副本。 */
def withoutSparsity: NumericAttribute = copy(sparsity = None)
/** 创建没有汇总统计信息的副本。 */
def withoutSummary: NumericAttribute = copy(min = None, max = None, std = None, sparsity = None)
override def isNumeric: Boolean = true
override def isNominal: Boolean = false
/** 将属性转换为元数据。 */
override private[attribute] def toMetadataImpl(withType: Boolean): Metadata = {
import org.apache.spark.ml.attribute.AttributeKeys._
val bldr = new MetadataBuilder()
if (withType) bldr.putString(TYPE, attrType.name)
name.foreach(bldr.putString(NAME, _))
index.foreach(bldr.putLong(INDEX, _))
min.foreach(bldr.putDouble(MIN, _))
max.foreach(bldr.putDouble(MAX, _))
std.foreach(bldr.putDouble(STD, _))
sparsity.foreach(bldr.putDouble(SPARSITY, _))
bldr.build()
}
/** 创建具有可选更改的属性副本。 */
private def copy(
name: Option[String] = name,
index: Option[Int] = index,
min: Option[Double] = min,
max: Option[Double] = max,
std: Option[Double] = std,
sparsity: Option[Double] = sparsity): NumericAttribute = {
new NumericAttribute(name, index, min, max, std, sparsity)
}
override def equals(other: Any): Boolean = {
other match {
case o: NumericAttribute =>
(name == o.name) &&
(index == o.index) &&
(min == o.min) &&
(max == o.max) &&
(std == o.std) &&
(sparsity == o.sparsity)
case _ =>
false
}
}
override def hashCode: Int = {
var sum = 17
sum = 37 * sum + name.hashCode
sum = 37 * sum + index.hashCode
sum = 37 * sum + min.hashCode
sum = 37 * sum + max.hashCode
sum = 37 * sum + std.hashCode
sum = 37 * sum + sparsity.hashCode
sum
}
}
/**
* :: DeveloperApi ::
* 数值属性的工厂方法。
*/
@DeveloperApi
object NumericAttribute extends AttributeFactory {
/** 默认的数值属性。 */
val defaultAttr: NumericAttribute = new NumericAttribute
/**
* 从元数据创建数值属性对象。
* @param metadata 元数据对象
* @return 数值属性对象
*/
private[attribute] override def fromMetadata(metadata: Metadata): NumericAttribute = {
import org.apache.spark.ml.attribute.AttributeKeys._
// 从元数据中提取名称、索引、最小值、最大值、标准差和稀疏度等属性信息,并使用这些信息创建一个新的NumericAttribute对象
val name = if (metadata.contains(NAME)) Some(metadata.getString(NAME)) else None
val index = if (metadata.contains(INDEX)) Some(metadata.getLong(INDEX).toInt) else None
val min = if (metadata.contains(MIN)) Some(metadata.getDouble(MIN)) else None
val max = if (metadata.contains(MAX)) Some(metadata.getDouble(MAX)) else None
val std = if (metadata.contains(STD)) Some(metadata.getDouble(STD)) else None
val sparsity = if (metadata.contains(SPARSITY)) Some(metadata.getDouble(SPARSITY)) else None
new NumericAttribute(name, index, min, max, std, sparsity)
}
}
/**
* 用于存储属性的键。
*/
private[attribute] object AttributeKeys {
val ML_ATTR: String = "ml_attr" // ML属性的键
val TYPE: String = "type" // 属性类型的键
val NAME: String = "name" // 属性名称的键
val INDEX: String = "idx" // 属性索引的键
val MIN: String = "min" // 最小值的键
val MAX: String = "max" // 最大值的键
val STD: String = "std" // 标准差的键
val SPARSITY: String = "sparsity" // 稀疏度的键
val ORDINAL: String = "ord" // 序数型属性的键
val VALUES: String = "vals" // 属性值列表的键
val NUM_VALUES: String = "num_vals" // 属性值数量的键
val ATTRIBUTES: String = "attrs" // 属性列表的键
val NUM_ATTRIBUTES: String = "num_attrs" // 属性数量的键
}
以下是对NumericAttribute
源码的分析:
sealed class NumericAttribute(
override val name: Option[String] = None,
override val index: Option[Int] = None,
val min: Option[Double] = None,
val max: Option[Double] = None,
val std: Option[Double] = None,
val sparsity: Option[Double] = None)
extends Attribute {
override def attrType: AttributeType = AttributeType.Numeric
override def withName(name: String): NumericAttribute =
new NumericAttribute(Some(name), index, min, max, std, sparsity)
override def withoutName: NumericAttribute =
new NumericAttribute(None, index, min, max, std, sparsity)
override def withIndex(index: Int): NumericAttribute =
new NumericAttribute(name, Some(index), min, max, std, sparsity)
override def withoutIndex: NumericAttribute =
new NumericAttribute(name, None, min, max, std, sparsity)
override def isNumeric: Boolean = true
override def isNominal: Boolean = false
private[spark] override def toMetadataImpl(withType: Boolean): Metadata = {
val metadataBuilder = new MetadataBuilder()
if (withType) {
metadataBuilder.putString(AttributeKeys.TYPE, AttributeType.Numeric.toString)
}
name.foreach(metadataBuilder.putString(AttributeKeys.NAME, _))
index.foreach(i => metadataBuilder.putLong(AttributeKeys.INDEX, i.toLong))
min.foreach(metadataBuilder.putDouble(AttributeKeys.MIN, _))
max.foreach(metadataBuilder.putDouble(AttributeKeys.MAX, _))
std.foreach(metadataBuilder.putDouble(AttributeKeys.STD, _))
sparsity.foreach(metadataBuilder.putDouble(AttributeKeys.SPARSITY, _))
metadataBuilder.build()
}
}
NumericAttribute
是表示数值属性的类,它继承自Attribute
抽象类。
该类具有以下主要特点和功能:
- 构造函数:接受可选的名称、索引以及最小值、最大值、标准差和稀疏度等属性值作为参数。
- 属性类型:重写了
attrType
方法,返回属性类型为AttributeType.Numeric
。 - 设置和获取属性值:提供了一系列方法(如
withName
、withIndex
、withoutName
、withoutIndex
)用于设置和获取属性的名称和索引。 - 数值属性判断:重写了
isNumeric
方法,返回true
,表示该属性是数值属性;重写了isNominal
方法,返回false
,表示该属性不是标称属性。 - 元数据转换:通过重写
toMetadataImpl
方法,将属性转换为Metadata
对象,其中包含了属性的各种信息(如类型、名称、索引、最小值、最大值、标准差和稀疏度等)。 - 属性复制:通过创建新的
NumericAttribute
对象,并传递相应的属性值,可以进行属性的复制和修改操作。
通过对NumericAttribute
源码的分析,我们可以看到它提供了一系列方法和功能,方便地表示和处理数值属性,并与其他Spark ML组件进行集成。
NominalAttribute
/**
* :: DeveloperApi ::
* 标称属性。
* @param name 可选的名称
* @param index 可选的索引
* @param isOrdinal 是否为序数型属性(可选)
* @param numValues 可选的值的数量。`numValues`和`values`只能定义其中一个。
* @param values 可选的值。`numValues`和`values`只能定义其中一个。
*/
@DeveloperApi
class NominalAttribute private[ml] (
override val name: Option[String] = None,
override val index: Option[Int] = None,
val isOrdinal: Option[Boolean] = None,
val numValues: Option[Int] = None,
val values: Option[Array[String]] = None) extends Attribute {
numValues.foreach { n =>
require(n >= 0, s"numValues不能为负数,但得到的是 $n.")
}
require(!(numValues.isDefined && values.isDefined),
"不能同时定义numValues和values.")
override def attrType: AttributeType = AttributeType.Nominal
override def isNumeric: Boolean = false
override def isNominal: Boolean = true
private lazy val valueToIndex: Map[String, Int] = {
values.map(_.zipWithIndex.toMap).getOrElse(Map.empty)
}
/** 获取特定值的索引。 */
def indexOf(value: String): Int = {
valueToIndex(value)
}
/** 检查属性是否包含特定值。 */
def hasValue(value: String): Boolean = valueToIndex.contains(value)
/** 根据索引获取值。 */
def getValue(index: Int): String = values.get(index)
override def withName(name: String): NominalAttribute = copy(name = Some(name))
override def withoutName: NominalAttribute = copy(name = None)
override def withIndex(index: Int): NominalAttribute = copy(index = Some(index))
override def withoutIndex: NominalAttribute = copy(index = None)
/**
* 使用新的值和空的`numValues`进行复制。
*/
def withValues(values: Array[String]): NominalAttribute = {
copy(numValues = None, values = Some(values))
}
/**
* 使用新的值和空的`numValues`进行复制。
*/
@varargs
def withValues(first: String, others: String*): NominalAttribute = {
copy(numValues = None, values = Some((first +: others).toArray))
}
/** 不包含值的复制。 */
def withoutValues: NominalAttribute = {
copy(values = None)
}
/**
* 使用新的`numValues`和空的`values`进行复制。
*/
def withNumValues(numValues: Int): NominalAttribute = {
copy(numValues = Some(numValues), values = None)
}
/**
* 不包含`numValues`的复制。
*/
def withoutNumValues: NominalAttribute = copy(numValues = None)
/**
* 获取值的数量,可以从`numValues`或`values`中获取。
* 如果未知,则返回None。
*/
def getNumValues: Option[Int] = {
if (numValues.nonEmpty) {
numValues
} else if (values.nonEmpty) {
Some(values.get.length)
} else {
None
}
}
/** 创建带有可选更改的属性副本。 */
private def copy(
name: Option[String] = name,
index: Option[Int] = index,
isOrdinal: Option[Boolean] = isOrdinal,
numValues: Option[Int] = numValues,
values: Option[Array[String]] = values): NominalAttribute = {
new NominalAttribute(name, index, isOrdinal, numValues, values)
}
override private[attribute] def toMetadataImpl(withType: Boolean): Metadata = {
import org.apache.spark.ml.attribute.AttributeKeys._
val bldr = new MetadataBuilder()
if (withType) bldr.putString(TYPE, attrType.name)
name.foreach(bldr.putString(NAME, _))
index.foreach(bldr.putLong(INDEX, _))
isOrdinal.foreach(bldr.putBoolean(ORDINAL, _))
numValues.foreach(bldr.putLong(NUM_VALUES, _))
values.foreach(v => bldr.putStringArray(VALUES, v))
bldr.build()
}
override def equals(other: Any): Boolean = {
other match {
case o: NominalAttribute =>
(name == o.name) &&
(index == o.index) &&
(isOrdinal == o.isOrdinal) &&
(numValues == o.numValues) &&
(values.map(_.toSeq) == o.values.map(_.toSeq))
case _ =>
false
}
}
override def hashCode: Int = {
var sum = 17
sum = 37 * sum + name.hashCode
sum = 37 * sum + index.hashCode
sum = 37 * sum + isOrdinal.hashCode
sum = 37 * sum + numValues.hashCode
sum = 37 * sum + values.map(_.toSeq).hashCode
sum
}
/**
* :: DeveloperApi ::
* 标称属性的工厂方法。
*/
@DeveloperApi
object NominalAttribute extends AttributeFactory {
/** 默认的标称属性。 */
final val defaultAttr: NominalAttribute = new NominalAttribute
private[attribute] override def fromMetadata(metadata: Metadata): NominalAttribute = {
import org.apache.spark.ml.attribute.AttributeKeys._
val name = if (metadata.contains(NAME)) Some(metadata.getString(NAME)) else None
val index = if (metadata.contains(INDEX)) Some(metadata.getLong(INDEX).toInt) else None
val isOrdinal = if (metadata.contains(ORDINAL)) Some(metadata.getBoolean(ORDINAL)) else None
val numValues =
if (metadata.contains(NUM_VALUES)) Some(metadata.getLong(NUM_VALUES).toInt) else None
val values =
if (metadata.contains(VALUES)) Some(metadata.getStringArray(VALUES)) else None
new NominalAttribute(name, index, isOrdinal, numValues, values)
}
}
NominalAttribute
是一个标称属性的类,它提供了一些工厂方法来创建和操作标称属性。
使用 NominalAttribute.defaultAttr
可以获取默认的标称属性对象。
可以使用 NominalAttribute.fromMetadata(metadata: Metadata)
方法从元数据中创建标称属性对象。该方法会解析元数据并返回相应的标称属性对象。
标称属性对象的常用方法包括:
-
withName(name: String): NominalAttribute
:设置属性的名称,并返回一个新的标称属性对象。 -
withoutName: NominalAttribute
:移除属性的名称,并返回一个新的标称属性对象。 -
withIndex(index: Int): NominalAttribute
:设置属性的索引,并返回一个新的标称属性对象。 -
withoutIndex: NominalAttribute
:移除属性的索引,并返回一个新的标称属性对象。 -
withValues(values: Array[String]): NominalAttribute
:设置属性的值,并返回一个新的标称属性对象。该方法会清空numValues
的值。 -
withoutValues: NominalAttribute
:移除属性的值,并返回一个新的标称属性对象。 -
withNumValues(numValues: Int): NominalAttribute
:设置属性的值的数量,并返回一个新的标称属性对象。该方法会清空属性的值。 -
withoutNumValues: NominalAttribute
:移除属性的值的数量,并返回一个新的标称属性对象。 -
getNumValues: Option[Int]
:获取属性的值的数量,如果已知则返回具体的数量,否则返回None
。 -
equals(other: Any): Boolean
:判断属性对象是否与另一个对象相等。 -
hashCode: Int
:计算属性对象的哈希值。
通过上述方法,可以使用 NominalAttribute
类创建和操作标称属性对象。
BinaryAttribute
/**
* :: DeveloperApi ::
* 二元属性。
* @param name 可选的名称
* @param index 可选的索引
* @param values 可选的值。如果设置,其大小必须为2。
*/
@DeveloperApi
class BinaryAttribute private[ml] (
override val name: Option[String] = None,
override val index: Option[Int] = None,
val values: Option[Array[String]] = None)
extends Attribute {
values.foreach { v =>
require(v.length == 2, s"二元属性的值数量必须为2,但得到的是 ${v.toSeq}。")
}
override def attrType: AttributeType = AttributeType.Binary
override def isNumeric: Boolean = true
override def isNominal: Boolean = true
override def withName(name: String): BinaryAttribute = copy(name = Some(name))
override def withoutName: BinaryAttribute = copy(name = None)
override def withIndex(index: Int): BinaryAttribute = copy(index = Some(index))
override def withoutIndex: BinaryAttribute = copy(index = None)
/**
* 使用新的值进行复制。
* @param negative 负面名称
* @param positive 正面名称
*/
def withValues(negative: String, positive: String): BinaryAttribute =
copy(values = Some(Array(negative, positive)))
/** 不包含值的复制。 */
def withoutValues: BinaryAttribute = copy(values = None)
/** 创建带有可选更改的属性副本。 */
private def copy(
name: Option[String] = name,
index: Option[Int] = index,
values: Option[Array[String]] = values): BinaryAttribute = {
new BinaryAttribute(name, index, values)
}
override private[attribute] def toMetadataImpl(withType: Boolean): Metadata = {
import org.apache.spark.ml.attribute.AttributeKeys._
val bldr = new MetadataBuilder
if (withType) bldr.putString(TYPE, attrType.name)
name.foreach(bldr.putString(NAME, _))
index.foreach(bldr.putLong(INDEX, _))
values.foreach(v => bldr.putStringArray(VALUES, v))
bldr.build()
}
override def equals(other: Any): Boolean = {
other match {
case o: BinaryAttribute =>
(name == o.name) &&
(index == o.index) &&
(values.map(_.toSeq) == o.values.map(_.toSeq))
case _ =>
false
}
}
override def hashCode: Int = {
var sum = 17
sum = 37 * sum + name.hashCode
sum = 37 * sum + index.hashCode
sum = 37 * sum + values.map(_.toSeq).hashCode
sum
}
}
/**
* :: DeveloperApi ::
* 二元属性的工厂方法。
*/
@DeveloperApi
object BinaryAttribute extends AttributeFactory {
/** 默认的二元属性。 */
final val defaultAttr: BinaryAttribute = new BinaryAttribute
private[attribute] override def fromMetadata(metadata: Metadata): BinaryAttribute = {
import org.apache.spark.ml.attribute.AttributeKeys._
val name = if (metadata.contains(NAME)) Some(metadata.getString(NAME)) else None
val index = if (metadata.contains(INDEX)) Some(metadata.getLong(INDEX).toInt) else None
val values =
if (metadata.contains(VALUES)) Some(metadata.getStringArray(VALUES)) else None
new BinaryAttribute(name, index, values)
}
}
以上是BinaryAttribute
类和BinaryAttribute
对象的源代码。BinaryAttribute
类代表一个二元属性,它具有名称、索引和值等属性。BinaryAttribute
对象是一个工厂对象,提供了创建和解析二元属性的方法。
BinaryAttribute
类的常用方法包括:
-
withName(name: String): BinaryAttribute
:设置属性的名称,并返回一个新的二元属性对象。 -
withoutName: BinaryAttribute
:移除属性的名称,并返回一个新的二元属性对象。 -
withIndex(index: Int): BinaryAttribute
:设置属性的索引,并返回一个新的二元属性对象。 -
withoutIndex: BinaryAttribute
:移除属性的索引,并返回一个新的二元属性对象。 -
withValues(negative: String, positive: String): BinaryAttribute
:设置属性的值,并返回一个新的二元属性对象。 -
withoutValues: BinaryAttribute
:移除属性
UnresolvedAttribute
/**
* :: DeveloperApi ::
* 一个未解析的属性。
*/
@DeveloperApi
object UnresolvedAttribute extends Attribute {
override def attrType: AttributeType = AttributeType.Unresolved
override def withIndex(index: Int): Attribute = this
override def isNumeric: Boolean = false
override def withoutIndex: Attribute = this
override def isNominal: Boolean = false
override def name: Option[String] = None
override private[attribute] def toMetadataImpl(withType: Boolean): Metadata = {
Metadata.empty
}
override def withoutName: Attribute = this
override def index: Option[Int] = None
override def withName(name: String): Attribute = this
}
以上是UnresolvedAttribute
对象的源代码。UnresolvedAttribute
代表一个未解析的属性,它的属性类型为未解析,并且没有名称和索引。它不是数值属性,也不是标称属性。
该对象提供以下方法:
-
attrType: AttributeType
:返回属性类型,此处为未解析。 -
withIndex(index: Int): Attribute
:设置属性的索引,并返回当前对象(因为未解析属性不具有索引)。 -
isNumeric: Boolean
:判断属性是否为数值属性,此处为false。 -
withoutIndex: Attribute
:移除属性的索引,并返回当前对象。 -
isNominal: Boolean
:判断属性是否为标称属性,此处为false。 -
name: Option[String]
:返回属性的名称,此处为None。 -
toMetadataImpl(withType: Boolean): Metadata
:将属性转换为元数据,此处返回一个空的元数据。 -
withoutName: Attribute
:移除属性的名称,并返回当前对象。 -
index: Option[Int]
:返回属性的索引,此处为None。 -
withName(name: String): Attribute
:设置属性的名称,并返回当前对象。