我正在使用PySpark数据框中的一些深层嵌套的数据。在尝试将结构扁平化为行和列时,我注意到当我调用withColumn
时,如果该行在源列中包含null
,那么该行将从结果数据框中删除。相反,我想找到一种保留行并在结果列中包含null
的方法。
要使用的示例数据框:
from pyspark.sql.functions import explode, first, col, monotonically_increasing_id
from pyspark.sql import Row
df = spark.createDataFrame([
Row(dataCells=[Row(posx=0, posy=1, posz=.5, value=1.5, shape=[Row(_type='square', _len=1)]),
Row(posx=1, posy=3, posz=.5, value=4.5, shape=[]),
Row(posx=2, posy=5, posz=.5, value=7.5, shape=[Row(_type='circle', _len=.5)])
])
])
我还有一个用于扁平化结构的功能:
def flatten_struct_cols(df):
flat_cols = [column[0] for column in df.dtypes if 'struct' not in column[1][:6]]
struct_columns = [column[0] for column in df.dtypes if 'struct' in column[1][:6]]
df = df.select(flat_cols +
[col(sc + '.' + c).alias(sc + '_' + c)
for sc in struct_columns
for c in df.select(sc + '.*').columns])
return df
模式如下:
df.printSchema()
root
|-- dataCells: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- posx: long (nullable = true)
| | |-- posy: long (nullable = true)
| | |-- posz: double (nullable = true)
| | |-- shape: array (nullable = true)
| | | |-- element: struct (containsNull = true)
| | | | |-- _len: long (nullable = true)
| | | | |-- _type: string (nullable = true)
| | |-- value: double (nullable = true)
起始数据框:
df.show(3)
+--------------------+
| dataCells|
+--------------------+
|[[0,1,0.5,Wrapped...|
+--------------------+
我首先分解数组,因为我想将此结构数组和结构数组转换为行和列。然后,将结构字段展平到新列中。
df = df.withColumn('dataCells', explode(col('dataCells')))
df = flatten_struct_cols(df)
df.show(3)
我的数据如下:
+--------------+--------------+--------------+---------------+---------------+
|dataCells_posx|dataCells_posy|dataCells_posz|dataCells_shape|dataCells_value|
+--------------+--------------+--------------+---------------+---------------+
| 0| 1| 0.5| [[1,square]]| 1.5|
| 1| 3| 0.5| []| 4.5|
| 2| 5| 0.5|[[null,circle]]| 7.5|
+--------------+--------------+--------------+---------------+---------------+
在我尝试explode
dataCells_shape
列为空/空值的列之前,一切都很好。
df = df.withColumn('dataCells_shape', explode(col('dataCells_shape')))
df.show(3)
将第二行从数据框中删除:
+--------------+--------------+--------------+---------------+---------------+
|dataCells_posx|dataCells_posy|dataCells_posz|dataCells_shape|dataCells_value|
+--------------+--------------+--------------+---------------+---------------+
| 0| 1| 0.5| [1,square]| 1.5|
| 2| 5| 0.5| [null,circle]| 7.5|
+--------------+--------------+--------------+---------------+---------------+
相反,我想保留该行并保留该列的空值以及其他列中的所有值。我尝试过在创建.withColumn
explode
时创建新的列,而不是覆盖旧的列,无论哪种方式都得到相同的结果。
我还尝试创建一个UDF
来执行explode
函数,如果该行不是空/空,但是我遇到了处理null
的JVM错误。
from pyspark.sql.functions import udf
from pyspark.sql.types import NullType, StructType
def explode_if_not_null(trow):
if trow:
return explode(trow)
else:
return NullType
func_udf = udf(explode_if_not_null, StructType())
df = df.withColumn('dataCells_shape_test', func_udf(df['dataCells_shape']))
df.show(3)
AttributeError: 'NoneType' object has no attribute '_jvm'
有人可以建议我在ArrayType
列爆炸或变平的同时null
列而不丢失行吗?
我正在使用PySpark 2.2.0
编辑:
按照可能提供的链接dupe,我尝试实现建议的.isNotNull().otherwise()
解决方案,该解决方案将结构模式提供给.otherwise
,但该行仍从结果集中退出。 / p>
df.withColumn("dataCells_shape_test", explode(when(col("dataCells_shape").isNotNull(), col("dataCells_shape"))
.otherwise(array(lit(None).cast(df.select(col("dataCells_shape").getItem(0))
.dtypes[0][1])
)
)
)
).show()
+--------------+--------------+--------------+---------------+---------------+--------------------+
|dataCells_posx|dataCells_posy|dataCells_posz|dataCells_shape|dataCells_value|dataCells_shape_test|
+--------------+--------------+--------------+---------------+---------------+--------------------+
| 0| 1| 0.5| [[1,square]]| 1.5| [1,square]|
| 2| 5| 0.5|[[null,circle]]| 7.5| [null,circle]|
+--------------+--------------+--------------+---------------+---------------+--------------------+
答案 0 :(得分:3)
感谢pault向我指出this question和this question关于将Python映射到Java的知识。我可以通过以下方式获得可行的解决方案:
from pyspark.sql.column import Column, _to_java_column
def explode_outer(col):
_explode_outer = sc._jvm.org.apache.spark.sql.functions.explode_outer
return Column(_explode_outer(_to_java_column(col)))
new_df = df.withColumn("dataCells_shape", explode_outer(col("dataCells_shape")))
+--------------+--------------+--------------+---------------+---------------+
|dataCells_posx|dataCells_posy|dataCells_posz|dataCells_shape|dataCells_value|
+--------------+--------------+--------------+---------------+---------------+
| 0| 1| 0.5| [1,square]| 1.5|
| 1| 3| 0.5| null| 4.5|
| 2| 5| 0.5| [null,circle]| 7.5|
+--------------+--------------+--------------+---------------+---------------+
root
|-- dataCells_posx: long (nullable = true)
|-- dataCells_posy: long (nullable = true)
|-- dataCells_posz: double (nullable = true)
|-- dataCells_shape: struct (nullable = true)
| |-- _len: long (nullable = true)
| |-- _type: string (nullable = true)
|-- dataCells_value: double (nullable = true)
请注意,此方法适用于pyspark 2.2版,因为explode_outer
是在spark 2.2中定义的(但由于某些原因,直到2.3版之前,API包装器才在pyspark中实现)。此解决方案为已经实现的java函数创建包装器。
答案 1 :(得分:0)
对于这种复杂的结构,将更容易编写映射函数并将其用于RDD接口的flatMap
方法中。结果,您将获得一个新的扁平化RDD,然后您必须通过应用新的架构来再次创建数据框。
def flat_arr(row):
rows = []
# apply some logic to fill rows list with more "rows"
return rows
rdd = df.rdd.flatMap(flat_arr)
schema = StructType(
StructField('field1', StringType()),
# define more fields
)
df = df.sql_ctx.createDataFrame(rdd, schema)
df.show()
此解决方案看起来比应用withColumn
更长,但这可能是解决方案的第一次迭代,因此您可以看到如何将其转换为withColumn
语句。但是我认为map函数在这里很合适,只是为了使事情保持清晰