【问题标题】:Separate a string column depending on first character appearance根据第一个字符的外观分隔字符串列
【发布时间】:2019-01-03 12:54:56
【问题描述】:

我想根据字符的第一次出现将 Spark DataFrame 的列分成 2 个不同的列,在本例中为下划线(“_”)。

我准备了一个 100% 可重现的例子:

模拟的 Spark DataFrame 是:

df = spark.createDataFrame(
    [
     (1, 1.8, 'newyork_3434_north'), 
     (4, 2.6, 'la_432432432_south'), 
     (6, 3.3, 'boston_234324_east'), 
     (8, 4.1, 'detroit_6757_west'), 
     (2, 5.7, 'miami_133123_north'), 
     (3, 6.2, 'atlanta_093394_west'), 
     (1, 6.1, 'houston_87342_east')
    ],
    ('ranking', "coordenate", "city")
)

上面的代码创建了一个类似下面的表:

ranking  coordenate  city
1        1.8         newyork_3434_north
4        2.6         la_432432432_south
6        3.3         boston_234324_east
8        4.1         detroit_6757_west
2        5.7         miami_133123_north
3        6.2         atlanta_093394_west 
1        6.1         houston_87342_east

我想要做的是根据第一个下划线的位置从左到右将列城市分成 2 个不同的列。

最终所需的表格将类似于:

ranking  coordenate  city       code
1        1.8         newyork    3434_north
4        2.6         la         432432432_south
6        3.3         boston     234324_east
8        4.1         detroit    6757_west
2        5.7         miami      133123_north
3        6.2         atlanta    093394_west
1        6.1         houston    87342_east

我看过几个关于这个话题的帖子,但他们没有谈论字符的第一次出现(link_1link_2 等),而是按字符串上的所有特定字符分割;或按字符串中字符的特定位置拆分。

我也尝试过 Python Pandas 方法,但正如预期的那样,它不适用于 PySpark 通过扩展或类比 (link_3)

提前感谢您的帮助。

【问题讨论】:

  • 你的数据框有多大?你真的需要火花吗?
  • 嗨@acushner,感谢您的回复。我同意你的观点,这似乎很简单,在 Pandas 中解决方案很简单(我已经知道如何在本地进行),但数据集是 123 GB。为此,我在 SO 中调整了一个非常简单易懂的数据集,但它是一个巨大的数据集,甚至只选择了我想要的列(“城市”)。提前致谢!
  • 使用 split 和 concat_ws 函数
  • 是否总是正好有 2 个下划线?
  • 嗨@pault,答案是否定的。在我说明的示例中,这只是巧合;我在我的 SparkDF 中看到了具有 2 个以上下划线的注册表。谢谢你的提问。

标签: python regex apache-spark pyspark


【解决方案1】:

我认为这里最好的选择是使用pyspark.sql.functions.regexp_extract()pyspark.sql.functions.regexp_replace()

import pyspark.sql.functions as f

df.select(
    "ranking",
    "coordenate",
    f.regexp_extract("city", pattern="^[A-Za-z]+(?=_)", idx=0).alias('city'),
    f.regexp_replace("city", "^[A-Za-z]+_", "").alias("code")
).show()
#+-------+----------+----------+---------------+
#|ranking|coordenate|      city|           code|
#+-------+----------+----------+---------------+
#|      1|       1.8|   newyork|     3434_north|
#|      4|       2.6|        la|432432432_south|
#|      6|       3.3|    boston|    234324_east|
#|      8|       4.1|   detroit|      6757_west|
#|      2|       5.7|     miami|   133123_north|
#|      3|       6.2|   atlanta|    093394_west|
#|      1|       6.1|   houston|     87342_east|
#+-------+----------+----------+---------------+

在这两种情况下,模式本质上是相同的:

  • ^[A-Za-z]+:匹配从字符串开头开始的任意数量的字母
  • (?=_): 下划线的正向预测

对于city,我们找到此模式并提取第一个匹配项。对于code,我们将前瞻更改为匹配并将模式替换为空字符串。


如果很难找到合适的正则表达式模式,这里有一种适用于 Spark 2.1 及更高版本的替代方法:

获取city 很简单——您可以使用pyspark.sql.functions.split() 将字符串拆分为下划线,然后使用getItem(0) 获取拆分列表的第一个元素。

对于code 部分,将city 拆分为下划线,并使用pyspark.sql.functions.posexplode() 分解生成的数组。然后过滤掉pos > 0,按原始列分组,使用pyspark.sql.functions.concat_ws加入收集到的token。

df.select(
        "*",
        f.posexplode(f.split("city", "_")).alias("pos", "token")
    )\
    .where("pos > 0")\
    .groupBy("ranking", "coordenate", "city")\
    .agg(f.concat_ws("_" ,f.collect_list("token")).alias("code"))\
    .select(
        "ranking",
        "coordenate",
        f.split("city", "_").getItem(0).alias("city"),
        "code"
    )\
    .show()

【讨论】:

  • 感谢@pault 的反馈。惊人的答案。将其标记为问题的答案。
【解决方案2】:

@pault 已经使用 regexsplitconcat_ws 内置函数给出了一个很棒的答案

这是使用udf 函数作为简单操作的替代方法

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

@udf(ArrayType(StringType()))
def splitUdf(x):
    splitted = x.split('_')
    return [splitted[0], '_'.join(splitted[1:])]

df.withColumn('city', splitUdf(col('city')))\
    .select(col('ranking'), col('coordenate'), col('city')[0].alias('city'), col('city')[1].alias('code'))\
    .show()

这应该给你

+-------+----------+-------+---------------+
|ranking|coordenate|   city|           code|
+-------+----------+-------+---------------+
|      1|       1.8|newyork|     3434_north|
|      4|       2.6|     la|432432432_south|
|      6|       3.3| boston|    234324_east|
|      8|       4.1|detroit|      6757_west|
|      2|       5.7|  miami|   133123_north|
|      3|       6.2|atlanta|    093394_west|
|      1|       6.1|houston|     87342_east|
+-------+----------+-------+---------------+

希望回答对你有帮助

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 2020-02-01
    • 1970-01-01
    • 2013-10-19
    • 1970-01-01
    • 2019-09-19
    相关资源
    最近更新 更多