I have to extract some codes from columns of a dataframe that looks like the following:
+---------+--------------------------------+--------------------+------+
|first |second |third |num |
+---------+--------------------------------+--------------------+------+
|AB12a |xxxxxx |some other data |100000|
|yyyyyyy |XYZ02, but possibly also GFH11b |Look at second col* |120000|
+---------+--------------------------------+--------------------+------+
The codes follow the regex "^([A-Z]+[0-9]+[a-z]*)" and are scattered across two columns (first and second) depending on whether the third column contains an asterisk. Since there can be more than one code in each column, I need all the regex matches in an array. In the example above, I need to extract AB12a from first, and [XYZ02, GFH11b] from second.
I found out that multiple matches are not supported by the default pyspark function regexp_extract (https://issues.apache.org/jira/browse/SPARK-24884), so I defined my own regexp_extract_all UDF:
from pyspark.sql.types import *
from pyspark.sql.functions import *
import re
def regexp_extract_all(s, pattern):
pattern = re.compile(pattern, re.M)
all_matches = re.findall(pattern, s)
return all_matches
pattern = "^([A-Z]+[0-9]+[a-z]*)"
udf_regexp_extract_all = udf(regexp_extract_all, ArrayType(StringType()))
I managed to get the UDF working if I apply it on each column separately:
# this extracts AB12a from first
df = df.withColumn("code", udf_regexp_extract_all("first", lit(pattern)))
# this extracts [XYZ02, GFH11b] from second
df = df.withColumn("code", udf_regexp_extract_all("second", lit(pattern)))
But I get a TypeError: expected string or buffer when working in a when clause:
# this gives at runtime TypeError: expected string or buffer
df = df.withColumn("code", when(col("third").like("%*%"),
udf_regexp_extract_all("second", lit(pattern)))
.otherwise(udf_regexp_extract_all("first", lit(pattern))))
I think I'm probably getting swamped with types at runtime, because something happens in a when clause that needs my UDF to be defined slightly differently.
Any idea?