Измените свойство nullable столбца в строке искровых данных

Я вручную создаю dataframe для некоторого тестирования. Код для его создания:

case class input(id:Long, var1:Int, var2:Int, var3:Double)
val inputDF = sqlCtx
  .createDataFrame(List(input(1110,0,1001,-10.00),
    input(1111,1,1001,10.00),
    input(1111,0,1002,10.00)))

Итак, схема выглядит так:

root
 |-- id: long (nullable = false)
 |-- var1: integer (nullable = false)
 |-- var2: integer (nullable = false)
 |-- var3: double (nullable = false)

Я хочу сделать 'nullable = true' для каждой из этих переменных. Как объявить это с самого начала или переключить его в новый фреймворк после его создания?

Ответ 1

Ответ

При импорте

import org.apache.spark.sql.types.{StructField, StructType}
import org.apache.spark.sql.{DataFrame, SQLContext}
import org.apache.spark.{SparkConf, SparkContext}

вы можете использовать

/**
 * Set nullable property of column.
 * @param df source DataFrame
 * @param cn is the column name to change
 * @param nullable is the flag to set, such that the column is  either nullable or not
 */
def setNullableStateOfColumn( df: DataFrame, cn: String, nullable: Boolean) : DataFrame = {

  // get schema
  val schema = df.schema
  // modify [[StructField] with name `cn`
  val newSchema = StructType(schema.map {
    case StructField( c, t, _, m) if c.equals(cn) => StructField( c, t, nullable = nullable, m)
    case y: StructField => y
  })
  // apply new schema
  df.sqlContext.createDataFrame( df.rdd, newSchema )
}

непосредственно.

Также вы можете сделать этот метод доступным через шаблон библиотеки "pimp my library" (см. мой SO post Каков наилучший способ определения пользовательских методов в DataFrame?), чтобы вы могли вызвать

val df = ....
val df2 = df.setNullableStateOfColumn( "id", true )

Изменить

Альтернативное решение 1

Используйте небольшую измененную версию setNullableStateOfColumn

def setNullableStateForAllColumns( df: DataFrame, nullable: Boolean) : DataFrame = {
  // get schema
  val schema = df.schema
  // modify [[StructField] with name `cn`
  val newSchema = StructType(schema.map {
    case StructField( c, t, _, m) ⇒ StructField( c, t, nullable = nullable, m)
  })
  // apply new schema
  df.sqlContext.createDataFrame( df.rdd, newSchema )
}

Альтернативное решение 2

Четко определите схему. (Используйте отражение, чтобы создать более общее решение)

configuredUnitTest("Stackoverflow.") { sparkContext =>

  case class Input(id:Long, var1:Int, var2:Int, var3:Double)

  val sqlContext = new SQLContext(sparkContext)
  import sqlContext.implicits._


  // use this to set the schema explicitly or
  // use refelection on the case class member to construct the schema
  val schema = StructType( Seq (
    StructField( "id", LongType, true),
    StructField( "var1", IntegerType, true),
    StructField( "var2", IntegerType, true),
    StructField( "var3", DoubleType, true)
  ))

  val is: List[Input] = List(
    Input(1110, 0, 1001,-10.00),
    Input(1111, 1, 1001, 10.00),
    Input(1111, 0, 1002, 10.00)
  )

  val rdd: RDD[Input] =  sparkContext.parallelize( is )
  val rowRDD: RDD[Row] = rdd.map( (i: Input) ⇒ Row(i.id, i.var1, i.var2, i.var3))
  val inputDF = sqlContext.createDataFrame( rowRDD, schema ) 

  inputDF.printSchema
  inputDF.show()
}

Ответ 2

Это поздний ответ, но он хотел дать альтернативное решение для людей, которые приходят сюда. Вы можете автоматически сделать DataFrame Column с нулевым значением с начала с помощью следующей модификации вашего кода:

case class input(id:Option[Long], var1:Option[Int], var2:Int, var3:Double)
val inputDF = sqlContext
  .createDataFrame(List(input(Some(1110),Some(0),1001,-10.00),
    input(Some(1111),Some(1),1001,10.00),
    input(Some(1111),Some(0),1002,10.00)))
inputDF.printSchema

Это даст:

root
 |-- id: long (nullable = true)
 |-- var1: integer (nullable = true)
 |-- var2: integer (nullable = false)
 |-- var3: double (nullable = false)

defined class input
inputDF: org.apache.spark.sql.DataFrame = [id: bigint, var1: int, var2: int, var3: double]

По существу, если вы объявляете поле как Option, используя Some([element]) или None в качестве фактических входов, тогда это поле должно быть нулевым. В противном случае поле не будет равно NULL. Надеюсь, это поможет!

Ответ 3

Более компактная версия установки всех столбцов параметр с нулевым значением

Вместо case StructField( c, t, _, m) ⇒ StructField( c, t, nullable = nullable, m) можно использовать _.copy(nullable = nullable). Тогда вся функция может быть записана как:

def setNullableStateForAllColumns( df: DataFrame, nullable: Boolean) : DataFrame = {
  df.sqlContext.createDataFrame(df.rdd, StructType(df.schema.map(_.copy(nullable = nullable))))
}

Ответ 4

Другой вариант, если вам нужно изменить фрейм данных на месте, а воссоздание невозможно, вы можете сделать что-то вроде этого:

.withColumn("col_name", when(col("col_name").isNotNull, col("col_name")).otherwise(lit(null)))

Затем Spark подумает, что этот столбец может содержать null, а значение nullability будет равно true. Кроме того, вы можете использовать udf, чтобы обернуть ваши значения в Option. Прекрасно работает даже для потоковых случаев.

Ответ 5

Просто используйте java.lang.Integer вместо scala.Int в вашем классе case.

case class input(id:Long, var1:java.lang.Integer , var2:java.lang.Integer , var3:java.lang.Double)

Ответ 6

Спасибо Мартин Сенн. Просто небольшое дополнение. В случае внутренних структурных типов, вам может понадобиться установить nullable рекурсивно, например так:

def setNullableStateForAllColumns(df: DataFrame, nullable: Boolean): DataFrame = {
    def set(st: StructType): StructType = {
      StructType(st.map {
        case StructField(name, dataType, _, metadata) =>
          val newDataType = dataType match {
            case t: StructType => set(t)
            case _ => dataType
          }
          StructField(name, newDataType, nullable = nullable, metadata)
      })
    }

    df.sqlContext.createDataFrame(df.rdd, set(df.schema))
  }