pyspark.sql.functions是一个内置函数的集合,该模块功能强大,API函数众多,可能并不会每个都会讲到,但是重点的、难的、经常使用的一定会讲解到的。
array_distinct(col)是一个集合函数,表示从数组中删除重复值。
>>> df = spark.createDataFrame([([1, 2, 3, 2],), ([4, 5, 5, 4],)], ['data'])
>>> df.select(array_distinct(df.data)).collect()
[Row(array_distinct(data)=[1, 2, 3]), Row(array_distinct(data)=[4, 5])]
相当于求差集,并且返回col1中剩余的元素
>>> from pyspark.sql import Row
>>> df = spark.createDataFrame([Row(c1=["b", "a", "c"], c2=["c", "d", "a", "f"])])
>>> df.select(array_except(df.c1, df.c2)).collect()
[Row(array_except(c1, c2)=['b'])]
集合函数:求两列集合的交集
>>> from pyspark.sql import Row
>>> df = spark.createDataFrame([Row(c1=["b", "a", "c"], c2=["c", "d", "a", "f"])])
>>> df.select(array_intersect(df.c1, df.c2)).collect()
[Row(array_intersect(c1, c2)=['a', 'c'])]
集合函数:排序
>>> df = spark.createDataFrame([([2, 1, None, 3],),([1],),([],)], ['data'])
>>> df.select(array_sort(df.data).alias('r')).collect()
[Row(r=[1, 2, 3, None]), Row(r=[1]), Row(r=[])]
集合函数:并集
>>> from pyspark.sql import Row
>>> df = spark.createDataFrame([Row(c1=["b", "a", "c"], c2=["c", "d", "a", "f"])])
>>> df.select(array_union(df.c1, df.c2)).collect()
[Row(array_union(c1, c2)=['b', 'a', 'c', 'd', 'f'])]
聚合函数,返回列表
>>> df2 = spark.createDataFrame([(2,), (5,), (5,)], ('age',))
>>> df2.agg(collect_list('age')).collect()
[Row(collect_list(age)=[2, 5, 5])]
聚合函数,返回set集合
>>> df2 = spark.createDataFrame([(2,), (5,), (5,)], ('age',))
>>> df2.agg(collect_set('age')).collect()
[Row(collect_set(age)=[5, 2])]
合并两列、以连接符何必两列
>>> df = spark.createDataFrame([('abcd','123')], ['s', 'd'])
>>> df.select(concat(df.s, df.d).alias('s')).collect()
[Row(s='abcd123')]
>>> df.select(concat_ws('-', df.s, df.d).alias('s')).collect()
[Row(s='abcd-123')]
这里需要注意一下,不仅可以求一列、还可以求多列去重之后的数目。
>>> df.agg(countDistinct(df.age, df.name).alias('c')).collect()
[Row(c=2)]
生成字典
>>> df.select(create_map('name', 'age').alias("map")).collect()
[Row(map={'Alice': 2}), Row(map={'Bob': 5})]
>>> df.select(create_map([df.name, df.age]).alias("map")).collect()
[Row(map={'Alice': 2}), Row(map={'Bob': 5})]
查看列值是否为空
>>> df = spark.createDataFrame([(1, None), (None, 2)], ("a", "b"))
>>> df.select(isnull("a").alias("r1"), isnull(df.a).alias("r2")).collect()
[Row(r1=False, r2=False), Row(r1=True, r2=True)]
从指定的字符串列中提取与Java正则表达式匹配的特定组。 如果正则表达式不匹配,或者指定的组不匹配,则返回空字符串。
>>> df = spark.createDataFrame([('100-200',)], ['str'])
>>> df.select(regexp_extract('str', r'(\d+)-(\d+)', 1).alias('d')).collect()
[Row(d='100')]
正则替换
>>> df = spark.createDataFrame([('100-200',)], ['str'])
>>> df.select(regexp_replace('str', r'(\d+)', '--').alias('d')).collect()
[Row(d='-----')]
返回特定位置的子串
>>> df = spark.createDataFrame([('abcd',)], ['s',])
>>> df.select(substring(df.s, 1, 2).alias('s')).collect()
[Row(s='ab')]
计算条件列表并返回多个可能的结果表达式之一。 如果未调用Column.otherwise(),则为不匹配的条件返回None。
>>> df.select(when(df['age'] == 2, 3).otherwise(4).alias("age")).collect()
[Row(age=3), Row(age=4)]
>>> df.select(when(df.age == 2, df.age + 1).alias("age")).collect()
[Row(age=3), Row(age=None)]
简单来说,pyspark.sql.functions.udf()用来将用户自定义的函数进行转化,使其可以在pyspark中被使用,udf是用户自定义函数用法中最基础的,下面举例说明。
>>>def age_name(age,name):
return str(age)+name
>>>age_name_udf=fn.udf(age_name,StringType())
>>>df=spark.createDataFrame([(5,"Alice",80),(5,"Bob",80),\
(10,"Tom",80)],["age","name","height"])
>>>slen_udf = fn.udf(lambda s: len(s), IntegerType())
>>>df.select(age_name_udf(df.age,df.name).alias("age_name"),\
slen_udf(df.name).alias("name_len")).collect()
[Row(age_name='5Alice', name_len=5),
Row(age_name='5Bob', name_len=3),
Row(age_name='10Tom', name_len=3)]
用户可以根据实际需求通过udf自定义函数实现相应的功能。
用过pandas和pyspark的人可能知道,pandas对于数据的操作有时候确实比pyspark强大或者说是方便,所以,有时候用pyspark处理数据不方便的时候,需要用到pandas,这时候就得将pyspark的df转化为pandas,即to_Pandas(),但是pyspark是分布式处理,而pandas是本地单机处理,一旦数据量的大的时候,to_pandas是行不通的。这时候pandas_udf派上用场了。
Pandas_UDF是在PySpark2.3中新引入的API,由Spark使用Arrow传输数据,使用Pandas处理数据。Pandas_UDF是使用关键字pandas_udf作为装饰器或包装函数来定义的,不需要额外的配置。目前,有三种类型的Pandas_UDF,分别是Scalar(标量映射)和Grouped Map(分组映射)、GROUPED_AGG(分组聚合)。其中,f为自定义函数,returnType为返回类型,functionType为函数类型,默认为Scalar类型。
Scalar Pandas UDF用于向量化标量操作。常常与 pyspark.sql.DataFrame.withColumn() and pyspark.sql.DataFrame.select()等函数一起使用。其中调用的Python函数需要使用pandas.Series作为输入并返回一个具有相同长度的pandas.Series。具体执行流程是,Spark将列分成批,并将每个批作为数据的子集进行函数的调用,进而执行panda UDF,最后将结果连接在一起。
from pyspark.sql.functions import pandas_udf,PandasUDFType
from pyarrow.lib import cpu_count,set_cpu_count
from pyspark.sql.types import IntegerType
slen = pandas_udf(lambda s: s.str.len(), IntegerType())
@pandas_udf(StringType())
def to_upper(s):
return s.str.upper()
@pandas_udf(IntegerType(),PandasUDFType.SCALAR)
def add_one(x):
return x + 1
df = spark.createDataFrame([(1, "John Doe", 21)],("id", "name", "age"))
df.select(slen("name").alias("slen(name)"), to_upper("name"), add_one("age")).collect()
#[Row(slen(name)=8, to_upper(name)='JOHN DOE', add_one(age)=22)]
其中,@pandas_udf是注解形式的用法,用起来更为方便。
Grouped map(分组映射)panda_udf与groupBy().apply()一起使用,实现了“split-apply-combine”模式。“split-apply-combine”包括三个步骤:
返回的pandas.DataFrame的列标签必须与定义的returnType模式中的字段名称匹配, 返回的pandas.DataFrame的长度可以是任意的。
df=spark.createDataFrame([(26,"tan",170),(24,"qian",171),(20,"tao",183),
(24,"liu",168),(26,"song",158),(26,"jiang",168)],
["age","name","height"])
ls=[StructField("age",IntegerType(),True),StructField("name",StringType(),True),StructField("height",IntegerType(),True)]
@pandas_udf(StructType(ls),PandasUDFType.GROUPED_MAP)
def agg(pdf):
return pdf.assign(name="".join(list(pdf.name)))
df.groupby("age").apply(agg).show()
+---+------------+------+
|age| name|height|
+---+------------+------+
| 26|tansongjiang| 170|
| 26|tansongjiang| 158|
| 26|tansongjiang| 168|
| 20| tao| 183|
| 24| qianliu| 171|
| 24| qianliu| 168|
+---+------------+------+
返回值类型,可以是StructType类型,也可以是字符串表示,如下例所示:
>>> from pyspark.sql.functions import pandas_udf, PandasUDFType
>>> df = spark.createDataFrame(
... [(1, 1.0), (1, 2.0), (2, 3.0), (2, 5.0), (2, 10.0)],
... ("id", "v"))
>>> :pandas_udf("id long, v double", PandasUDFType.GROUPED_MAP)
... def normalize(pdf):
... v = pdf.v
... return pdf.assign(v=(v - v.mean()) / v.std())
>>> df.groupby("id").apply(normalize).show()
+---+-------------------+
| id| v|
+---+-------------------+
| 1|-0.7071067811865475|
| 1| 0.7071067811865475|
| 2|-0.8320502943378437|
| 2|-0.2773500981126146|
| 2| 1.1094003924504583|
+---+-------------------+
Grouped aggregate Panda UDF类似于Spark聚合函数。Grouped aggregate Panda UDF常常与groupBy().agg(),它定义了来自一个或多个的聚合。级数到标量值,其中每个pandas.Series表示组中的一列。 需要注意的是,这种类型的UDF不支持部分聚合,组的所有数据都将加载到内存中。returnType应该是基本数据类型,例如DoubleType。 返回的标量可以是python原始类型,例如int或float或numpy数据类型,比如numpy.int64或numpy.float64。
df=spark.createDataFrame([(26,"tan",170),(24,"qian",171),(20,"tao",183),
(24,"liu",168),(26,"song",158),(26,"jiang",168)],
["age","name","height"])
@pandas_udf("string",PandasUDFType.GROUPED_AGG)
def list_udf(v):
return str(v.to_list())
@pandas_udf("int",PandasUDFType.GROUPED_AGG)
def mean_udf(v):
return v.mean()
df.groupBy("age").agg(list_udf("name"),mean_udf("height")).show()
+---+--------------------+----------------+
|age| list_udf(name)|mean_udf(height)|
+---+--------------------+----------------+
| 26|['tan', 'song', '...| 165|
| 20| ['tao']| 183|
| 24| ['qian', 'liu']| 169|
+---+--------------------+----------------+
pyspark.sql.functions的基本用法到这里就讲完了,后续会陆续更新的内容。