如何计算pyspark中包含过多列的数据的不同值

时间:2019-11-28 08:28:40

标签: apache-spark pyspark

在处理Spark中具有大量列的数据时遇到问题。

我当前正在使用countDistinct函数,如下所示:

from pyspark.sql import functions as F
distinct_cnts = df.agg(*(F.countDistinct(col).alias(col) for col in df.columns)).toPandas().T[0].to_dict()

对于3300列,仅50行的数据(采样数据在1mb左右)

我正在使用Spark集群环境(驱动程序和执行程序为1gb)。

当我尝试运行上述功能时,遇到内存问题和stackoverflow错误。

java.lang.StackOverflowError

我不太了解1mb左右的数据如何引起内存问题。有人可以解释一下吗?

当我尝试为spark驱动程序和执行程序分配更多的内存(每个3gb并设置dynamicAllocation)时,上面的函数可以工作,但是每列的另一个计算工作又会导致相同的问题。 例如,功能如下:

df.select(*(F.sum(F.col(c).isNull().cast('int')).alias(c) for c in f.columns)).toPandas().T[0].to_dict()

除了火花配置之外,还有其他解决方法吗? (更好的代码编写方式)

1 个答案:

答案 0 :(得分:0)

这里有两种方法可以尝试/挑战,但是都用Scala编写。注意,我尚未在宽数据帧上对其进行测试,但如有可能,可以随时共享一些匿名数据:

让我们说我们有一个宽的DataFrame(这里只有6列):

val wideDF = Seq(
    ("1","a","b","c","d","e","f"),
    ("2","a","b","c","d2","e","f"),
    ("3","a","b","c2","d3","e","f"),
    ("4","a2","b2","c3","d4","e2","f2")
  ).toDF("key","col1","col2","col3","col4","col5","col6")

+---+----+----+----+----+----+----+
|key|col1|col2|col3|col4|col5|col6|
+---+----+----+----+----+----+----+
|  1|   a|   b|   c|   d|   e|   f|
|  2|   a|   b|   c|  d2|   e|   f|
|  3|   a|   b|  c2|  d3|   e|   f|
|  4|  a2|  b2|  c3|  d4|  e2|  f2|
+---+----+----+----+----+----+----+

方法1:

在这里,我将所有DF列(3300)并创建100个存储桶,每个存储桶包含33列。我的存储桶可以并行化以实现更好的可伸缩性:

val allColumns = wideDF.columns.grouped(100).toList

val reducedDF = allColumns.par.map{colBucket:Array[String] =>
    //We will process aggregates on narrowed dataframes
    wideDF.select(colBucket.map(c => countDistinct($"$c").as(s"distinct_$c")):_*)
  }.reduce(_ join _)

reducedDF.show(false)

+------------+------------+------------+------------+------------+------------+
|distinctcol1|distinctcol2|distinctcol3|distinctcol4|distinctcol5|distinctcol6|
+------------+------------+------------+------------+------------+------------+
|2           |2           |3           |4           |2           |2           |
+------------+------------+------------+------------+------------+------------+

方法2

创建(或使用Panda的)Transpose方法,然后按列名称进行汇总(也可以使用RDD API完成)

def transposeDF(dataframe: DataFrame, transposeBy: Seq[String]): DataFrame = {
    val (cols, types) = dataframe.dtypes.filter{ case (c, _) => !transposeBy.contains(c)}.unzip
    require(types.distinct.size == 1)

    val kvs = explode(array(
      cols.map(c => struct(lit(c).alias("column_name"), col(c).alias("column_value"))): _*
    ))

    val byExprs = transposeBy.map(col)

    dataframe
      .select(byExprs :+ kvs.alias("_kvs"): _*)
      .select(byExprs ++ Seq($"_kvs.column_name", $"_kvs.column_value"): _*)
  }

// We get 1 record per key and column name
transposeDF(wideDF, Seq("key")).groupBy("column_name").agg(countDistinct($"column_value").as("distinct_count")).show(false)

+-----------+--------------+
|column_name|distinct_count|
+-----------+--------------+
|col3       |3             |
|col4       |4             |
|col1       |2             |
|col6       |2             |
|col5       |2             |
|col2       |2             |
+-----------+--------------+