使用自定义分区程序在Pyspark中对数据帧进行分区

时间:2018-10-13 07:45:23

标签: pyspark apache-spark-sql

正在寻找有关在Pyspark中使用自定义分区程序的信息。我有一个数据框,其中包含各个国家/地区的国家/地区数据。因此,如果我在“国家/地区”列上进行分区,它将把我的数据分配到n个分区中,并将类似的国家/地区数据保留到特定分区中。当我看到使用glom()方法时,这将创建倾斜的分区数据。某些国家(例如美国和中国)在特定数据框中具有大量数据。我想重新划分数据框,以便如果这些国家是美国和中国,那么它将进一步分成大约10个分区,其他国家(如IND,THA,AUS等)的分区保持相同。我们可以在Pyspark代码中扩展分区器类吗?我已经在下面的链接中阅读了此内容,我们可以在scala Spark应用程序中扩展scala分区程序类,并可以修改分区程序类以使用自定义逻辑根据需求对数据进行重新分区。就像我拥有的​​那个..请帮助在Pyspark中实现此解决方案..请参见下面的链接What is an efficient way to partition by column but maintain a fixed partition count?


我正在使用Spark版本2.3.0.2,以下是我的数据框结构:

datadf= spark.sql("""
    SELECT    
        ID_NUMBER ,SENDER_NAME ,SENDER_ADDRESS ,REGION_CODE ,COUNTRY_CODE
    from udb.sometable
""");

传入的数据包含六个国家/地区的数据,例如AUSINDTHARUSCHNUSACHNUSA的数据倾斜。

因此,如果我对repartitionCOUNTRY_CODE,则两个分区包含很多数据,而其他分区则很好。我使用glom()方法进行了检查。

newdf = datadf.repartition("COUNTRY_CODE")

from pyspark.sql import SparkSession
from pyspark.sql import  HiveContext, DataFrameWriter, DataFrame

newDF = datadf.repartitionByRange(3,"COUNTRY_CODE","USA")

我正尝试将我的数据仅重新划分为国家USACHN的3个分区,并希望将其他国家/地区的数据保留为单个分区。

This is what I am expecting 
AUS- one partition
IND- one partition
THA- one partition
RUS- one partition
CHN- three partition
USA- three partition
  

回溯(最近一次通话最后一次):文件“”,第1行,在      文件   “ /usr/hdp/current/spark2-client/python/pyspark/sql/dataframe.py”,行   1182,在 getattr 中       “'%s'对象没有属性'%s'”%((自身。名称,名称))AttributeError:'DataFrame'对象没有属性   'repartitionByRange'

3 个答案:

答案 0 :(得分:2)

尝试使用散列进行类似的操作:

newDf = oldDf.repartition(N, $"col1", $"coln")

或用于测距方法:

newDF = oldDF.repartitionByRange(N, $"col1", $"coln")

DF尚无自定义分区。

在您的情况下,我会进行哈希处理,但是并不能保证。

但是,如果数据偏斜,则可能需要做一些额外的工作,例如2列进行分区是最简单的方法。

例如现有或新列-在这种情况下,该列适用于给定国家/地区的分组,例如1 .. N,并在两个cols上进行分区。

对于具有很多分组的国家,您将获得N个合成子部门;对于低基数的其他人,只有一个这样的组号。不是太难。两个分区占用的空间可能超过1个col。

在我看来,对分区进行统一的编号填充需要付出很多努力,而这并不是真正可以实现的,但是这里的第二种最佳方法就足够了。在一定程度上相当于自定义分区。

否则,可以在DF上使用.withColumn来模拟具有这些规则的自定义分区并填充新的DF列,然后应用repartitionByRange。也不难。

答案 1 :(得分:1)

结构化API中没有自定义分区程序,因此要使用自定义分区程序,您需要下拉至RDD API。简单的3个步骤如下:

  1. 将结构化API转换为RDD API
dataRDD = dataDF.rdd
  1. 在RDD API中应用自定义分区程序
import random

# Extract key from Row object
dataRDD = dataRDD.map(lambda r: (r[0], r))

def partitioner(key):
    if key == "CHN":
        return random.randint(1, 10)
    elif key == "USA":
        return random.randint(11, 20)
    else:
        # distinctCountryDict is a dict mapping distinct countries to distinct integers
        # these distinct integers should not overlap with range(1, 20)
        return distinctCountryDict[key]

numPartitions = 100
dataRDD = dataRDD.partitionBy(numPartitions, partitioner)

# Remove key extracted previously
dataRDD = dataRDD.map(lambda r: r[1])
  1. 将RDD API转换回结构化API
dataDF = dataRDD.toDF()

这样,您可以在结构化API中获得两全其美,Spark类型和优化的物理计划,并在低级RDD API中获得自定义分区。而且只有在绝对必要时,我们才使用低级API。

答案 2 :(得分:0)

在 PySpark 上没有直接应用用户定义的分区器的方法,捷径是创建一个带有 UDF 的新列,根据业务逻辑为每个记录分配一个分区 ID。并使用新列进行分区,这样数据就可以均匀分布了。

numPartitions= 3
df = df.withColumn("Hash#", udf_country_hash(df['Country']))
df = df.withColumn("Partition#", df["Hash#"] % numPartitions)
df.repartition(numPartitions, "Partition#")

请查看代码的在线版本@ https://databricks-prod-cloudfront.cloud.databricks.com/public/4027ec902e239c93eaaa8714f173bcfc/8963851468310921/2231943684776180/5846184720595634/latest.html

根据我的经验,将 DataFrame 转换为 RDD 再转换回 DataFrame 是一项代价高昂的操作,最好避免它。