[英]More efficient way to loop through PySpark DataFrame and create new columns
[英]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
和神奇的常数3
( approx_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.