Spark SQL: застосувати агреговані функції до списку стовпців


77

Чи є спосіб застосувати сукупну функцію до всіх (або списку) стовпців фрейму даних, коли виконується groupBy? Іншими словами, чи є спосіб уникнути цього для кожного стовпця:

df.groupBy("col1")
  .agg(sum("col2").alias("col2"), sum("col3").alias("col3"), ...)

Відповіді:


135

Існує кілька способів застосування сукупних функцій до декількох стовпців.

GroupedDataклас надає ряд методів для найбільш поширених функцій, в тому числі count, max, min, meanі sum, які можуть бути безпосередньо використаний наступним чином :

  • Python:

    df = sqlContext.createDataFrame(
        [(1.0, 0.3, 1.0), (1.0, 0.5, 0.0), (-1.0, 0.6, 0.5), (-1.0, 5.6, 0.2)],
        ("col1", "col2", "col3"))
    
    df.groupBy("col1").sum()
    
    ## +----+---------+-----------------+---------+
    ## |col1|sum(col1)|        sum(col2)|sum(col3)|
    ## +----+---------+-----------------+---------+
    ## | 1.0|      2.0|              0.8|      1.0|
    ## |-1.0|     -2.0|6.199999999999999|      0.7|
    ## +----+---------+-----------------+---------+
    
  • Scala

    val df = sc.parallelize(Seq(
      (1.0, 0.3, 1.0), (1.0, 0.5, 0.0),
      (-1.0, 0.6, 0.5), (-1.0, 5.6, 0.2))
    ).toDF("col1", "col2", "col3")
    
    df.groupBy($"col1").min().show
    
    // +----+---------+---------+---------+
    // |col1|min(col1)|min(col2)|min(col3)|
    // +----+---------+---------+---------+
    // | 1.0|      1.0|      0.3|      0.0|
    // |-1.0|     -1.0|      0.6|      0.2|
    // +----+---------+---------+---------+
    

За бажанням ви можете передати список стовпців, які слід агрегувати

df.groupBy("col1").sum("col2", "col3")

Ви також можете передати словник / карту зі стовпцями a, клавішами та функціями як значення:

  • Python

    exprs = {x: "sum" for x in df.columns}
    df.groupBy("col1").agg(exprs).show()
    
    ## +----+---------+
    ## |col1|avg(col3)|
    ## +----+---------+
    ## | 1.0|      0.5|
    ## |-1.0|     0.35|
    ## +----+---------+
    
  • Scala

    val exprs = df.columns.map((_ -> "mean")).toMap
    df.groupBy($"col1").agg(exprs).show()
    
    // +----+---------+------------------+---------+
    // |col1|avg(col1)|         avg(col2)|avg(col3)|
    // +----+---------+------------------+---------+
    // | 1.0|      1.0|               0.4|      0.5|
    // |-1.0|     -1.0|3.0999999999999996|     0.35|
    // +----+---------+------------------+---------+
    

Нарешті, ви можете використовувати varargs:

  • Python

    from pyspark.sql.functions import min
    
    exprs = [min(x) for x in df.columns]
    df.groupBy("col1").agg(*exprs).show()
    
  • Scala

    import org.apache.spark.sql.functions.sum
    
    val exprs = df.columns.map(sum(_))
    df.groupBy($"col1").agg(exprs.head, exprs.tail: _*)
    

Існують деякі інші способи досягнення подібного ефекту, але більшої частини часу цього має бути більш ніж достатньо.

Дивитися також:


Здається, aggregateByце буде застосовним тут. Це швидше (набагато швидше), ніж groupBy. О, почекайте - DataFrameце не викриває aggregateBy- aggвказується groupBy. Ну, це означає, що DataFramesце повільно ..
Стівен Бош,

2
@javadba Ні, це означає лише, що Dataset.groupBy/ Dataset.groupByKeyта RDD.groupBy/ RDD.groupByKey мають, як правило, різну семантику. У випадку простих DataFrameагрегацій перевірте це . У цьому є ще щось, але тут це не важливо.
zero323

3
@javadba Дякую. Ось ще один корисний (суб’єктивно, попередження про саморекламу) ресурс: git.io/vM1Ch
zero323

7
Як додати псевдонім до стовпців?
GeekFactory

4
@GeekFactoryexprs = [min(x).alias("{0}".format(x)) for x in df.columns]
zero323

21

Ще один приклад тієї ж концепції - але скажімо - у вас є 2 різні стовпці - і ви хочете застосувати різні функції agg до кожного з них, тобто

f.groupBy("col1").agg(sum("col2").alias("col2"), avg("col3").alias("col3"), ...)

Ось спосіб його досягти - хоча я ще не знаю, як додати псевдонім у цьому випадку

Див. Приклад нижче - Використання Карт

val Claim1 = StructType(Seq(StructField("pid", StringType, true),StructField("diag1", StringType, true),StructField("diag2", StringType, true), StructField("allowed", IntegerType, true), StructField("allowed1", IntegerType, true)))
val claimsData1 = Seq(("PID1", "diag1", "diag2", 100, 200), ("PID1", "diag2", "diag3", 300, 600), ("PID1", "diag1", "diag5", 340, 680), ("PID2", "diag3", "diag4", 245, 490), ("PID2", "diag2", "diag1", 124, 248))

val claimRDD1 = sc.parallelize(claimsData1)
val claimRDDRow1 = claimRDD1.map(p => Row(p._1, p._2, p._3, p._4, p._5))
val claimRDD2DF1 = sqlContext.createDataFrame(claimRDDRow1, Claim1)

val l = List("allowed", "allowed1")
val exprs = l.map((_ -> "sum")).toMap
claimRDD2DF1.groupBy("pid").agg(exprs) show false
val exprs = Map("allowed" -> "sum", "allowed1" -> "avg")

claimRDD2DF1.groupBy("pid").agg(exprs) show false

1

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

Як правило, я так розглядаю цю справу:

val dimensionFields = List("col1")
val metrics = List("col2", "col3", "col4")
val columnOfInterests = dimensions ++ metrics

val df = spark.read.table("some_table"). 
    .select(columnOfInterests.map(c => col(c)):_*)
    .groupBy(dimensions.map(d => col(d)): _*)
    .agg(metrics.map( m => m -> "sum").toMap)
    .toDF(columnOfInterests:_*)    // that's the interesting part

Останній рядок по суті перейменовує кожні стовпці агрегованого кадру даних у вихідні поля, по суті змінюючись sum(col2)та sum(col3)просто col2та col3.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.