Пересчет на время, связанных с ведрами


Скажем, у меня есть df, выглядящий так:

    price   quantity
0   100     20
1   102     31
2   105     25
3   99      40
4   104     10
5   103     20
6   101     55

Здесь нет временных интервалов. Мне нужно рассчитать средневзвешенную цену по объему для каждых 50 позиций в количестве. Каждая строка (индекс) в выходных данных будет представлять 50 единиц (в отличие от, скажем, 5-минутных интервалов), выходной столбец будет представлять собой взвешенную по объему цену.

Какой-нибудь аккуратный способ сделать это с помощью панд или numpy, если уж на то пошло?? Я попробовал использовать цикл, разделяющий каждую строку на одну цену товара, и они группируют их, как это:

def grouper(n, iterable):
    it = iter(iterable)
    while True:
       chunk = tuple(itertools.islice(it, n))
       if not chunk:
           return
       yield chunk
Но это длится вечно, и у меня кончается память.. Df - это несколько миллионов строк.

Редактировать: Вывод, который я хочу видеть на основе вышеизложенного:

     vwap
0    101.20
1    102.12
2    103.36
3    101.00

Каждые 50 позиций получают новую среднюю цену.

1 2

1 ответ:

Я ударил по своей первой бите, столкнувшись с этой проблемой. Вот мое следующее появление на тарелке. Надеюсь, я смогу запустить мяч в игру и забить гол.

Во-первых, давайте обратимся к некоторым комментариям, связанным с ожидаемым результатом этой работы. ОП опубликовал то, что, по его мнению, результаты должны быть использованы с использованием небольших выборочных данных, которые он предоставил. Тем не менее, @user7138814 и я оба пришли к одному и тому же результату, который отличался от ОП. позвольте мне объяснить, как я считаю, что средневзвешенное значение ровно 50 единицы измерения должны быть рассчитаны на примере ОП. Я использую этот лист в качестве иллюстрации.

Введите описание изображения здесь

Первые 2 столбца (A и B)являются исходными значениями, заданными ОП. учитывая эти значения, цель состоит в том, чтобы вычислить средневзвешенное значение для каждого блока ровно 50 единиц. К сожалению, эти величины не делятся равномерно на 50. Столбцы C и D показывают, как создать четные блоки по 50 единиц, разделив исходные количества по мере необходимости. Желтый цвет заштрихованные области показывают, как было разделено исходное количество, и каждая из зеленых ограниченных ячеек суммируется ровно до 50 единиц. Как только 50 единиц определены, средневзвешенное значение может быть вычислено в столбце E. Как вы можете видеть, значения в E соответствуют тому, что @user7138814 опубликовал в своем комментарии, поэтому я думаю, что мы согласны с методологией.
После долгих проб и ошибок конечным решением является функция, которая оперирует массивами numpy базовых ценовых и количественных рядов. Функция такова далее оптимизирован с помощью Numba decorator для jit-компиляции кода Python в машинный код. На моем ноутбуке он обрабатывал 3 миллиона массивов строк в секунду.

Вот функция.

@numba.jit
def vwap50_jit(price_col, quantity_col):
    n_rows = len(price_col)
    assert len(price_col) == len(quantity_col)

    qty_cumdif = 50  # cum difference of quantity to track when 50 units are reached
    pq = 0.0  # cumsum of price * quantity
    vwap50 = []  # list of weighted averages
    for i in range(n_rows):
        price, qty = price_col[i], quantity_col[i]

        # if current qty will cause more than 50 units
        # divide the units
        if qty_cumdif < qty:
            pq += qty_cumdif * price
            # at this point, 50 units accumulated. calculate average.
            vwap50.append(pq / 50)
            qty -= qty_cumdif
            # continue dividing
            while qty >= 50:
                qty -= 50
                vwap50.append(price)
            # remaining qty and pq become starting
            # values for next group of 50
            qty_cumdif = 50 - qty
            pq = qty * price
        # process price, qty pair as-is
        else:
            qty_cumdif -= qty
            pq += qty * price
    return np.array(vwap50)

Результаты обработки выборочных данных ОП.

Out[6]: 
   price  quantity
0    100        20
1    102        31
2    105        25
3     99        40
4    104        10
5    103        20
6    101        55

vwap50_jit(df.price.values, df.quantity.values)
Out[7]: array([101.2 , 102.06, 101.76, 101.  ])
Обратите внимание, что я использую метод .values для передачи массивов numpy серии pandas. Это одно из требований использования numba. Numba осознает numpy и не работает с объектами панды.

Он работает довольно хорошо на 3 миллион массивов строк, создавая выходной массив из 2,25 миллиона взвешенных средних.

df = pd.DataFrame({'price': np.random.randint(95, 150, 3000000),
                  'quantity': np.random.randint(1, 75, 3000000)})


%timeit vwap50_jit(df.price.values, df.quantity.values)
154 ms ± 4.15 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

vwap = vwap50_jit(df.price.values, df.quantity.values)

vwap.shape
Out[11]: (2250037,)