代码之家  ›  专栏  ›  技术社区  ›  Andi

使用整洁的polars数据帧计算横截面排名

  •  2
  • Andi  · 技术社区  · 11 月前

    我需要计算一些交易证券的横截面排名。考虑以下几点 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 创建a 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 可能有数百万行。

    1 回复  |  直到 11 月前
        1
  •  2
  •   m-sarabi    11 月前

    好吧,你可以简化这个过程,而不需要 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    │
    └─────────┴────────────┴──────────┴──────┘