【问题标题】:Scala UDF -- Combine column values in a specific formattingScala UDF——以特定格式组合列值
【发布时间】:2018-09-05 15:24:32
【问题描述】:

DF1 是我现在拥有的,我想让 DF1 看起来像 DF2。

期望的输出:

 DF1                                                           DF2
+---------+----------------------------------------+          +---------+-------------------------------------------------------------------+
|   ID    |         Category                       |          |   ID    |                  category_name                                    |
+---------+----------------------------------------+          +---------+-------------------------------------------------------------------+  
|  31898  |   CP Bill Payment                      |          |  31898  |  CP Bill Payment + CP e-Transfer + CP IMT (CPS Limit + CPS Payee) |  
|  31898  |   CP e-Transfer + CP IMT               |          |  32614  |  CP Bill Payment + CP e-Transfer + CP Other Transfer (CPS Blocked)|
|  31898  |   CPS Limit + CPS Payee                |          |  35431  |  CP Bill Payment + CP e-Transfer                                  |
|  32614  |   CP e-Transfer + CP Other Transfer    |          |  33987  |  CP IMT (CPS Limit)                                               |
|  32614  |   CP Bill Payment                      |  =====>  |  35672  |  CPS Blocked                                                      |
|  32614  |   CPS Blocked                          |  =====>  |  37612  |  CPS Blocked + CPS Stop/Cancel/Reverse                            |
|  35431  |   CP e-Transfer                        |          +---------+-------------------------------------------------------------------+
|  35431  |   CP Bill Payment                      |
|  33987  |   CP IMT                               |
|  33987  |   CPS Limit                            |
|  35672  |   CPS Blocked                          |
|  37612  |   CPS Blocked + CPS Stop/Cancel/Reverse|
+---------+----------------------------------------+

我有以下代码:

val DF2 = DF1.groupBy("ID").agg(collect_set("Category").as("CategorySet"))
.groupBy("ID")
.agg(collect_set("Category").as("CategorySet"))
.withColumn( "category_name",
  when(array_contains($"CategorySet", "CP Bill Payment") && array_contains($"CategorySet", "CP e-Transfer + CP IMT") && array_contains($"CategorySet", "CPS Limit + CPS Payee"), "CP Bill Payment + CP e-Transfer + CP IMT (CPS Limit + CPS Payee)").otherwise("---other conditions---"))
.select("ID","category_name")

逻辑是对于同一个ID,31898/32614/33987:如果包含CP*和CPS*,应该是CP*(CPS*)或者CP*+CP*(CPS*); 35431:如果数组中没有 CPS*,只需使用 + 连接数组中的所有元素; 35672/37612:否则,只是数组中的元素。顺便说一句,Category 应该按升序排序。

代码有效,只是可能的组合太多了。如何使用 UDF 做同样的事情?或者是否有任何内置函数可以做到这一点?提前谢谢你

【问题讨论】:

  • 您可以创建多个 UDAF 来包含您需要的逻辑。
  • 你能给我举个例子吗?对scala udf不太熟悉
  • 你的ID是String类型吗?
  • 十进制 (28,0)

标签: scala apache-spark dataframe user-defined-functions


【解决方案1】:

我现在能想到的:

//UDF
def mapColumn(col: String) = udf { (xs: Seq[String]) => 
                        xs.map { x => 
                          if (x.contains(col+" ")) x else null
                        }.filter(_ != null).mkString(" + ")
                     }

import org.apache.spark.sql.functions._
val df1 = df.groupBy("Id").agg(
                               mapColumn("CP")(sort_array(collect_set("Category"))).as("CategorySetCP"),
                               mapColumn("CPS")(sort_array(collect_set("Category"))).as("CategorySetCPS")
                               ).withColumn("CategorySetCPS_New",concat(lit(" ("),'CategorySetCPS,lit(")")))
                               .withColumn("category_name",
                                           when(length($"CategorySetCP") > 0 and length($"CategorySetCPS") > 0,concat($"CategorySetCP",$"CategorySetCPS_New")).
                                           otherwise(when(length($"CategorySetCP") >0 and length($"CategorySetCPS") === 0,$"CategorySetCP").
                                           otherwise($"CategorySetCPS"))
                                           )
           .select('Id,'category_name)

df1.show(false)

输出:

+-----+-----------------------------------------------------------------+
|Id   |category_name                                                    |
+-----+-----------------------------------------------------------------+
|33987|CP IMT (CPS Limit)                                               |
|32614|CP Bill Payment + CP e-Transfer + CP Other Transfer (CPS Blocked)|
|35672|CPS Blocked                                                      |
|35431|CP Bill Payment + CP e-Transfer                                  |
|31898|CP Bill Payment + CP e-Transfer + CP IMT (CPS Limit + CPS Payee) |
|35612|CPS Blocked + CPS Stop/Cancel/Reverse                            |
+-----+-----------------------------------------------------------------+       

希望这会有所帮助!

【讨论】:

    【解决方案2】:

    这是一个如何使用 UDAF 的示例。显然,您不需要 UDAF 来通过 id 连接列值,但它允许添加更多逻辑。例如,要通过 ID 字段连接值,您可以创建如下 UDAF:

    class ConcatenateStrings extends UserDefinedAggregateFunction {
      override def inputSchema: StructType = StructType(StructField("input", StringType) :: Nil)
    
      override def bufferSchema: StructType = StructType(StructField("pair", StringType) :: Nil)
    
      override def dataType: DataType = StringType
    
      override def deterministic: Boolean = true
    
      override def initialize(buffer: MutableAggregationBuffer): Unit = buffer(0) = ""
    
      override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
          val b = buffer.getAs[String](0)
          val i = input.getAs[String](0)
          buffer(0) = { if(b.isEmpty) b + i else b + " + " + i }
      }
    
      override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
        val b1 = buffer1.getAs[String](0)
        val b2 = buffer2.getAs[String](0)
        if(!b1.isEmpty)
          buffer1(0) = (b1) ++ " + " ++ (b2)
        else
          buffer1(0) = b2
      }
    
      override def evaluate(buffer: Row): Any = {
        val yourString = buffer.getAs[String](0)
        // Compute your logic and return another String
        yourString + "@procesed"
      }
    }
    

    然后你可以在你的聚合调用中包含:

    object testAppl0 {
    
      def main(args: Array[String]) : Unit = {
    
        val agg0 = new ConcatenateStrings()
    
        implicit val spark: SparkSession =
          SparkSession
            .builder()
            .appName("Test")
            .master("local[1]")
            .getOrCreate()
    
        import spark.implicits._
    
        val rows = Seq(Row(31898,"CP Bill Payment"), Row(31898,"CP e-Transfer + CP IMT"), Row(31898,"CPS Limit + CPS Payee "))
    
        val schema = List(
          StructField("ID", IntegerType, true),
          StructField("Category", StringType, true))
    
        val df =  spark.createDataFrame(
          spark.sparkContext.parallelize(rows),
          StructType(schema)
        )
    
        df.groupBy("ID").agg(agg0($"Category")).show(false)
    
      }
    }
    

    它将返回一个新列“concatenatestrings(Category)”:

    +-----+--------------------------------------------------------------------------+
    |ID   |concatenatestrings(Category)                                              |
    +-----+--------------------------------------------------------------------------+
    |31898|CP Bill Payment + CP e-Transfer + CP IMT + CPS Limit + CPS Payee @procesed|
    +-----+--------------------------------------------------------------------------+
    

    检查一下,也许有帮助

    【讨论】:

    • 谢谢!但逻辑是将任何具有 CPS 的字符串放在括号中;所以前面有CP的字符串是主类,前面有CPS的字符串是子类。在您的示例中,应该是这样的:CP Bill Payment + CP e-Transfer + CP IMT (CPS Limit + CPS Payee)。 @EmiCareOfCell44
    • 您能给我一个可以在我的问题中使用 DataFrame 的示例吗?那真的很有帮助!非常感谢@EmiCareOfCell44
    • 所以也许UDAF不是必须的,只需要一个函数来查找字符串有CPS是前面在字符串前面有CP后放在括号()中,连接字符串有CP在前面有 + 号 @EmiCareOfCell44
    • 也许,这取决于你的逻辑。使用 UDAF,您可以创建更复杂的逻辑,但如果它很简单,它们就没有必要了。
    • 如何使用函数来做到这一点?你能给我一个可以在我的问题中使用 DataFrame 的例子吗? @EmiCareOfCell44
    猜你喜欢
    • 2020-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-16
    • 1970-01-01
    • 2020-01-13
    • 1970-01-01
    相关资源
    最近更新 更多