Получить отличную комбинацию пар элементов из столбца массива [строка]

#scala #dataframe #apache-spark #apache-spark-sql

#scala #фрейм данных #apache-spark #apache-spark-sql

Вопрос:

У меня есть следующий фрейм данных

 val input = Seq(("ZZ","a","a","b","b"),
("ZZ","a","b","c","d"),
("YY","b","e",null,"f"),
("YY","b","b",null,"f"),
("VV","a",null,"",""))
.toDF("main","value1","value2","value3","value4")
input.show()

 ---- ------ ------ ------ ------ 
|main|value1|value2|value3|value4|
 ---- ------ ------ ------ ------ 
|  ZZ|     a|     a|     b|     b|
|  ZZ|     a|     b|     c|     d|
|  YY|     b|     e|  null|     f|
|  YY|     b|     b|  null|     f|
|  VV|     a|  null|      |      |
 ---- ------ ------ ------ ------ 
  

Я сделал следующее, чтобы выровнять данные

 val newdf = input.select('main,array('value1,'value2,'value3,'value4).alias("values"))
.groupBy('main).agg(collect_set('values).alias("values"))
.select('main, flatten($"values").alias("values"))
newdf.show()

 ---- -------------------- 
|main|              values|
 ---- -------------------- 
|  ZZ|[a, a, b, b, a, b...|
|  YY|[b, e,, f, b, b,, f]|
|  VV|            [a,, , ]|
 ---- -------------------- 
  

Теперь мне нужно выбрать каждую уникальную комбинацию элементов в виде пары из массива и иметь их в виде отдельных строк данных. Таким образом, для фрейма данных, приведенного выше, результат будет

  ---- ------ ------ 
|main|value1|value2|
 ---- ------ ------ 
|  ZZ|     a|     b|
|  ZZ|     a|     c|
|  ZZ|     a|     d|
|  ZZ|     b|     c|
|  ZZ|     d|     c|
|  YY|     b|     f|
|  YY|     b|     e|
|  YY|     e|     f|
|  VV|     a|      |
 ---- ------ ------ 
  

Как мне свести к столбцу уникальные элементы, которые я могу разделить как отдельные строки?

Ответ №1:

Используйте explode два раза и фильтруйте.

 val newdf = input.select('main,array('value1,'value2,'value3,'value4).alias("values"))
.groupBy('main).agg(flatten(collect_set('values)).alias("values"))
.withColumn("value1", explode(array_distinct('values)))
.withColumn("value2", explode(array_distinct('values)))
.filter("value1 < value2")
.select('main, 'value1, 'value2)
newdf.show()

 ---- ------ ------ 
|main|value1|value2|
 ---- ------ ------ 
|  ZZ|     a|     b|
|  ZZ|     a|     c|
|  ZZ|     a|     d|
|  ZZ|     b|     c|
|  ZZ|     b|     d|
|  ZZ|     c|     d|
|  YY|     b|     e|
|  YY|     b|     f|
|  YY|     e|     f|
|  VV|      |     a|
 ---- ------ ------ 
  

Комментарии:

1. Эта логика работает отлично, но когда для одной строки значения являются ("XX", "a", null, null) , фильтр отфильтровывает всю строку, и мы теряем значение «a». Я обошел это, заменив null пустой строкой.

Ответ №2:

I need to pick every unique combination of items as a pair например, если ZZ имеет значения [a, b, c, d], то, по сути, вы создадите 6 пар (4 выбирают 2)? В таком случае вы можете захотеть создать UDAF (определяемую пользователем агрегатную функцию).

 input
.select('main,array('value1,'value2,'value3,'value4).alias("values"))
.groupBy('main).agg(<here comes your UDAF>)
  

Этот UDAF должен быть таким, чтобы он собирал значения в виде набора (или списка .distinct ) и создавал все пары комбинаций (можно выполнить с помощью 2 циклов for).

После этого ваш фрейм данных должен выглядеть следующим образом

  ---- ------------------------------------ 
|main|              values                |
 ---- ------------------------------------ 
|  ZZ|[(a,b),(a,c),(a,d),(b,c),(b,d),(c,d)|
 ---- ------------------------------------ 
  

Затем вы можете .explode() получить фрейм данных, подобный

  ---- ------- 
|main|values |
 ---- ------- 
|  ZZ|(a,b)  |
 ---- ------- 
|  ZZ|(a,c)  |
 ---- ------- 
|  ZZ|(a,d)  |
 ---- ------- 
|  ZZ|(b,c)  |
 ---- ------- 
|  ZZ|(b,d)  |
 ---- ------- 
|  ZZ|(c,d)  |
 ---- ------- 
  

Затем вы можете сделать столбец value1 первым значением этого кортежа, а столбец value2 вторым значением.