Csv 如何将所有列都是字符串的数据帧转换为具有特定架构的数据帧

Csv 如何将所有列都是字符串的数据帧转换为具有特定架构的数据帧,csv,apache-spark,apache-spark-sql,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,其中的列如所

想象一下以下输入:

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|
+---+-----+
当然,我希望列id的类型为int,但它的类型为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_记录,其中包含错误的输入数据。也就是说,我希望实现与许可模式下的CSV解析器相同的功能

非常感谢您的帮助

val cols = Array(col("id").cast(IntegerType),col("name"))
dfTwoColTypeString.select(cols:_*).printSchema
根 |-id:integer nullable=true |-名称:string 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:integer nullable=true
|-名称:string nullable=true

如果需要CSV读取,并且架构已知,则可以在读取期间分配:

spark.read.schema(mySchema).csv("filename.csv")
考虑到dfTwoColTypeString是一个数据帧,您还可以如下转换其模式类型

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

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

val resultDF = mySchema.fields.foldLeft(dfTwoColTypeString)((df, c) => df.withColumn(c.name, col(c.name).cast(c.dataType)))
resultDF.printSchema()
  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")
输出:

root
 |-- id: integer (nullable = true)
 |-- name: string (nullable = true)
为了检查值是否与类型匹配,可以使用以下代码:

val resultDF = mySchema.fields.foldLeft(dfTwoColTypeString)((df, c) => df.withColumn(c.name, col(c.name).cast(c.dataType)))
resultDF.printSchema()
  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  |
+----+-----+---------+

这正是问题所在:它不是逗号分隔的,而是双冒号分隔的。。。因此,我需要自己分割输入,不能再使用csv阅读器。所以这个答案没有帮助。可以更改读取分隔符,更多信息:当您有多个不同的分隔符和正则表达式将一行解析为分隔值时?这是csv阅读器无法做到的。我需要一个CSV阅读器,可以接收数据集[列表[字符串]。。。i、 这些值已经以列表的形式分开,现在我只想像csv阅读器在下一步中所做的那样,根据目标模式来转换这些值。这就是我想要的功能。据我所知,csv阅读器只接受一个字符作为分隔符,而不是像“::”这样的字符串……这不是我的意思。您必须将id从字符串强制转换为整数,就像手动操作一样。但是我想要的是,这个cast由给定的目标模式生成。例如,当您读取CSV文件时,所有列当然首先被读取为字符串,然后根据CSV文件的模式自动转换。也就是说,我不能写任何代码来转换列。第二种方法是错误的。当您从RDD创建数据帧时,spark假设给定的模式适合给定的RDD,但不强制转换或检查是否所有关于该模式的行都有效。当您执行df.showfalse时,您可以看到您的解决方案是错误的。直到现在,所有的行都被处理了,您将看到一条错误消息,该消息说,列id不是schema@Hiro.Protagonist我将再次交叉检查第二种方法。第一个呢。它适合你的口味吗requirement@Hiro.Protagonist这就是我们显式地将字符串转换到的第一种方法所做的int@Chandan.Ray第一种方法是手动执行。它不使用目标架构。我不想手工编写转换代码,因为想要的转换显然是由目标模式给出的。例如,csv转换器只需要目标模式即可进行转换。我不需要手动转换。我要找的是一种和CSV阅读器一样智能的东西。也就是说,与解析csv文件或Dataset[String]不同,我有一个Dataset[List[String]],与csv解析器一样,我希望有一个函数,可以将List[String]转换为与目标架构给定类型对应的列表,而无需手动强制转换每一列。我要求的任务不同。我不想直接用手投。我想要一个方法,它能够使用给定的目标模式来强制转换列。这就是csv阅读器在给定模式时所做的。这个解决方案非常好,非常接近我所需要的。当字符串无法转换为目标模式类型时,我如何处理这种情况。例如,如果id列包含无法转换为整数的badid。扩展架构以包含列损坏的\ U记录是没有问题的,如果无法强制转换值,则应将整行放入损坏的列中。例如,与许可模式下的CSV解析器一样,该解析器有一列记录损坏。dfTwoColTypeString可以在转换之前进行筛选,guess,需要额外的筛选脚本。或者在dfInput期间,可以根据架构检查map值,如果值不正确,则可以将所有输入字符串放入错误记录的额外列中。对于这样的行,所有常规列都可以设置为null。听起来不错。。。这将是一个完美的解决方案。。。因为我是一个火花初学者。。。您能提供一个代码示例来说明如何做到这一点吗?