如何将Scala数据框的每一行映射到新架构

时间:2020-01-25 07:50:34

标签: scala dataframe apache-spark databricks

我正在使用scala在spark中处理不同类型和不同模式的流事件,我需要解析它们,并将它们保存为易于以常规方式进一步处理的格式。

我有一个如下所示的事件数据框:

val df = Seq(("{\"a\": 1, \"b\": 2, \"c\": 3 }", "One", "001") ,("{\"a\": 6, \"b\": 2, \"d\": 2, \"f\": 8 }", "Two", "089"), ("{\"a\": 3, \"b\": 4, \"c\": 6 }", "One", "123")).toDF("col1", "col2", "col3")

这是

+------------------------------------+--------+------+
|   body                             |   type |   id |
+------------------------------------+--------+------+
|{"a": 1, "b": 2, "c": 3 }           |   "One"|   001|
|{"a": 6, "d": 2, "f": 8, "g": 10}   |   "Two"|   089|
|{"a": 3, "b": 4, "c": 6 }           | "Three"|   123|
+------------------------------------+--------+------+

,我想把它变成这个。我们可以假设所有类型“ One”将具有相同的架构,并且所有类型的事件都将共享一些相似的数据,例如条目“ a”,我希望将其显示在其自己的列中

+---+--------------------------------+--------+------+
| a |  data                          |   y    |   z  |
+---+--------------------------------+--------+------+
| 1 |{"b": 2, "c": 3 }               |   "One"|   001|
| 6 |{"d": 2, "f": 8, "g": 10}       |   "Two"|   089|
| 3 |{"b": 4, "c": 6 }               | "Three"|   123|
+------------------------------------+--------+------+

2 个答案:

答案 0 :(得分:2)

一种实现方法是将json数据作为Map处理,如下所示:

import org.apache.spark.sql.types.{MapType, StringType, IntegerType}
import org.apache.spark.sql.functions.{from_json, expr}

val df = Seq(
  ("{\"a\": 1, \"b\": 2, \"c\": 3 }", "One", "001") ,
  ("{\"a\": 6, \"b\": 2, \"d\": 2, \"f\": 8 }", "Two", "089"), 
  ("{\"a\": 3, \"b\": 4, \"c\": 6 }", "One", "123")
).toDF("body", "type", "id")

val mapSchema = MapType(StringType, IntegerType)

df.withColumn("map", from_json($"body", mapSchema))
  .withColumn("data_keys", expr("filter(map_keys(map), k -> k != 'a')"))
  .withColumn("data_values", expr("transform(data_keys, k -> element_at(map,k))"))
  .withColumn("data", expr("to_json(map_from_arrays(data_keys, data_values))"))
  .withColumn("a", $"map".getItem("a"))
  .select($"a", $"data", $"type".as("y"), $"id".as("z"))
  .show(false)

// +---+-------------------+---+---+
// |a  |data               |y  |z  |
// +---+-------------------+---+---+
// |1  |{"b":2,"c":3}      |One|001|
// |6  |{"b":2,"d":2,"f":8}|Two|089|
// |3  |{"b":4,"c":6}      |One|123|
// +---+-------------------+---+---+

分析

  1. withColumn("map", from_json($"body", mapSchema)):首先根据给定的json数据生成一个Map。
  2. withColumn("data_keys", expr("filter(map_keys(map), k -> k != 'a')")):通过滤除不等于a的键来检索新地图的键。我们在此处使用filter函数,该函数返回一个数组,即{"a": 1, "b": 2, "c": 3 } -> [b, c]
  3. withColumn("data_values", expr("transform(data_keys, k -> element_at(map,k))")):结合使用先前的键和transform来填充新地图的值。
  4. withColumn("data", expr("to_json(map_from_arrays(data_keys, data_values))")):使用map_from_arraysdata_keysdata_values生成地图。最后,调用to_json将地图转换回json。

答案 1 :(得分:0)

首先,您需要按以下方式定义json模式:

val schema = spark.read.json(df.select("col1").as[String]).schema

然后,您可以将列col1转换为json(第一行),然后只需选择要提取的json元素(第二行)即可:

df.select(from_json($"col1", schema).as("data"), $"col2", $"col3")
.select($"data.a", $"data", $"col2", $"col3")

输出:

+---+-------------+----+----+
|  a|         data|col2|col3|
+---+-------------+----+----+
|  1|  [1, 2, 3,,]| One| 001|
|  6|[6, 2,, 2, 8]| Two| 089|
|  3|  [3, 4, 6,,]| One| 123|
+---+-------------+----+----+

我知道它与您想要的不完全相同,但是它将为您提供线索。

其他选项,如果您想完全解构json,则可以使用数据。*

    df.select(from_json($"col1", schema).as("data"), $"col2", $"col3").select($"data.*", $"col2", $"col3")

+---+---+----+----+----+----+----+
|  a|  b|   c|   d|   f|col2|col3|
+---+---+----+----+----+----+----+
|  1|  2|   3|null|null| One| 001|
|  6|  2|null|   2|   8| Two| 089|
|  3|  4|   6|null|null| One| 123|
+---+---+----+----+----+----+----+