Панды группового скользящего среднего с нестандартным размером окна - PullRequest
2 голосов
/ 18 апреля 2019

Определение проблемы:

Для Фрейма данных Pandas я пытаюсь сгруппировать по скользящему среднему с изменяемым размером окна, указанным в каждой строке относительно индекса времени даты.

Пример:

Для следующих df еженедельных данных:

| week_start_date | material | location | quantity | window_size |
|-----------------|----------|----------|----------|-------------|
| 2019-01-28      | C        | A        | 870      | 1           |
| 2019-02-04      | C        | A        | 920      | 3           |
| 2019-02-18      | C        | A        | 120      | 1           |
| 2019-02-25      | C        | A        | 120      | 2           |
| 2019-03-04      | C        | A        | 120      | 1           |
| 2018-12-31      | D        | A        | 1200     | 8           |
| 2019-01-21      | D        | A        | 720      | 8           |
| 2019-01-28      | D        | A        | 480      | 8           |
| 2019-02-04      | D        | A        | 600      | 8           |
| 2019-02-11      | D        | A        | 720      | 8           |
| 2019-02-18      | D        | A        | 80       | 8           |
| 2019-02-25      | D        | A        | 600      | 8           |
| 2019-03-04      | D        | A        | 1200     | 8           |
| 2019-01-14      | E        | B        | 150      | 1           |
| 2019-01-28      | E        | B        | 1416     | 1           |
| 2019-02-04      | F        | B        | 1164     | 1           |
| 2019-01-28      | G        | B        | 11520    | 8           |

Окно должно быть относительно фактической даты, установленной в week_start_date, а не обрабатывать ее как целое числоindex.

Его нужно сгруппировать по material и location.

Скользящее среднее для столбца quantity.

Размер окна должен изменяться/ изменить в зависимости от значения в столбце window_size.Это значение меняется со временем - оно представляет количество недель назад, за которое необходимо агрегировать количество.

Когда строка недоступна, среднее значение должно предполагать, что значение равно 0, т. Е. Когдастрока с недельными датами недоступна mean(null, null, null, 1000) = 1000, но на самом деле она должна иметь значение: среднее (0,0,0,1000) = 250 Однако - это должно применяться только после измерения первого наблюдения.

Фиксированное окноотносительно столбца даты:

Я могу получить статическое окно за 8 недель (56 дней), используя следующее:

df.set_index('week_start_date').groupby(['material', 'location'])['quantity'].rolling('56D', min_periods=1).mean()

Я исследовал использование расширение но не увенчались успехом.

Как можно установить размер окна относительно каждой строки, которую он читает?

Пример данных:

# Example Data
df = pd.DataFrame({'week_start_date': ['2019-01-28','2019-02-04','2019-02-18','2019-02-25','2019-03-04','2018-12-31','2019-01-21','2019-01-28','2019-02-04','2019-02-11','2019-02-18','2019-02-25','2019-03-04','2019-01-14','2019-01-28','2019-02-04','2019-01-28'],
'material': ['C','C','C','C','C','D','D','D','D','D','D','D','D','E','E','F','G'],
'location': ['A','A','A','A','A','A','A','A','A','A','A','A','A','B','B','B','B'],
'quantity': ['870','920','120','120','120','1200','720','480','600','720','80','600','1200','150','1416','1164','11520'],
'min_of_pdt_or_8_weeks': ['1','3','1','2','1','8','8','8','8','8','8','8','8','1','3','1','8']})
# Fix formats
df['week_start_date'] = pd.to_datetime(df['week_start_date'])
df['actual_week_qty'] = df['quantity'].astype(float)

Ожидаемый результат:

| material | location | week_start_date | quantity | 
| C        | A        | 2019-01-28      | 870      | 
| C        | A        | 2019-04-02      | 306.6667 | 
| C        | A        | 2019-02-18      | 520      | 
| C        | A        | 2019-02-25      | 386.6667 | 
| D        | A        | 2018-12-31      | 1200     | 
| D        | A        | 2019-01-21      | 960      | 
| D        | A        | 2019-01-28      | 800      | 
| D        | A        | 2019-04-02      | 600      | 
| D        | A        | 2019-11-02      | 720      | 
| D        | A        | 2019-02-18      | 400      | 
| D        | A        | 2019-02-25      | 466.6667 | 
| D        | A        | 2019-04-03      | 650      | 
| E        | B        | 2019-01-14      | 150      | 
| E        | B        | 2019-01-28      | 783      | 
| F        | B        | 2019-04-02      | 1164     | 
| G        | B        | 2019-01-28      | 11520    |

1 Ответ

0 голосов
/ 19 апреля 2019

Наивный способ сделать это - выполнить 8 (при условии, что это ограничено!) Вычислений и объединить результаты:

In [11]: d = {w: df.set_index('week_start_date')
                   .groupby(['material', 'location'])['quantity']
                   .rolling(f'{7*w}D', min_periods=1)
                   .mean()
                   .reset_index(name="mean")
                   .assign(window_size=w)
              for w in range(1, 9)}

, затем вы можете объединить эти DataFrames вместе и объединить с исходным, поскольку у нас есть столбец window_size слева и справа, он будет внутренним.

In [12]: pd.concat(d.values()).merge(df, how="inner")
Out[12]:
   material location week_start_date          mean  window_size  quantity
0         C        A      2019-01-28    870.000000            1     870.0
1         C        A      2019-02-18    520.000000            1     120.0
2         C        A      2019-04-03    320.000000            1     120.0
3         E        B      2019-01-14    150.000000            1     150.0
4         F        B      2019-04-02   1164.000000            1    1164.0
5         C        A      2019-02-25    386.666667            2     120.0
6         C        A      2019-04-02    920.000000            3     920.0
7         E        B      2019-01-28    783.000000            3    1416.0
8         D        A      2018-12-31   1200.000000            8    1200.0
9         D        A      2019-01-21    960.000000            8     720.0
10        D        A      2019-01-28    800.000000            8     480.0
11        D        A      2019-04-02    600.000000            8     600.0
12        D        A      2019-11-02    720.000000            8     720.0
13        D        A      2019-02-18    400.000000            8      80.0
14        D        A      2019-02-25    466.666667            8     600.0
15        D        A      2019-04-03    650.000000            8    1200.0
16        G        B      2019-01-28  11520.000000            8   11520.0

Примечание. Предполагается, что вы установили fillna для window_size равным 8:

df.window_size = df.window_size.replace('NaN', 8).astype(int)  # in your example

Кроме того, вы хотите убедиться, что передали формат to_datetime, чтобы не допустить двусмысленности, панды могут быть в состоянии сделать хорошую работу здесь, сделав вывод ... но я бы не стал полагатьсяна нем (используйте явно format='%d/%m/%Y).Вы хотите избавиться от странных форматов даты, как только прочитаете их, это также можно передать в read_csv (dayfirst = True) и друзьям.


Я не совсем уверен, что эточто вы хотите, так как есть разница между вашим входным df и ожидаемым (например, в ожидаемом нет ГБ ...).

Несмотря на это, я подозреваю, что для этого есть один способ съемки, но это будет зависеть от разреженности недели / материала / местоположения (если она плотная, то будет намного проще, если разреженной, это может быть лучшим вариантом) ...
Теперь я думаю об этомВы можете сделать это полностью на subDataFrame материала / местоположения, можете ли вы упростить эту проблему, чтобы просто быть функцией этого DataFrame (просто неделя + значение, игнорирующее материал / местоположение) или это будет слишком медленным?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...