Я пытался выполнить простое скользящее среднее списка с той особенностью, что конец списка является его началом, но когда, например, со списком от 1 до 9 с использованием 3 выборок, я пытаюсь:
original_list = [*range(1,10)]
print(sum(original_list[-1:2])/3)
Выходное значение равно 0,0 и должно быть (9+1+2)/3 = 4.
Моей первой догадкой было то, что я выбрал неправильные значения индексов, поэтому распечатал их по отдельности:
print(original_list[-1:], original_list[:2])
# Print:
# [9] [1, 2]
Все было так, как и ожидалось, так что же не так в моем предыдущем отпечатке? Как я должен это делать?
Я попробовал сделать как отдельные, так и объединить их так:
print(sum(original_list[-1:] + original_list[:2]) / 3)
# Print:
# 4
Это правильное значение, однако, если я увеличу число образцов, например, до 5, и попробую:
import math
original_list = [*range(1,10)]
def MA(index):
to_be_averaged = original_list[-2 + index:] + original_list[:3 + index]
print(to_be_averaged)
print(sum(to_be_averaged) / len(to_be_averaged))
MA(0)
# Print
# [8, 9, 1, 2, 3]
# 4.6
MA(1)
# Print
# [9, 1, 2, 3, 4]
# 3.8
MA(2)
# Print
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5]
# 4.285714285714286
Что за бардак на индексе 2, что происходит? Как я должен это делать? Я чувствую, что это должен быть простой способ получить диапазон от a до b независимо, являются ли индексы положительными или отрицательными.
iirc где-то в cycle есть функция collections
Я видел это! тогда как бы вы это сделали, если бы ввели индекс исходного списка и размер выборки?
«Я чувствую, что это должен быть простой способ получить диапазон от a до b независимо от того, являются ли индексы положительными или отрицательными». Имейте в виду, что списки линейны. Поэтому иногда (если второй индекс меньше первого) вам придется объединить два подсписка вместо использования только одного.
Вас волнует скорость? Если да, то насколько велики ваши списки и образцы?






Это... не так работает нарезка. Здесь есть несколько заблуждений, поэтому нам следует разобраться в них одно за другим.
Прежде всего, когда вы начинаете с отрицательного индекса на срезе, мы работаем с конца списка. Таким образом, l[-1:] -> [9] будет давать только одно значение, если вы также не используете отрицательный шаг. Если вы используете отрицательный шаг, вы получите больше значений, но он не будет повторяться так, как вы хотите, а будет работать в обратном направлении: l[-1:2:-1] -> [9, 8, 7, 6, 5, 4].
Итак, это наша первая проблема. Далее вы должны помнить, что срезы списка представляют собой полуинтервалы ([)) — они включают начальный индекс, но не конечный индекс. Таким образом, даже если бы это произошло, l[-1:2] не включало бы 2, а остановилось бы на 1.
С учетом всего вышесказанного, возможно, вам лучше всего подойдет двусторонняя очередь для поворота списка в нужное вам состояние, чтобы вы могли легко разрезать его, а затем восстановить. Бывший:
from collections import deque
d = deque(range(1,10))
def rolling(d, start, end):
if start < 0:
d.rotate(-start)
print(sum(list(d)[0:end+1])/len(list(d)[0:end+1]))
if start < 0:
d.rotate(start)
rolling(d, -1, 2) # 4
print(d) # deque([1, 2, 3, 4, 5, 6, 7, 8, 9])
По сути, срез — это блок списка, он может пропускать значения, но не обтекает края. С помощью list[-1:2] вы говорите вернуть все значения, которые находятся после 9-го индекса и до 2-го, что, очевидно, не является ни одним из них.
Полный список такой же, как list[:n1] + list[n1:-n2] + list[-n2:], и вам нужны первый и последний фрагменты, так что вы правы в том, что вам нужно делать list[:n] и list[-n:] отдельно.
Что касается того, почему ваша функция MA вводит странное поведение для index=1, то это просто потому, что вы используете original_list[-2 + index:] вместо original_list[-2 - index:]. Когда вы ввели вход как 2, срез возвращал весь список, поэтому вы получили гораздо больше значений, чем ожидалось.
С этим исправлено:
def MA(index):
to_be_averaged = original_list[-2 - index:] + original_list[:3 + index]
print(to_be_averaged)
print(sum(to_be_averaged) / len(to_be_averaged))
>>> MA(0)
[8, 9, 1, 2, 3]
4.6
>>> MA(1)
[7, 8, 9, 1, 2, 3, 4]
4.857142857142857
>>> MA(2)
[6, 7, 8, 9, 1, 2, 3, 4, 5]
5.0
itertools.cycle не работает так, как я думал, поэтому я придумал следующее:
def cycle(arr, start, size):
print(f'start: {start}, size: {size}')
start = start % len(arr)
end = start+size
cycles = end // len(arr)
arr=arr*(1+cycles)
res = arr[start:end]
print(res)
print('average:', sum(res)/len(res))
print()
работает с действительно выходящими за пределы диапазона индексами и образцами
start: -15, size: 32
[4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8]
average: 5.15625
start: -11, size: 5
[8, 9, 1, 2, 3]
average: 4.6
start: -1, size: 3
[9, 1, 2]
average: 4.0
start: 0, size: 9
[1, 2, 3, 4, 5, 6, 7, 8, 9]
average: 5.0
start: 0, size: 18
[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
average: 5.0
start: 58, size: 3
[5, 6, 7]
average: 6.0
Как, по вашему мнению, itertools.cycle работает?
Спасибо за ответы и комментарии, это очень помогло мне разобраться в вопросе!
Однако ни один из них не обратился к главному вопросу о том, как делать скользящую среднюю в том виде, в каком она была представлена. В итоге я применил небольшие хитрости и изменил способ создания нового списка для усреднения (вместо нарезки и понимания списка).
import math
import numpy as np
def MA(index, sample):
N = len(original_list)
neighbors = math.floor(sample/2)
desired_range = (np.array(range(sample)) - neighbors + index) % N
to_be_averaged = [original_list[i] for i in desired_range]
print(to_be_averaged)
return sum(to_be_averaged)/sample
Я пытаюсь объяснить построчно:
N = len(original_list)
Получает общую длину исходного списка. В этом случае это можно сделать только один раз, но в моем приложении я могу использовать эту функцию для разных списков, поэтому я также ввожу список, и эта переменная N изменяется.
neighbors = math.floor(sample/2)
Это позволяет мне узнать, сколько выборок вверх и вниз я возьму. Помните, что для вычислений этого типа обычно используются нечетные числа, поэтому ваш индекс находится в центре. math.floor просто округляет до наименьшего целого, если больше ничего не передается.
desired_range = (np.array(range(sample)) - neighbors + index) % N
И есть самое забавное: здесь я пытаюсь получить нужный диапазон, но в индексах, которые будут использоваться позже. Я создаю np.array, чтобы я мог выполнять операции сложения и вычитания в списке, который я создаю с помощью range, размер диапазона - это выборка, которую я хочу, и я настраиваю его так, чтобы он был предыдущим, а после образцов просто разрезаю его, вычитая соседей. , затем я добавляю нужный индекс, чтобы у меня были индексы интересующих позиций, однако может случиться так, что мы добавим слишком много и выйдем за пределы списка, поэтому использование мода % N обтекает размер списка и все, что выше N, начинается с начала списка.
Остальное — это, по сути, понимание списка в пределах желаемого диапазона, который мы уже создали, а также суммирование и деление на размер выборки.
Надеюсь, поможет!
original_list = [*range(5)]
# Tests
[print(MA(i,3)) for i in range((len(original_list)))]
# [4, 0, 1]
# 1.6666666666666667
# [0, 1, 2]
# 1.0
# [1, 2, 3]
# 2.0
# [2, 3, 4]
# 3.0
# [3, 4, 0]
# 2.3333333333333335
[print(MA(i,5)) for i in range((len(original_list)))]
# [3, 4, 0, 1, 2]
# 2.0
# [4, 0, 1, 2, 3]
# 2.0
# [0, 1, 2, 3, 4]
# 2.0
# [1, 2, 3, 4, 0]
# 2.0
# [2, 3, 4, 0, 1]
# 2.0
original_list[-1:2]пусто. Срезы не идут назад, если вы явно не используете отрицательный шаг.