获取第一个先前较小值的索引

3

我有一个数据帧,长这样:

index value
0     1
1     1
2     2
3     3
4     2
5     1
6     1

我希望每个值都返回前一个较小值的索引,以及前一个“1”值的索引。如果值为1,则不需要它们(两个值都可以是-1或其他值)。
因此,我想要的是:
index value  previous_smaller_index  previous_1_index
0     1            -1                      -1
1     1            -1                      -1
2     2             1                       1
3     3             2                       1
4     2             1                       1
5     1            -1                      -1
6     1            -1                      -1

我尝试使用滚动、累积函数等方法,但是无法弄清楚。欢迎任何帮助!

编辑: SpghttCd已经为“前1个”问题提供了一个不错的解决方案。我正在寻找一个漂亮的pandas一行代码解决“前小”问题。(当然,对于这两个问题,更好和更有效的解决方案都受到欢迎)

4个回答

6
  • 使用向量化的numpy广播比较和argmax,可以找到"previous_smaller_index"。

  • 使用cumsummed掩码上的groupbyidxmax,可以解决"previous_1_index"。

m = df.value.eq(1)
u = np.triu(df.value.values < df.value[:,None]).argmax(1)
v = m.cumsum()

df['previous_smaller_index'] = np.where(m, -1, len(df) - u - 1)
df['previous_1_index'] = v.groupby(v).transform('idxmax').mask(m, -1)

df
   index  value  previous_smaller_index  previous_1_index
0      0      1                      -1                -1
1      1      1                      -1                -1
2      2      2                       1                 1
3      3      3                       2                 1
4      4      2                       1                 1
5      5      1                      -1                -1
6      6      1                      -1                -1

如果您想将这些内容压缩成一行,可以将几行内容压缩成一行:
m = df.value.eq(1)
df['previous_smaller_index'] = np.where(
    m, -1, len(df) - np.triu(df.value.values < df.value[:,None]).argmax(1) - 1
)[::-1]

# Optimizing @SpghttCd's `previous_1_index` calculation a bit
df['previous_1_index'] = (np.where(
    m, -1, df.index.where(m).to_series(index=df.index).ffill(downcast='infer'))
)

df

   index  value  previous_1_index  previous_smaller_index
0      0      1                -1                      -1
1      1      1                -1                      -1
2      2      2                 1                       1
3      3      3                 1                       2
4      4      2                 1                       1
5      5      1                -1                      -1
6      6      1                -1                      -1

总体表现

使用perfplot进行设置和性能基准测试。 代码可以在此处找到。

enter image description here

计时是相对的(y轴是对数刻度)。


previous_1_index 表现

相关代码的Gist。

enter image description here


@BinyaminEven 我只是想指出,我已经为我的解决方案添加了一行代码版本。 - cs95
谢谢!这是一个有趣的解决方案,我需要将其分解以完全理解它。与SpghttCd的解决方案比较效率将会很有趣。他的代码,特别是prev_1,要短得多。 - Binyamin Even
@BinyaminEven 请记住,代码短并不意味着性能好。请参考这个答案,其中一个25行的numpy函数胜过了pandas的一行代码。 - cs95
我并没有说他的代码更高效,实际上对于 prev_small,他使用了 apply,而你没有。但是我有一种感觉,他的 prev_1 代码运行速度更快。需要进行 "timeit 测试"。 - Binyamin Even
1
好的,谢谢!因为我向他承诺过,所以我会接受他的答案,但我很乐意给你悬赏(我大约8小时后可以给)。 - Binyamin Even
显示剩余4条评论

2
你可以尝试:
df = pd.DataFrame({'value': [1,  1,  2,  3,  2,  1,  1, 2, 3, 4, 5]})

df['prev_smaller_idx'] = df.apply(lambda x: df.index[:x.name][(x.value>df.value)[:x.name]].max(), axis=1)

df['prev_1_idx'] = pd.Series(df.index.where(df.value==1)).shift()[df.value!=1].ffill()

#    value  prev_smaller_idx  prev_1_idx
#0       1               NaN         NaN
#1       1               NaN         NaN
#2       2               1.0         1.0
#3       3               2.0         1.0
#4       2               1.0         1.0
#5       1               NaN         NaN
#6       1               NaN         NaN
#7       2               6.0         6.0
#8       3               7.0         6.0
#9       4               8.0         6.0
#10      5               9.0         6.0

1
这个函数应该可以工作:

def func(values, null_val=-1):
    # Initialize with arbitrary value
    prev_small = values * -2
    prev_1 = values * -2

    # Loop through values and find previous values
    for n, x in enumerate(values):
        prev_vals = values.iloc[:n]
        prev_small[n] = prev_vals[prev_vals < x].index[-1] if (prev_vals < x).any() else null_val
        prev_1[n] = prev_vals[prev_vals == 1].index[-1] if x != 1 and (prev_vals == 1).any() else null_val

    return prev_small, prev_1

df = pd.DataFrame({'value': [1,  1,  2,  3,  2,  1,  1,]})
df['previous_small'], df['previous_1'] = func(df['value'])

输出:

   value  previous_small  previous_1
0      1              -1          -1
1      1              -1          -1
2      2               1           1
3      3               2           1
4      2               1           1
5      1              -1          -1
6      1              -1          -1

1
这是关于编程的,要做的是previous_smaller_index
l=list(zip(df['index'],df.value))[::-1]

t=[]
n=len(l)
for x in l:
    if x[1]==1:
        t.append(-1)
    else:
        t.append(next(y for y in l[n-x[0]:] if y[1]<x[1])[0])
df['previous_smaller_index']=t[::-1]
df
Out[71]: 
   index  value  previous_smaller_index
0      0      1                      -1
1      1      1                      -1
2      2      2                       1
3      3      3                       2
4      4      2                       1
5      5      1                      -1
6      6      1                      -1

获取前一个1
df['index'].where(df.value==1).ffill().where(df.value!=1,-1)
Out[77]: 
0   -1.0
1   -1.0
2    1.0
3    1.0
4    1.0
5   -1.0
6   -1.0
Name: index, dtype: float64

将其重新分配回去。
df['previous_1_index']=df['index'].where(df.value==1).ffill().where(df.value!=1,-1)



df
Out[79]: 
   index  value  previous_smaller_index  previous_1_index
0      0      1                      -1              -1.0
1      1      1                      -1              -1.0
2      2      2                       1               1.0
3      3      3                       2               1.0
4      4      2                       1               1.0
5      5      1                      -1              -1.0
6      6      1                      -1              -1.0

这里的 l 是什么?您能否重新编写第一部分,以便它与 df 上下文相关? - cs95
另外,当我运行你的代码来获取“previous_smaller_index”时,我得到了t = [-1, -1, 5.0, 4.0, 5.0, -1, -1],你能检查一下吗? - cs95
1
@coldspeed明白了,我忘记在这里复制完整的代码了,编辑一下!:-) 对不起,兄弟。 - BENY
@coldspeed 看起来还不够快,你的解决方案性能要好得多。 - BENY
这是一个不错的替代方案。我认为如果没有Numba,进一步优化previous_1_index将非常困难。而且考虑到OP想要“一行代码”,我不确定他们会对Numba解决方案有多少兴趣。 - cs95
1
@coldspeed Numba应该会更快。一行代码很整洁,但是如果考虑性能,我认为多行代码并不意味着“糟糕” :-) (仅代表个人观点。) - BENY

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接