我试图找出是否有一个函数可以检查spark DataFrame的列是否包含列表中的任何值:
# define a dataframe
rdd = sc.parallelize([(0,100), (0,1), (0,2), (1,2), (1,10), (1,20), (3,18), (3,18), (3,18)])
df = sqlContext.createDataFrame(rdd, ["id", "score"])
# define a list of scores
l = [1]
# filter out records by scores by list l
records = df.filter(~df.score.contains(l))
# expected: (0,100), (0,1), (1,10), (3,18)
运行此代码时出现问题:
java.lang.RuntimeException: Unsupported literal type class java.util.ArrayList [1]
有没有办法做到这一点,还是我们必须遍历列表才能传递包含?
答案 0 :(得分:0)
如果我对您的理解正确,那么您希望有一个列表,其中包含仅1
个元素。您要检查此元素是否出现在乐谱中的位置。在这种情况下,使用字符串而不是直接使用数字更容易。
您可以使用自定义地图功能执行此操作,并通过udf进行应用(直接应用会导致某些奇怪的行为,并且仅在某些情况下有效)。
找到以下代码:
rdd = sc.parallelize([(0,100), (0,1), (0,2), (1,2), (1,10), (1,20), (3,18), (3,18), (3,18)])
df = sqlContext.createDataFrame(rdd, ["id", "score"])
l = [1]
def filter_list(score, l):
found = True
for e in l:
if str(e) not in str(score): #The filter that checks if an Element e
found = False #does not appear in the score
if found:
return True #boolean value if the all elements were found
else:
return False
def udf_filter(l):
return udf(lambda score: filter_list(score, l)) #make a udf function out of the filter list
df.withColumn("filtered", udf_filter(l)(col("score"))).filter(col("filtered")==True).drop("filtered").show()
#apply the function and store results in "filtered" column afterwards
#only select the successful filtered rows and drop the column
输出:
+---+-----+
| id|score|
+---+-----+
| 0| 100|
| 0| 1|
| 1| 10|
| 3| 18|
| 3| 18|
| 3| 18|
+---+-----+
答案 1 :(得分:0)
我看到了一些without using a udf
的方法。
您可以在pyspark.sql.functions.regexp_extract
中使用列表推导,利用以下事实:如果没有匹配项,则返回空字符串。
尝试提取列表l
中的所有值并连接结果。如果最终的连接字符串是一个空字符串,则表示没有匹配的值。
例如:
from pyspark.sql.functions import concat, regexp_extract
records = df.where(concat(*[regexp_extract("score", str(val), 0) for val in l]) != "")
records.show()
#+---+-----+
#| id|score|
#+---+-----+
#| 0| 100|
#| 0| 1|
#| 1| 10|
#| 3| 18|
#| 3| 18|
#| 3| 18|
#+---+-----+
如果您查看执行计划,您会发现它足够聪明,将score
列隐式转换为string
:
records.explain()
#== Physical Plan ==
#*Filter NOT (concat(regexp_extract(cast(score#11L as string), 1, 0)) = )
#+- Scan ExistingRDD[id#10L,score#11L]
另一种方法是使用pyspark.sql.Column.like
(或与rlike
类似):
from functools import reduce
from pyspark.sql.functions import col
records = df.where(
reduce(
lambda a, b: a|b,
map(
lambda val: col("score").like(val.join(["%", "%"])),
map(str, l)
)
)
)
产生与上面相同的输出,并具有以下执行计划:
#== Physical Plan ==
#*Filter Contains(cast(score#11L as string), 1)
#+- Scan ExistingRDD[id#10L,score#11L]
如果只需要不同的记录,则可以执行以下操作:
records.distinct().show()
#+---+-----+
#| id|score|
#+---+-----+
#| 0| 1|
#| 0| 100|
#| 3| 18|
#| 1| 10|
#+---+-----+