Spark extrair colunas de string
Precisa de ajuda na análise de uma string, onde contém valores para cada atributo. abaixo está minha string de amostra ...
otherPartofString Name=<Series VR> Type=<1Ac4> SqVal=<34> conn ID=<2>
às vezes, a string pode incluir outros valores com um delimitador diferente, como
otherPartofString Name=<Series X> Type=<1B3> SqVal=<34> conn ID=<2> conn Loc=sfo dest=chc bridge otherpartofString..
as colunas de saída serão
Name | Type | SqVal | ID | Loc | dest
-------------------------------------------
Series VR | 1Ac4 | 34 | 2 | null | null
Series X | 1B3 | 34 | 2 | sfo | chc
Respostas
Como discutimos, para usar a função str_to_map em seus dados de amostra, podemos configurar pairDelim e keyValueDelim para o seguinte:
pairDelim: '(?i)>? *(?=Name|Type|SqVal|conn ID|conn Loc|dest|$)'
keyValueDelim: '=<?'
Onde pariDelim não faz distinção entre maiúsculas e minúsculas (?i)com um opcional >seguido por zero ou mais ESPAÇOS, seguido por uma das chaves predefinidas (que usamos '|'.join(keys)para gerá-la dinamicamente) ou o final da âncora de string $. keyValueDelim é um '=' com um opcional <.
from pyspark.sql import functions as F
df = spark.createDataFrame([
("otherPartofString Name=<Series VR> Type=<1Ac4> SqVal=<34> conn ID=<2>",),
("otherPartofString Name=<Series X> Type=<1B3> SqVal=<34> conn ID=<2> conn Loc=sfo dest=chc bridge otherpartofString..",)
],["value"])
keys = ["Name", "Type", "SqVal", "conn ID", "conn Loc", "dest"]
# add the following conf for Spark 3.0 to overcome duplicate map key ERROR
#spark.conf.set("spark.sql.mapKeyDedupPolicy", "LAST_WIN")
df.withColumn("m", F.expr("str_to_map(value, '(?i)>? *(?={}|$)', '=<?')".format('|'.join(keys)))) \
.select([F.col('m')[k].alias(k) for k in keys]) \
.show()
+---------+----+-----+-------+--------+--------------------+
| Name|Type|SqVal|conn ID|conn Loc| dest|
+---------+----+-----+-------+--------+--------------------+
|Series VR|1Ac4| 34| 2| null| null|
| Series X| 1B3| 34| 2| sfo|chc bridge otherp...|
+---------+----+-----+-------+--------+--------------------+
Precisaremos fazer um pós-processamento para os valores da última chave mapeada, uma vez que não há âncora ou padrão para distingui-los de outro texto não relacionado (isso pode ser um problema, pois pode acontecer com qualquer chave), por favor, deixe me sabe se você pode especificar algum padrão.
Editar: se o uso do mapa for menos eficiente para pesquisas que não diferenciam maiúsculas de minúsculas, visto que requer um pré-processamento caro, tente o seguinte:
ptn = '|'.join(keys)
df.select("*", *[F.regexp_extract('value', r'(?i)\b{0}=<?([^=>]+?)>? *(?={1}|$)'.format(k,ptn), 1).alias(k) for k in keys]).show()
No caso dos colchetes angulares <e >forem usados apenas quando os valores ou sua próxima chave adjacente contiverem caracteres não-word, pode ser simplificado com algum pré-processamento:
df.withColumn('value', F.regexp_replace('value','=(\w+)','=<$1>')) \
.select("*", *[F.regexp_extract('value', r'(?i)\b{0}=<([^>]+)>'.format(k), 1).alias(k) for k in keys]) \
.show()
Edit-2: adicionado um dicionário para lidar com aliases de chave:
keys = ["Name", "Type", "SqVal", "ID", "Loc", "dest"]
# aliases are case-insensitive and added only if exist
key_aliases = {
'Type': [ 'ThisType', 'AnyName' ],
'ID': ['conn ID'],
'Loc': ['conn Loc']
}
# set up regex pattern for each key differently
key_ptns = [ (k, '|'.join([k, *key_aliases[k]]) if k in key_aliases else k) for k in keys ]
#[('Name', 'Name'),
# ('Type', 'Type|ThisType|AnyName'),
# ('SqVal', 'SqVal'),
# ('ID', 'ID|conn ID'),
# ('Loc', 'Loc|conn Loc'),
# ('dest', 'dest')]
df.withColumn('value', F.regexp_replace('value','=(\w+)','=<$1>')) \
.select("*", *[F.regexp_extract('value', r'(?i)\b(?:{0})=<([^>]+)>'.format(p), 1).alias(k) for k,p in key_ptns]) \
.show()
+--------------------+---------+----+-----+---+---+----+
| value| Name|Type|SqVal| ID|Loc|dest|
+--------------------+---------+----+-----+---+---+----+
|otherPartofString...|Series VR|1Ac4| 34| 2| | |
|otherPartofString...| Series X| 1B3| 34| 2|sfo| chc|
+--------------------+---------+----+-----+---+---+----+