В прошлой статье мы говорили о ранжирующей функции ROWS NUMBER
в PySpark. Сегодня поговорим о RANK
DENSE_RANK
, а также узнаем, чем они различаются.
Данные с затонувшего Титаника
Возьмем для примера датасет с затонувшим Титаником. Его можно взять тут. У него много столбцов, поэтому вы покажем только его схему.
df = spark.read.csv("titanic_dataset.csv", header=True, inferScheme=True) """ root |-- PassengerId: integer (nullable = true) |-- Survived: integer (nullable = true) |-- Pclass: integer (nullable = true) |-- Name: string (nullable = true) |-- Sex: string (nullable = true) |-- Age: double (nullable = true) |-- SibSp: integer (nullable = true) |-- Parch: integer (nullable = true) |-- Ticket: string (nullable = true) |-- Fare: double (nullable = true) |-- Cabin: string (nullable = true) |-- Embarked: string (nullable = true) """
Как использовать RANK в PySpark
Функция RANK
вычисляет ранг для каждой партиции. Эта функция очень похожа на ROW NUMBER
. Функция ROWS NUMBER
нумерует строки последовательно (например, 1, 2, 3, 4). RANK
же выдает ранг каждой партиции или порядку, при этом сохраняя внутренний подсчет (например, 1, 2, 2, 4, 4 6).
Поясним это на примере. Определим ранг в PySpark в порядке стоимости билета Fare
:
SELECT t.Name, t.Survived, t.Fare, RANK() OVER(ORDER BY t.Fare) as rnk, ROW NUMBER() OVER(ORDER BY t.Fare) as rown FROM titanic t; """ +--------------------+--------+------+---+----+ | Name|Survived| Fare|rnk|rown| +--------------------+--------+------+---+----+ | Leonard, Mr. Lionel| 0| 0.0| 1| 1| |Harrison, Mr. Wil...| 0| 0.0| 1| 2| |Tornquist, Mr. Wi...| 1| 0.0| 1| 3| |"Parkes, Mr. Fran...| 0| 0.0| 1| 4| |Johnson, Mr. Will...| 0| 0.0| 1| 5| |Cunningham, Mr. A...| 0| 0.0| 1| 6| |Campbell, Mr. Wil...| 0| 0.0| 1| 7| |"Frost, Mr. Antho...| 0| 0.0| 1| 8| | Johnson, Mr. Alfred| 0| 0.0| 1| 9| |Parr, Mr. William...| 0| 0.0| 1| 10| |Watson, Mr. Ennis...| 0| 0.0| 1| 11| |Knight, Mr. Robert J| 0| 0.0| 1| 12| |Andrews, Mr. Thom...| 0| 0.0| 1| 13| | Fry, Mr. Richard| 0| 0.0| 1| 14| |Reuchlin, Jonkhee...| 0| 0.0| 1| 15| | Betros, Mr. Tannous| 0|4.0125| 16| 16| |Carlsson, Mr. Fra...| 0| 5.0| 17| 17| |Nysveen, Mr. Joha...| 0|6.2375| 18| 18| |Lemberopolous, Mr...| 0|6.4375| 19| 19| |Holm, Mr. John Fr...| 0| 6.45| 20| 20| +--------------------+--------+------+---+----+ """
Как видим, 16-я строка имеет ранг 16, а не 2.
Этот же запрос для нахождения ранга в PySpark:
import pyspark.sql.functions as F from pyspark.sql import Window df.select( "Name", "Survived", "Fare", F.rank().over(w).alias("rnk"), F.row_number().over(w).alias("rown") )
Мы также можем указать партиции (группы) и считать ранги для каждой партиции и порядка. Например, выдадим ранги по классу пассажира Pclass
и убывающему порядку следования стоимости проезда Fare
. Такой SQL-запрос формируется следующим образом:
SELECT t.Name, t.Pclass, t.Fare, RANK() OVER(PARTITION BY Pclass ORDER BY t.Fare DESC) as rnk, FROM titanic t; """ +--------------------+------+--------+---+ | Name|Pclass| Fare|rnk| +--------------------+------+--------+---+ | Ward, Miss. Anna| 1|512.3292| 1| |Cardeza, Mr. Thom...| 1|512.3292| 1| |Lesurer, Mr. Gust...| 1|512.3292| 1| |Fortune, Mr. Char...| 1| 263.0| 4| |Fortune, Miss. Ma...| 1| 263.0| 4| |Fortune, Miss. Al...| 1| 263.0| 4| | Fortune, Mr. Mark| 1| 263.0| 4| |Ryerson, Miss. Em...| 1| 262.375| 8| |"Ryerson, Miss. S...| 1| 262.375| 8| |Baxter, Mr. Quigg...| 1|247.5208| 10| |Baxter, Mrs. Jame...| 1|247.5208| 10| |Bidois, Miss. Ros...| 1| 227.525| 12| | Robbins, Mr. Victor| 1| 227.525| 12| |Astor, Mrs. John ...| 1| 227.525| 12| |Endres, Miss. Car...| 1| 227.525| 12| | Farthing, Mr. John| 1|221.7792| 16| |Widener, Mr. Harr...| 1| 211.5| 17| |Madill, Miss. Geo...| 1|211.3375| 18| |Allen, Miss. Elis...| 1|211.3375| 18| |Robert, Mrs. Edwa...| 1|211.3375| 18| +--------------------+------+--------+---+ """
Чтобы указать убывающий порядок в самом PySpark, то лучше всего это сделать через функцию desc
:
w = Window.partitionBy("Pclass").orderBy(F.desc("Fare")) df.select( "Name", "Pclass", "Fare", F.rank().over(w).alias("rnk") )
Use case: взять самые высокие значения по каждой группе
Вдруг нам понадобилось взять первые N значений (наименьших или наибольших) в каждой группе. Мы можем это сделать так же, как это делали с ROW NUMBER
в предыдущей статье, т.е. через подзапрос и обычный фильтр.
Например, SQL-запрос возьмем пассажиров из каждого класса, которые заплатили больше всего,
SELECT * FROM ( SELECT t.Name, t.Pclass, t.Fare, RANK() OVER(PARTITION BY Pclass ORDER BY t.Fare) AS rnk FROM titanic t ) x WHERE x.rnk < 4; """ +--------------------+------+--------+---+ | Name|Pclass| Fare|rnk| +--------------------+------+--------+---+ | Ward, Miss. Anna| 1|512.3292| 1| |Cardeza, Mr. Thom...| 1|512.3292| 1| |Lesurer, Mr. Gust...| 1|512.3292| 1| |Sage, Master. Tho...| 3| 69.55| 1| |Sage, Miss. Const...| 3| 69.55| 1| | Sage, Mr. Frederick| 3| 69.55| 1| |Sage, Mr. George ...| 3| 69.55| 1| |Sage, Miss. Stell...| 3| 69.55| 1| |Sage, Mr. Douglas...| 3| 69.55| 1| |"Sage, Miss. Doro...| 3| 69.55| 1| |Hood, Mr. Ambrose Jr| 2| 73.5| 1| |Hickman, Mr. Stan...| 2| 73.5| 1| |Davies, Mr. Charl...| 2| 73.5| 1| |Hickman, Mr. Leon...| 2| 73.5| 1| | Hickman, Mr. Lewis| 2| 73.5| 1| +--------------------+------+--------+---+ """
То же самое в PySpark:
w = Window.partitionBy("Pclass").orderBy(F.desc("Fare")) x = df.select( "Name", "Pclass", "Fare", F.rank().over(w).alias("rnk") ) x.where("rnk < 4").show()
RANK vs DENSE RANK в PySpark
Apache Spark поддерживает еще и DENSE RANK
. В отличие от обычного RANK
он выдает ранг группам последовательно (1, 2, 3, 4, 5). Например, выведем оба ранга:
df.select( "Name", "Pclass", "Fare", F.rank().over(w).alias("rnk"), F.dense_rank().over(w).alias("dense") ) """ +--------------------+------+--------+---+-----+ | Name|Pclass| Fare|rnk|dense| +--------------------+------+--------+---+-----+ | Ward, Miss. Anna| 1|512.3292| 1| 1| |Cardeza, Mr. Thom...| 1|512.3292| 1| 1| |Lesurer, Mr. Gust...| 1|512.3292| 1| 1| |Fortune, Mr. Char...| 1| 263.0| 4| 2| |Fortune, Miss. Ma...| 1| 263.0| 4| 2| |Fortune, Miss. Al...| 1| 263.0| 4| 2| | Fortune, Mr. Mark| 1| 263.0| 4| 2| |Ryerson, Miss. Em...| 1| 262.375| 8| 3| |"Ryerson, Miss. S...| 1| 262.375| 8| 3| |Baxter, Mr. Quigg...| 1|247.5208| 10| 4| |Baxter, Mrs. Jame...| 1|247.5208| 10| 4| |Bidois, Miss. Ros...| 1| 227.525| 12| 5| | Robbins, Mr. Victor| 1| 227.525| 12| 5| |Astor, Mrs. John ...| 1| 227.525| 12| 5| |Endres, Miss. Car...| 1| 227.525| 12| 5| | Farthing, Mr. John| 1|221.7792| 16| 6| |Widener, Mr. Harr...| 1| 211.5| 17| 7| |Madill, Miss. Geo...| 1|211.3375| 18| 8| |Allen, Miss. Elis...| 1|211.3375| 18| 8| |Robert, Mrs. Edwa...| 1|211.3375| 18| 8| +--------------------+------+--------+---+-----+ """
На 4-й строке плотный ранг дал строке значение 2, а не 4, как это делает обычный ранг.
А в следующей статье поговорим о функциях LEAD
и LAG
. Еще больше подробностей о оконных функциях в PySpark вы узнаете на наших образовательных курсах в лицензированном учебном центре обучения и повышения квалификации руководителей и IT-специалистов (менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data) в Москве:
Код курса
MLSP
Ближайшая дата курса
по запросу
Продолжительность
ак.часов
Стоимость обучения
0 руб.
- Анализ данных с Apache Spark
- Машинное обучение в Apache Spark
- Графовые алгоритмы в Apache Spark
- Потоковая обработка в Apache Spark
- Основы Apache Spark для разработчиков