Teniendo en cuenta la Tabla 1 con una columna «x» de tipo String.
Quiero crear la Tabla 2 con una columna en forma de «y» que es un número entero representación de las cadenas de fecha dada en «x».

Esencial es mantener null valores en la columna «y».

La tabla 1 (Dataframe df1):

+----------+
|         x|
+----------+
|2015-09-12|
|2015-09-13|
|      null|
|      null|
+----------+
root
 |-- x: string (nullable = true)

La tabla 2 (Dataframe df2):

+----------+--------+                                                                  
|         x|       y|
+----------+--------+
|      null|    null|
|      null|    null|
|2015-09-12|20150912|
|2015-09-13|20150913|
+----------+--------+
root
 |-- x: string (nullable = true)
 |-- y: integer (nullable = true)

Mientras que la función definida por el usuario (udf) para convertir los valores de la columna «x» en la columna «y» es:

val extractDateAsInt = udf[Int, String] (
  (d:String) => d.substring(0, 10)
      .filterNot( "-".toSet)
      .toInt )

y obras, el trato con los valores null no es posible.

Aunque, puedo hacer algo como

val extractDateAsIntWithNull = udf[Int, String] (
  (d:String) => 
    if (d != null) d.substring(0, 10).filterNot( "-".toSet).toInt 
    else 1 )

He encontrado ninguna manera, de «producir» null valores a través de udf (por supuesto, como Ints no puede ser null).

Mi solución actual para la creación de df2 (Tabla 2), es la siguiente:

//holds data of table 1  
val df1 = ... 

//filter entries from df1, that are not null
val dfNotNulls = df1.filter(df1("x")
  .isNotNull)
  .withColumn("y", extractDateAsInt(df1("x")))
  .withColumnRenamed("x", "right_x")

//create df2 via a left join on df1 and dfNotNull having 
val df2 = df1.join( dfNotNulls, df1("x") === dfNotNulls("right_x"), "leftouter" ).drop("right_x")

Preguntas:

  • La solución actual parece engorroso (y que probablemente no sea eficiente wrt. el rendimiento). Hay una manera mejor?
  • @Spark-desarrolladores: hay un tipo de NullableInt a los previstos y disponibles, tales que la siguiente udf es posible (véase el extracto de Código ) ?

Extracto de código

val extractDateAsNullableInt = udf[NullableInt, String] (
  (d:String) => 
    if (d != null) d.substring(0, 10).filterNot( "-".toSet).toInt 
    else null )
InformationsquelleAutor Martin Senne | 2015-09-02

3 Comentarios

  1. 51

    Aquí es donde Optionviene muy bien:

    val extractDateAsOptionInt = udf((d: String) => d match {
      case null => None
      case s => Some(s.substring(0, 10).filterNot("-".toSet).toInt)
    })

    o para hacer un poco más seguro en el caso general:

    import scala.util.Try
    
    val extractDateAsOptionInt = udf((d: String) => Try(
      d.substring(0, 10).filterNot("-".toSet).toInt
    ).toOption)

    Todo el crédito va a Dmitriy Selivanov que ha señalado esta solución como (falta?) editar aquí.

    Alternativa es manejar null fuera de la UDF:

    import org.apache.spark.sql.functions.{lit, when}
    import org.apache.spark.sql.types.IntegerType
    
    val extractDateAsInt = udf(
       (d: String) => d.substring(0, 10).filterNot("-".toSet).toInt
    )
    
    df.withColumn("y",
      when($"x".isNull, lit(null))
        .otherwise(extractDateAsInt($"x"))
        .cast(IntegerType)
    )
    • Hola zero323, suena genial. Va a intentar que fuera, y tan pronto como funciona, lo recompense! BTW, gracias por la rápida respuesta!!!
    • La opción de variante que funciona y es realmente bueno!!! Thx por señalar!
  2. 11

    Scala en realidad tiene una buena fábrica de función Opción(), que pueden hacer que sea aún más concisa:

    val extractDateAsOptionInt = udf((d: String) => 
      Option(d).map(_.substring(0, 10).filterNot("-".toSet).toInt))

    Internamente la Opción del objeto de aplicar el método es solo hacer la verificación null para usted:

    def apply[A](x: A): Option[A] = if (x == null) None else Some(x)
  3. 10

    Código suplementario

    Con el agradable respuesta de @zero323, he creado el siguiente código, para tener funciones definidas por el usuario disponibles que controlar los valores null como se describe. La esperanza, es útil para los demás!

    /**
     * Set of methods to construct [[org.apache.spark.sql.UserDefinedFunction]]s that
     * handle `null` values.
     */
    object NullableFunctions {
    
      import org.apache.spark.sql.functions._
      import scala.reflect.runtime.universe.{TypeTag}
      import org.apache.spark.sql.UserDefinedFunction
    
      /**
       * Given a function A1 => RT, create a [[org.apache.spark.sql.UserDefinedFunction]] such that
       *   * if fnc input is null, None is returned. This will create a null value in the output Spark column.
       *   * if A1 is non null, Some( f(input) will be returned, thus creating f(input) as value in the output column.
       * @param f function from A1 => RT
       * @tparam RT return type
       * @tparam A1 input parameter type
       * @return a [[org.apache.spark.sql.UserDefinedFunction]] with the behaviour describe above
       */
      def nullableUdf[RT: TypeTag, A1: TypeTag](f: Function1[A1, RT]): UserDefinedFunction = {
        udf[Option[RT],A1]( (i: A1) => i match {
          case null => None
          case s => Some(f(i))
        })
      }
    
      /**
       * Given a function A1, A2 => RT, create a [[org.apache.spark.sql.UserDefinedFunction]] such that
       *   * if on of the function input parameters is null, None is returned.
       *     This will create a null value in the output Spark column.
       *   * if both input parameters are non null, Some( f(input) will be returned, thus creating f(input1, input2)
       *     as value in the output column.
       * @param f function from A1 => RT
       * @tparam RT return type
       * @tparam A1 input parameter type
       * @tparam A2 input parameter type
       * @return a [[org.apache.spark.sql.UserDefinedFunction]] with the behaviour describe above
       */
      def nullableUdf[RT: TypeTag, A1: TypeTag, A2: TypeTag](f: Function2[A1, A2, RT]): UserDefinedFunction = {
        udf[Option[RT], A1, A2]( (i1: A1, i2: A2) =>  (i1, i2) match {
          case (null, _) => None
          case (_, null) => None
          case (s1, s2) => Some((f(s1,s2)))
        } )
      }
    }

Dejar respuesta

Please enter your comment!
Please enter your name here