Skip to main content
 首页 » 编程设计

python之PySpark DataFrames之使用不同类型列之间的比较进行过滤

2024年09月03日38itcoder

假设您有一个数据框,其中包含各种类型的列(字符串、 double ...)和一个特殊值“miss”,表示字符串类型列中的“缺失值”。

from pyspark.sql import SparkSession 
import pandas as pd 
 
spark = SparkSession.builder.getOrCreate() 
 
pdf = pd.DataFrame([ 
    [1, 'miss'], 
    [2, 'x'], 
    [None, 'y'] 
], columns=['intcol', 'strcol']) 
 
df = spark.createDataFrame(data=pdf) 

我正在尝试计算每列的非缺失值的数量,使用如下过滤:

col = df['strcol'] 
df.filter(col.isNotNull() & (col != 'miss')).show() 

适用于字符串列:

+------+------+ 
|intcol|strcol| 
+------+------+ 
|   2.0|     x| 
|   NaN|     y| 
+------+------+ 

但是,对于数字列,它会过滤掉所有行:

col = df['intcol'] 
df.filter(col.isNotNull() & (col != 'miss')).show() 
+------+------+ 
|intcol|strcol| 
+------+------+ 
+------+------+ 

这似乎是因为数字列与字符串值的跨类型比较导致全空值:

df.select(df['intcol'] != 'miss').show() 
+---------------------+ 
|(NOT (intcol = miss))| 
+---------------------+ 
|                 null| 
|                 null| 
|                 null| 
+---------------------+ 

我觉得有点出乎意料(例如 1 != '' 是 True,在“普通”Python 中不是 null)

我的问题实际上是几个问题:

  • 为什么交叉类型比较会产生空值?
  • 以“预期方式”测试不同类型之间是否相等的最佳方法是什么?或者(就我而言)我是否需要包含根据列类型切换的单独逻辑?
  • 似乎 df.filter(~df['intcol'].isin(['miss'])) 可以完成这项工作,但我想知道这样效率是否较低?

请您参考如下方法:

让我们从为什么开始。 DataFrame API 是适用于 SQL 和 SQL 评估规则的 DSL。每当您对不同类型的对象应用运算符时,CAST 操作都会根据预定义的规则应用于优先级较低的操作数。在一般数值类型中,具有更高的优先级,因此(遵循执行计划df.select(df['intcol'] != 'miss').explain(True)):

== Parsed Logical Plan == 
'Project [NOT (intcol#0 = miss) AS (NOT (intcol = miss))#12] 
+- LogicalRDD [intcol#0, strcol#1], false 

改写为

== Analyzed Logical Plan == 
(NOT (intcol = miss)): boolean 
Project [NOT (intcol#0 = cast(miss as double)) AS (NOT (intcol = miss))#12] 
+- LogicalRDD [intcol#0, strcol#1], false 

其中 'miss' 是 CASTED 到 double,然后转换为 NULL

== Optimized Logical Plan == 
Project [null AS (NOT (intcol = miss))#22] 
+- LogicalRDD [intcol#0, strcol#1], false 

作为此操作数的转换是未定义的。

因为与 NULL 的相等性也是未定义的 - Difference between === null and isNull in Spark DataDrame - filter 产生空结果。

现在如何解决这个问题。两者都显式转换:

df.filter(df['intcol'].cast("string") != 'miss') 

和 null 安全相等:

df.filter(~df['intcol'].cast("string").eqNullSafe('miss')) 

应该可以解决问题。

另请注意,NaN 值不是 NULL 并且通过 Pandas 进行的转换是有损的 - Pandas dataframe to Spark dataframe, handling NaN conversions to actual null?