Скажем, у меня есть список запросов на веб-сайт за несколько дней подряд. Я хочу рассчитать количество дней, в течение которых счетчик запросов текущего дня находится в пределах некоторого допуска (% от счетчика текущего дня).
Синтетический пример:
>>> 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% соответственно)?
@BENY Конечно, как вы думаете, какие детали мне было бы полезно добавить?
Просто почему строка 1 16 con10 равна 1
@BENY В строке 1 req=16, поэтому диапазон -+10% составляет lo-hi=14,4-17,6. con10=1, потому что столько последовательных строк, которые предшествуют строке 1, попадают в этот диапазон (т.е. строка 0 имеет значение 15, которое находится между 14,4-17,6). Дайте мне знать, если это прояснит ситуацию.
Как насчет idx 3 с req=15, почему это не 3 в con10
?
Спасибо @CainãMaxCouto-Silva - это была ошибка с моей стороны, я исправил пост.
Ваше последнее значение тоже должно быть 0, верно!?
@ CainãMaxCouto-Silva Правильно, я тоже обновил это. Спасибо, что поймали этих двоих!
Обновлено:
Вот как это сделать (взяв последние предыдущие строки):
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']) & (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
Однако он использует цикл :(
Оригинальный ответ:
Вы можете использовать функцию для вычисления 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']) & (req <= df.loc[past_idx, 'hi'])).sum()
)
return s # df.assign(**{f'con{tolerance}': s})
где он возвращает 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 фрейм данных, заменив оператор return на df.assign(**{f'con{tolerance}': s})
, а не s
.
Спасибо! Однако последовательное требование важно. Кроме того, поскольку объем данных не является незначительным, есть ли способ избежать части цикла python (for idx in df.index:
), то есть сделать это только с пандами?
Я так не думаю. Хотя pandas предоставляет rolling
, вы не ищете конкретный подход к окну. Вам нужно будет проанализировать все предыдущие строки для каждой строки, а затем определить, есть ли группа последних последовательных True
s... Однако можно адаптировать этот код для его достижения (если вам интересно).
Я обновил вопрос. К сожалению, я не смог найти способ только с пандами, без цикла, но он работает. Дайте мне знать, если у вас есть какие-либо сомнения. Лучший!
Вы можете хотеть показать больше деталей об этом .