Python 在PySpark中使用ApacheSpark数据帧消除重音的最佳方法是什么?

Python 在PySpark中使用ApacheSpark数据帧消除重音的最佳方法是什么?,python,apache-spark,pyspark,apache-spark-sql,unicode-normalization,Python,Apache Spark,Pyspark,Apache Spark Sql,Unicode Normalization,我需要从不同的数据集中删除西班牙语和其他语言字符的重音符号 我已经在本文提供的代码中完成了一个函数,该函数消除了特殊的重音。问题是函数速度慢,因为它使用UDF。 我只是想知道我是否可以提高我的函数的性能,以在更短的时间内得到结果,因为这对小数据帧是好的,但对大数据帧不是 提前谢谢 在此代码中,您将能够按显示的方式运行它: 导入sql类型 从pyspark.sql.types导入StringType、IntegerType、StructType、StructField 从pyspark.sql.f

我需要从不同的数据集中删除西班牙语和其他语言字符的重音符号

我已经在本文提供的代码中完成了一个函数,该函数消除了特殊的重音。问题是函数速度慢,因为它使用UDF。 我只是想知道我是否可以提高我的函数的性能,以在更短的时间内得到结果,因为这对小数据帧是好的,但对大数据帧不是

提前谢谢

在此代码中,您将能够按显示的方式运行它:

导入sql类型 从pyspark.sql.types导入StringType、IntegerType、StructType、StructField 从pyspark.sql.functions导入udf,col 导入Unicode数据 构建一个简单的数据框架: schema=StructType[StructFieldcity,StringType,True, StructFieldcountry,StringType,True, StructFieldpopulation,IntegerType,True] 国家=['委内瑞拉','US@A“,”巴西“,”西班牙“] 城市=[‘马拉开波’、‘纽约’、‘圣保罗’、‘马德里’] 人口=[3708000019795791123414186489162] 数据帧: df=sqlContext.createDataFramelistzipcities,国家,人口,schema=schema df.show 课堂测试: 定义初始自我,df: self.df=df def clearAccentsself,列: 此函数用于删除字符串列数据帧中的重音符号, 它不会删除主要字符,但只删除特殊的平铺。 :param columns String或列名列表。 筛选dataFrame中的所有字符串列 validCols=[c代表c,过滤器中的t:t[1]='string',self.df.dtypes] 如果没有或[]提供了列参数: 如果columns==*:columns=validCols[:] 接收字符串作为参数 def移除_重音输入: 首先,规范化字符串: nfkdStr=unicodedata。规范化'NFKD',inputStr 保留没有其他字符组合的字符,即重音字符 withOutAccents=u.join[c代表nfkdStr中的c,如果不是unicodedata.combiningc] 不带口音地返回 函数=udflambda x:如果x!=没有其他x,StringType exprs=[functioncolc.aliasc if c in columns,c in validCols else c for c in self.df.columns] self.df=self.df.选择*exprs foo=Testdf foo.clearAccentscolumns=* foo.df.show
此解决方案仅适用于Python,但仅在可能的重音数量较低时才有用,例如,一种单一语言(如西班牙语)和手动指定字符替换

在没有UDF的情况下,似乎没有内置的方法可以直接执行您要求的操作,但是您可以链接许多regexp_replace调用来替换每个可能的重音字符。我测试了该解决方案的性能,结果表明,只有在需要替换的口音非常有限的情况下,它才能运行得更快。如果是这样的话,它可以比UDF更快,因为它是在Python之外优化的

从pyspark.sql.functions导入col,regexp\u替换 重音\u替换\u西班牙语=[ u'á',a',u'Á',a', u'e',e',u'e',e', “我”,“我”,“我”, u'ò','o',u'Ó','o', u'u|ü,'u',u'218|Ű,'u', u‘ñ’,‘n’, 看见http://stackoverflow.com/a/18123985/3810493 对于其他角色 这会将其他非ASCII字符转换为问号: “[^\x00-\x7F],”?” ] def remove_Accents柱: r=colcolumn 对于a,b,用重音替换西班牙语: r=regexp\u替换程序,a,b 返回r.alias'remove_accents'+column+ df=sqlContext.createDataFrame['Olá'],['Olé'],['Núñez'],['str'] df.选择删除重音符号'str'。显示
我没有将性能与其他响应进行比较,这个函数也没有那么通用,但至少值得考虑,因为您不需要将Scala或Java添加到构建过程中。

一个可能的改进是构建一个自定义函数,它将处理Unicode规范化和相应的Python包装器。它应该减少JVM和Python之间传递数据的总体开销,并且不需要对Spark本身进行任何修改或访问私有API

在JVM方面,您需要一个类似于此的转换器:

包net.zero323.spark.ml.feature 导入java.text.Normalizer 导入org.apache.spark.ml.UnaryTransformer 导入org.apache.spark.ml.param_ 导入org.apache.spark.ml.util_ 导入org.apache.spark.sql.types.{DataType,StringType} 类Unicode非规范化器重写val uid:字符串 扩展UnaryTransformer[字符串,字符串,反规范化器]{ def this=thisIdentified.randomUIDunicode\u规范化器 私有val窗体=映射 NFC->Normalizer.Form.NFC,NFD->Normalizer.Form.NFD, NFKC->Normalizer.Form.NFKC,NFKD->Normalizer.Form.NFKD val form:Param[String]=新的Paramthis,form,unicode form NFC,NFD,NFKC,NFKD中的一种, ParamValidators.inarayForms.keys.toArray def setNvalue:字符串:this.type=setform,value def getForm:String=$form setDefaultform->NFKD 覆盖受保护的def CreateTransfunc:String=>String={ val normalizer=forms$form s:String=>Normalizer.normalizes,normalizeperform } 覆盖受保护的def validateInputTypeinputType:数据类型:单位={ requireinputType==StringType,输入类型必须是字符串类型,但得到$inputType。 } 覆盖受保护的def outputDataType:DataType=StringType } 相应的生成定义调整Spark和Scala版本以匹配Spark部署:

名称:=unicode规范化 版本:=1.0 交叉标度偏差:=序号2.11.12,2.12.8 组织:=net.zero323 val sparkVersion=2.4.0 libraryDependencies++=Seq org.apache.spark%%spark core%sparkVersion, org.apache.spark%%spark sql%sparkVersion, org.apache.spark%%spark mllib%sparkVersion 在Python方面,您需要一个类似于此的包装器

从pyspark.ml.param.shared导入* 从pyspark.ml.util仅在Spark<2.0中导入关键字_ 仅从pyspark导入关键字_ 从pyspark.ml.wrapper导入JavaTransformer 类UnicodenormalizarJavaTransformer、HasInputCol、HasOutputCol: @仅关键字_ def\uuuuu初始化\uuuuu self,form=NFKD,inputCol=None,outputCol=None: 超级反规范化器,自初始化__ self.\u java\u obj=self.\u new\u java\u obj net.zero323.spark.ml.feature.unicondenormalizer,self.uid self.form=Paramself,form, unicode格式NFC、NFD、NFKC、NFKD之一 kwargs=自身。初始。输入。火花中的kwargs<2.0 kwargs=自我。\输入\ kwargs self.setParams**kwargs @仅关键字_ def setParamsself,form=NFKD,inputCol=None,outputCol=None: kwargs=self.setParams.\u输入\u火花中的kwargs<2.0 kwargs=自我。\输入\ kwargs 返回自我设置**kwargs def setFormself,值: 返回self.\u setform=value def getFormself: 返回self.getOrDefaultself.form 构建Scala包:

sbt+封装 在启动shell或提交时包含它。例如,对于Scala 2.11的Spark构建:

bin/pyspark-jars到/target/scala-2.11/unicode-normalization_2.11-1.0.jar的路径\ -/target/scala-2.11/unicode-normalization_2.11-1.0.jar的驱动程序类路径 你应该准备好出发了。剩下的只是一点regexp的魔力:

从pyspark.sql.functions导入regexp\u replace normalizer=unicode非规范化形式=NFKD, inputCol=文本,outputCol=文本 df=sc.parallelize[ 1号,马拉开波,2号,纽约, 圣保罗3号,马德里4号 ].toDF[id,text] 标准化器 .transformdf .selectregexp\u replacetext\u normalized\p{M}, 显示 +-------------------+ |regexp\u replacetext\u规范化,\p{M}| +-------------------+ |马拉开波| |纽约| |圣保罗| |~z~马德里| +-------------------+
请注意,这遵循与内置文本转换器相同的约定,并且不是空安全的。您可以通过在CreateTransfunc中检查null来轻松纠正此问题。

使用python的另一种方法:

现在让我们测试一下:

df = sc.parallelize([
(1, "Maracaibó"), (2, "New York"),
(3, "   São Paulo   "), (4, "~Madrid"),
(5, "São Paulo"), (6, "Maracaibó")
]).toDF(["id", "text"])

df.select(clean_text("text")).show()
## +---------------+
## |           text|
## +---------------+
## |      Maracaibo|
## |       New York|
## |   Sao Paulo   |
## |        ~Madrid|
## |      Sao Paulo|
## |      Maracaibo|
## +---------------+
确认@zero323这是我的实现。 除了口音,我还删除了特殊字符。因为我需要透视和保存一个表,而您不能保存一个列名为的表,;{}\n\t=\/个字符


import re

from pyspark.sql import SparkSession
from pyspark.sql.types import IntegerType, StringType, StructType, StructField
from unidecode import unidecode

spark = SparkSession.builder.getOrCreate()
data = [(1, "  \\ / \\ {____} aŠdá_ \t =  \n () asd ____aa 2134_ 23_"), (1, "N"), (2, "false"), (2, "1"), (3, "NULL"),
        (3, None)]
schema = StructType([StructField("id", IntegerType(), True), StructField("txt", StringType(), True)])
df = SparkSession.builder.getOrCreate().createDataFrame(data, schema)
df.show()

for col_name in ["txt"]:
    tmp_dict = {}
    for col_value in [row[0] for row in df.select(col_name).distinct().toLocalIterator()
                      if row[0] is not None]:
        new_col_value = re.sub("[ ,;{}()\\n\\t=\\\/]", "_", col_value)
        new_col_value = re.sub('_+', '_', new_col_value)
        if new_col_value.startswith("_"):
            new_col_value = new_col_value[1:]
        if new_col_value.endswith("_"):
            new_col_value = new_col_value[:-1]
        new_col_value = unidecode(new_col_value)
        tmp_dict[col_value] = new_col_value.lower()
    df = df.na.replace(to_replace=tmp_dict, subset=[col_name])
df.show()
如果你不能像我一样访问外部库,你可以用

new_col_value = new_col_value.translate(str.maketrans(
                    "ä,ö,ü,ẞ,á,ä,č,ď,é,ě,í,ĺ,ľ,ň,ó,ô,ŕ,š,ť,ú,ů,ý,ž,Ä,Ö,Ü,ẞ,Á,Ä,Č,Ď,É,Ě,Í,Ĺ,Ľ,Ň,Ó,Ô,Ŕ,Š,Ť,Ú,Ů,Ý,Ž",
                    "a,o,u,s,a,a,c,d,e,e,i,l,l,n,o,o,r,s,t,u,u,y,z,A,O,U,S,A,A,C,D,E,E,I,L,L,N,O,O,R,S,T,U,U,Y,Z"))

我删除了我的评论,但您能添加一个注释,说明它不等同于问题中的代码吗?我更新了我的答案。现在应该清楚的是,它不是完全等同的,它的用途有限。感谢您的反馈。使用此函数后,可以在
new_col_value = new_col_value.translate(str.maketrans(
                    "ä,ö,ü,ẞ,á,ä,č,ď,é,ě,í,ĺ,ľ,ň,ó,ô,ŕ,š,ť,ú,ů,ý,ž,Ä,Ö,Ü,ẞ,Á,Ä,Č,Ď,É,Ě,Í,Ĺ,Ľ,Ň,Ó,Ô,Ŕ,Š,Ť,Ú,Ů,Ý,Ž",
                    "a,o,u,s,a,a,c,d,e,e,i,l,l,n,o,o,r,s,t,u,u,y,z,A,O,U,S,A,A,C,D,E,E,I,L,L,N,O,O,R,S,T,U,U,Y,Z"))