Прикрепить метаданные к векторному столбцу в Spark

Контекст: у меня есть фрейм данных с двумя столбцами: метка и функции.

org.apache.spark.sql.DataFrame = [label: int, features: vector]

Где features — это mllib.linalg.VectorUDT числового типа, созданный с помощью VectorAssembler.

Вопрос. Есть ли способ назначить схему вектору признаков? Я хочу отслеживать название каждой функции.

На данный момент пробовали:

val defaultAttr = NumericAttribute.defaultAttr
val attrs = Array("feat1", "feat2", "feat3").map(defaultAttr.withName)
val attrGroup = new AttributeGroup("userFeatures", attrs.asInstanceOf[Array[Attribute]])

scala> attrGroup.toMetadata 
res197: org.apache.spark.sql.types.Metadata = {"ml_attr":{"attrs":{"numeric":[{"idx":0,"name":"f1"},{"idx":1,"name":"f2"},{"idx":2,"name":"f3"}]},"num_attrs":3}}

Но не был уверен, как применить это к существующему фрейму данных.


person gstvolvr    schedule 10.02.2016    source источник


Ответы (1)


Тут как минимум два варианта:

  1. На существующем DataFrame вы можете использовать метод as с аргументом metadata:

    import org.apache.spark.ml.attribute._
    
    val rdd = sc.parallelize(Seq(
      (1, Vectors.dense(1.0, 2.0, 3.0))
    ))
    val df = rdd.toDF("label", "features")
    
    df.withColumn("features", $"features".as("_", attrGroup.toMetadata))
    
  2. Когда вы создаете новый DataFrame, преобразуйте AttributeGroup toStructField и используете его как схему для данного столбца:

    import org.apache.spark.sql.types.{StructType, StructField, IntegerType}
    
    val schema = StructType(Array(
      StructField("label", IntegerType, false),
      attrGroup.toStructField()
    ))
    
    spark.createDataFrame(
      rdd.map(row => Row.fromSeq(row.productIterator.toSeq)),
      schema)
    

Если векторный столбец был создан с использованием VectorAssembler метаданных столбца, описывающего родительские столбцы, уже должны быть прикреплены.

import org.apache.spark.ml.feature.VectorAssembler

val raw = sc.parallelize(Seq(
  (1, 1.0, 2.0, 3.0)
)).toDF("id", "feat1", "feat2", "feat3")

val assembler = new VectorAssembler()
  .setInputCols(Array("feat1", "feat2", "feat3"))
  .setOutputCol("features")

val dfWithMeta = assembler.transform(raw).select($"id", $"features")
dfWithMeta.schema.fields(1).metadata

// org.apache.spark.sql.types.Metadata = {"ml_attr":{"attrs":{"numeric":[
//   {"idx":0,"name":"feat1"},{"idx":1,"name":"feat2"},
//   {"idx":2,"name":"feat3"}]},"num_attrs":3}

Векторные поля не доступны напрямую с использованием точечного синтаксиса (например, $features.feat1), но могут использоваться специализированными инструментами, такими как VectorSlicer:

import org.apache.spark.ml.feature.VectorSlicer

val slicer = new VectorSlicer()
  .setInputCol("features")
  .setOutputCol("featuresSubset")
  .setNames(Array("feat1", "feat3"))

slicer.transform(dfWithMeta).show
// +---+-------------+--------------+
// | id|     features|featuresSubset|
// +---+-------------+--------------+
// |  1|[1.0,2.0,3.0]|     [1.0,3.0]|
// +---+-------------+--------------+

Для PySpark см. Как я могу объявить столбец как категориальную функцию в DataFrame для использования в мл

person zero323    schedule 10.02.2016
comment
Можно ли извлечь столбец feat1, используя имя вместо индекса? - person gstvolvr; 10.02.2016
comment
Что-то вроде. Вы можете использовать VectorSlicer. - person zero323; 10.02.2016
comment
@zero323 Zero323 У меня есть сомнения относительно второго подхода. Похоже, Спарку это не нравится. По какой-то причине он считает, что преобразует метку StructField в кортеж. - person eliasah; 08.08.2017
comment
@eliasah Подход правильный, переданная функция была неправильной. Спасибо. - person zero323; 09.08.2017
comment
@zero323 отличный ответ. Очень информативно и полезно - person Gabe Church; 06.03.2020