pandas 或 Polars:查找比当前元素大的前一个元素的索引

mar*_*lli 10 python pandas python-polars

假设我的数据如下所示:

data = {
    'value': [1,9,6,7,3, 2,4,5,1,9]
}
Run Code Online (Sandbox Code Playgroud)

对于每一行,我想找到比当前元素大的最新前一个元素的行号。

所以,我的预期输出是:

[None, 0, 1, 2, 1, 1, 3, 4, 1, 0]
Run Code Online (Sandbox Code Playgroud)
  • 第一个元素1没有前一个元素,所以我想None在结果中
  • 下一个元素9至少比它之前的所有元素一样大,所以我想0在结果中
  • 下一个元素的6前一个元素9比它大。他们之间的距离是1。所以,我想要1在这里的结果。

我知道我可以在 Python 中循环执行此操作(如果我编写扩展,则可以在 C/Rust 中)。

我的问题:是否可以完全使用数据帧操作来解决这个问题?熊猫或者北极熊,都可以。但仅限数据帧操作。

因此,请不要执行以下操作:

  • apply
  • map_elements
  • map_rows
  • iter_rows
  • Python for 循环遍历行并从数据帧中逐一提取元素

And*_*ely 9

很难向量化此类问题,但您可以使用模块来加速任务。这个问题也可以很容易地并行化:

from numba import njit, prange

@njit(parallel=True)
def get_values(values):
    out = np.zeros_like(values, dtype=np.float64)

    for i in prange(len(values)):
        idx = np.int64(i)
        v = values[idx]

        while idx > -1 and values[idx] <= v:
            idx -= 1

        if idx > -1:
            out[i] = i - idx

    out[0] = np.nan
    return out

data = {
    "value": [1, 9, 6, 7, 3, 2, 4, 5, 1, 9],
    "out": [None, 0, 1, 2, 1, 1, 3, 4, 1, 0],
}
df = pd.DataFrame(data)

df["out2"] = get_values(df["value"].values)
print(df)
Run Code Online (Sandbox Code Playgroud)

印刷:

   value  out  out2
0      1  NaN   NaN
1      9  0.0   0.0
2      6  1.0   1.0
3      7  2.0   2.0
4      3  1.0   1.0
5      2  1.0   1.0
6      4  3.0   3.0
7      5  4.0   4.0
8      1  1.0   1.0
9      9  0.0   0.0
Run Code Online (Sandbox Code Playgroud)

基准(1-100 之间有 1_000_000 个项目):

from timeit import timeit

data = {
    "value": np.random.randint(1, 100, size=1_000_000),
}
df = pd.DataFrame(data)

t = timeit('df["out"] = get_values(df["value"].values)', globals=globals(), number=1)
print(t)
Run Code Online (Sandbox Code Playgroud)

在我的机器 (AMD 5700x) 上打印:

0.3559090679627843
Run Code Online (Sandbox Code Playgroud)


Lau*_* B. 3

我猜您正在寻找在 Rust 中实现的算法部分,所以我向您建议以下内容:

\n
import pandas as pd\nimport time\nimport numpy as np\n\ndata = {\n    \'value\': [1, 9, 6, 7, 3, 2, 4, 5, 1, 9]\n}\ndf = pd.DataFrame(data)\n\nvalues = df[\'value\'].tolist()\n\n\nstart = time.time()\n### Algorithm for implementation in Rust, C ...\n_max = values[0]\nr = [None]\nfor i in range(1, len(values)):\n    prev = values[:i+1][:-1]\n    last = values[:i+1][-1]\n    dist=0\n    _max = max(prev) if last >= _max else _max\n    for j in range(len(prev)-1, -1, -1):\n        if last < _max:\n            dist+=1\n        else:\n            r.append(dist)\n            break\n        if last < prev[j]:\n            r.append(dist)\n            break\nend = time.time()\n\nprint(end-start)\nprint(r)\n
Run Code Online (Sandbox Code Playgroud)\n
[None, 0, 1, 2, 1, 1, 3, 4, 1, 0]\n
Run Code Online (Sandbox Code Playgroud)\n

要在 Rust、Python 或其他语言中计算后在数据帧中实现计算:

\n
[None, 0, 1, 2, 1, 1, 3, 4, 1, 0]\n
Run Code Online (Sandbox Code Playgroud)\n
   value  out\n0      1  NaN\n1      9  0.0\n2      6  1.0\n3      7  2.0\n4      3  1.0\n5      2  1.0\n6      4  3.0\n7      5  4.0\n8      1  1.0\n9      9  0.0\n
Run Code Online (Sandbox Code Playgroud)\n

Rust 实现(参见 PyO3):

\n

(应保留Python算法的先验逻辑)

\n

在线编译器:https://play.rust-lang.org/?version =stable&mode=debug&edition=2021

\n
df[\'out\'] = r\n\nprint(df)\n
Run Code Online (Sandbox Code Playgroud)\n

结果(在线测试):

\n
Time elapsed is: 5.02\xc2\xb5s\n[None, Some(0), Some(1), Some(2), Some(1), Some(1), Some(3), Some(4), Some(1), Some(0)]\n
Run Code Online (Sandbox Code Playgroud)\n

笔记 :

\n

要在 Rust 中并行化任务,您可以使用radius.rayon它提供的并行迭代器来并行化许多数据处理任务。

\n