PySpark 2.2 взорвать, сбросив нулевые строки (как реализовать explode_outer)? [Дубликат]

1

Я работаю с некоторыми глубоко вложенными данными в фреймворке 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]|
+--------------+--------------+--------------+---------------+---------------+--------------------+
  • 0
    вместо того, чтобы использовать UDF, можете ли вы попробовать использовать встроенные искры when ? это будет df = df.withColumn('dataCells', when(col('dataCells').isNotNull),explode(col('dataCells'))) примерно так: df = df.withColumn('dataCells', when(col('dataCells').isNotNull),explode(col('dataCells')))
  • 0
    Я попробую и доложу. Спасибо за идею. Я также только что заметил, что в Spark 2.3 и выше есть explode_outer который, вероятно, будет делать то, что мне нужно, но я пока застрял на 2.2.x.
Показать ещё 7 комментариев
Теги:
apache-spark
apache-spark-sql
pyspark

2 ответа

1

Благодаря 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-функции.

0

для этой сложной структуры было бы легче написать функцию отображения и использовать ее в методе 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. Но, на мой взгляд, функция карты была бы уместна здесь, чтобы все было ясно

  • 0
    Разве использование RDD не помешало бы оптимизации операций оптимизатором катализатора?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню