Поляры, объединение продаж и покупок, метод ФИФО

У меня есть два фрейма данных:

Один с покупками

df_buy = pl.DataFrame(
    {
        "BuyId": [1, 2],
        "Item": ["A", "A"],
        "BuyDate": [date.fromisoformat("2023-01-01"), date.fromisoformat("2024-03-07")],
        "Quantity": [40, 50],
    }
)
BuyId Элемент Дата покупки Количество 1 А 01.01.2023 40 2 А 07.03.2024 50

И еще с распродажами:

df_sell = pl.DataFrame(
    {
        "SellId": [3, 4],
        "Item": ["A", "A"],
        "SellDate": [date.fromisoformat("2024-04-01"), date.fromisoformat("2024-05-01")],
        "Quantity": [10, 45],
    }
)

SellId Элемент Дата продажи Количество 3 А 01.04.2024 10 4 А 2024-05-01 45

Я хочу определить, какие продажи произошли от каких покупок, используя метод FIFO.

Результат должен быть примерно таким.

BuyId Элемент Дата покупки ОставшеесяКоличество SellId Дата продажи ПродатьКоличество КоличествоПослепродажи 1 А 01.01.2023 40 3 01.04.2024 10 30 1 А 01.01.2023 30 4 2024-05-01 30 0 2 А 07.03.2024 50 4 2024-05-01 15 35

Я знаю, что могу сделать это с помощью цикла for, но мне хотелось знать, есть ли более векторизованный способ сделать это.

Редактировать:

Добавлен новый пример для тестирования:

df_buy = pl.DataFrame(
    {
        "BuyId": [5, 1, 2],
        "Item": ["B", "A", "A"],
        "BuyDate": [date.fromisoformat("2023-01-01"), date.fromisoformat("2023-01-01"), date.fromisoformat("2024-03-07")],
        "Quantity": [10, 40, 50],
    }
)

df_sell = pl.DataFrame(
    {
        "SellId": [6, 3, 4],
        "Item": ["B", "A", "A"],
        "SellDate": [
            date.fromisoformat("2024-04-01"),
            date.fromisoformat("2024-04-01"),
            date.fromisoformat("2024-05-01"),
        ],
        "Quantity": [5, 10, 45],
    }
)

Не легко, нет. Это «журнальная» операция, которая действительно требует построчной обработки.

Tim Roberts 21.08.2024 04:16
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
1
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
  1. Рассчитайте общее количество купленных на данный момент и общее количество проданных товаров.
  • cum_sum() для расчета промежуточной суммы.
df_buy_total = (
    df_buy
    .with_columns(QuantityTotal = pl.col.Quantity.cum_sum().over("Item"))
)

df_sell_total = (
    df_sell
    .with_columns(QuantityTotal = pl.col.Quantity.cum_sum().over("Item"))
)

┌───────┬──────┬────────────┬──────────┬───────────────┐
│ BuyId ┆ Item ┆ BuyDate    ┆ Quantity ┆ QuantityTotal │
│ ---   ┆ ---  ┆ ---        ┆ ---      ┆ ---           │
│ i64   ┆ str  ┆ date       ┆ i64      ┆ i64           │
╞═══════╪══════╪════════════╪══════════╪═══════════════╡
│ 1     ┆ A    ┆ 2023-01-01 ┆ 40       ┆ 40            │
│ 2     ┆ A    ┆ 2024-03-07 ┆ 50       ┆ 90            │
└───────┴──────┴────────────┴──────────┴───────────────┘

┌────────┬──────┬────────────┬──────────┬───────────────┐
│ SellId ┆ Item ┆ SellDate   ┆ Quantity ┆ QuantityTotal │
│ ---    ┆ ---  ┆ ---        ┆ ---      ┆ ---           │
│ i64    ┆ str  ┆ date       ┆ i64      ┆ i64           │
╞════════╪══════╪════════════╪══════════╪═══════════════╡
│ 3      ┆ A    ┆ 2024-04-01 ┆ 10       ┆ 10            │
│ 4      ┆ A    ┆ 2024-05-01 ┆ 45       ┆ 55            │
└────────┴──────┴────────────┴──────────┴───────────────┘
  1. Найдите, какие BuyId и SellId принадлежат друг другу. Для этого мы можем использовать join_asof()
df_sell2buy = (
    df_sell_total
    .join_asof(
        df_buy_total, on = "QuantityTotal", strategy = "forward", by = "Item", coalesce=False, suffix = "Buy"
    )
    .select(
        "BuyId", "SellId", "Item", "BuyDate", "SellDate",
        "QuantityTotalBuy",
        pl.col.QuantityTotal.alias("QuantityTotalSell")
    )
    .filter(pl.col.BuyId.is_not_null(), pl.col.QuantityTotalBuy > pl.col.QuantityTotalSell)
)

df_buy2sell = (
    df_buy_total
    .join_asof(
        df_sell_total, on = "QuantityTotal", strategy = "forward", by = "Item", coalesce=False, suffix = "Sell"
    )
    .select(
        "BuyId", "SellId", "Item", "BuyDate", "SellDate",
        pl.col.QuantityTotal.alias("QuantityTotalBuy"),
        pl.col.QuantityTotalSell.forward_fill().over("Item").fill_null(0)
    )
)

┌───────┬────────┬──────┬────────────┬────────────┬──────────────────┬───────────────────┐
│ BuyId ┆ SellId ┆ Item ┆ BuyDate    ┆ SellDate   ┆ QuantityTotalBuy ┆ QuantityTotalSell │
│ ---   ┆ ---    ┆ ---  ┆ ---        ┆ ---        ┆ ---              ┆ ---               │
│ i64   ┆ i64    ┆ str  ┆ date       ┆ date       ┆ i64              ┆ i64               │
╞═══════╪════════╪══════╪════════════╪════════════╪══════════════════╪═══════════════════╡
│ 1     ┆ 3      ┆ A    ┆ 2023-01-01 ┆ 2024-04-01 ┆ 40               ┆ 10                │
│ 2     ┆ 4      ┆ A    ┆ 2024-03-07 ┆ 2024-05-01 ┆ 90               ┆ 55                │
└───────┴────────┴──────┴────────────┴────────────┴──────────────────┴───────────────────┘

┌───────┬────────┬──────┬────────────┬────────────┬──────────────────┬───────────────────┐
│ BuyId ┆ SellId ┆ Item ┆ BuyDate    ┆ SellDate   ┆ QuantityTotalBuy ┆ QuantityTotalSell │
│ ---   ┆ ---    ┆ ---  ┆ ---        ┆ ---        ┆ ---              ┆ ---               │
│ i64   ┆ i64    ┆ str  ┆ date       ┆ date       ┆ i64              ┆ i64               │
╞═══════╪════════╪══════╪════════════╪════════════╪══════════════════╪═══════════════════╡
│ 1     ┆ 4      ┆ A    ┆ 2023-01-01 ┆ 2024-05-01 ┆ 40               ┆ 55                │
│ 2     ┆ null   ┆ A    ┆ 2024-03-07 ┆ null       ┆ 90               ┆ 55                │
└───────┴────────┴──────┴────────────┴────────────┴──────────────────┴───────────────────┘
  1. Объедините оба связанных DataFrame
df_result = (
    pl.concat([df_sell2buy, df_buy2sell])
    .filter(~(
        pl.col.SellId.is_null() &
        pl.col.SellId.is_not_null().any().over("BuyId")
    ))
    .sort("Item", "BuyId", "SellId", nulls_last=True)
)

┌───────┬────────┬──────┬────────────┬────────────┬──────────────────┬───────────────────┐
│ BuyId ┆ SellId ┆ Item ┆ BuyDate    ┆ SellDate   ┆ QuantityTotalBuy ┆ QuantityTotalSell │
│ ---   ┆ ---    ┆ ---  ┆ ---        ┆ ---        ┆ ---              ┆ ---               │
│ i64   ┆ i64    ┆ str  ┆ date       ┆ date       ┆ i64              ┆ i64               │
╞═══════╪════════╪══════╪════════════╪════════════╪══════════════════╪═══════════════════╡
│ 1     ┆ 3      ┆ A    ┆ 2023-01-01 ┆ 2024-04-01 ┆ 40               ┆ 10                │
│ 1     ┆ 4      ┆ A    ┆ 2023-01-01 ┆ 2024-05-01 ┆ 40               ┆ 55                │
│ 2     ┆ 4      ┆ A    ┆ 2024-03-07 ┆ 2024-05-01 ┆ 90               ┆ 55                │
└───────┴────────┴──────┴────────────┴────────────┴──────────────────┴───────────────────┘
  1. Подсчитать конечный результат
(
    df_result
    .with_columns(
        QuantityRunning = pl.min_horizontal("QuantityTotalBuy", "QuantityTotalSell"),
    )    
    .with_columns(
        QuantityRunningPrev = pl.col.QuantityRunning.shift(1).over("Item").fill_null(0)
    )
    .select(
        "BuyId", "Item", "BuyDate",
        (pl.col.QuantityTotalBuy - pl.col.QuantityRunningPrev).alias("RemainingQuantity"),
        "SellId", "SellDate", 
        (pl.col.QuantityRunning - pl.col.QuantityRunningPrev).alias("SellQuantity"),
        (pl.col.QuantityTotalBuy - pl.col.QuantityRunning).alias("QuantityAfterSell")
    )
)

┌───────┬──────┬────────────┬───────────────────┬────────┬────────────┬──────────────┬───────────────────┐
│ BuyId ┆ Item ┆ BuyDate    ┆ RemainingQuantity ┆ SellId ┆ SellDate   ┆ SellQuantity ┆ QuantityAfterSell │
│ ---   ┆ ---  ┆ ---        ┆ ---               ┆ ---    ┆ ---        ┆ ---          ┆ ---               │
│ i64   ┆ str  ┆ date       ┆ i64               ┆ i64    ┆ date       ┆ i64          ┆ i64               │
╞═══════╪══════╪════════════╪═══════════════════╪════════╪════════════╪══════════════╪═══════════════════╡
│ 1     ┆ A    ┆ 2023-01-01 ┆ 40                ┆ 3      ┆ 2024-04-01 ┆ 10           ┆ 30                │
│ 1     ┆ A    ┆ 2023-01-01 ┆ 30                ┆ 4      ┆ 2024-05-01 ┆ 30           ┆ 0                 │
│ 2     ┆ A    ┆ 2024-03-07 ┆ 50                ┆ 4      ┆ 2024-05-01 ┆ 15           ┆ 35                │
└───────┴──────┴────────────┴───────────────────┴────────┴────────────┴──────────────┴───────────────────┘

Спасибо Роман. Я попытался поместить в инвентарь другой предмет «B» с покупкой и без продаж, и покупка исчезла. Также, если бы я попробовал купить и продать предмет «B», и количества начали смешиваться между предметами. Как я могу это исправить?

rlartiga 21.08.2024 16:09

@rlartiga, пожалуйста, посмотрите обновленную версию

Roman Pekar 21.08.2024 17:04

@rlartiga да, код не обновил, теперь должно быть лучше

Roman Pekar 21.08.2024 17:38

Это сработало!! Спасибо!

rlartiga 21.08.2024 17:39

Другие вопросы по теме