使用Spark内置函数或方法解析Pyspark中的csv文件

使用Spark内置函数或方法解析Pyspark中的csv文件,csv,apache-spark,pyspark,Csv,Apache Spark,Pyspark,我正在使用spark版本2.3,并且正在处理一些poc,其中,我必须加载一些csv文件来spark数据帧 以下面的csv为例,我需要对其进行解析并将其加载到dataframe中。给定的csv有多个需要识别的坏记录 id,name,age,loaded_date,sex 1,ABC,32,2019-09-11,M 2,,33,2019-09-11,M 3,XYZ,35,2019-08-11,M 4,PQR,32,2019-30-10,M #invalid date 5,EFG,32,

我正在使用spark版本2.3,并且正在处理一些poc,其中,我必须加载一些csv文件来spark数据帧

以下面的csv为例,我需要对其进行解析并将其加载到dataframe中。给定的csv有多个需要识别的坏记录

id,name,age,loaded_date,sex
1,ABC,32,2019-09-11,M
2,,33,2019-09-11,M
3,XYZ,35,2019-08-11,M
4,PQR,32,2019-30-10,M   #invalid date
5,EFG,32,               #missing other column details
6,DEF,32,2019/09/11,M   #invalid date format
7,XYZ,32,2017-01-01,9   #last column has to be character only
8,KLM,XX,2017-01-01,F
9,ABC,3.2,2019-10-10,M  #decimal value for integer data type
10,ABC,32,2019-02-29,M  #invalid date
如果我必须使用python或pandas函数解析它,那么这将是一项简单的任务

我就是这样定义这个模式的

from pyspark.sql.types import*
schema = StructType([
            StructField("id",            IntegerType(), True),
            StructField("name",          StringType(), True),
            StructField("age",           IntegerType(), True),
            StructField("loaded_date",   DateType(), True),
            StructField("sex",           StringType(), True),
            StructField("corrupt_record",StringType(), True)])



df=spark.read.format("com.databricks.spark.csv") \
.option("header", "true") \
.option("dateFormat", "yyyy-MM-dd") \
.option("nanValue","0") \
.option("nullValue"," ") \
.option("treatEmptyValuesAsNulls","false") \
.option("columnNameOfCorruptRecord", "corrupt_record") \
.schema(schema).load(file)

>>> df.show(truncate=False)
+----+----+----+-----------+----+----------------------+
|id  |name|age |loaded_date|sex |corrupt_record        |
+----+----+----+-----------+----+----------------------+
|1   |ABC |32  |2019-09-11 |M   |null                  |
|2   |null|33  |2019-09-11 |M   |null                  |
|3   |XYZ |35  |2019-08-11 |M   |null                  |
|4   |PQR |32  |2021-06-10 |M   |null                  |
|5   |EFG |32  |null       |null|5,EFG,32,             |
|null|null|null|null       |null|6,DEF,32,2019/09/11,M |
|7   |XYZ |32  |2017-01-01 |9   |null                  |
|null|null|null|null       |null|8,KLM,XX,2017-01-01,F |
|null|null|null|null       |null|9,ABC,3.2,2019-10-10,M|
|10  |ABC |32  |2019-03-01 |M   |null                  |
+----+----+----+-----------+----+----------------------+
上述代码按预期解析了许多记录,但未能检查无效日期。请参阅记录
'4'
&
'10'
。它已转换为一些垃圾日期

我可以将日期作为字符串类型加载,并创建一些udf,或者使用cast正确解析它,并查看输入的日期是否有效。是否有任何方法可以首先检查无效日期,而无需在代码中使用自定义udf或更高版本


另外,我正在寻找一种方法来处理记录
'7'
,该记录的最后一列有一个数字值。

您可以尝试类似的方法,而无需自定义项:

val data = spark.read.option("header", "true").csv("data/yourdata.csv")
val data2 = data.select('id,
('age.cast("double")
  .cast("int")
  .cast("string")
  .equalTo('age) && 'age.cast("int").isNotNull )
  .equalTo("true")
  .as("isINT"),
'loaded_date.cast("date").isNotNull.as("isDATE"),
('sex.cast("int").isNotNull || 'sex.isNull).notEqual("true").as("isCHAR"))

data2.show()
+---+-----+------+------+
| id|isINT|isDATE|isCHAR|
+---+-----+------+------+
|  1| true|  true|  true|
|  2| true|  true|  true|
|  3| true|  true|  true|
|  4| true| false|  true|
|  5| true| false| false|
|  6| true| false|  true|
|  7| true|  true| false|
|  8|false|  true|  true|
|  9|false|  true|  true|
| 10| true| false|  true|
+---+-----+------+------+

val corrupted = data2.select('id,
    concat(data2.columns.map(data2(_)).drop(1):_*).contains("false").as("isCorrupted")
  )
  corrupted.show()

+---+-----------+
| id|isCorrupted|
+---+-----------+
|  1|      false|
|  2|      false|
|  3|      false|
|  4|       true|
|  5|       true|
|  6|       true|
|  7|       true|
|  8|       true|
|  9|       true|
| 10|       true|
+---+-----------+

data.join(corrupted,"id").show()

+---+----+---+-----------+----+-----------+
| id|name|age|loaded_date| sex|isCorrupted|
+---+----+---+-----------+----+-----------+
|  1| ABC| 32| 2019-09-11|   M|      false|
|  2|null| 33| 2019-09-11|   M|      false|
|  3| XYZ| 35| 2019-08-11|   M|      false|
|  4| PQR| 32| 2019-30-10|   M|       true|
|  5| EFG| 32|       null|null|       true|
|  6| DEF| 32| 2019/09/11|   M|       true|
|  7| XYZ| 32| 2017-01-01|   9|       true|
|  8| KLM| XX| 2017-01-01|   F|       true|
|  9| ABC|3.2| 2019-10-10|   M|       true|
| 10| ABC| 32| 2019-02-29|   M|       true|
+---+----+---+-----------+----+-----------+
正如OP所说,我在
PySpark
-

首先,只需在没有任何预先指定的模式的情况下加载数据,就像@AndrzejS所做的那样

df = spark.read.option("header", "true").csv("data/yourdata.csv")
df.show()
+---+----+---+-----------+----+
| id|name|age|loaded_date| sex|
+---+----+---+-----------+----+
|  1| ABC| 32| 2019-09-11|   M|
|  2|null| 33| 2019-09-11|   M|
|  3| XYZ| 35| 2019-08-11|   M|
|  4| PQR| 32| 2019-30-10|   M|
|  5| EFG| 32|       null|null|
|  6| DEF| 32| 2019/09/11|   M|
|  7| XYZ| 32| 2017-01-01|   9|
|  8| KLM| XX| 2017-01-01|   F|
|  9| ABC|3.2| 2019-10-10|   M|
| 10| ABC| 32| 2019-02-29|   M|
+---+----+---+-----------+----+
然后,我们需要确定哪些值不适合列的模式。对于eg
XX
32
不能是
age
,因此这些值需要标记为
Null
。如果该值是一个
整数
或其他值,我们将进行测试。类似地,如果
loaded\u date
确实是
date
或不是,我们进行测试,最后我们检查
性别
是否为
F/M
。请参考我关于这些测试的帖子

df = df.select('id','name',
               'age', (col('age').cast('int').isNotNull() & (col('age').cast('int') - col('age') == 0)).alias('ageInt'),
               'loaded_date',(col('loaded_date').cast('date').isNotNull()).alias('loaded_dateDate'),
               'sex'
              )
df.show()
+---+----+---+------+-----------+---------------+----+
| id|name|age|ageInt|loaded_date|loaded_dateDate| sex|
+---+----+---+------+-----------+---------------+----+
|  1| ABC| 32|  true| 2019-09-11|           true|   M|
|  2|null| 33|  true| 2019-09-11|           true|   M|
|  3| XYZ| 35|  true| 2019-08-11|           true|   M|
|  4| PQR| 32|  true| 2019-30-10|          false|   M|
|  5| EFG| 32|  true|       null|          false|null|
|  6| DEF| 32|  true| 2019/09/11|          false|   M|
|  7| XYZ| 32|  true| 2017-01-01|           true|   9|
|  8| KLM| XX| false| 2017-01-01|           true|   F|
|  9| ABC|3.2| false| 2019-10-10|           true|   M|
| 10| ABC| 32|  true| 2019-02-29|          false|   M|
+---+----+---+------+-----------+---------------+----+
最后,使用
if/else
,即pyspark is
when/else
将不相关的值标记为
Null

df = df.withColumn('age',when(col('ageInt')==True,col('age')).otherwise(None))\
       .withColumn('loaded_date',when(col('loaded_dateDate')==True,col('loaded_date')).otherwise(None))\
       .withColumn('sex',when(col('sex').isin('M','F'),col('sex')).otherwise(None))\
       .drop('ageInt','loaded_dateDate')
df.show()
+---+----+----+-----------+----+
| id|name| age|loaded_date| sex|
+---+----+----+-----------+----+
|  1| ABC|  32| 2019-09-11|   M|
|  2|null|  33| 2019-09-11|   M|
|  3| XYZ|  35| 2019-08-11|   M|
|  4| PQR|  32|       null|   M|
|  5| EFG|  32|       null|null|
|  6| DEF|  32|       null|   M|
|  7| XYZ|  32| 2017-01-01|null|
|  8| KLM|null| 2017-01-01|   F|
|  9| ABC|null| 2019-10-10|   M|
| 10| ABC|  32|       null|   M|
+---+----+----+-----------+----+

这是scala,但python代码在本例中非常类似。您能在pyspark中转换上面的代码吗?谢谢