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.
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.