Explicación detallada de la función de ventana de chispa (ventana)

Dirección del proyecto github: bitcarmanlee de fácil-algoritmo-entrevista-y-práctica a
menudo tiene a los estudiantes mensajes privados o dejan mensajes para hacer preguntas relacionadas, número V bitcarmanlee. Los compañeros de clase de star on github, dentro del alcance de mi capacidad y tiempo, haré todo lo posible para ayudarlos a responder preguntas relacionadas y progresar juntos.

1. ¿Por qué necesita una función de ventana?

Antes de 1.4, Spark SQL admitía dos tipos de funciones para calcular un único valor de retorno. La primera es una función incorporada o función UDF, toman el valor en una sola fila como entrada y generan un único valor de retorno para cada fila de entrada. El otro son funciones agregadas, típicamente SUM, MAX, AVG, que operan en un grupo de datos de fila y calculan un valor de retorno para cada grupo.

Las dos funciones mencionadas anteriormente se utilizan ampliamente en la práctica, pero todavía hay una gran cantidad de operaciones que no pueden expresarse solo con este tipo de funciones. Uno de los escenarios más comunes es que muchas veces necesita operar en un conjunto de filas y aún así devolver un valor para cada fila de entrada. Los dos métodos anteriores son inútiles. Por ejemplo, es muy difícil calcular la media móvil, calcular la suma acumulada o acceder al valor de la fila que aparece antes de la fila actual. Afortunadamente, después de 1.4, Spark SQL proporciona funciones de ventana para compensar las deficiencias anteriores.

El núcleo de la función de ventana es "Marco", o directamente lo llamamos marco, un marco es una serie de datos de varias líneas o muchos grupos. Luego, básicamente podemos agruparlos para cumplir con las funciones que las funciones ordinarias anteriores no pueden completar. Para ver la aplicación específica con claridad, miramos directamente el ejemplo. Hablar es fácil. Enséñame el código.

2. Construya el conjunto de datos

Para facilitar las pruebas, primero construimos un conjunto de datos

import org.apache.spark.SparkConf
import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.functions._

  def test() = {
    val sparkConf = new SparkConf().setMaster("local[2]")
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    val data = Array(("lili", "ml", 90),
      ("lucy", "ml", 85),
      ("cherry", "ml", 80),
      ("terry", "ml", 85),
      ("tracy", "cs", 82),
      ("tony", "cs", 86),
      ("tom", "cs", 75))

    val schemas = Seq("name", "subject", "score")
    val df = spark.createDataFrame(data).toDF(schemas: _*)

    df.show()
 }

Después de ejecutar el método de prueba anterior localmente, el resultado es el siguiente

+------+-------+-----+
|  name|subject|score|
+------+-------+-----+
|  lili|     ml|   90|
|  lucy|     ml|   85|
|cherry|     ml|   80|
| terry|     ml|   85|
| tracy|     cs|   82|
|  tony|     cs|   86|
|   tom|     cs|   75|
+------+-------+-----+

Estructura de datos completada

3. Ver clasificaciones en grupos

Un escenario que se utiliza con frecuencia es: es necesario comprobar la clasificación de cada especialización Este es un problema típico de agrupación, es decir, cuando la función de ventana muestra sus talentos.

Una ventana debe definir tres partes:

1. Problema de agrupación, ¿cómo agrupar filas? Cuando se seleccionan los datos de la ventana, solo tiene efecto sobre los datos del grupo
2. Ordenar, ¿de qué manera ordenar? Al seleccionar los datos de la ventana, primero se ordenarán de acuerdo con el método especificado
3. Selección de marco, según el comportamiento actual, ¿cómo seleccionar las filas circundantes?

En comparación con las tres partes anteriores, la sintaxis de la función de ventana es generalmente:

window_func(args) OVER ( [PARTITION BY col_name, col_name, ...] [ORDER BY col_name, col_name, ...] [ROWS | RANGE BETWEEN (CURRENT ROW | (UNBOUNDED |[num]) PRECEDING) AND (CURRENT ROW | ( UNBOUNDED | [num]) FOLLOWING)] )

Entre ellos,
window_func es la función de ventana
over, lo que significa que esta es una función de ventana
partición por corresponde a la agrupación, es decir, según la agrupación de columnas, el
orden por corresponde a la clasificación y las
filas de clasificación de columnas corresponden a la selección de marco correspondiente. .

La función window_func en Spark incluye las siguientes tres categorías:
1. La función de clasificación (función de clasificación) incluye rank, dense_rank, row_number, percent_rank, ntile, etc. Lo veremos con ejemplos más adelante.
2. Las funciones analíticas incluyen cume_dist, lag, etc.
3. Las funciones agregadas (funciones agregadas) son nuestras funciones max, min, sum, avg, etc. de uso común.

Regrese a los requisitos anteriores y verifique la clasificación de cada especialización

  def test() = {
    val sparkConf = new SparkConf().setMaster("local[2]")
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()
    val sqlContext = spark.sqlContext


    val data = Array(("lili", "ml", 90),
      ("lucy", "ml", 85),
      ("cherry", "ml", 80),
      ("terry", "ml", 85),
      ("tracy", "cs", 82),
      ("tony", "cs", 86),
      ("tom", "cs", 75))

    val schemas = Seq("name", "subject", "score")
    val df = spark.createDataFrame(data).toDF(schemas: _*)
    df.createOrReplaceTempView("person_subject_score")

    val sqltext = "select name, subject, score, rank() over (partition by subject order by score desc) as rank from person_subject_score";
    val ret = sqlContext.sql(sqltext)
    ret.show()
  }

El código anterior se ejecuta y los resultados son los siguientes

+------+-------+-----+----+
|  name|subject|score|rank|
+------+-------+-----+----+
|  tony|     cs|   86|   1|
| tracy|     cs|   82|   2|
|   tom|     cs|   75|   3|
|  lili|     ml|   90|   1|
|  lucy|     ml|   85|   2|
| terry|     ml|   85|   2|
|cherry|     ml|   80|   4|
+------+-------+-----+----+

Centrarse en la parte de la ventana:

rank() over (partition by subject order by score desc) as rank

La función rank () significa tomar el rango de cada fila en el grupo, dividir por tema para agrupar por tema, y ​​ordenar por puntaje desc significa ordenar por puntaje y en orden inverso, para que pueda obtener el ranking de cada estudiante en esta especialidad!

Row_number y dense_rank también son funciones de ventana relacionadas con la clasificación. Veamos la diferencia entre ellas a través de ejemplos:

  def test() = {
    val sparkConf = new SparkConf().setMaster("local[2]")
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()
    val sqlContext = spark.sqlContext


    val data = Array(("lili", "ml", 90),
      ("lucy", "ml", 85),
      ("cherry", "ml", 80),
      ("terry", "ml", 85),
      ("tracy", "cs", 82),
      ("tony", "cs", 86),
      ("tom", "cs", 75))

    val schemas = Seq("name", "subject", "score")
    val df = spark.createDataFrame(data).toDF(schemas: _*)
    df.createOrReplaceTempView("person_subject_score")

    val sqltext = "select name, subject, score, rank() over (partition by subject order by score desc) as rank from person_subject_score";
    val ret = sqlContext.sql(sqltext)
    ret.show()

    val sqltext2 = "select name, subject, score, row_number() over (partition by subject order by score desc) as row_number from person_subject_score";
    val ret2 = sqlContext.sql(sqltext2)
    ret2.show()

    val sqltext3 = "select name, subject, score, dense_rank() over (partition by subject order by score desc) as dense_rank from person_subject_score";
    val ret3 = sqlContext.sql(sqltext3)
    ret3.show()
  }
+------+-------+-----+----+
|  name|subject|score|rank|
+------+-------+-----+----+
|  tony|     cs|   86|   1|
| tracy|     cs|   82|   2|
|   tom|     cs|   75|   3|
|  lili|     ml|   90|   1|
|  lucy|     ml|   85|   2|
| terry|     ml|   85|   2|
|cherry|     ml|   80|   4|
+------+-------+-----+----+

+------+-------+-----+----------+
|  name|subject|score|row_number|
+------+-------+-----+----------+
|  tony|     cs|   86|         1|
| tracy|     cs|   82|         2|
|   tom|     cs|   75|         3|
|  lili|     ml|   90|         1|
|  lucy|     ml|   85|         2|
| terry|     ml|   85|         3|
|cherry|     ml|   80|         4|
+------+-------+-----+----------+

+------+-------+-----+----------+
|  name|subject|score|dense_rank|
+------+-------+-----+----------+
|  tony|     cs|   86|         1|
| tracy|     cs|   82|         2|
|   tom|     cs|   75|         3|
|  lili|     ml|   90|         1|
|  lucy|     ml|   85|         2|
| terry|     ml|   85|         2|
|cherry|     ml|   80|         3|
+------+-------+-----+----------+

A través del ejemplo anterior, no es difícil ver la diferencia entre los tres: el
rango genera números de secuencia discontinuos. El ejemplo anterior es 1,2,2,4. Este
rango_denso genera números de secuencia continuos. El ejemplo anterior es 1,2, 2, 3 Este tipo de
número de fila, como sugiere el nombre, genera un número de fila. El ejemplo anterior es 1, 2, 3, 4.
No se preocupe por la definición de la función, ¡solo mire el ejemplo anterior para entenderlo!

4. Ver cuantiles

Veamos otro ejemplo. Queremos comprobar el cuantil de una determinada persona en la profesión, ¿qué debemos hacer? En
este momento se puede utilizar la función cume_dist.
El método de cálculo de esta función es: el número de filas en el grupo que es menor o igual al valor de la fila actual / el número total de filas en el grupo

Todavía mira el código

    val sqltext5 = "select name, subject, score, cume_dist() over (partition by subject order by score desc) as cumedist from person_subject_score";
    val ret5 = sqlContext.sql(sqltext5)
    ret5.show()

Combinando el código de inicialización de datos anterior con la lógica SQL anterior, el resultado final es el siguiente:

+------+-------+-----+------------------+
|  name|subject|score|          cumedist|
+------+-------+-----+------------------+
|  tony|     cs|   86|0.3333333333333333|
| tracy|     cs|   82|0.6666666666666666|
|   tom|     cs|   75|               1.0|
|  lili|     ml|   90|              0.25|
|  lucy|     ml|   85|              0.75|
| terry|     ml|   85|              0.75|
|cherry|     ml|   80|               1.0|
+------+-------+-----+------------------+

Se puede ver que los requisitos anteriores se cumplen perfectamente.

5. Utilice la API de DataFrame para completar la consulta de la ventana

El ejemplo anterior usa la API SqlContext. En DataFrame, hay API correspondientes para completar la consulta. El método específico también es muy simple. Puede usar la API DataFrame para llamar al método over () en las funciones compatibles, como rank ( ). sobre (…)

Tome el requisito anterior como ejemplo. Si queremos ver la clasificación de los estudiantes en las especialidades, use la API de DataFrame de la siguiente manera:

  def test() = {
    val sparkConf = new SparkConf().setMaster("local[2]")
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    val data = Array(("lili", "ml", 90),
      ("lucy", "ml", 85),
      ("cherry", "ml", 80),
      ("terry", "ml", 85),
      ("tracy", "cs", 82),
      ("tony", "cs", 86),
      ("tom", "cs", 75))

    val schemas = Seq("name", "subject", "score")
    val df = spark.createDataFrame(data).toDF(schemas: _*)
    df.createOrReplaceTempView("person_subject_score")

    val window = Window.partitionBy("subject").orderBy(col("score").desc)
    val df2 = df.withColumn("rank", rank().over(window))
    df2.show()
  }

El resultado es el siguiente:

+------+-------+-----+----+
|  name|subject|score|rank|
+------+-------+-----+----+
|  tony|     cs|   86|   1|
| tracy|     cs|   82|   2|
|   tom|     cs|   75|   3|
|  lili|     ml|   90|   1|
|  lucy|     ml|   85|   2|
| terry|     ml|   85|   2|
|cherry|     ml|   80|   4|
+------+-------+-----+----+

Supongo que te gusta

Origin blog.csdn.net/bitcarmanlee/article/details/113617901
Recomendado
Clasificación