Попробуйте этот код:
def correct_outliers(s, threshold, n_prev=3, n_next=1):
local_s = s.copy()
updated_index = local_s.to_frame().apply(lambda x: correct_outlier(x, local_s, threshold), axis=1)
return local_s
def correct_outlier(x, s, threshold, n_prev=3, n_next=1):
if x.isna()[0] or x[0] < threshold:
lower_index, upper_index = get_fixed_index(x.name, n_prev, n_next)
s[x.name] = s.loc[lower_index:x.name-1].append(s.loc[x.name+1:upper_index]).mean(skipna=True)
return True
return False
Пояснения и соображения
- Я использовал
pandas.Series
для работы с данными - Функция
correct_outliers
получает каквведите ряд панд, значение threshold
и границы окна (n_prev
и n_nex
) - Функция
correct_outlier
вызывается correct_outliers
и применяется элемент за элементом кряд во вводе с использованием series.apply
- Функция
get_fixed_bounds
задает индекс текущего элемента i
и значения границ окна с учетом ваших запросов, сформулированных вами в приложении - Суть функции
correct_outlier
заключается в следующем:
Если текущее значение серии меньше значения threshold
, то текущее значение серии заменяется на average
рассчитано в интервале, определяемом фиксированными границами (исключая нулевые значения и текущее значение)
Пример
Приведены следующие ряды данных:
s = pd.Series([10, 20, 2, 40, 50, 60, 70, 80, 0, 100], dtypes='float')
0 10.0
1 20.0
2 2.0
3 40.0
4 50.0
5 60.0
6 70.0
7 80.0
8 0.0
9 100.0
Определить пороговое значение иwindows:
threshold = 5 # s.mean(skipna=True) in your example
n_prev = 3 # 3 element before the current
n_next = 1 # 1 element after the current
Теперь вызовите correct_outliers:
fixed_series = correct_outliers(s, n_prev, n_next, threshold), axis=1)
И дает:
0 10.0
1 20.0
2 30.0
3 40.0
4 50.0
5 60.0
6 70.0
7 80.0
8 77.5
9 100.0
Пошаговое выполнение:
Учитываяте же входные данные, что и в предыдущем примере, я покажу вам пошаговое выполнение для x = 2
, как вы меня просили.
После вызова correct_outliers
ряд повторяется с помощью функции apply, и к каждому элементу применяется функция correct_outlier
, в случае, если рассматриваемый элемент равен x = 2
, шагвыполнение шага будет следующим:
--- correct_outlier(), input: x: 2.0 threshold:5.0 n_prev: 3 n_next: 1
step:
if_condition: x is nan or x<threshold? True
--- get_fixed_index(), input: current_index: 2 n_prev: 3 n_next: 1
step: if_condition: current_index-n_prev>=0? False
output: lower_index: 0 upper_index: 4
slice of series: [10. 20. 40. 50.] mean: 30.0
@@@@ replace the value 2.0 with 30
Extra
Функции correct_outlier
и correct_outliers
не особенно эффективны, в основном по этим причинам:
- Вся серия повторяется на чистом Python, и это никогда не будет хорошей идеей .Если возможно, вы всегда должны использовать библиотечные функции для анализа данных (такие как Pandas, Numpy, ...), которые реализованы на C / C ++ и, следовательно, на несколько порядков более эффективны, чем реализация на чистом Python.
- Мы можем обойтись без функции добавления между двумя рядами, используемой в
correct_outliers
, мы можем решить проблему, просто выполнив взвешенное среднее (что, очевидно, намного быстрее)
Первая точка - это реальнаяУзкое место.
Как решить?
Ниже я предлагаю два оптимизированных решения для функций, которые мы видели:
def correct_outliers_opt(s, threshold, n_prev=3, n_next=1):
tmp_s = s.copy()
tmp_s[tmp_s < threshold].to_frame().apply(lambda x: correct_outlier4(x, tmp_s, threshold), axis=1)
return tmp_s
def correct_outlier_opt(x, s, threshold, n_prev=3, n_next=1):
i = x.name
lower_index, upper_index = get_fixed_index(x.name, n_prev, n_next)
n = upper_index - lower_index
mean = s.loc[lower_index:i-1].mean(skipna=True)*(i-lower_index)/n + ss.loc[i+1:upper_index].mean(skipna=True)*(upper_index-i)/n
s[i] = mean
return mean
Ключевым моментом являетсяв пределах correct_outliers_opt
и имеет следующий вид:
tmp_s[tmp_s < threshold]
Таким образом, я фильтрую ряд (используя преимущества функций Pandas вместо чистого питона), прежде чем итерировать его: таким образом, только значениякоторые удовлетворяют условию, будут повторяться.В нашем примере мы выполняем итерацию в python только с двумя значениями, которые нам нужно заменить вместо итерации, чем весь ряд.
Второе, что было оптимизировано (что оказывает гораздо меньшее влияние на производительность, чем предыдущиеточка) - это вычисление среднего значения в функции correct_outlier_opt
: вместо добавления между рядами теперь среднее значение вычисляется отдельно для двух рядов, а затем делается взвешенное среднее для получения одного результата.
Сравнение времени выполнения
Оба получают одинаковые входные данные и возвращают одинаковые выходные данные, но с отчетливо разными временами выполнения.
Время выполнения было вычислено для следующего экземпляра теста:
threshold = 5
n_prev, n_next = 3, 1
N = 1000
ss = pd.Series([10, 20, 2, 40, 50, 60, 70, 80, 0, 100] * N, dtype='float') # total len N * 10
correct_outliers:
%%timeit
correct_outliers(ss, threshold)
# Execution time: 2.95 s ± 417 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
оптимизированная версия:
%%timeit
correct_outliers_opt(ss, threshold)
#Execution time: 545 ms ± 16.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Как видите, оптимизированная версия примерно в 6 раз быстрее.