PySpark DataFrame上的分组数据的熊猫式转换

时间:2015-12-25 16:39:50

标签: python pandas apache-spark pyspark apache-spark-sql

如果我们有一个由一列类别和一列值组成的Pandas数据框,我们可以通过执行以下操作来删除每个类别中的均值:

df["DemeanedValues"] = df.groupby("Category")["Values"].transform(lambda g: g - numpy.mean(g))

据我所知,Spark数据帧不直接提供这种分组/转换操作(我在Spark 1.5.0上使用PySpark)。那么,实现这种计算的最佳方法是什么?

我尝试使用group-by / join,如下所示:

df2 = df.groupBy("Category").mean("Values")
df3 = df2.join(df)

但它很慢,因为据我所知,每个类别都需要对DataFrame进行全面扫描。

我认为(但尚未验证)如果我将group-by / mean的结果收集到字典中,然后在UDF中使用该字典,我可以加快速度,如下所示:

nameToMean = {...}
f = lambda category, value: value - nameToMean[category]
categoryDemeaned = pyspark.sql.functions.udf(f, pyspark.sql.types.DoubleType())
df = df.withColumn("DemeanedValue", categoryDemeaned(df.Category, df.Value))

在不牺牲性能的情况下,是否存在表达此类操作的惯用方法?

3 个答案:

答案 0 :(得分:7)

  

据我所知,每个类别都需要对DataFrame进行全面扫描。

不,它没有。 DataFrame聚合使用类似于aggregateByKey的逻辑执行。请参阅DataFrame groupBy behaviour/optimization较慢的部分是join,需要排序/改组。但它仍然不需要每组扫描。

如果这是一个确切的代码,你使用它很慢,因为你没有提供连接表达式。因此,它只是执行笛卡尔积。所以它不仅效率低下而且不正确。你想要这样的东西:

from pyspark.sql.functions import col

means = df.groupBy("Category").mean("Values").alias("means")
df.alias("df").join(means, col("df.Category") == col("means.Category"))
  

我认为(但尚未验证)如果我将group-by / mean的结果收集到字典中,然后在UDF中使用该字典,我可以加快速度。

虽然表现会因具体情况而有所不同。使用Python UDF的一个问题是它必须将数据移入和移出Python。不过,这绝对值得一试。您应该考虑使用nameToMean的广播变量。

  

在不牺牲性能的情况下,是否存在表达此类操作的惯用方法?

在PySpark 1.6中,您可以使用broadcast功能:

df.alias("df").join(
    broadcast(means), col("df.Category") == col("means.Category"))

但在< = 1.5。

中不可用

答案 1 :(得分:1)

实际上,使用Hive OVER表达式在Spark中有一种惯用的方法。

df.registerTempTable('df')
with_category_means = sqlContext.sql('select *, mean(Values) OVER (PARTITION BY Category) as category_mean from df')

引擎盖下,这是使用窗口功能。我不确定这是否比你的解决方案更快,但

答案 2 :(得分:0)

您可以使用Window来完成

import pyspark.sql.functions as F
from pyspark.sql.window import Window

window_var = Window().partitionBy('Categroy')
df = df.withColumn('DemeanedValues', F.col('Values') - F.mean('Values').over(window_var))