PySpark-查找具有多个不同值的DataFrame列的有效方法

时间:2019-04-12 10:49:03

标签: python apache-spark dataframe pyspark

我需要一种有效的方法来列出和删除Spark DataFrame中的一元列(我使用PySpark API)。我将一元列定义为最多具有一个不同值的一列,出于定义的目的,我也将null视为一个值。这意味着在某些行中具有一个不同的non-null值而在其他行中具有null的列不是一元列。

基于对this question的回答,我设法写出一种有效的方法来获取空列(它们是我的一元列的子集)的列表,并按如下方式删除它们:

counts = df.summary("count").collect()[0].asDict()
null_cols = [c for c in counts.keys() if counts[c] == '0']
df2 = df.drop(*null_cols)

基于我对Spark内部工作的非常有限的理解,这很快,因为方法摘要同时操作了整个数据帧(我的初始DataFrame中大约有300列)。不幸的是,我找不到类似的方法来处理第二种一元列-没有null值但是lit(something)的一元列。

我目前所拥有的是这个(使用我从上面的代码片段中获得的df2):

prox_counts = (df2.agg(*(F.approx_count_distinct(F.col(c)).alias(c)
                         for c in df2.columns
                         )
                       )
                  .collect()[0].asDict()
               )
poss_unarcols = [k for k in prox_counts.keys() if prox_counts[k] < 3]
unar_cols = [c for c in poss_unarcols if df2.select(c).distinct().count() < 2]

本质上,我首先以快速但近似的方式找到一元的列,然后更详细,更慢地查看“候选”。

我不喜欢它的原因是:a)即使进行了近似的预选择,它仍然相当缓慢,需要一分钟的时间才能运行,即使此时我只有大约70列(大约600万列)行)和b)我使用带有神奇常数approx_count_distinct的{​​{1}}(3不计入approx_count_distinct,因此null而不是3 )。由于我不确定2在内部的工作方式,因此我有点担心approx_count_distinct不是一个特别好的常数,因为该函数可能会估计不同(3)个值的数量例如5,当它实际上是1时,因此可能需要一个更高的常数以确保候选列表non-null中没有丢失。

是否有一种更聪明的方式来执行此操作,理想情况下,我什至不必不必单独删除null列并一次完成所有操作(尽管这实际上非常快,因此是一个大问题) ?

3 个答案:

答案 0 :(得分:0)

我建议您看看以下功能

pyspark.sql.functions.collect_set(col)

https://spark.apache.org/docs/latest/api/python/pyspark.sql.html?highlight=dataframe

它将以col的形式返回所有值,并消除乘法元素。然后,您可以检查结果的长度(是否等于1)。我想知道性能,但我认为它肯定会胜过distinct()。count()。让我们在星期一看看:)

答案 1 :(得分:0)

您可以df.na.fill(“ some non exisitng value”)。summary(),然后从原始数据框中删除相关列

答案 2 :(得分:0)

到目前为止,我发现的最佳解决方案是这种方法(它比其他提出的答案要快,尽管不理想,请参见下文):

rows = df.count()
nullcounts = df.summary("count").collect()[0].asDict()
del nullcounts['summary']
nullcounts = {key: (rows-int(value)) for (key, value) in nullcounts.items()}

# a list for columns with just null values
null_cols = []
# a list for columns with no null values
full_cols = []

for key, value in nullcounts.items():
    if value == rows:
        null_cols.append(key)
    elif value == 0:
        full_cols.append(key)

df = df.drop(*null_cols)

# only columns in full_cols can be unary
# all other remaining columns have at least 1 null and 1 non-null value
try:
    unarcounts = (df.agg(*(F.countDistinct(F.col(c)).alias(c) for c in full_cols))
                    .collect()[0]
                    .asDict()
                  )
    unar_cols = [key for key in unarcounts.keys() if unarcounts[key] == 1]
except AssertionError:
    unar_cols = []

df = df.drop(*unar_cols)

这相当快地工作,主要是因为我没有太多的“完整列”,即不包含null行的列,而我仅使用快速{{1}尽可能多地整理列的方法。

遍历一列的所有行似乎对我来说是非常浪费的,因为一旦找到两个不同的值,我就不在乎该列其余部分的内容。我不认为这可以在pySpark中解决(但是我是初学者),这似乎需要UDF,而pySpark UDF太慢了,以至于它不可能比使用summary("count")更快。不过,只要数据框中有许多没有countDistinct()行的列,该方法就会非常慢(我不确定有多少人可以信任null来区分一两个不同的列中的值)

据我所知,它胜过approx_count_distinct()的方法,实际上并不需要像我意识到的那样填充collect_set()的值(请参阅代码中的注释)。