【问题标题】:Struct Types Flattening in ScalaScala 中的结构类型扁平化
【发布时间】:2019-03-30 21:07:44
【问题描述】:

我正在尝试从 Spark 数据框架中的结构类型创建一个列表。架构看起来像这样

root
|
|-- plotList: array (nullable = true)
|    |-- element: string (containsNull = true)
|-- plot: struct (nullable = true)
|    |-- test: struct (nullable = true)
|    |    |-- body: string (nullable = true)
|    |    |-- colorPair: struct (nullable = true)
|    |    |    |-- background: string (nullable = true)
|    |    |    |-- foreground: string (nullable = true)
|    |    |-- eta: struct (nullable = true)
|    |    |    |-- etaText: string (nullable = true)
|    |    |    |-- etaType: string (nullable = true)
|    |    |    |-- etaValue: string (nullable = true)
|    |    |-- headline: string (nullable = true)
|    |    |-- plotType: string (nullable = true)
|    |    |-- priority: long (nullable = true)
|    |    |-- plotCategory: string (nullable = true)
|    |    |-- productType: string (nullable = true)
|    |    |-- theme: string (nullable = true)
|    |-- temp: struct (nullable = true)
|    |    |-- body: string (nullable = true)
|    |    |-- colorPair: struct (nullable = true)
|    |    |    |-- background: string (nullable = true)
|    |    |    |-- foreground: string (nullable = true)
|    |    |-- eta: struct (nullable = true)
|    |    |    |-- etaText: string (nullable = true)
|    |    |    |-- etaType: string (nullable = true)
|    |    |    |-- etaValue: string (nullable = true)
|    |    |-- headline: string (nullable = true)
|    |    |-- logo: string (nullable = true)
|    |    |-- plotType: string (nullable = true)
|    |    |-- priority: long (nullable = true)
|    |    |-- plotCategory: string (nullable = true)
|    |    |-- plotType: string (nullable = true)
|    |    |-- theme: string (nullable = true)

我正在尝试编写一个 UDF,它可以将 plot 列转换为可以在下一次迭代中分解的元素列表。情节线上的东西 - > [test,temp] 我可以从 test 和 temp 中选择一些特定的列。非常感谢任何正确方向的指示。我尝试了 UDF 的多种变体,但它们似乎都不起作用。

编辑:

我想从绘图列的子列创建一个扁平结构。我正在考虑为此使用案例类。类似的东西

case class ColorPair(back:String, fore:String)
case class Eta(EtaText: String, EtaType: String, EtaValue: String)
case class Plot(body:String, colorPair: ColorPair, eta: Eta, headline: String, plotType: String, priority: String, plotCategory: String, plotType: String, theme: String)

所以,基本上在这结束时,我期待像List(Plot) 这样的东西,然后我可以在后续步骤中explode。因为 explode 不能直接在 Struct Types 上工作,所以我必须进行这个转换。在 python 世界中,我很容易将此列作为字典阅读,但在 Scala 中不存在类似的内容(据我所知)。

【问题讨论】:

  • 您能告诉我们您尝试了什么以及为什么不满意吗?这真的会帮助我们了解您想要做什么。
  • 据我所知,df.select($"plotList", array($"plot.test", $"plot.temp") as "plot") 似乎可以解决问题,但我不确定我是否了解您的需求。
  • @Oli 我试图概括您提到的选择操作。本质上,我试图从 test 和 temp 中选择某些子列(比如 eta 和 plotType),例如 [(eta,plotType),(eta,plotType)],在后期我将分解这个结构。我也尝试过选择操作,但这不是我想要的。此外,在实际数据集中,我有更多的元素,然后是临时和测试。所以,我基本上不能手动完成所有这些。
  • 我认为如果您可以 1. 将您的架构简化为问题的最小实例(所有这些字段对于我们解决您的问题可能不是必需的)2. 提供一个示例你想要达到的目标。
  • @Oli 所示示例是此问题所需的最小实例。嵌套结构类型和字符串类型的混合。我已经更新了这个问题,确切地说是希望在这里实现什么。非常感谢您对此进行调查。

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


【解决方案1】:

如果我理解正确,那么您正在寻找一种方法来遍历您的架构,当找到 colorPaireta 时,返回这些字段:

plot.test.colorPair
plot.test.eta
plot.temp.colorPair
plot.temp.eta

为了为您的案例生成数据(模式),我编写了以下代码:

  case class Eta(etaText: String, etaType: String, etaValue: String)
  case class ColorPair(background: String, foreground: String)
  case class Test(body: String, colorPair: ColorPair, eta: Eta, headline: String, plotType: String, priority: Long, plotCategory: String, productType: String, theme: String)
  case class Temp(body: String, colorPair: ColorPair, eta: Eta ,headline: String, logo: String, plotType: String, priority: Long, plotCategory: String, productType: String, theme: String)
  case class Plot(test: Test, temp: Temp)
  case class Root(plotList: Array[String], plot: Plot)

  def getSchema(): StructType ={
    import org.apache.spark.sql.types._
    import org.apache.spark.sql.catalyst.ScalaReflection
    val schema = ScalaReflection.schemaFor[Root].dataType.asInstanceOf[StructType]

    schema.printTreeString()
    schema
  }

这将有输出:

root
 |-- plotList: array (nullable = true)
 |    |-- element: string (containsNull = true)
 |-- plot: struct (nullable = true)
 |    |-- test: struct (nullable = true)
 |    |    |-- body: string (nullable = true)
 |    |    |-- colorPair: struct (nullable = true)
 |    |    |    |-- background: string (nullable = true)
 |    |    |    |-- foreground: string (nullable = true)
 |    |    |-- eta: struct (nullable = true)
 |    |    |    |-- etaText: string (nullable = true)
 |    |    |    |-- etaType: string (nullable = true)
 |    |    |    |-- etaValue: string (nullable = true)
 |    |    |-- headline: string (nullable = true)
 |    |    |-- plotType: string (nullable = true)
 |    |    |-- priority: long (nullable = false)
 |    |    |-- plotCategory: string (nullable = true)
 |    |    |-- productType: string (nullable = true)
 |    |    |-- theme: string (nullable = true)
 |    |-- temp: struct (nullable = true)
 |    |    |-- body: string (nullable = true)
 |    |    |-- colorPair: struct (nullable = true)
 |    |    |    |-- background: string (nullable = true)
 |    |    |    |-- foreground: string (nullable = true)
 |    |    |-- eta: struct (nullable = true)
 |    |    |    |-- etaText: string (nullable = true)
 |    |    |    |-- etaType: string (nullable = true)
 |    |    |    |-- etaValue: string (nullable = true)
 |    |    |-- headline: string (nullable = true)
 |    |    |-- logo: string (nullable = true)
 |    |    |-- plotType: string (nullable = true)
 |    |    |-- priority: long (nullable = false)
 |    |    |-- plotCategory: string (nullable = true)
 |    |    |-- productType: string (nullable = true)
 |    |    |-- theme: string (nullable = true)

最后,下一个代码应该展平所需的字段:

def flattenSchema(schema: StructType, targetFields: List[String], prefix: String = null): Array[String]=
  {
    import org.apache.spark.sql.types._
    schema.fields.flatMap(f => {
      val colName = if (prefix == null) f.name else (prefix + "." + f.name)

      f.dataType match {
        case st : StructType =>
          val found = st.filter(s => targetFields.contains(s.name))

          if(found.isEmpty) {
            flattenSchema(st, targetFields, colName)
          }
          else
            found.flatMap(sf => {
              val st = sf.dataType.asInstanceOf[StructType]
              st.map(st => s"${colName}.${sf.name}.${st.name}")
            })

        case _ => Array[String]()
      }
    })
  }

上面的代码扫描架构以查找存在于targetFields 列表中的字段,然后使用flatMap 检索这些字段的架构。

这应该是输出:

plot.test.colorPair.background
plot.test.colorPair.foreground
plot.test.eta.etaText
plot.test.eta.etaType
plot.test.eta.etaValue
plot.temp.colorPair.background
plot.temp.colorPair.foreground
plot.temp.eta.etaText
plot.temp.eta.etaType
plot.temp.eta.etaValue

【讨论】:

  • 感谢@Alexandros 的输入。我想在下一阶段使用它来选择列,所以我更新了代码以返回Array[String](f.name) ,以防类型不是结构。我试过这个,但由于某种原因,代码直到plot.test.eta.etaText 或任何第 4 级的东西都没有。我错过了什么吗?
  • 嗨@prateek 您必须在选择语句中使用结果,即 df.select("data.plot.test.eta.etaText")。在您的情况下,您有 json 数据?
  • 是的,我搞定了。尽管嵌套 stuct 的爆炸按预期工作,但这并不是我想要的。我可能会提出另一个与此相关的问题。接受您的答案,因为它在许多其他用例中非常有用。非常感谢!
  • ok 完美,所以这将通过任何模式并提取 colorPar 和 eta 的路径
  • 另外,可以在docs.databricks.com/spark/latest/dataframes-datasets/…找到更简洁的 DataFrameFlatener 实现
猜你喜欢
  • 1970-01-01
  • 2011-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-17
  • 2017-04-22
  • 2022-09-26
  • 2021-11-19
相关资源
最近更新 更多