web-dev-qa-db-fra.com

Calculer l'écart type des données groupées dans un Spark DataFrame

J'ai des journaux d'utilisateurs que j'ai extraits d'un fichier csv et convertis en un DataFrame afin de tirer parti des fonctionnalités de requête SparkSQL. Un seul utilisateur créera de nombreuses entrées par heure, et je voudrais recueillir des informations statistiques de base pour chaque utilisateur; vraiment juste le nombre d'instances utilisateur, la moyenne et l'écart-type de nombreuses colonnes. J'ai pu obtenir rapidement les informations de moyenne et de comptage en utilisant groupBy ($ "user") et l'agrégateur avec les fonctions SparkSQL pour count et avg:

val meanData = selectedData.groupBy($"user").agg(count($"logOn"),
avg($"transaction"), avg($"submit"), avg($"submitsPerHour"), avg($"replies"),
avg($"repliesPerHour"), avg($"duration"))

Cependant, je n'arrive pas à trouver une manière tout aussi élégante de calculer l'écart type. Jusqu'à présent, je ne peux le calculer qu'en mappant une chaîne, une double paire et en utilisant l'utilitaire StatCounter (). Stdev:

val stdevduration = duration.groupByKey().mapValues(value =>
org.Apache.spark.util.StatCounter(value).stdev)

Cela retourne un RDD cependant, et je voudrais essayer de tout garder dans un DataFrame pour que d'autres requêtes soient possibles sur les données retournées.

13
the3rdNotch

Spark 1.6 +

Vous pouvez utiliser stddev_pop pour calculer l'écart type de la population et stddev/stddev_samp pour calculer l'écart type de l'échantillon sans biais:

import org.Apache.spark.sql.functions.{stddev_samp, stddev_pop}

selectedData.groupBy($"user").agg(stdev_pop($"duration"))

Spark 1.5 et inférieur ( La réponse originale):

Pas si joli et biaisé (identique à la valeur renvoyée par describe) mais en utilisant la formule:

wikipedia sdev

vous pouvez faire quelque chose comme ça:

import org.Apache.spark.sql.functions.sqrt

selectedData
    .groupBy($"user")
    .agg((sqrt(
        avg($"duration" * $"duration") -
        avg($"duration") * avg($"duration")
     )).alias("duration_sd"))

Vous pouvez bien sûr créer une fonction pour réduire l'encombrement:

import org.Apache.spark.sql.Column
def mySd(col: Column): Column = {
    sqrt(avg(col * col) - avg(col) * avg(col))
}

df.groupBy($"user").agg(mySd($"duration").alias("duration_sd"))

Il est également possible d'utiliser Hive UDF:

df.registerTempTable("df")
sqlContext.sql("""SELECT user, stddev(duration)
                  FROM df
                  GROUP BY user""")

Source de l'image: https://en.wikipedia.org/wiki/Standard_deviation

35
zero323