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

时间:2019-05-17 17:03:54

标签: python pandas numpy dataframe

我想找出一行的值连续达到max的次数。

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

  • Ps2:在此示例中,startDay = 1和endDay = 7,但是有些行包含 不同的开始或结束日期。 (例如startDay = 2,endDay = 5或 startDay = 4,endDay = 3。 arr_bool控制此条件)

我的数据:

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

我应该搜索每行的时间间隔(例如从开始到结束的循环) 我可以在时间间隔中找到最大值,但找不到连续命中max的行的计数。

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)

3 个答案:

答案 0 :(得分:4)

纯块状切片和东西

这项工作的重点是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,我发现了从一个不等于最大值的实例到不等于最大值的下一位置的位置数。这将始终比我们要查找的数字大一,但我们稍后会对其进行纠正。

此外,diff会将长度减少一倍,但实际上,我们不需要一堆东西,因为我不需要从一行到上一行的差值。幸运的是,我可以消除所有零差或负差,因为它们没有任何意义。

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

请注意,实际上比这还多

Ph。我已经厌倦了打字...

答案 1 :(得分:2)

这是另一个numpy解决方案。首先,将计时与@piRSquared进行比较以供参考。在一个大型示例中,我的代码速度提高了约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())

答案 2 :(得分:0)

尝试以下解决方案:

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

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.groupby itertools 版本启动了一个新组 根据源值的每次更改,因此每个组都包含一系列 相同的值(从当前行开始)。

第一步是获取当前行的适当片段。

  • sd是起始索引(包括),
  • ed是结束索引(不包括),
  • rr是适当的切片(进一步称为 row )。

如果当前行中的最大值> 0,则列表理解 上面使用的:

  • groupby 的结果中读取 key grp (当前组)。
  • 如果(当前组中包含的值)是 最大值值,那么添加到结果中的值就是长度 在当前组中。

函数返回的值是该列表中的最大值, 即最大值的最长序列的长度。

如果当前行仅包含零(最大值== 0),则返回0。

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

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

当然,您必须导入itertools

与其他答案相比,我的解决方案的优势在于 明显更短。