我有一个数据框,其中有一列需要清洗。 我期待可以在Java / Scala中的Spark UDF中应用的正则表达式模式,该模式将从字符串中提取有效内容。
第userId
列的输入行示例,如下数据框所示:
[[105286112,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [115090439,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [29818926,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX]]
名为“ userId”的列的预期转换:
看起来像这样的字符串
105286112|115090439|29818926
我需要逻辑/方法来修改userId
列,以使UDF相同。可以使用正则表达式或其他方法来实现吗?
输入的DataFrame如下所示:
+--------------------+--------------------+
| dt_geo_cat_brand| userId |
+--------------------+--------------------+
|2017-10-30_17-18 ...|[[133207500,2017-...|
|2017-10-19_21-22 ...|[[194112773,2017-...|
|2017-10-29_17-18 ...|[[274188233,2017-...|
|2017-10-29_14-16 ...|[[86281353,2017-1...|
|2017-10-01_09-10 ...|[[92478766,2017-1...|
|2017-10-09_17-18 ...|[[156663365,2017-...|
|2017-10-06_17-18 ...|[[111869972,2017-...|
|2017-10-13_09-10 ...|[[64404465,2017-1...|
|2017-10-13_07-08 ...|[[146355663,2017-...|
|2017-10-22_21-22 ...|[[54096488,2017-1...|
+--------------------+--------------------+
架构:
root
|-- dt_geo_cat_brand: string (nullable = true)
|-- userId: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- _1: string (nullable = true)
| | |-- _2: string (nullable = true)
所需的输出:
+--------------------+--------------------+
| dt_geo_cat_brand| userId |
+--------------------+--------------------+
|2017-10-30_17-18 ...|133207500,1993333444|
|2017-10-19_21-22 ...|122122212,3432323333|
|2017-10-29_17-18 ...|274188233,8869696966|
|2017-10-29_14-16 ...|862813534,444344444,43444343434|
|2017-10-01_09-10 ...|92478766,880342342,4243244432,5554335535|
+--------------------+--------------------+
以此类推...
答案 0 :(得分:1)
使用以下正则表达式编写UDF。它将提取所需的内容。
import ss.implicits._
val df = ss.read.csv(path).as("")
df.show()
val reg = "\\[\\[(\\d*).*\\],\\s*\\[(\\d*).*\\],\\s*\\[(\\d*).*" // regex which can extract the required data
val input = "[[105286112,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [115090439,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [29818926,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX]]" // input string
val mat = reg.r.findAllIn(input) // extracting the data
println(mat)
while (mat.hasNext) {
mat.next()
println(mat.group(1) + "|" + mat.group(2)+ "|" + mat.group(3)) // each group will print the 3 extracted fields
}
输出:
105286112|115090439|29818926
使用UDF:
import ss.implicits._
val reg = "\\[\\[(\\d*).*\\],\\s*\\[(\\d*).*\\],\\s*\\[(\\d*).*"
def reg_func = { (s: String) =>
{
val mat = reg.r.findAllIn(s)
println(mat)
var out = ""
while (mat.hasNext) {
mat.next()
out = mat.group(1) + "|" + mat.group(2) + "|" + mat.group(3)
}
out
}
}
val reg_udf = udf(reg_func)
val df = ss.read.text(path)
.withColumn("Extracted_fields", reg_udf($"value"))
df.show(false)
输入:创建一些示例第二条记录
[[105286112,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [115090439,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [29818926,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX]]
[[105286113,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [115090440,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [29818927,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX]]
输出:
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+
|value |Extracted_fields |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+
|[[105286112,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [115090439,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [29818926,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX]]|105286112|115090439|29818926|
|[[105286113,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [115090440,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX], [29818927,2017-11-19_14-16 >> ABCDE >> GrocersRetail >> XXX]]|105286113|115090440|29818927|
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+
答案 1 :(得分:1)
您不需要正则表达式即可解决此问题。数据被格式化为结构数组,然后查看架构,您想要的是每个结构的_1
字符串。这可以通过UDF来解决,该UDF提取值,然后使用mkString("|")
将所有内容转换为字符串以获取预期的输出:
val extract_id = udf((arr: Seq[Row]) => {
arr.map(_.getAs[String](0)).mkString("|")
})
df.withColumn("userId", extract_id($"userId"))
根据评论1进行添加:
如果要将分区dt_geo_cat_brand
上的结果保存在csv文件中(所有值都在其自己的行中),则可以按以下方式进行操作。首先,从udf返回列表而不是字符串,然后使用explode
:
val extract_id = udf((arr: Seq[Row]) => {
arr.map(_.getAs[String](0))
})
val df2 = df.withColumn("userId", explode(extract_id($"userId")))
然后在保存时使用partitionBy(dt_geo_cat_brand)
。这将根据dt_geo_cat_brand
列中的值创建一个文件夹结构。根据分区的不同,每个文件夹中的csv文件的数量可以有所不同,但是它们都具有dt_geo_cat_brand
中单个值的值(如果需要单个文件并有足够的内存,请在保存前使用repartition(1)
)。
df2.write.partitionBy("dt_geo_cat_brand").csv(baseOutputBucketPath)
根据评论2的附加内容:
要在另存为单独文件时不使用partitionBy
,可以执行以下操作(建议使用partitioBy
方法)。首先,在dt_geo_cat_brand
中找到所有不同的值:
val vals = df.select("dt_geo_cat_brand").distinct().as[String].collect()
对于每个值,请过滤数据框并保存(在这里使用爆炸的df2
数据框作为加法#1):
vals.foreach { v =>
df2.filter($"dt_geo_cat_brand" === v)
.select("userId")
.write
.csv(s"$baseOutputBucketPath=$v/")})
}
或者,如果使用了udf,则不要使用爆炸的数据帧,而是在"|"
上分割:
vals.foreach { v =>
df.filter($"dt_geo_cat_brand" === v)
.select(split($"userId", "\\|").as("userId"))
.write
.csv(s"$baseOutputBucketPath=$v/")})
}