我正在尝试火花数据帧。使用来自Cascading Framework的先前知识,其具有陷阱机制以将错误的行(具有空值的行)过滤到称为陷阱的单独Tap中。那些不知道的人让我说清楚。当您获得从文本文件中读取的错误行时。框架要么从整个数据中清除坏行,要么停止执行。现在在apache spark中,我观察到坏行并没有妨碍执行。这很好,但是当从数据中获取业务见解时,数据的质量很重要!
所以,我有一个包含大量行的文本文件(你可以选择任何数据集),其中很少有记录包含空值。现在我使用 spark.read.csv 将文本文件加载到Dataframe中。现在,我想要做的是分析Dataframe并动态创建一个名为“isMyRowBad”的列,其中逻辑将一次分析每一行,如果逻辑找出具有空值的行,它将该特定行上的isMyRowBad列标记为 true ,并且没有空值的列,相应的列isMyRowBad应该对于那个干净的特定行应该为false。
为您提供传入和传出数据集的概述
收到数据框
fname,lname,age
will,smith,40
Dwayne,Nunn,36
Aniruddha,Sinha,
Maria,,22
OUTGOING DATAFRAME
fname,lname,age,isMyRowBad
will,smith,40,false
Dwayne,Nunn,36,false
Aniruddha,Sinha,,true
Maria,,22,true
上面对好行和坏行进行分类的方法可能看起来有点愚蠢,但它确实有意义,因为我不需要多次运行过滤器操作。让我们来看看,怎么样?
假设我有一个名为inDf的Dataframe作为inputDf和AnalysedDf:(DataFrame,DataFrame)作为输出Df Tuple
现在,我确实尝试了这部分代码
val analyzedDf: (DataFrame, DataFrame) = (inputDf.filter(_.anyNull),inputDf.filter(!_.anyNull))
此代码隔离好行和坏行。我同意!但是这会导致性能下降,因为过滤器会运行两次,这意味着过滤器将在整个数据集中迭代两次!(如果您认为在考虑50个字段和至少584000行(即至少584000行)时运行过滤器两次确实有意义,可以反驳这一点是250 MB的数据)!)
这也是
val analyzedDf: DataFrame = inputDf.select("*").withColumn("isMyRowBad", <this point, I am not able to analyze row>
上面的代码段显示了我无法弄清楚如何扫描整行并使用布尔值将行标记为错误。
希望,你们都明白我的目标是什么。如果您在片段中找到,因为我立即在此处输入了语法错误(将在未来的编辑中更正),请忽略语法错误。
请给我一个关于如何继续挑战的提示(一些代码片段或伪代码就足够了)。如果你不明白我打算做什么,请联系我。
任何帮助将不胜感激。提前谢谢!
P.S:BigData / spark / hadoop / scala等有很多才华横溢的人请求你在任何可能写错的地方(概念上)纠正我<小时/> 下面的代码给我一个解决方案。请看看
package aniruddha.data.quality
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.sql.types.{IntegerType, StringType, StructField, StructType}
import org.apache.spark.sql.functions._
/**
* Created by aniruddha on 8/4/17.
*/
object DataQualityCheck extends App {
val spark = SparkSession.builder().master("local[*]").getOrCreate()
import spark.implicits._
val schema: StructType = StructType(List(
StructField("fname", StringType, nullable = true),
StructField("lname", StringType, nullable = true),
StructField("age", IntegerType, nullable = true),
StructField("pan", StringType, nullable = true),
StructField("married", StringType, nullable = true)
))
val inputDataFrame: DataFrame = spark
.read
.schema(schema)
.option("header",true)
.option("delimiter",",")
.csv("inputData/infile")
//inputDataFrame.show()
val analysedDataFrame: DataFrame = inputDataFrame.select("*").withColumn("isRowBad", when($"pan".isNull||$"lname".isNull||$"married".isNull,true).otherwise(false))
analysedDataFrame show
}
输入
fname,lname,age,pan,married
aniruddha,sinha,23,0AA22,no
balajee,venkatesh,23,0b96,no
warren,shannon,72,,
wes,borland,63,0b22,yes
Rohan,,32,0a96,no
james,bond,66,007,no
输出
+---------+---------+---+-----+-------+--------+
| fname| lname|age| pan|married|isRowBad|
+---------+---------+---+-----+-------+--------+
|aniruddha| sinha| 23|0AA22| no| false|
| balajee|venkatesh| 23| 0b96| no| false|
| warren| shannon| 72| null| null| true|
| wes| borland| 63| 0b22| yes| false|
| Rohan| null| 32| 0a96| no| true|
| james| bond| 66| 007| no| false|
+---------+---------+---+-----+-------+--------+
代码工作正常,但我的when函数有问题。我们不能只选择所有列而不进行硬编码吗?
答案 0 :(得分:2)
据我所知,你不能用内置的csv解析器做到这一点。如果解析器遇到错误(failFast模式)而不是注释,则可以使解析器停止。
但是,您可以使用自定义csv解析器执行此操作,该解析器可以一次性处理数据。除非我们想做一些聪明的类型内省,否则最简单的方法是创建一个帮助类来注释文件的结构:
case class CSVColumnDef(colPos: Int, colName: String, colType: String)
val columns = List(CSVColumnDef(0,"fname","String"),CSVColumnDef(1,"lname","String"),CSVColumnDef(2,"age", "Int"))
接下来,我们需要一些函数来a)拆分输入,b)从拆分数据中提取数据,c)检查行是否坏:
import scala.util.Try
def splitToSeq(delimiter: String) = udf[Seq[String],String](_.split(delimiter))
def extractColumnStr(i: Int) = udf[Option[String],Seq[String]](s => Try(Some(s(i))).getOrElse(None))
def extractColumnInt(i: Int) = udf[Option[Int],Seq[String]](s => Try(Some(s(i).toInt)).getOrElse(None))
def isRowBad(delimiter: String) = udf[Boolean,String](s => {
(s.split(delimiter).length != columns.length) || (s.split(delimiter).exists(_.length==0))
})
要使用这些,我们首先需要阅读文本文件(因为我没有它,并且允许人们复制这个答案,我将创建一个rdd):
val input = sc.parallelize(List(("will,smith,40"),("Dwayne,Nunn,36"),("Aniruddha,Sinha,"),("Maria,,22")))
input.take(5).foreach(println)
鉴于此输入,我们可以创建一个包含单个列的数据框,即原始行,并将我们的拆分列添加到其中:
val delimiter = ","
val raw = "raw"
val delimited = "delimited"
val compDF = input.toDF(raw).withColumn(delimited, splitToSeq(delimiter)(col(raw)))
最后,我们可以提取我们之前定义的所有列,并检查行是否错误:
val df = columns.foldLeft(compDF){case (acc,column) => column.colType match {
case "Int" => acc.withColumn(column.colName, extractColumnInt(column.colPos)(col(delimited)))
case _ => acc.withColumn(column.colName, extractColumnStr(column.colPos)(col(delimited)))
}}.
withColumn("isMyRowBad", isRowBad(delimiter)(col(raw))).
drop(raw).drop(delimited)
df.show
df.printSchema
这个解决方案的优点在于,spark执行计划程序非常智能,可以将所有这些.withColumn
操作构建到数据上的单个传递(map
)中,而不会进行零混乱。令人讨厌的是,它比使用漂亮的闪亮csv库更加开发工作,我们需要以某种方式定义列。如果你想要更聪明一点,你可以从文件的第一行获取列名(提示:.mapPartitionsWithIndex
),并将所有内容解析为字符串。我们也无法定义一个案例类来描述整个DF,因为你有太多的列来以这种方式接近解决方案。希望这会有所帮助...
答案 1 :(得分:2)
这可以使用udf来完成。虽然Ben Horsburgh给出的答案肯定很棒,但我们可以做到这一点,而不必深入了解Dataframes背后的内部架构。
以下代码可以给你一个想法
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types.{StringType, StructField, StructType}
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
/**
* Created by vaijnath on 10/4/17.
*/
object DataQualityCheck extends App {
val spark = SparkSession.builder().master("local[*]").getOrCreate()
import spark.implicits._
val schema: StructType = StructType(List(
StructField("fname", StringType, nullable = true),
StructField("lname", StringType, nullable = true),
StructField("married", StringType, nullable = true)
))
val inputDataFrame: DataFrame = spark
.read
.schema(schema)
.option("header",false)
.option("delimiter",",")
.csv("hydrograph.engine.spark/testData/inputFiles/delimitedInputFile.txt")
//inputDataFrame.show()
def isBad(row:Row):Boolean={
row.anyNull
}
val simplefun=udf(isBad(_:Row))
val cols=struct(inputDataFrame.schema.fieldNames.map(e=> col(e)):_*)
// println(cols+"******************") //for debugging
val analysedDataFrame: DataFrame = inputDataFrame.withColumn("isRowBad", simplefun(cols))
analysedDataFrame.show
}
如果您遇到任何问题,请回复我。我相信这个解决方案可能是合适的,因为您似乎在寻找使用数据帧的代码。
感谢。