Pandas вычисляет количество последовательных значений в пределах допуска

#python #pandas

#python #pandas

Вопрос:

Допустим, у меня есть список запросов на веб-сайт за последовательные дни. Я хочу рассчитать количество дней, в течение которых количество запросов текущего дня находится в пределах некоторого допуска (% от количества текущих дней).

Синтетический пример:

 >>> pd.DataFrame({'req': {0: 15, 1: 16, 2: 14, 3: 15, 4: 16, 5: 16, 6: 17, 7: 30, 8: 31, 9: 35, 10: 32, 11: 35, 12: 34, 13: 33, 14: 37}, 'lo': {0: 13.5, 1: 14.4, 2: 12.6, 3: 13.5, 4: 14.4, 5: 14.4, 6: 15.3, 7: 27.0, 8: 27.9, 9: 31.5, 10: 28.8, 11: 31.5, 12: 30.6, 13: 29.7, 14: 33.3}, 'hi': {0: 16.5, 1: 17.6, 2: 15.4, 3: 16.5, 4: 17.6, 5: 17.6, 6: 18.7, 7: 33.0, 8: 34.1, 9: 38.5, 10: 35.2, 11: 38.5, 12: 37.4, 13: 36.3, 14: 40.7}, 'con10': {0: 0, 1: 1, 2: 0, 3: 3, 4: 1, 5: 2, 6: 2, 7: 0, 8: 1, 9: 0, 10: 3, 11: 2, 12: 4, 13: 6, 14: 0}})
    req    lo    hi  con10
0    15  13.5  16.5      0
1    16  14.4  17.6      1
2    14  12.6  15.4      0
3    15  13.5  16.5      3
4    16  14.4  17.6      1
5    16  14.4  17.6      2
6    17  15.3  18.7      2
7    30  27.0  33.0      0
8    31  27.9  34.1      1
9    35  31.5  38.5      0
10   32  28.8  35.2      3
11   35  31.5  38.5      2
12   34  30.6  37.4      4
13   33  29.7  36.3      6
14   37  33.3  40.7      0
 

Выше:

  • req это количество запросов за этот день,
  • lo и hi является диапазоном допуска для этого дня и
  • con10 это количество последовательных дней, предшествующих этому дню, когда количество запросов находится в пределах заданного допуска (в данном случае 10%).

Любые указания относительно того, как я буду вычислять con для данного допуска (или, в более общем смысле, список допусков, т. Е. con05 con07 con10 Для 5/7/10% соответственно)?

Комментарии:

1. Возможно, вы захотите показать более подробную информацию об этом.

2. @BENY Конечно, какие детали, по вашему мнению, мне было бы полезно добавить?

3. Просто почему в строке 1 16 con10 равно 1

4. Как насчет idx 3 с req = 15, почему это не 3 дюйма con10 ?

5. Ваше последнее значение также должно быть 0, верно !?

Ответ №1:

Обновлено:

Вот способ сделать это (взяв последние предыдущие строки):

 def last_within_range(df, target_col='req', tolerance=10):
    
    df = df.copy()
    s = pd.Series(dtype=int, index=df.index)
    
    # Get low and high tolerance
    df['lo'] = df[target_col] - df[target_col] * tolerance/100
    df['hi'] = df[target_col]   df[target_col] * tolerance/100
    
    # Find how many last rows the current value from `req` is within the desired range  
    for idx in df.index[1:]:
        past_idx = df.index[:df.index.get_loc(idx)]
        req = df.loc[idx, 'req']
        # Get bool values and identify groups to get the last one
        values = (req >= df.loc[past_idx, 'lo']) amp; (req <= df.loc[past_idx, 'hi'])
        grps = (values != values.shift()).cumsum()
        # If the last group is True, then get its sum
        s[idx] = grps.eq(grps.iloc[-1]).sum() if values.iloc[-1] == True else 0
        
    return df.assign(**{f'con{tolerance}': s})

last_within_range(df, tolerance=10)
 

Вывод:

     req    lo    hi  con10
0    15  13.5  16.5      0
1    16  14.4  17.6      1
2    14  12.6  15.4      0
3    15  13.5  16.5      3
4    16  14.4  17.6      1
5    16  14.4  17.6      2
6    17  15.3  18.7      2
7    30  27.0  33.0      0
8    31  27.9  34.1      1
9    35  31.5  38.5      0
10   32  28.8  35.2      3
11   35  31.5  38.5      2
12   34  30.6  37.4      4
13   33  29.7  36.3      6
14   37  33.3  40.7      0
 

Однако он использует цикл: (


Оригинальный ответ:

Вы можете использовать функцию для получения calcule lo , а hi затем использовать их в цикле по индексам. Взгляните на следующую функцию:

 def last_within_range(df, target_col='req', tolerance=10):
    
    df = df.copy()
    s = pd.Series(dtype=int, index=df.index)
    
    # Get low and high tolerance
    df['lo'] = df[target_col] - df[target_col] * tolerance/100
    df['hi'] = df[target_col]   df[target_col] * tolerance/100
    
    # Find how many last rows the current value from `req` is within the desired range  
    for idx in df.index:
        past_idx = df.index[:df.index.get_loc(idx)]
        req = df.loc[idx, 'req']
        s[idx] = (
            ((req >= df.loc[past_idx, 'lo']) amp; (req <= df.loc[past_idx, 'hi'])).sum()
        )
        
    return s # df.assign(**{f'con{tolerance}': s})
 

где он возвращает a Series в качестве вывода. Так, например:

 df['con5'] = last_within_range(df, tolerance=5)
df['con7'] = last_within_range(df, tolerance=7)
df['con10'] = last_within_range(df, tolerance=10)
 
     req  con5  con7  con10
0    15     0     0      0
1    16     0     1      1
2    14     0     1      1
3    15     1     2      3
4    16     1     3      3
5    16     2     4      4
6    17     0     3      3
7    30     0     0      0
8    31     1     1      1
9    35     0     0      0
10   32     1     2      3
11   35     1     1      2
12   34     2     3      4
13   33     2     5      6
14   37     0     2      3
 

Обратите внимание, что он не возвращает, сколько последовательных предыдущих строк находятся в пределах ожидаемого диапазона, но он возвращает, сколько из предыдущих строк соответствует вашим критериям.

Кроме того, если вы хотите видеть выходные данные вместе с вычисляемым lo и hi , вместо этого вы запускаете return dataframe , заменив оператор return на df.assign(**{f'con{tolerance}': s}) , а не s .

Комментарии:

1. Спасибо! Однако важно требование последовательности. Кроме того, поскольку объем данных не является незначительным, есть ли способ избежать части цикла python ( for idx in df.index: ), т. Е. Делать это только с pandas?

2. Я так не думаю. Хотя pandas предоставляет a rolling , вы не ищете конкретный оконный подход. Вам нужно будет проанализировать все предыдущие строки для каждой строки, а затем определить, есть ли группа последних последовательных True s… Однако этот код можно адаптировать для его достижения (если вам интересно).

3. Я обновил вопрос. К сожалению, я не смог найти способ только с pandas, без цикла, но это работает. Дайте мне знать, если у вас есть какие-либо сомнения. Лучшее!