Учитывая,
# importing pandas as pd
import pandas as pd
import numpy as np
# Create sample dataframe
raw_data = {'ID': ['A1', 'B1', 'C1', 'D1'],
'Domain': ['Finance', 'IT', 'IT', 'Finance'],
'Target': [1, 2, 3, '0.9%'],
'Criteria':['<=', '<=', '>=', '>='],
"1/01":[0.9, 1.1, 2.1, 1],
"1/02":[0.4, 0.3, 0.5, 0.9],
"1/03":[1, 1, 4, 1.1],
"1/04":[0.7, 0.7, 0.1, 0.7],
"1/05":[0.7, 0.7, 0.1, 1],
"1/06":[0.9, 1.1, 2.1, 0.6],}
df = pd.DataFrame(raw_data, columns = ['ID', 'Domain', 'Target','Criteria', '1/01',
'1/02','1/03', '1/04','1/05', '1/06'])
Эту проблему легче решить, разбив ее на две части (абсолютные пороги и относительные пороги) и пройдя ее шаг за шагом по базовым массивам numpy.
РЕДАКТИРОВАТЬ: подробное объяснение впереди, пропустите до конца только конечную функцию
Сначала создайте список столбцов даты, чтобы получить доступ только к соответствующим столбцам в каждой строке.
date_columns = ['1/01', '1/02','1/03', '1/04','1/05', '1/06']
df[date_columns].values
#Output:
array([[0.9, 0.4, 1. , 0.7, 0.7, 0.9],
[1.1, 0.3, 1. , 0.7, 0.7, 1.1],
[2.1, 0.5, 4. , 0.1, 0.1, 2.1],
[1. , 0.9, 1.1, 0.7, 1. , 0.6]])
Затем мы можем использовать np.diff , чтобы легко получить различия между датами в базовом массиве.Мы также возьмем абсолют, потому что это то, что нас интересует.
np.abs(np.diff(df[date_columns].values))
#Output:
array([[0.5, 0.6, 0.3, 0. , 0.2],
[0.8, 0.7, 0.3, 0. , 0.4],
[1.6, 3.5, 3.9, 0. , 2. ],
[0.1, 0.2, 0.4, 0.3, 0.4]])
Теперь, просто беспокоясь об абсолютных порогах, это так же просто, как просто проверить, больше ли значения разностей, чемlimit.
abs_threshold = 0.5
np.abs(np.diff(df[date_columns].values)) > abs_threshold
#Output:
array([[False, True, False, False, False],
[ True, True, False, False, False],
[ True, True, True, False, True],
[False, False, False, False, False]])
Мы можем видеть, что сумма по этому массиву для каждой строки даст нам нужный нам результат (сумма по логическим массивам использует базовые значения True = 1 и False = 0. Таким образом, выэффективно подсчитывая, сколько True присутствует).Для пороговых значений в процентах нам просто нужно сделать дополнительный шаг, разделив все различия с исходными значениями перед сравнением.Собираем все вместе.
Для уточнения:
Мы можем видеть, как сумма по каждой строке может дать нам подсчет значений, пересекающих абсолютный порог, следующим образом.
abs_fluctuations = np.abs(np.diff(df[date_columns].values)) > abs_threshold
print(abs_fluctuations.sum(-1))
#Output:
[1 2 4 0]
Чтобы начать с относительных порогов, мы можем создать массив различий, как и раньше.
dates = df[date_columns].values #same as before, but just assigned
differences = np.abs(np.diff(dates)) #same as before, just assigned
pct_threshold=0.5 #aka 50%
print(differences.shape) #(4, 5) aka 4 rows, 5 columns if you want to think traditional tabular 2D shapes only
print(dates.shape) #(4, 6) 4 rows, 6 columns
Теперь обратите внимание, что массив разностей будет иметь на 1 меньше столбцов, что также имеет смысл.потому что для 6 дат будет 5 «разниц», по одной на каждый пробел.
Теперь, просто сосредоточившись на 1 строке, мы видим, что вычислить процентные изменения просто.
print(dates[0][:2]) #for first row[0], take the first two dates[:2]
#Output:
array([0.9, 0.4])
print(differences[0][0]) #for first row[0], take the first difference[0]
#Output:
0.5
изменение от 0.9 to 0.4
- это изменение 0.5
в абсолютном выражении.но в процентном выражении это изменение 0.5/0.9
(разница / оригинал) * 100 (где я упустил умножение на 100, чтобы упростить задачу) или 55.555%
или 0.5555
..
Главное, что нужно понять на этом этапе, - это то, что нам нужно выполнить это деление по «исходным» значениям для всех различий, чтобы получить процентные изменения.Тем не менее, массив дат имеет один «столбец» слишком много.Итак, мы делаем простой срез.
dates[:,:-1] #For all rows(:,), take all columns except the last one(:-1).
#Output:
array([[0.9, 0.4, 1. , 0.7, 0.7],
[1.1, 0.3, 1. , 0.7, 0.7],
[2.1, 0.5, 4. , 0.1, 0.1],
[1. , 0.9, 1.1, 0.7, 1. ]])
Теперь я могу просто рассчитать относительные или процентные изменения по поэлементному делению
relative_differences = differences / dates[:,:-1]
И затем, то же самое, что и раньше.выберите порог, посмотрите, пересекается ли он
rel_fluctuations = relative_differences > pct_threshold
#Output:
array([[ True, True, False, False, False],
[ True, True, False, False, True],
[ True, True, True, False, True],
[False, False, False, False, False]])
Теперь, если мы хотим рассмотреть, пересекается ли либо один из абсолютных или относительных порогов, нам просто нужновзять побитовое ИЛИ |
(оно даже есть в предложении!) и затем взять сумму по строкам.
Собрав все это вместе, мы можем просто создать функцию, которая готова к использованию.Обратите внимание, что функции не являются чем-то особенным, это просто способ группировки строк кода для простоты использования.использовать функцию так же просто, как вызвать ее, вы использовали функции / методы, не осознавая этого все время.
date_columns = ['1/01', '1/02','1/03', '1/04','1/05', '1/06'] #if hardcoded.
date_columns = df.columns[5:] #if you wish to assign dynamically, and all dates start from 5th column.
def get_FCount(df, date_columns, abs_threshold=0.5, pct_threshold=0.5):
'''Expects a list of date columns with atleast two values.
returns a 1D array, with FCounts for every row.
pct_threshold: percentage, where 1 means 100%
'''
dates = df[date_columns].values
differences = np.abs(np.diff(dates))
abs_fluctuations = differences > abs_threshold
rel_fluctuations = differences / dates[:,:-1] > pct_threshold
return (abs_fluctuations | rel_fluctuations).sum(-1) #we took a bitwise OR. since we are concerned with values that cross even one of the thresholds.
df['FCount'] = get_FCount(df, date_columns) #call our function, and assign the result array to a new column
print(df['FCount'])
#Output:
0 2
1 3
2 4
3 0
Name: FCount, dtype: int32