фильтровать фрейм данных по нескольким столбцам после взрыва

#apache-spark #pyspark #apache-spark-sql

#apache-spark #pyspark #apache-spark-sql

Вопрос:

Мой df содержит названия продуктов и соответствующую информацию. Здесь уместны имя и страна, в которую продано:

  -------------------- ------------------------- 
|        Product_name|collect_set(Countries_en)|
 -------------------- ------------------------- 
|                null|     [Belgium,United K...|
|     #5 pecan/almond|                [Belgium]|
| #8 mango/strawberry|                [Belgium]|
|amp; Sully A Mild Th...|         [Belgium,France]|
|amp;quot;70CL Liqueu...|         [Belgium,France]|
|amp;quot;Gingembreamp;q...|                [Belgium]|
|amp;quot;Les Schtrou...|         [Belgium,France]|
|amp;quot;Sho-keyamp;quo...|                [Belgium]|
|amp;quot;mini Chupa ...|         [Belgium,France]|
|      'S Lands beste|                [Belgium]|
|'T vlierbos confi...|                [Belgium]|
|(H)eat me - Spagh...|                [Belgium]|
|       -cheese flips|                [Belgium]|
|     .soupe cerfeuil|                [Belgium]|
|1 1/2 Minutes Bas...|     [Belgium,Luxembourg]|
|   1/2 Reblochon AOP|                [Belgium]|
|  1/2 nous de jambon|                [Belgium]|
|1/2 tarte cerise ...|                [Belgium]|
|10 Original Knack...|     [Belgium,France,S...|
|    10 pains au lait|         [Belgium,France]|
 -------------------- ------------------------- 
  

пример входных данных:

 [Row(code=2038002038.0, Product_name='Formula 2 men multi vitaminic', Countries_en='France,Ireland,Italy,Mexico,United States,Argentina-espanol,Armenia-pyсский,Aruba-espanol,Asia-pacific,Australia-english,Austria-deutsch,Azerbaijan-русский,Belarus-pyсский,Belgium-francais,Belgium-nederlands,Bolivia-espanol,Bosnia-i-hercegovina-bosnian,Botswana-english,Brazil-portugues,Bulgaria-български,Cambodia-english,Cambodia-ភាសាខ្មែរ,Canada-english,Canada-francais,Chile-espanol,China-中文,Colombia-espanol,Costa-rica-espanol,Croatia-hrvatski,Cyprus-ελληνικά,Czech-republic-čeština,Denmark-dansk,Ecuador-espanol,El-salvador-espanol,Estonia-eesti,Europe,Finland-suomi,France-francais,Georgia-ქართული,Germany-deutsch,Ghana-english,Greece-ελληνικά,Guatemala-espanol,Honduras-espanol,Hong-kong-粵語,Hungary-magyar,Iceland-islenska,India-english,Indonesia-bahasa-indonesia,Ireland-english,Israel-עברית,Italy-italiano,Jamaica-english,Japan-日本語,Kazakhstan-pyсский,Korea-한국어,Kyrgyzstan-русский,Latvia-latviešu,Lebanon-english,Lesotho-english,Lithuania-lietuvių,Macau-中文,Malaysia-bahasa-melayu,Malaysia-english,Malaysia-中文,Mexico-espanol,Middle-east-africa,Moldova-roman,Mongolia-монгол-хэл,Namibia-english,Netherlands-nederlands,New-zealand-english,Nicaragua-espanol,North-macedonia-македонски-јазик,Norway-norsk,Panama-espanol,Paraguay-espanol,Peru-espanol,Philippines-english,Poland-polski,Portugal-portugues,Puerto-rico-espanol,Republica-dominicana-espanol,Romania-romană,Russia-русский,Serbia-srpski,Singapore-english,Slovak-republic-slovenčina,Slovenia-slovene,South-africa-english,Spain-espanol,Swaziland-english,Sweden-svenska,Switzerland-deutsch,Switzerland-francais,Taiwan-中文,Thailand-ไทย,Trinidad-tobago-english,Turkey-turkce,Ukraine-yкраї́нська,United-kingdom-english,United-states-english,United-states-espanol,Uruguay-espanol,Venezuela-espanol,Vietnam-tiếng-việt,Zambia-english', Traces_en=None, Additives_tags=None, Main_category_en='Vitamins', Image_url='https://static.openfoodfacts.org/images/products/203/800/203/8/front_en.12.400.jpg', Quantity='60 compresse', Packaging_tags='barattolo,tablet', )]
  

Поскольку я хочу выяснить, в какие страны, кроме Бельгии, продаются продукты, я разделил столбец страны, чтобы показать каждую страну по отдельности, используя приведенный ниже код

 #create df with grouped products
countriesDF = productsDF
.select("Product_name", "Countries_en")
.groupBy("Product_name")
.agg(F.collect_set("Countries_en").cast("string").alias("Countries"))
.orderBy("Product_name")

#split df to show countries the product is sold to in a seperate column
countriesDF = countriesDF
   .where(col("Countries")!="null")
.select("Product_name",
        F.split("Countries", ",").alias("Countries"),
        F.posexplode(F.split("Countries", ",")).alias("pos", "val")
    )
    .drop("val")
    .select(
        "Product_name",
        F.concat(F.lit("Countries"),F.col("pos").cast("string")).alias("name"),
        F.expr("Countries[pos]").alias("val")
    )
    .groupBy("Product_name").pivot("name").agg(F.first("val"))
.show()
  

Однако в этой таблице теперь более 400 столбцов только для стран, что непрезентабельно. Итак, мой вопрос:

  1. правильно ли я выполняю разделение / взрыв?
  2. могу ли я разделить df, чтобы я получал страны в качестве имен столбцов (например, «Франция» вместо «страны1» и т. Д.), Подсчитывая количество раз, Когда продукт продается в этой стране?

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

1. Какова ваша конечная цель? Чтобы вычислить количество Product_name для каждой страны? или вы действительно хотите отобразить эту count( Product_name X COUNTRY) матрицу?

2. Вопрос в том, в какие страны (кроме Бельгии) продаются продукты. Поскольку их так много, если смотреть на весь df, я пытаюсь использовать второй из ваших вариантов. Таким образом, они сгруппированы по названию продукта в 1-м столбце и количеству продуктов, проданных в каждой стране.

Ответ №1:

Некоторые примеры данных :

 val sampledf = Seq(("p1","BELGIUM,GERMANY"),("p1","BELGIUM,ITALY"),("p1","GERMANY"),("p2","BELGIUM")).toDF("Product_name","Countries_en")
  

Преобразовать в требуемый df :

 df = sampledf
.withColumn("country_list",split(col("Countries_en"),","))
.select(col("Product_name"), explode(col("country_list")).as("country"))

 ------------ ------- 
|Product_name|country|
 ------------ ------- 
|          p1|BELGIUM|
|          p1|GERMANY|
|          p1|BELGIUM|
|          p1|  ITALY|
|          p1|GERMANY|
|          p2|BELGIUM|
 ------------ ------- 
  

Если вам нужно только количество для каждой страны :

 countDF = df.groupBy("Product_name","country").count()

countDF.show()
 ------------ ------- ----- 
|Product_name|country|count|
 ------------ ------- ----- 
|          p1|BELGIUM|    2|
|          p1|GERMANY|    1|
|          p2|BELGIUM|    1|
 ------------ ------- ----- 
  

Кроме Бельгии :

 countDF.filter(col("country") =!="BELGIUM").show()

 ------------ ------- ----- 
|Product_name|country|count|
 ------------ ------- ----- 
|          p1|GERMANY|    1|
 ------------ ------- ----- 
  

И если вы действительно хотите, чтобы страны были столбцами :

 countDF.groupBy("Product_name").pivot("country").agg(first("count"))

 ------------ ------- ------- 
|Product_name|BELGIUM|GERMANY|
 ------------ ------- ------- 
|          p2|      1|   null|
|          p1|      2|      1|
 ------------ ------- ------- 
  

И вы можете .drop("BELGIUM") этого добиться.

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

1. поскольку столбцы «Countries_en» содержат несколько таких стран, |Belgium,France| это не решает мою проблему. Также причина, по которой сначала нужно разделить этот столбец

2. Не могли бы вы опубликовать исходный входной фрейм данных smaple, о котором идет речь?

3. @JackSomeone теперь обработал эту Belgium,France часть. Пожалуйста, проверьте

4. добавлен образец. Я думаю, что ваше добавление идет в правильном направлении, но когда вы смотрите на результат, он определяет каждую строку как одно, а не несколько событий, верно?

5. может быть, проще просто получить список стран =! Бельгия?

Ответ №2:

Используемый окончательный код:

 #create df where countries are split off
df = productsDF
.withColumn("country_list",split(col("Countries_en"),","))
.select(col("Product_name"), explode(col("country_list")).alias("Country"))

#create count and filter out Country Belgium, Product Name can be changed as needed
countDF = df.groupBy("Product","Country").count()
.filter(col("Country") !="Belgium")
.filter(col('Product') == 'Café').show()