Я работаю с некоторыми глубоко вложенными данными в фреймворке 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, обрабатывающими null
.
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
Редактировать:
После ссылки, предоставленной в качестве возможного обмана, я попытался реализовать предлагаемое .isNotNull().otherwise()
предоставляющее схему структуры, в .otherwise
но строка все равно выпадает из набора результатов.
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]|
+--------------+--------------+--------------+---------------+---------------+--------------------+
Благодаря pault для указания мне на этот вопрос и на этот вопрос о отображении 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
определяется в искробезбеке 2.2 (но по какой-то причине обертка API не была реализована в pyspark до версии 2.3). Это решение создает оболочку для уже реализованной java-функции.
для этой сложной структуры было бы легче написать функцию отображения и использовать ее в методе flatMap
интерфейса RDD. В результате вы получите новый плоский 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
. Но, на мой взгляд, функция карты была бы уместна здесь, чтобы все было ясно
when
? это будетdf = df.withColumn('dataCells', when(col('dataCells').isNotNull),explode(col('dataCells')))
примерно так:df = df.withColumn('dataCells', when(col('dataCells').isNotNull),explode(col('dataCells')))
explode_outer
который, вероятно, будет делать то, что мне нужно, но я пока застрял на 2.2.x.