如何将所有列均为字符串的DataFrame转换为具有特定架构的DataFrame

时间:2018-09-04 19:14:38

标签: csv apache-spark apache-spark-sql

想象以下输入:

val data = Seq (("1::Alice"), ("2::Bob"))
val dfInput = data.toDF("input")
val dfTwoColTypeString = dfInput.map(row => row.getString(0).split("::")).map{ case Array(id, name) => (id, name) }.toDF("id", "name")

现在,我有一个带有所需列的DataFrame:

scala> dfTwoColTypeString.show
+---+-----+
| id| name|
+---+-----+
|  1|Alice|
|  2|  Bob|
+---+-----+

我当然希望具有int类型的列ID,但它的类型为String:

scala> dfTwoColTypeString.printSchema
root
 |-- id: string (nullable = true)
 |-- name: string (nullable = true)

因此,我定义了以下架构:

val mySchema = StructType(Array(
    StructField("id", IntegerType, true),
    StructField("name", StringType, true)
    ))

将DataFrame dfTwoColTypeString转换或转换为给定目标架构的最佳方法是什么。

奖金:如果不能将给定的输入强制转换或转换为目标模式,我希望获得一个空行,并带有一个额外的列“ bad_record”,其中包含错误的输入数据。也就是说,我想完成与PERMISSIVE模式下的CSV解析器相同的操作。

任何帮助都很感激。

4 个答案:

答案 0 :(得分:1)

如果在读取数据时需要转换,则可以使用以下代码:

val resultDF = mySchema.fields.foldLeft(dfTwoColTypeString)((df, c) => df.withColumn(c.name, col(c.name).cast(c.dataType)))
resultDF.printSchema()

输出:

root
 |-- id: integer (nullable = true)
 |-- name: string (nullable = true)

要检查值的匹配类型,可以使用以下代码:

  val dfTwoColTypeString = dfInput.map(
  row =>
    row.getString(0).split("::"))
  .map {
        case Array(id, name) =>
          if (ConvertUtils.canBeCasted((id, name), mySchema))
            (id, name, null)
          else (null, null, id + "::" + name)}
  .toDF("id", "name", "malformed")

可以在自定义类(此处为ConvertUtils)中创建两个新的静态函数:

def canBeCasted(values: Product, mySchema: StructType): Boolean = {
    mySchema.fields.zipWithIndex.forall(v => canBeCasted(values.productElement(v._2).asInstanceOf[String], v._1.dataType))
  }

import scala.util.control.Exception.allCatch

def canBeCasted(value: String, dtype: DataType): Boolean = dtype match {
    case StringType => true
    case IntegerType => (allCatch opt value.toInt).isDefined
    // TODO add other types here
    case _ => false
  }

输出错误的“ cc :: Bob”值:

+----+-----+---------+
|id  |name |malformed|
+----+-----+---------+
|1   |Alice|null     |
|null|null |cc::Bob  |
+----+-----+---------+

答案 1 :(得分:0)

val cols = Array(col("id").cast(IntegerType),col("name"))
dfTwoColTypeString.select(cols:_*).printSchema

根  |-id:整数(nullable = true)  |-名称:字符串(nullable = true)

//另一种方法

import org.apache.spark.sql.types.{StringType,IntegerType,StructType,StructField}
val mySchema = StructType(Array(StructField("id", IntegerType, true),StructField("name", StringType, true)))
val df = spark.createDataFrame(dfTwoColTypeString.rdd,mySchema)
df.printSchema

根  |-id:整数(nullable = true)  |-名称:字符串(nullable = true)

答案 2 :(得分:0)

如果需要CSV读取并且知道模式,则可以在读取过程中进行分配:

spark.read.schema(mySchema).csv("filename.csv")

答案 3 :(得分:0)

考虑将dfTwoColTypeString用作数据框,您还可以如下转换其模式类型。

dfTwoColTypeString.withColumn("id", col("id").cast("Int"))