web-dev-qa-db-fra.com

Comment agréger sur une fenêtre de temps de roulement avec des groupes dans Spark

J'ai des données que je souhaite regrouper par une certaine colonne, puis agréger une série de champs en fonction d'une fenêtre de temps de roulement du groupe.

Voici quelques exemples de données:

df = spark.createDataFrame([Row(date='2016-01-01', group_by='group1', get_avg=5, get_first=1),
                            Row(date='2016-01-10', group_by='group1', get_avg=5, get_first=2),
                            Row(date='2016-02-01', group_by='group2', get_avg=10, get_first=3),
                            Row(date='2016-02-28', group_by='group2', get_avg=20, get_first=3),
                            Row(date='2016-02-29', group_by='group2', get_avg=30, get_first=3),
                            Row(date='2016-04-02', group_by='group2', get_avg=8, get_first=4)])

Je veux regrouper par group_by, puis créez des fenêtres temporelles qui commencent à la date la plus ancienne et s'étendent jusqu'à 30 jours sans entrée pour ce groupe. Une fois ces 30 jours écoulés, la fenêtre de temps suivante commence avec la date de la ligne suivante qui ne se trouve pas dans la fenêtre précédente.

Je veux ensuite agréger, par exemple en obtenant la moyenne de get_avg, et le premier résultat de get_first.

La sortie de cet exemple devrait donc être:

group_by    first date of window    get_avg  get_first
group1      2016-01-01              5        1
group2      2016-02-01              20       3
group2      2016-04-02              8        4

edit: désolé, j'ai réalisé que ma question n'était pas spécifiée correctement. Je veux en fait une fenêtre qui se termine après 30 jours d'inactivité. J'ai modifié la partie group2 de l'exemple en conséquence.

9
Mike S

Réponse révisée :

Vous pouvez utiliser ici une astuce simple sur les fonctions de la fenêtre. Un tas d'importations:

from pyspark.sql.functions import coalesce, col, datediff, lag, lit, sum as sum_
from pyspark.sql.window import Window

définition de la fenêtre:

w = Window.partitionBy("group_by").orderBy("date")

Cast date en DateType:

df_ = df.withColumn("date", col("date").cast("date"))

Définissez les expressions suivantes:

# Difference from the previous record or 0 if this is the first one
diff = coalesce(datediff("date", lag("date", 1).over(w)), lit(0))

# 0 if diff <= 30, 1 otherwise
indicator = (diff > 30).cast("integer")

# Cumulative sum of indicators over the window
subgroup = sum_(indicator).over(w).alias("subgroup")

Ajoutez une expression subgroup à la table:

df_.select("*", subgroup).groupBy("group_by", "subgroup").avg("get_avg")
+--------+--------+------------+
|group_by|subgroup|avg(get_avg)|
+--------+--------+------------+
|  group1|       0|         5.0|
|  group2|       0|        20.0|
|  group2|       1|         8.0|
+--------+--------+------------+

first n'a pas de sens avec les agrégations, mais si la colonne augmente de façon monotone, vous pouvez utiliser min. Sinon, vous devrez également utiliser les fonctions de la fenêtre.

Testé à l'aide de Spark 2.1. Peut nécessiter des sous-requêtes et une instance de Window lorsqu'il est utilisé avec une version antérieure de Spark.

La réponse d'origine (non pertinent dans la portée spécifiée)

Depuis Spark 2.0 vous devriez pouvoir utiliser ne fonction window :

Répartissez les lignes dans une ou plusieurs fenêtres temporelles en fonction d'une colonne spécifiant l'horodatage. Les débuts de fenêtre sont inclusifs mais les extrémités de fenêtre sont exclusives, par ex. 12:05 sera dans la fenêtre [12: 05,12: 10) mais pas dans [12: 00,12: 05).

from pyspark.sql.functions import window

df.groupBy(window("date", windowDuration="30 days")).count()

mais vous pouvez voir le résultat,

+---------------------------------------------+-----+
|window                                       |count|
+---------------------------------------------+-----+
|[2016-01-30 01:00:00.0,2016-02-29 01:00:00.0]|1    |
|[2015-12-31 01:00:00.0,2016-01-30 01:00:00.0]|2    |
|[2016-03-30 02:00:00.0,2016-04-29 02:00:00.0]|1    |
+---------------------------------------------+-----+

vous devrez être un peu prudent en ce qui concerne les fuseaux horaires.

17
user6910411