繁体   English   中英

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

[英]PySpark - an efficient way to find DataFrame columns with more than 1 distinct value

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

根据对这个问题的回答,我设法编写了一种有效的方法来获取 null 列的列表(这是我的一元列的子集)并将它们删除如下:

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 内部工作原理的非常有限的理解,这很快,因为方法摘要同时操作整个数据帧(我的初始数据帧中大约有 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和神奇的常数3approx_count_distinct不计算null ,因此3而不是2 )。 由于我不确定approx_count_distinct如何在内部工作,我有点担心3不是一个特别好的常数,因为 function 可能会估计不同( non-null )值的数量,比如 5 当它真的是 1 等等也许需要一个更高的常数来保证候选列表poss_unarcols中没有任何遗漏。

有没有更聪明的方法来做到这一点,理想情况下,这样我什至不必单独删除 null 列并一口气完成所有操作(尽管这实际上非常快,所以这是一个大问题)?

我建议您查看以下功能

pyspark.sql.functions.collect_set(col)

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

它将返回col中的所有值,并消除多个元素。 然后你可以检查结果的长度(是否等于一)。 我会想知道性能,但我认为它肯定会击败distinct()。count()。 让我们看看周一:)

你可以df.na.fill(“一些非exisitng值”)。summary()然后从原始数据框中删除相关列

到目前为止,我找到的最佳解决方案是(它比其他提议的答案更快,虽然不理想,见下文):

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行的列,我只遍历这些行的所有行,使用快速summary("count")方法来分类。列尽我所能。

通过列的所有行看起来非常浪费我,因为一旦找到两个不同的值,我真的不关心列的其余部分是什么。 我不认为这可以在pySpark中解决(但我是初学者),这似乎需要UDF和pySpark UDF这么慢,以至于它不可能比使用countDistinct()更快。 尽管如此,只要数据approx_count_distinct()有许多列没有null行,这个方法就会很慢(而且我不确定有多少人可以信任approx_count_distinct()来区分列中的一个或两个不同的值)

据我所知,它击败了collect_set()方法,并且实际上没有必要填充null值,因为我意识到了(参见代码中的注释)。

我尝试了您的解决方案,但在我的情况下它太慢了,所以我只是抓住了数据框的第一行并检查了重复项。 结果证明它的性能要好得多。 我确信有更好的方法,但我不知道它是什么!

first_row = df.limit(1).collect()[0]

drop_cols = [
    key for key, value in df.select(
        [
            sqlf.count(
                sqlf.when(sqlf.col(column) != first_row[column], column)
            ).alias(column)
            for column in df.columns
        ]
    ).collect()[0].asDict().items() 
    if value == 0
]

df = df.drop(*[drop_cols])

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM