如何找到一行的值连续达到max的次数

2024-09-29 02:22:04 发布

您现在位置:Python中文网/ 问答频道 /正文

我想知道一行的值连续达到max的次数。你知道吗

  • Ps1:我的数据有500K行,所以我担心计算速度

  • Ps2:在本例中,startDay=1,endDay=7,但有些行 不同的开始或结束日期。(例如startDay=2,endDay=5或 开始日期=4,结束日期=3。arr\u bool control this conditions)

我的数据:

import pandas as pd
import numpy as np
idx = ['id1', 'id2', 'id3', 'id4', 'id5',
       'id6', 'id7', 'id8', 'id9', 'id10']
data = {'Day1':[0,0,1,0,1,1,0,0,1,1],
        'Day2':[0,1,1,1,2,1,0,1,1,2],
        'Day3':[1,3,1,1,1,0,0,1,3,2],
        'Day4':[1,2,0,1,1,0,0,2,1,1],
        'Day5':[0,2,1,1,1,1,0,2,1,1],
        'Day6':[1,0,1,1,2,1,0,2,1,1],
        'Day7':[0,0,0,1,1,1,0,0,3,1]}

startday = pd.DataFrame([1,1,1,1,1,1,1,1,1,1],columns=['start'], index=idx)
endday = pd.DataFrame([7,7,7,7,7,7,7,7,7,7],columns=['end'], index=idx)
df = pd.DataFrame(data, index=idx)
Neg99 = -999
Neg90 = -900

我应该搜索每一行的时间间隔 我可以找到时间间隔中的最大值,但找不到连续命中最大值的行的值计数。你知道吗

arr_bool = (np.less_equal.outer(startday.start, range(1,8)) 
            & np.greater_equal.outer(endday.end, range(1,8))
            )
df_result = pd.DataFrame(df.mask(~arr_bool).max(axis=1),
                                        index=idx, columns=['result'])

最后条件:

df_result.result= np.select( condlist = [startday.start > endday.end,
                                         ~arr_bool.any(axis=1)],
                         choicelist = [Neg99,Neg90], 
                         default = df_result.result)

我想要的结果

result_i_want = pd.DataFrame([2,1,3,6,1,3,0,3,1,2],columns=['result'], index=idx)

这是@WeNYoBen的解决方案,但进展缓慢

s=((df.eq(df.max(1),0))&(df.ne(0)))
s.apply(lambda x : x[x].groupby((~x).cumsum()).count().max(),1).fillna(0)

Tags: columnsdataframedfindexnpresultstartmax
3条回答

尝试以下解决方案:

从定义要应用于每行的函数开始:

def fn(row):
    sd = startday.loc[row.name, 'start'] - 1
    ed = endday.loc[row.name, 'end']
    rr = row.values[sd:ed]
    vMax = rr.max()
    if vMax > 0:
        gr = itertools.groupby(rr)
        return max([ len(list(grp)) for key, grp in gr if key == vMax ])
    else:
        return 0

itertools.groupby(上面使用)和 pd.groupbyitertools版本启动了一个新组 在源值的每个更改上,因此每个组包含一个序列 具有相同的值(来自当前行)。你知道吗

第一步是获取当前行的适当切片。你知道吗

  • sd是起始索引(包括)
  • ed是结束索引(不包括)
  • rr是正确的片(进一步称为)。你知道吗

如果当前行中的最大值为>;0,则列表将显示 以上使用:

  • groupby的结果中读取grp(当前组)。你知道吗
  • 如果(当前组中包含的值)是 最大值,则添加到结果中的值就是长度 当前组的。你知道吗

函数返回的值是此列表中的最大值, i、 e.最大值的最长序列的长度。你知道吗

如果当前行仅包含零(max==0),则返回0。你知道吗

然后,唯一要做的就是将上述函数应用于每一行:

df['result'] = df.apply(fn, axis=1)

当然,您必须导入itertools。你知道吗

与其他答案相比,我的解决方案的优点是 明显更短。你知道吗

下面是另一个numpy解决方案。首先,将计时与@pirsquares进行比较,以供参考。在给出完全相同的结果的同时,我的代码在一个大型示例中的速度提高了约14倍。你知道吗

# both methods give the expected result on small OP example                                                        
      result                                                                                                    
id1        2                                                                                                    
id2        1                                                                                                    
id3        3                                                                                                    
id4        6                                                                                                    
id5        1                                                                                                    
id6        3                                                                                                    
id7        0                                                                                                    
id8        3                                                                                                    
id9        1                                                                                                    
id10       2                                                                                                    
      result                                                                                                    
id1        2                                                                                                    
id2        1                                                                                                    
id3        3                                                                                                    
id4        6                                                                                                    
id5        1                                                                                                    
id6        3                                                                                                    
id7        0                                                                                                    
id8        3                                                                                                    
id9        1
id10       2

# timings on 50,000 rows random example
pp 12.89263810031116
pi 189.0821446024347
# comparison of results
result    True
dtype: bool

代码:

import pandas as pd
import numpy as np

# OP example
idx = ['id1', 'id2', 'id3', 'id4', 'id5',
       'id6', 'id7', 'id8', 'id9', 'id10']
data = {'Day1':[0,0,1,0,1,1,0,0,1,1],
        'Day2':[0,1,1,1,2,1,0,1,1,2],
        'Day3':[1,3,1,1,1,0,0,1,3,2],
        'Day4':[1,2,0,1,1,0,0,2,1,1],
        'Day5':[0,2,1,1,1,1,0,2,1,1],
        'Day6':[1,0,1,1,2,1,0,2,1,1],
        'Day7':[0,0,0,1,1,1,0,0,3,1]}

startday = pd.DataFrame([1,1,1,1,1,1,1,1,1,1],columns=['start'], index=idx)
endday = pd.DataFrame([7,7,7,7,7,7,7,7,7,7],columns=['end'], index=idx)
df = pd.DataFrame(data, index=idx)
Neg99 = -999
Neg90 = -900

# large example
IDX = [f'id{i}' for i in range(1,50_001)]
STARTDAY, ENDDAY = (pd.DataFrame({c:l}, index=IDX) for c,l in zip(('start','end'), np.sort(np.random.randint(1,8,(2,50_000)), axis=0)))
DF = pd.DataFrame({f'Day{i}':l for i,l in enumerate(np.random.randint(0,4,(7, 50_000)), 1)}, index=IDX)

def pp():
    if restrict_max:
        data = np.where((startday.values<=np.arange(1,8)) & (endday.values>=np.arange(1,8)), df.values, 0)
        mask = data==np.maximum((data==0).all(1), data.max(1))[:, None]
    else:
        mask = (df.values==np.maximum((df.values==0).all(1), df.values.max(1))[:, None]) & (startday.values<=np.arange(1,8)) & (endday.values>=np.arange(1,8))
    y, x = np.where(np.diff(mask, axis=1, prepend=False, append=False))
    y = y[::2]
    x = x[1::2]-x[::2]
    res = np.zeros(df.values.shape[:1], int)
    nl = np.flatnonzero(np.diff(y, prepend=-1))
    res[y[nl]] = np.maximum.reduceat(x, nl)
    return pd.DataFrame({'result': res}, index=df.index)

def pi():
    sd = startday.start.values
    ed = endday.end.values

    dr = ed - sd + 1

    i = np.arange(len(df)).repeat(dr)
    j = np.concatenate([np.arange(s - 1, e) for s, e in zip(sd, ed)])

    v = df.values

    mx = np.empty(len(v), dtype=v.dtype)
    mx.fill(v.min())
    np.maximum.at(mx, i, v[i, j])

    b = np.ones((v.shape[0], v.shape[1] + 2), bool)

    b[i, j + 1] = (v[i, j] != mx[i]) | (mx[i] == 0)

    x, y = np.where(b)

    y_ = np.diff(y)
    mask = y_ > 0
    y__ = y_[mask]
    x__ = x[1:][mask]

    c = np.empty(len(v), int)
    c.fill(y__.min())
    np.maximum.at(c, x__, y__)

    return pd.DataFrame({'result': c - 1}, index=df.index)

restrict_max=True

print(pp())
print(pi())
df, startday, endday = DF, STARTDAY, ENDDAY

from timeit import timeit

print('pp', timeit(pp,number=10)*100)
print('pi', timeit(pi,number=10)*100)
print((pp()==pi()).all())

纯粹的裸体切片之类的

这一努力的重点是OP要求速度。这应该会有帮助。如果您有权访问像numba这样的JIT库,那么应该使用它并在每一行上循环。你知道吗

sd = startday.start.values
ed = endday.end.values

dr = ed - sd + 1

i = np.arange(len(df)).repeat(dr)
j = np.concatenate([np.arange(s - 1, e) for s, e in zip(sd, ed)])

v = df.values

mx = np.empty(len(v), dtype=v.dtype)
mx.fill(v.min())
np.maximum.at(mx, i, v[i, j])

b = np.ones((v.shape[0], v.shape[1] + 2), bool)

b[i, j + 1] = (v[i, j] != mx[i]) | (mx[i] == 0)

x, y = np.where(b)

y_ = np.diff(y)
mask = y_ > 0
y__ = y_[mask]
x__ = x[1:][mask]

c = np.empty(len(v), int)
c.fill(y__.min())
np.maximum.at(c, x__, y__)

c - 1

array([2, 1, 3, 6, 1, 3, 0, 3, 1, 2])

说明

我不谈显而易见的事情。你知道吗

这表示每个间隔中的天数

dr = ed - sd + 1

ij中对应的展平列索引的展平相关行索引

i = np.arange(len(df)).repeat(dr)
j = np.concatenate([np.arange(s - 1, e) for s, e in zip(sd, ed)])

mx将是每个间隔的最大值。你知道吗

b将是一个布尔数组,其宽度比v宽2列。在这种情况下,它看起来像:

#       Buffer                                                  Buffer
#        / \                                                    / \
array([[ True,  True,  True, False, False,  True, False,  True,  True],
       [ True,  True,  True, False,  True,  True,  True,  True,  True],
       [ True, False, False, False,  True, False, False,  True,  True],
       [ True,  True, False, False, False, False, False, False,  True],
       [ True,  True, False,  True,  True,  True, False,  True,  True],
       [ True, False, False,  True,  True, False, False, False,  True],
       [ True, False, False, False, False, False, False, False,  True],
       [ True,  True,  True,  True, False, False, False,  True,  True],
       [ True,  True,  True, False,  True,  True,  True, False,  True],
       [ True,  True, False, False,  True,  True,  True,  True,  True]])

使用缓冲列的原因是我可以在使用np.where之后计算位置的差异

现在我填充b,其中v值不等于mx中的最大值

 #             not equal to max       is equal to zero
 b[i, j + 1] = (v[i, j] != mx[i]) | (mx[i] == 0)

然后我找到这些位置在y中的位置。你知道吗

通过使用diff,我找到了从一个不等于max的实例到下一个不等于max的实例的位置数。这总是比我们要查找的数字大一个,但我们稍后会更正它。你知道吗

另外,diff会将长度减少1,但实际上,有很多东西我们不需要,因为我不需要计算一行相对于前一行的差值。幸运的是,我可以消除所有零或负的差异,因为它们没有意义。你知道吗

我再次使用了np.maximum.at,但是这次是在差异上找到最大的差异,这将是每行连续最大值的最长长度。你知道吗

请注意,实际上还有一个

呸。我厌倦了打字。。。你知道吗

相关问题 更多 >