Мне нужно вычислить перекрестные рейтинги ряда торговых ценных бумаг. Рассмотрим следующее pl.DataFrame
в длинном (аккуратном) формате. Он состоит из трех разных символов с соответствующими ценами, причем каждый символ также имеет специальный (т. е. локальный) торговый календарь.
df = pl.DataFrame(
{
"symbol": [*["symbol1"] * 6, *["symbol2"] * 5, *["symbol3"] * 5],
"date": [
"2023-12-30", "2023-12-31", "2024-01-03", "2024-01-04", "2024-01-05", "2024-01-06",
"2023-12-30", "2024-01-03", "2024-01-04", "2024-01-05", "2024-01-06",
"2023-12-30", "2023-12-31", "2024-01-03", "2024-01-04", "2024-01-05",
],
"price": [
100, 105, 110, 115, 120, 125,
200, 210, 220, 230, 240,
3000, 3100, 3200, 3300, 3400,
],
}
)
print(df)
shape: (16, 3)
┌─────────┬────────────┬───────┐
│ symbol ┆ date ┆ price │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 │
╞═════════╪════════════╪═══════╡
│ symbol1 ┆ 2023-12-30 ┆ 100 │
│ symbol1 ┆ 2023-12-31 ┆ 105 │
│ symbol1 ┆ 2024-01-03 ┆ 110 │
│ symbol1 ┆ 2024-01-04 ┆ 115 │
│ symbol1 ┆ 2024-01-05 ┆ 120 │
│ … ┆ … ┆ … │
│ symbol3 ┆ 2023-12-30 ┆ 3000 │
│ symbol3 ┆ 2023-12-31 ┆ 3100 │
│ symbol3 ┆ 2024-01-03 ┆ 3200 │
│ symbol3 ┆ 2024-01-04 ┆ 3300 │
│ symbol3 ┆ 2024-01-05 ┆ 3400 │
└─────────┴────────────┴───────┘
Первым шагом является вычисление периодической доходности с помощью pct_change
, а затем с помощью pivot
для выравнивания символов по датам.
returns = df.drop_nulls().with_columns(
pl.col("price").pct_change(n=2).over("symbol").alias("return")
).pivot(on = "symbol", index = "date", values = "return")
print(returns)
shape: (6, 4)
┌────────────┬──────────┬──────────┬──────────┐
│ date ┆ symbol1 ┆ symbol2 ┆ symbol3 │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ f64 │
╞════════════╪══════════╪══════════╪══════════╡
│ 2023-12-30 ┆ null ┆ null ┆ null │
│ 2023-12-31 ┆ null ┆ null ┆ null │
│ 2024-01-03 ┆ 0.1 ┆ null ┆ 0.066667 │
│ 2024-01-04 ┆ 0.095238 ┆ 0.1 ┆ 0.064516 │
│ 2024-01-05 ┆ 0.090909 ┆ 0.095238 ┆ 0.0625 │
│ 2024-01-06 ┆ 0.086957 ┆ 0.090909 ┆ null │
└────────────┴──────────┴──────────┴──────────┘
Следующий шаг — использовать concat_list
для создания list
для вычисления рангов в каждой строке (по убыванию, т. е. наивысший доход получает ранг 1).
ranks = (
returns.with_columns(all_symbols=pl.concat_list(pl.all().exclude("date")))
.select(
pl.all().exclude("all_symbols"),
pl.col("all_symbols")
.list.eval(
pl.element().rank(descending=True, method = "ordinal").cast(pl.UInt8)
)
.alias("rank"),
)
)
print(ranks)
shape: (6, 5)
┌────────────┬──────────┬──────────┬──────────┬────────────────────┐
│ date ┆ symbol1 ┆ symbol2 ┆ symbol3 ┆ rank │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ f64 ┆ list[u8] │
╞════════════╪══════════╪══════════╪══════════╪════════════════════╡
│ 2023-12-30 ┆ null ┆ null ┆ null ┆ [null, null, null] │
│ 2023-12-31 ┆ null ┆ null ┆ null ┆ [null, null, null] │
│ 2024-01-03 ┆ 0.1 ┆ null ┆ 0.066667 ┆ [1, null, 2] │
│ 2024-01-04 ┆ 0.095238 ┆ 0.1 ┆ 0.064516 ┆ [2, 1, 3] │
│ 2024-01-05 ┆ 0.090909 ┆ 0.095238 ┆ 0.0625 ┆ [2, 1, 3] │
│ 2024-01-06 ┆ 0.086957 ┆ 0.090909 ┆ null ┆ [2, 1, null] │
└────────────┴──────────┴──────────┴──────────┴────────────────────┘
Теперь мы наконец подошли к самому вопросу:
Я хотел бы снова развернуть ranks
и создать аккуратный фрейм данных. Я ищу следующие столбцы: symbol
, date
, return
и rank
. Я думал о создании трех новых столбцов (в основном с помощью explode
для распаковки списка, но при этом будут создаваться только новые строки, а не столбцы).
Кроме того, мне интересно, нужно ли мне вообще поворачивать df
или есть лучший способ напрямую работать с оригиналом df
в аккуратном формате? На самом деле мне нужна производительность, поскольку df
может иметь миллионы строк.
🤔 А знаете ли вы, что...
Python поддерживает параллельное и асинхронное программирование с помощью модулей asyncio и multiprocessing.
Что ж, вы можете упростить процесс без необходимости explode
и избежать необходимости поворачивать и отменять поворот:
returns = df.drop_nulls().with_columns(
pl.col("price").pct_change(n=2).over("symbol").alias("return")
)
shape: (16, 4)
┌─────────┬────────────┬───────┬──────────┐
│ symbol ┆ date ┆ price ┆ return │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ f64 │
╞═════════╪════════════╪═══════╪══════════╡
│ symbol1 ┆ 2023-12-30 ┆ 100 ┆ null │
│ symbol1 ┆ 2023-12-31 ┆ 105 ┆ null │
│ symbol1 ┆ 2024-01-03 ┆ 110 ┆ 0.1 │
│ symbol1 ┆ 2024-01-04 ┆ 115 ┆ 0.095238 │
│ symbol1 ┆ 2024-01-05 ┆ 120 ┆ 0.090909 │
│ … ┆ … ┆ … ┆ … │
│ symbol3 ┆ 2023-12-30 ┆ 3000 ┆ null │
│ symbol3 ┆ 2023-12-31 ┆ 3100 ┆ null │
│ symbol3 ┆ 2024-01-03 ┆ 3200 ┆ 0.066667 │
│ symbol3 ┆ 2024-01-04 ┆ 3300 ┆ 0.064516 │
│ symbol3 ┆ 2024-01-05 ┆ 3400 ┆ 0.0625 │
└─────────┴────────────┴───────┴──────────┘
Далее ранжируйте возвращаемые значения:
ranked_returns = returns.with_columns(
pl.col("return").rank(descending=True).over("date").cast(pl.UInt8).alias("rank")
)
shape: (16, 5)
┌─────────┬────────────┬───────┬──────────┬──────┐
│ symbol ┆ date ┆ price ┆ return ┆ rank │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ f64 ┆ u8 │
╞═════════╪════════════╪═══════╪══════════╪══════╡
│ symbol1 ┆ 2023-12-30 ┆ 100 ┆ null ┆ null │
│ symbol1 ┆ 2023-12-31 ┆ 105 ┆ null ┆ null │
│ symbol1 ┆ 2024-01-03 ┆ 110 ┆ 0.1 ┆ 1 │
│ symbol1 ┆ 2024-01-04 ┆ 115 ┆ 0.095238 ┆ 2 │
│ symbol1 ┆ 2024-01-05 ┆ 120 ┆ 0.090909 ┆ 2 │
│ … ┆ … ┆ … ┆ … ┆ … │
│ symbol3 ┆ 2023-12-30 ┆ 3000 ┆ null ┆ null │
│ symbol3 ┆ 2023-12-31 ┆ 3100 ┆ null ┆ null │
│ symbol3 ┆ 2024-01-03 ┆ 3200 ┆ 0.066667 ┆ 2 │
│ symbol3 ┆ 2024-01-04 ┆ 3300 ┆ 0.064516 ┆ 3 │
│ symbol3 ┆ 2024-01-05 ┆ 3400 ┆ 0.0625 ┆ 3 │
└─────────┴────────────┴───────┴──────────┴──────┘
И выберите только столбцы symbol
, date
, return
и rank
:
tidy_df = ranked_returns.select(["symbol", "date", "return", "rank"])
shape: (16, 4)
┌─────────┬────────────┬──────────┬──────┐
│ symbol ┆ date ┆ return ┆ rank │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ u8 │
╞═════════╪════════════╪══════════╪══════╡
│ symbol1 ┆ 2023-12-30 ┆ null ┆ null │
│ symbol1 ┆ 2023-12-31 ┆ null ┆ null │
│ symbol1 ┆ 2024-01-03 ┆ 0.1 ┆ 1 │
│ symbol1 ┆ 2024-01-04 ┆ 0.095238 ┆ 2 │
│ symbol1 ┆ 2024-01-05 ┆ 0.090909 ┆ 2 │
│ … ┆ … ┆ … ┆ … │
│ symbol3 ┆ 2023-12-30 ┆ null ┆ null │
│ symbol3 ┆ 2023-12-31 ┆ null ┆ null │
│ symbol3 ┆ 2024-01-03 ┆ 0.066667 ┆ 2 │
│ symbol3 ┆ 2024-01-04 ┆ 0.064516 ┆ 3 │
│ symbol3 ┆ 2024-01-05 ┆ 0.0625 ┆ 3 │
└─────────┴────────────┴──────────┴──────┘