Изменить все элементы в ArrayType

У меня есть DataFrame со столбцом ArrayType(StringType):

+------------------------------------+
|colname                             |
+------------------------------------+
|[foo_XX_foo, bar_YY_bar]            |
|[qwe_ZZ_rty, asd_AA_fgh, zxc_BB_vbn]|
+------------------------------------+

Теперь я хотел бы извлечь строку между первым и вторым _, т.е. ожидаемый результат:

+------------+
|newcolname  |
+------------+
|[XX, YY]    |
|[ZZ, AA, BB]|
+------------+

Следуя этому ответу, я попытался использовать expr() с transform, но мне не удалось заставить его работать. Даже пример перевода всех строк в верхний регистр, как в приведенном выше ответе, у меня не работает, я получаю следующую ошибку:

pyspark.sql.utils.ParseException: u"\следующий ввод '>', ожидающий {'(', 'SELECT', ...

Как я могу изменить все элементы в ArrayType? Я хотел бы избежать использования udf.


person pfnuesel    schedule 25.10.2019    source источник
comment
Какую версию Spark вы используете?   -  person Bala    schedule 25.10.2019
comment
@Бала Искра 2.3.2   -  person pfnuesel    schedule 25.10.2019
comment
@pfnuesel transform доступен только в версии 2.4+. Ваш лучший вариант, вероятно, udf, хотя есть хакерский способ с использованием concat_ws, regexp_extract и split   -  person pault    schedule 25.10.2019


Ответы (2)


Немного небезопасно, но попробуйте что-то вроде этого:

df = spark.sparkContext.parallelize([
  [["foo_XX_foo", "bar_YY_bar"]],
  [["qwe_ZZ_rty", "asd_AA_fgh", "zxc_BB_vbn"]]
]).toDF(['colname'])

df.selectExpr('transform(colname, x -> split(x, "_")[1]) as newcolname').show()

что приводит к:

+------------+
|  newcolname|
+------------+
|    [XX, YY]|
|[ZZ, AA, BB]|
+------------+
person etherealyn    schedule 25.10.2019

Поскольку вы используете Spark версии 2.3.2, transform Вам недоступно. Таким образом, как объясняется в сообщении , в общем случае лучше всего использовать udf.

Однако в этом конкретном случае вы можете избежать udf, используя некоторые хакерские замены регулярных выражений.

from pyspark.sql.functions import col, concat_ws, regexp_replace, split, trim

df.withColumn(
    "newcolname",
    regexp_replace(concat_ws(",", col("colname")), "((?<=_)[^_,]+(?=_))", " $1 ")
).withColumn(
    "newcolname",
    regexp_replace(col("newcolname"), "(_[^_ ]+_)", "")
).withColumn(
    "newcolname",
    regexp_replace(col("newcolname"), "([^_ ]+_)", "")
).withColumn(
    "newcolname",
    regexp_replace(col("newcolname"), "_([^_ ]+)", "")
).withColumn(
    "newcolname",
    split(trim(col("newcolname")), "\s+")
).show(truncate=False)
#+------------------------------------+------------+
#|colname                             |newcolname  |
#+------------------------------------+------------+
#|[foo_XX_foo, bar_YY_bar]            |[XX, YY]    |
#|[qwe_ZZ_rty, asd_AA_fgh, zxc_BB_vbn]|[ZZ, AA, BB]|
#+------------------------------------+------------+

Объяснение

Сначала мы берем столбец ArrayType(StringType()) и объединяем элементы вместе, чтобы сформировать одну строку. Я использовал запятую в качестве разделителя, которая работает только в том случае, если запятая не появляется в ваших данных.

Далее мы выполняем серию regexp_replace вызовов.

Первый шаблон ((?<=_)[^_,]+(?=_)) идентифицирует содержимое, которое вы действительно хотите извлечь: текст, заключенный в квадратные скобки с символом подчеркивания. Затем соответствующие группы заменяются группой соответствия, окруженной пробелами " $1 ". Как и раньше с разделителем-запятой, это предполагает, что в ваших данных нет пробелов.

Например:

df.select(
    regexp_replace(
        concat_ws(",", col("colname")), 
        "((?<=_)[^_,]+(?=_))", 
        " $1 "
    ).alias("pattern1")
).show(truncate=False)
#+--------------------------------------+
#|pattern1                              |
#+--------------------------------------+
#|foo_ XX _foo,bar_ YY _bar             |
#|qwe_ ZZ _rty,asd_ AA _fgh,zxc_ BB _vbn|
#+--------------------------------------+

Следующие 3 вызова regexp_replace выборочно удаляют ненужные части этой строки.

Наконец, в конце остается только желаемый контент. Строка обрезается для удаления конечных/начальных пробелов и разбивается на пробелы, чтобы получить окончательный результат.

person pault    schedule 25.10.2019