这是一种允许您从预定义列表动态设置聚合的解决方案。该解决方案使用 map_from_arrays 等,因此与 Spark >= 2.4.0 兼容:
from pyspark.sql.functions import lit, expr, array, map_from_arrays
df = spark.createDataFrame([
[1, 2.3, 5000],
[2, 5.3, 4000],
[3, 2.1, 3000],
[4, 1.5, 4500]
], ["cola", "colb", "colc"])
aggs = ["min", "max", "avg", "sum"]
aggs_select_expr = [f"value[{idx}] as {agg}" for idx, agg in enumerate(aggs)]
agg_keys = []
agg_values = []
# generate map here where key is col name and value an array of aggregations
for c in df.columns:
agg_keys.append(lit(c)) # the key i.e cola
agg_values.append(array(*[expr(f"{a}({c})") for a in aggs])) # the value i.e [expr("min(a)"), expr("max(a)"), expr("avg(a)"), expr("sum(a)")]
df.agg(
map_from_arrays(
array(agg_keys),
array(agg_values)
).alias("aggs")
) \
.select(explode("aggs")) \
.selectExpr("key as col", *aggs_select_expr) \
.show(10, False)
# +----+------+------+------+-------+
# |col |min |max |avg |sum |
# +----+------+------+------+-------+
# |cola|1.0 |4.0 |2.5 |10.0 |
# |colb|1.5 |5.3 |2.8 |11.2 |
# |colc|3000.0|5000.0|4125.0|16500.0|
# +----+------+------+------+-------+
说明: 使用表达式array(*[expr(f"{a}({c})") for a in aggs]) 我们创建一个包含当前列的所有聚合的数组。生成的数组的每个项目都使用语句expr(f"{a}({c})" 进行评估,这将产生即expr("min(a)")。
该数组将包含agg_values 的值,它们与agg_keys 将通过表达式map_from_arrays(array(agg_keys), array(agg_values)) 组成我们的最终映射。 map的结构是这样的:
map(
cola -> [min(cola), max(cola), avg(cola), sum(cola)]
colb -> [min(colb), max(colb), avg(colb), sum(colb)]
colc -> [min(cola), max(colc), avg(cola), sum(colc)]
)
为了提取我们需要的信息,我们必须用explode("aggs") 分解先前的地图,这将创建两列key 和value,我们在选择语句中使用它们。
aggs_select_expr 将包含["value[0] as min", "value[1] as max", "value[2] as avg", "value[3] as sum"] 形式的值,这将是selectExpr statememnt 的输入。
更新:
我意识到通过省略聚合(也称为隐式 groupBy 到 agg)有一种更高效的方法。我们可以通过create_map 内置函数实现同样的功能:
from pyspark.sql.functions import create_map, expr, array
from itertools import chain
df = spark.createDataFrame([
[1, 2.3, 5000],
[2, 5.3, 4000],
[3, 2.1, 3000],
[4, 1.5, 4500]
], ["cola", "colb", "colc"])
aggs = ["min", "max", "avg", "sum"]
aggs_select_expr = [f"value[{idx}] as {agg}" for idx, agg in enumerate(aggs)]
df.select(explode(
create_map(*list(
chain(*[(lit(c), array(*[expr(f"{a}({c})") for a in aggs]))
for c in df.columns
])))
)
) \
.selectExpr("key as col", *aggs_select_expr)
注意:除了代码更少之外,第二种方法的主要优点是它只包含窄转换而不包含宽转换,即groupBy。我们将提高性能,因为它避免了洗牌。