通过正则表达式捕获组在spark dataframe列中拆分字符串

时间:2018-10-31 18:37:30

标签: python-3.x apache-spark pyspark apache-spark-sql

鉴于以下数据框,我想将数字列拆分为一个数组,其中数组中原始数字的每个元素包含3个字符

给出数据框:

+---+------------------+
| id|           numbers|
+---+------------------+
|742|         000000000|
|744|            000000|
|746|003000000000000000|
+---+------------------+

预期的数据框:

+---+----------------------------------+
| id|           numbers                |
+---+----------------------------------+
|742| [000, 000, 000]                  |
|744| [000, 000]                       |
|746| [003, 000, 000, 000, 000, 000]   |
+---+----------------------------------+

我在使用下面给出的split函数和正则表达式时尝试了不同的正则表达式,我觉得应该在第一次尝试时就可以使用:

import pyspark.sql.functions as f

df = spark.createDataFrame(
    [
        [742, '000000000'], 
        [744, '000000'], 
        [746, '003000000000000000'], 
    ],
    ["id", "numbers"]
)

df = df.withColumn("numbers", f.split("numbers", "[0-9]{3}"))

df.show()

结果是

+---+--------------+
| id|       numbers|
+---+--------------+
|742|      [, , , ]|
|744|        [, , ]|
|746|[, , , , , , ]|
+---+--------------+

我想了解我在做什么错。是否可以设置用于获取所有匹配项的全局标志,或者我完全错过了正则表达式中的某些内容?

3 个答案:

答案 0 :(得分:2)

split将删除字符串被分割的模式;您需要为此创建一个udf:

from pyspark.sql.functions import udf
from pyspark.sql.types import ArrayType, StringType
import re

# create a udf with re.findall
split_by_three = f.udf(lambda s: re.findall(r'\d{3}', s), ArrayType(StringType()))
df.withColumn('numbers', split_by_three('numbers')).show(3, False)

#+---+------------------------------+
#|id |numbers                       |
#+---+------------------------------+
#|742|[000, 000, 000]               |
#|744|[000, 000]                    |
#|746|[003, 000, 000, 000, 000, 000]|
#+---+------------------------------+

df.withColumn('numbers', split_by_three('numbers')).printSchema()
#root
# |-- id: long (nullable = true)
# |-- numbers: array (nullable = true)
# |    |-- element: string (containsNull = true)

答案 1 :(得分:2)

以下是不使用udf的方法:

df = df.withColumn(
    "numbers",
    f.split(f.regexp_replace("numbers", "([0-9]{3})(?!$)", r"$1,"), ",")
)

df.show(truncate=False)
#+---+------------------------------+
#|id |numbers                       |
#+---+------------------------------+
#|742|[000, 000, 000]               |
#|744|[000, 000]                    |
#|746|[003, 000, 000, 000, 000, 000]|
#+---+------------------------------+

首先使用pyspark.sql.functions.regexp_replace将3位数的序列替换为后跟逗号的序列。然后用逗号分割结果字符串。

替换模式"$1,"表示第一个捕获组,后跟一个逗号。

在匹配模式中,我们还为字符串的末尾(?!$)加上了一个负号,以避免在字符串的末尾添加逗号。

参考:REGEXP_REPLACE capturing groups

答案 2 :(得分:1)

@pault和@Psidom方式都很棒!这是另一种选择;

>>> split_udf = F.udf(lambda x: ','.join([''.join(i) for i in zip(*[iter(x)]*3)]))
>>> df.withColumn('numbers', F.split(split_udf('numbers'),',')).show(truncate=False)
+---+------------------------------+
|id |numbers                       |
+---+------------------------------+
|742|[000, 000, 000]               |
|744|[000, 000]                    |
|746|[003, 000, 000, 000, 000, 000]|
+---+------------------------------+