簡體   English   中英

Pyspark 顯示每行具有最低值的列

[英]Pyspark show the column that has the lowest value for each row

我有以下數據框

在此處輸入圖片說明

df_old_list= [
  { "Col1":"0", "Col2" : "7","Col3": "8", "Col4" : "","Col5": "20"},
{"Col1":"5", "Col2" : "5","Col3": "5", "Col4" : "","Col5": "28"},
 { "Col1":"-1", "Col2" : "-1","Col3": "13", "Col4" : "","Col5": "83"},

 {"Col1":"-1", "Col2" : "6","Col3": "6", "Col4" : "","Col5": "18"},

 { "Col1":"5", "Col2" : "4","Col3": "2", "Col4" : "","Col5": "84"},

  { "Col1":"0", "Col2" : "0","Col3": "14", "Col4" : "7","Col5": "86"}
]

spark = SparkSession.builder.getOrCreate()
df_old_list = spark.createDataFrame(Row(**x) for x in df_old_list)
df_old_list.show()

+----+----+----+----+----+
|Col1|Col2|Col3|Col4|Col5|
+----+----+----+----+----+
|   0|   7|   8|    |  20|
|   5|   5|   5|    |  28|
|  -1|  -1|  13|    |  83|
|  -1|   6|   6|    |  18|
|   5|   4|   2|    |  84|
|   0|   0|  14|   7|  86|
+----+----+----+----+----+

我想從每一行的每一列中獲得最低值。

這是我迄今為止能夠實現的目標

df1=df_old_list.selectExpr("*","array_sort(split(concat_ws(',',*),','))[0] lowest_col")

df1.show()

+----+----+----+----+----+----------+
|Col1|Col2|Col3|Col4|Col5|lowest_col|
+----+----+----+----+----+----------+
|   0|   7|   8|    |  20|          |
|   5|   5|   5|    |  28|          |
|  -1|  -1|  13|    |  83|          |
|  -1|   6|   6|    |  18|          |
|   5|   4|   2|    |  84|          |
|   0|   0|  14|   7|  86|         0|
+----+----+----+----+----+----------+

問題是 Col4 是空白的,因此它無法計算最低值。 我正在尋找的東西是這樣的:無論空白列如何,我都會得到最低值,而且如果有多個最低數字,那么列字段名稱應顯示在 minimum_col_title 中作為連接。

+-----------------+----------+----+----+----+----+----+
|lowest_cols_title|lowest_col|Col1|Col2|Col3|Col4|Col5|
+-----------------+----------+----+----+----+----+----+
|             Col1|         0|   0|   7|   8|    |  20|
|   Col1;Col2;Col3|         5|   5|   5|   5|    |  28|
|        Col1;Col2|        -1|  -1|  -1|  13|    |  83|
|             Col1|        -1|  -1|   6|   6|    |  18|
|             Col3|         5|   5|   4|   2|    |  84|
|        Col1;Col2|         0|   0|   0|  14|   7|  86|
+-----------------+----------+----+----+----+----+----+

您可以使用pyspark.sql.functions.least

返回列名列表的最小值,跳過空值。 此函數至少需要 2 個參數。 如果所有參數都為空,它將返回空值。

一旦我們有了最小列,我們就可以將最小值與所有列進行比較並創建另一列。

創建數據框:

from pyspark.sql import Row
from pyspark.sql.functions import col,least,when,array,concat_ws
df_old_list= [
  { "Col1":"0", "Col2" : "7","Col3": "8", "Col4" : "","Col5": "20"},  {"Col1":"5", "Col2" : "5","Col3": "5", "Col4" : "","Col5": "28"},
  { "Col1":"-1", "Col2" : "-1","Col3": "13", "Col4" : "","Col5": "83"},  {"Col1":"-1", "Col2" : "6","Col3": "6", "Col4" : "","Col5": "18"},
  { "Col1":"5", "Col2" : "4","Col3": "2", "Col4" : "","Col5": "84"},  { "Col1":"0", "Col2" : "0","Col3": "14", "Col4" : "7","Col5": "86"}]
df = spark.createDataFrame(Row(**x) for x in df_old_list)
from pyspark.sql.functions import least, when

計算行式最小值和具有最小值的所有列。

collist = df.columns
min_ = least(*[
    when(col(c) == "", float("inf")).otherwise(col(c).cast('int'))
    for c in df.columns
]).alias("lowest_col")

df = df.select("*", min_)
df = df.select("*",concat_ws(";",array([
       when(col(c)==col("lowest_col") ,c).otherwise(None) 
       for c in collist
     ])).alias("lowest_cols_title") )

df.show(10,False)

輸出:

+----+----+----+----+----+----------+-----------------+
|Col1|Col2|Col3|Col4|Col5|lowest_col|lowest_cols_title|
+----+----+----+----+----+----------+-----------------+
|0   |7   |8   |    |20  |0.0       |Col1             |
|5   |5   |5   |    |28  |5.0       |Col1;Col2;Col3   |
|-1  |-1  |13  |    |83  |-1.0      |Col1;Col2        |
|-1  |6   |6   |    |18  |-1.0      |Col1             |
|5   |4   |2   |    |84  |2.0       |Col3             |
|0   |0   |14  |7   |86  |0.0       |Col1;Col2        |
+----+----+----+----+----+----------+-----------------+

有幾種方法可以避免空列。

  1. 問題是您的列具有字符串類型:
df_old_list.dtypes
Out[13]: 
[('Col1', 'string'),
 ('Col2', 'string'),
 ('Col3', 'string'),
 ('Col4', 'string'),
 ('Col5', 'string')]

所以你可以將它們轉換為 int:

df_old_list = df_old_list.withColumn('Col1', F.col('Col1').cast('int'))
df_old_list = df_old_list.withColumn('Col2', F.col('Col2').cast('int'))
df_old_list = df_old_list.withColumn('Col3', F.col('Col3').cast('int'))
df_old_list = df_old_list.withColumn('Col4', F.col('Col4').cast('int'))
df_old_list = df_old_list.withColumn('Col5', F.col('Col5').cast('int'))
df_old_list.show()
+----+----+----+----+----+
|Col1|Col2|Col3|Col4|Col5|
+----+----+----+----+----+
|   0|   7|   8|null|  20|
|   5|   5|   5|null|  28|
|  -1|  -1|  13|null|  83|
|  -1|   6|   6|null|  18|
|   5|   4|   2|null|  84|
|   0|   0|  14|   7|  86|
+----+----+----+----+----+

現在您的代碼結果如下:

df1=df_old_list.selectExpr("*","array_sort(split(concat_ws(',',*),','))[0] lowest_col")
   ...: 
   ...: df1.show()
+----+----+----+----+----+----------+
|Col1|Col2|Col3|Col4|Col5|lowest_col|
+----+----+----+----+----+----------+
|   0|   7|   8|null|  20|         0|
|   5|   5|   5|null|  28|        28|
|  -1|  -1|  13|null|  83|        -1|
|  -1|   6|   6|null|  18|        -1|
|   5|   4|   2|null|  84|         2|
|   0|   0|  14|   7|  86|         0|
+----+----+----+----+----+----------+
  1. 例如,您可以用一些大整數替換空值
df_old_list = df_old_list.replace('', str((1 << 31) - 1))
df_old_list.show()
+----+----+----+----------+----+
|Col1|Col2|Col3|      Col4|Col5|
+----+----+----+----------+----+
|   0|   7|   8|2147483647|  20|
|   5|   5|   5|2147483647|  28|
|  -1|  -1|  13|2147483647|  83|
|  -1|   6|   6|2147483647|  18|
|   5|   4|   2|2147483647|  84|
|   0|   0|  14|         7|  86|
+----+----+----+----------+----+

然后進行操作:

df1=df_old_list.selectExpr("*","array_sort(split(concat_ws(',',*),','))[0] lowest_col")

df1.show()
+----+----+----+----------+----+----------+
|Col1|Col2|Col3|      Col4|Col5|lowest_col|
+----+----+----+----------+----+----------+
|   0|   7|   8|2147483647|  20|         0|
|   5|   5|   5|2147483647|  28|2147483647|
|  -1|  -1|  13|2147483647|  83|        -1|
|  -1|   6|   6|2147483647|  18|        -1|
|   5|   4|   2|2147483647|  84|         2|
|   0|   0|  14|         7|  86|         0|
+----+----+----+----------+----+----------+

您可以注意到,您在第二行找到最小值的方法為 false 結果。 因此,我也可以建議您嘗試通過轉置數據框並在列而不是行中查找最小值來進行這種操作。 如果您使用箭頭,這可以通過 pandas_udf 非常快地完成。

使用@venky__ 答案,我為您找到了解決方案:

from pyspark.sql import functions as F

join_key = df_old_list.columns

min_ = F.least(
    *[F.when(F.col(c).isNull() | (F.col(c) == ""), float("inf")).otherwise(F.col(c).cast('int')) 
      for c in join_key]
).alias("lowest_col")

df_with_lowest_col = df_old_list.select("*", min_.cast('int'))

df_exploded = df_old_list.withColumn(
    'vars_and_vals',
    F.explode(F.array(
        *(F.struct(F.lit(c).alias('var'), F.col(c).alias('val')) for c in join_key)
    )))

cols = join_key + [F.col("vars_and_vals")[x].alias(x) for x in ['var', 'val']]

df_exploded = df_exploded.select(*cols)

df = df_exploded.join(df_with_lowest_col, join_key)

df = df.filter('val = lowest_col')

df_with_col_names = df.groupby(*join_key).agg(
    F.array_join(F.collect_list('var'), ';').alias('lowest_cols_title')
)

res_df = df_with_lowest_col.join(df_with_col_names, join_key)

結果:

res_df.show()

+----+----+----+----+----+----------+-----------------+
|Col1|Col2|Col3|Col4|Col5|lowest_col|lowest_cols_title|
+----+----+----+----+----+----------+-----------------+
|   0|   0|  14|   7|  86|         0|        Col1;Col2|
|  -1|   6|   6|    |  18|        -1|             Col1|
|   5|   5|   5|    |  28|         5|   Col1;Col2;Col3|
|   0|   7|   8|    |  20|         0|             Col1|
|  -1|  -1|  13|    |  83|        -1|        Col1;Col2|
|   5|   4|   2|    |  84|         2|             Col3|
+----+----+----+----+----+----------+-----------------+

它看起來很復雜,我認為可以優化,但它有效。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM