Панды: скользящее среднее по временному интервалу

У меня есть куча данных опроса; Я хочу вычислить скользящее среднее значение Pandas, чтобы получить оценку на каждый день на основе трехдневного окна. Согласно этому вопросу, функции rolling_* вычисляют окно на основе на указанном количестве значений, а не на конкретном диапазоне дат и времени.

Как мне реализовать эту функцию?

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

polls_subset.tail(20)
Out[185]: 
            favorable  unfavorable  other

enddate                                  
2012-10-25       0.48         0.49   0.03
2012-10-25       0.51         0.48   0.02
2012-10-27       0.51         0.47   0.02
2012-10-26       0.56         0.40   0.04
2012-10-28       0.48         0.49   0.04
2012-10-28       0.46         0.46   0.09
2012-10-28       0.48         0.49   0.03
2012-10-28       0.49         0.48   0.03
2012-10-30       0.53         0.45   0.02
2012-11-01       0.49         0.49   0.03
2012-11-01       0.47         0.47   0.05
2012-11-01       0.51         0.45   0.04
2012-11-03       0.49         0.45   0.06
2012-11-04       0.53         0.39   0.00
2012-11-04       0.47         0.44   0.08
2012-11-04       0.49         0.48   0.03
2012-11-04       0.52         0.46   0.01
2012-11-04       0.50         0.47   0.03
2012-11-05       0.51         0.46   0.02
2012-11-07       0.51         0.41   0.00

На выходе будет только одна строка для каждой даты.


person Anov    schedule 02.04.2013    source источник
comment
В трекере ошибок Pandas есть открытая проблема, запрашивающая эту функцию: github.com/pydata/pandas/issues / 936. Функциональности пока нет. Ответы на этот вопрос описывают способ получить желаемый эффект, но обычно он будет довольно медленным по сравнению со встроенными rolling_* функциями.   -  person BrenBarn    schedule 02.04.2013
comment
По общему признанию, doc отстой и не показывает никаких примеры, и даже не описывает на простом английском языке вы можете передать Rolling (..., window = '7d')   -  person smci    schedule 16.04.2021


Ответы (9)


Тем временем была добавлена ​​возможность временного окна. См. Эту ссылку.

In [1]: df = DataFrame({'B': range(5)})

In [2]: df.index = [Timestamp('20130101 09:00:00'),
   ...:             Timestamp('20130101 09:00:02'),
   ...:             Timestamp('20130101 09:00:03'),
   ...:             Timestamp('20130101 09:00:05'),
   ...:             Timestamp('20130101 09:00:06')]

In [3]: df
Out[3]: 
                     B
2013-01-01 09:00:00  0
2013-01-01 09:00:02  1
2013-01-01 09:00:03  2
2013-01-01 09:00:05  3
2013-01-01 09:00:06  4

In [4]: df.rolling(2, min_periods=1).sum()
Out[4]: 
                       B
2013-01-01 09:00:00  0.0
2013-01-01 09:00:02  1.0
2013-01-01 09:00:03  3.0
2013-01-01 09:00:05  5.0
2013-01-01 09:00:06  7.0

In [5]: df.rolling('2s', min_periods=1).sum()
Out[5]: 
                       B
2013-01-01 09:00:00  0.0
2013-01-01 09:00:02  1.0
2013-01-01 09:00:03  3.0
2013-01-01 09:00:05  3.0
2013-01-01 09:00:06  7.0
person Martin    schedule 07.10.2016
comment
Это должен быть главный ответ. - person Ivan; 06.03.2018
comment
Документация для аргументов смещения (например, '2s'), которые может принимать rolling, находится здесь: pandas.pydata.org/pandas-docs/stable/user_guide/ - person Guilherme Salomé; 08.03.2019
comment
Что делать, если во фрейме данных есть несколько столбцов; как указать конкретные столбцы? - person Brain_overflowed; 17.07.2019
comment
@Brain_overflowed установлен как индекс - person jamfie; 22.05.2020
comment
Мин_период не кажется надежным с этим методом. Для min_periods ›1 вы можете получить NaN там, где их не ожидаете, из-за точности временной метки / переменной частоты дискретизации. - person Albert James Teddy; 18.09.2020

Как насчет чего-то вроде этого:

Сначала выполните повторную выборку кадра данных на одномерные интервалы. Это принимает среднее значение для всех повторяющихся дней. Используйте параметр fill_method, чтобы заполнить отсутствующие значения даты. Затем передайте передискретизированный кадр в pd.rolling_mean с окном 3 и min_periods = 1:

pd.rolling_mean(df.resample("1D", fill_method="ffill"), window=3, min_periods=1)

            favorable  unfavorable     other
enddate
2012-10-25   0.495000     0.485000  0.025000
2012-10-26   0.527500     0.442500  0.032500
2012-10-27   0.521667     0.451667  0.028333
2012-10-28   0.515833     0.450000  0.035833
2012-10-29   0.488333     0.476667  0.038333
2012-10-30   0.495000     0.470000  0.038333
2012-10-31   0.512500     0.460000  0.029167
2012-11-01   0.516667     0.456667  0.026667
2012-11-02   0.503333     0.463333  0.033333
2012-11-03   0.490000     0.463333  0.046667
2012-11-04   0.494000     0.456000  0.043333
2012-11-05   0.500667     0.452667  0.036667
2012-11-06   0.507333     0.456000  0.023333
2012-11-07   0.510000     0.443333  0.013333

ОБНОВЛЕНИЕ: как Бен указывает в комментариях, с пандами 0.18 .0 синтаксис изменен. С новым синтаксисом это будет:

df.resample("1d").sum().fillna(0).rolling(window=3, min_periods=1).mean()
person Zelazny7    schedule 02.04.2013
comment
извините, Pandas newb, что именно ffill использует в качестве правила для предоставления пропущенных значений? - person Anov; 02.04.2013
comment
Есть несколько вариантов заливки. ffill обозначает прямое заполнение и просто передает самое последнее не пропущенное значение. Аналогично bfill для обратной заливки, делает то же самое в обратном порядке. - person Zelazny7; 02.04.2013
comment
Возможно, я ошибаюсь, но игнорируете ли вы несколько показаний за один и тот же день? - person Andy Hayden; 07.01.2014
comment
Отличный ответ. Просто отметим, что в pandas 0.18.0 изменился синтаксис . Новый синтаксис: df.resample("1D").ffill(limit=0).rolling(window=3, min_periods=1).mean() - person Ben; 15.04.2016
comment
Чтобы воспроизвести результаты исходного ответа в pandas версии 0.18.1, я использую: df.resample("1d").mean().rolling(window=3, min_periods=1).mean() - person JohnE; 21.05.2016

У меня был тот же вопрос, но с нерегулярно расположенными точками данных. Ресамплинг здесь не вариант. Итак, я создал свою собственную функцию. Может быть, и другим будет полезно:

from pandas import Series, DataFrame
import pandas as pd
from datetime import datetime, timedelta
import numpy as np

def rolling_mean(data, window, min_periods=1, center=False):
    ''' Function that computes a rolling mean

    Parameters
    ----------
    data : DataFrame or Series
           If a DataFrame is passed, the rolling_mean is computed for all columns.
    window : int or string
             If int is passed, window is the number of observations used for calculating 
             the statistic, as defined by the function pd.rolling_mean()
             If a string is passed, it must be a frequency string, e.g. '90S'. This is
             internally converted into a DateOffset object, representing the window size.
    min_periods : int
                  Minimum number of observations in window required to have a value.

    Returns
    -------
    Series or DataFrame, if more than one column    
    '''
    def f(x):
        '''Function to apply that actually computes the rolling mean'''
        if center == False:
            dslice = col[x-pd.datetools.to_offset(window).delta+timedelta(0,0,1):x]
                # adding a microsecond because when slicing with labels start and endpoint
                # are inclusive
        else:
            dslice = col[x-pd.datetools.to_offset(window).delta/2+timedelta(0,0,1):
                         x+pd.datetools.to_offset(window).delta/2]
        if dslice.size < min_periods:
            return np.nan
        else:
            return dslice.mean()

    data = DataFrame(data.copy())
    dfout = DataFrame()
    if isinstance(window, int):
        dfout = pd.rolling_mean(data, window, min_periods=min_periods, center=center)
    elif isinstance(window, basestring):
        idx = Series(data.index.to_pydatetime(), index=data.index)
        for colname, col in data.iterkv():
            result = idx.apply(f)
            result.name = colname
            dfout = dfout.join(result, how='outer')
    if dfout.columns.size == 1:
        dfout = dfout.ix[:,0]
    return dfout


# Example
idx = [datetime(2011, 2, 7, 0, 0),
       datetime(2011, 2, 7, 0, 1),
       datetime(2011, 2, 7, 0, 1, 30),
       datetime(2011, 2, 7, 0, 2),
       datetime(2011, 2, 7, 0, 4),
       datetime(2011, 2, 7, 0, 5),
       datetime(2011, 2, 7, 0, 5, 10),
       datetime(2011, 2, 7, 0, 6),
       datetime(2011, 2, 7, 0, 8),
       datetime(2011, 2, 7, 0, 9)]
idx = pd.Index(idx)
vals = np.arange(len(idx)).astype(float)
s = Series(vals, index=idx)
rm = rolling_mean(s, window='2min')
person user2689410    schedule 27.08.2013
comment
Не могли бы вы включить соответствующий импорт? - person Bryce Drennan; 11.02.2014
comment
Не могли бы вы предоставить пример кадра входных данных, который будет работать при вычислении скользящего окна временного интервала, спасибо - person joshlk; 08.04.2014
comment
Добавил пример в исходный пост. - person user2689410; 09.04.2014
comment
То же самое можно сейчас сделать с помощью s.rolling('2min', min_periods=1).mean() - person kampta; 02.05.2017

Код пользователя2689410 был именно тем, что мне было нужно. Предоставление моей версии (кредиты user2689410), которая выполняется быстрее из-за одновременного вычисления среднего значения для целых строк в DataFrame.

Надеюсь, мои суффиксные соглашения читаются: _s: string, _i: int, _b: bool, _ser: Series и _df: DataFrame. Если вы найдете несколько суффиксов, типом может быть и то, и другое.

import pandas as pd
from datetime import datetime, timedelta
import numpy as np

def time_offset_rolling_mean_df_ser(data_df_ser, window_i_s, min_periods_i=1, center_b=False):
    """ Function that computes a rolling mean

    Credit goes to user2689410 at http://stackoverflow.com/questions/15771472/pandas-rolling-mean-by-time-interval

    Parameters
    ----------
    data_df_ser : DataFrame or Series
         If a DataFrame is passed, the time_offset_rolling_mean_df_ser is computed for all columns.
    window_i_s : int or string
         If int is passed, window_i_s is the number of observations used for calculating
         the statistic, as defined by the function pd.time_offset_rolling_mean_df_ser()
         If a string is passed, it must be a frequency string, e.g. '90S'. This is
         internally converted into a DateOffset object, representing the window_i_s size.
    min_periods_i : int
         Minimum number of observations in window_i_s required to have a value.

    Returns
    -------
    Series or DataFrame, if more than one column

    >>> idx = [
    ...     datetime(2011, 2, 7, 0, 0),
    ...     datetime(2011, 2, 7, 0, 1),
    ...     datetime(2011, 2, 7, 0, 1, 30),
    ...     datetime(2011, 2, 7, 0, 2),
    ...     datetime(2011, 2, 7, 0, 4),
    ...     datetime(2011, 2, 7, 0, 5),
    ...     datetime(2011, 2, 7, 0, 5, 10),
    ...     datetime(2011, 2, 7, 0, 6),
    ...     datetime(2011, 2, 7, 0, 8),
    ...     datetime(2011, 2, 7, 0, 9)]
    >>> idx = pd.Index(idx)
    >>> vals = np.arange(len(idx)).astype(float)
    >>> ser = pd.Series(vals, index=idx)
    >>> df = pd.DataFrame({'s1':ser, 's2':ser+1})
    >>> time_offset_rolling_mean_df_ser(df, window_i_s='2min')
                          s1   s2
    2011-02-07 00:00:00  0.0  1.0
    2011-02-07 00:01:00  0.5  1.5
    2011-02-07 00:01:30  1.0  2.0
    2011-02-07 00:02:00  2.0  3.0
    2011-02-07 00:04:00  4.0  5.0
    2011-02-07 00:05:00  4.5  5.5
    2011-02-07 00:05:10  5.0  6.0
    2011-02-07 00:06:00  6.0  7.0
    2011-02-07 00:08:00  8.0  9.0
    2011-02-07 00:09:00  8.5  9.5
    """

    def calculate_mean_at_ts(ts):
        """Function (closure) to apply that actually computes the rolling mean"""
        if center_b == False:
            dslice_df_ser = data_df_ser[
                ts-pd.datetools.to_offset(window_i_s).delta+timedelta(0,0,1):
                ts
            ]
            # adding a microsecond because when slicing with labels start and endpoint
            # are inclusive
        else:
            dslice_df_ser = data_df_ser[
                ts-pd.datetools.to_offset(window_i_s).delta/2+timedelta(0,0,1):
                ts+pd.datetools.to_offset(window_i_s).delta/2
            ]
        if  (isinstance(dslice_df_ser, pd.DataFrame) and dslice_df_ser.shape[0] < min_periods_i) or \
            (isinstance(dslice_df_ser, pd.Series) and dslice_df_ser.size < min_periods_i):
            return dslice_df_ser.mean()*np.nan   # keeps number format and whether Series or DataFrame
        else:
            return dslice_df_ser.mean()

    if isinstance(window_i_s, int):
        mean_df_ser = pd.rolling_mean(data_df_ser, window=window_i_s, min_periods=min_periods_i, center=center_b)
    elif isinstance(window_i_s, basestring):
        idx_ser = pd.Series(data_df_ser.index.to_pydatetime(), index=data_df_ser.index)
        mean_df_ser = idx_ser.apply(calculate_mean_at_ts)

    return mean_df_ser
person Mark Horvath    schedule 09.10.2014

Этот пример, похоже, требует взвешенного среднего, как это предлагается в комментарии @andyhayden. Например, есть два опроса 25 октября и по одному 26 октября и 27 октября. Если вы просто передискретизируете, а затем возьмете среднее значение, это фактически придаст вдвое больший вес опросам 26 октября и 27 октября по сравнению с опросами 25 октября.

Чтобы придать равный вес каждому опросу, а не каждому дню, можно сделать что-то вроде следующего.

>>> wt = df.resample('D',limit=5).count()

            favorable  unfavorable  other
enddate                                  
2012-10-25          2            2      2
2012-10-26          1            1      1
2012-10-27          1            1      1

>>> df2 = df.resample('D').mean()

            favorable  unfavorable  other
enddate                                  
2012-10-25      0.495        0.485  0.025
2012-10-26      0.560        0.400  0.040
2012-10-27      0.510        0.470  0.020

Это дает вам сырые ингредиенты для вычисления среднего значения на основе опроса вместо среднего значения на основе дня. Как и раньше, опросы усредняются на 25/10, но также сохраняется вес для 10/25, который вдвое превышает вес на 26 октября или 27 октября, чтобы отразить, что два опроса были проведены 25 октября.

>>> df3 = df2 * wt
>>> df3 = df3.rolling(3,min_periods=1).sum()
>>> wt3 = wt.rolling(3,min_periods=1).sum()

>>> df3 = df3 / wt3  

            favorable  unfavorable     other
enddate                                     
2012-10-25   0.495000     0.485000  0.025000
2012-10-26   0.516667     0.456667  0.030000
2012-10-27   0.515000     0.460000  0.027500
2012-10-28   0.496667     0.465000  0.041667
2012-10-29   0.484000     0.478000  0.042000
2012-10-30   0.488000     0.474000  0.042000
2012-10-31   0.530000     0.450000  0.020000
2012-11-01   0.500000     0.465000  0.035000
2012-11-02   0.490000     0.470000  0.040000
2012-11-03   0.490000     0.465000  0.045000
2012-11-04   0.500000     0.448333  0.035000
2012-11-05   0.501429     0.450000  0.032857
2012-11-06   0.503333     0.450000  0.028333
2012-11-07   0.510000     0.435000  0.010000

Обратите внимание, что скользящее среднее для 10/27 теперь составляет 0,51500 (взвешенное по опросу), а не 52,1667 (взвешенное по дням).

Также обратите внимание, что с версии 0.18.0 в API для resample и rolling были внесены изменения.

прокатка (что нового в панды 0.18.0)

resample (что нового в pandas 0.18.0) < / а>

person JohnE    schedule 21.05.2016

Чтобы сохранить простоту, я использовал цикл и что-то вроде этого, чтобы вы начали (мой индекс - это время):

import pandas as pd
import datetime as dt

#populate your dataframe: "df"
#...

df[df.index<(df.index[0]+dt.timedelta(hours=1))] #gives you a slice. you can then take .sum() .mean(), whatever

а затем вы можете запускать функции на этом срезе. Вы можете увидеть, как добавление итератора для запуска окна, отличного от первого значения в вашем индексе фреймов данных, затем свернет окно (например, вы также можете использовать правило> для начала).

Обратите внимание, что это может быть менее эффективно для СУПЕР больших данных или очень маленьких приращений, поскольку ваша срезка может стать более сложной (для меня достаточно хорошо работает для сотен тысяч строк данных и нескольких столбцов, хотя для почасовых окон в течение нескольких недель)

person Vlox    schedule 08.03.2017

Я обнаружил, что код user2689410 сломался, когда я попытался с помощью window = '1M', поскольку дельта за рабочий месяц вызвала эту ошибку:

AttributeError: 'MonthEnd' object has no attribute 'delta'

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

Спасибо за указатели, вот моя попытка - надеюсь, она пригодится.

def rolling_mean(data, window, min_periods=1, center=False):
""" Function that computes a rolling mean
Reference:
    http://stackoverflow.com/questions/15771472/pandas-rolling-mean-by-time-interval

Parameters
----------
data : DataFrame or Series
       If a DataFrame is passed, the rolling_mean is computed for all columns.
window : int, string, Timedelta or Relativedelta
         int - number of observations used for calculating the statistic,
               as defined by the function pd.rolling_mean()
         string - must be a frequency string, e.g. '90S'. This is
                  internally converted into a DateOffset object, and then
                  Timedelta representing the window size.
         Timedelta / Relativedelta - Can directly pass a timedeltas.
min_periods : int
              Minimum number of observations in window required to have a value.
center : bool
         Point around which to 'center' the slicing.

Returns
-------
Series or DataFrame, if more than one column
"""
def f(x, time_increment):
    """Function to apply that actually computes the rolling mean
    :param x:
    :return:
    """
    if not center:
        # adding a microsecond because when slicing with labels start
        # and endpoint are inclusive
        start_date = x - time_increment + timedelta(0, 0, 1)
        end_date = x
    else:
        start_date = x - time_increment/2 + timedelta(0, 0, 1)
        end_date = x + time_increment/2
    # Select the date index from the
    dslice = col[start_date:end_date]

    if dslice.size < min_periods:
        return np.nan
    else:
        return dslice.mean()

data = DataFrame(data.copy())
dfout = DataFrame()
if isinstance(window, int):
    dfout = pd.rolling_mean(data, window, min_periods=min_periods, center=center)

elif isinstance(window, basestring):
    time_delta = pd.datetools.to_offset(window).delta
    idx = Series(data.index.to_pydatetime(), index=data.index)
    for colname, col in data.iteritems():
        result = idx.apply(lambda x: f(x, time_delta))
        result.name = colname
        dfout = dfout.join(result, how='outer')

elif isinstance(window, (timedelta, relativedelta)):
    time_delta = window
    idx = Series(data.index.to_pydatetime(), index=data.index)
    for colname, col in data.iteritems():
        result = idx.apply(lambda x: f(x, time_delta))
        result.name = colname
        dfout = dfout.join(result, how='outer')

if dfout.columns.size == 1:
    dfout = dfout.ix[:, 0]
return dfout

И пример с 3-дневным временным окном для вычисления среднего значения:

from pandas import Series, DataFrame
import pandas as pd
from datetime import datetime, timedelta
import numpy as np
from dateutil.relativedelta import relativedelta

idx = [datetime(2011, 2, 7, 0, 0),
           datetime(2011, 2, 7, 0, 1),
           datetime(2011, 2, 8, 0, 1, 30),
           datetime(2011, 2, 9, 0, 2),
           datetime(2011, 2, 10, 0, 4),
           datetime(2011, 2, 11, 0, 5),
           datetime(2011, 2, 12, 0, 5, 10),
           datetime(2011, 2, 12, 0, 6),
           datetime(2011, 2, 13, 0, 8),
           datetime(2011, 2, 14, 0, 9)]
idx = pd.Index(idx)
vals = np.arange(len(idx)).astype(float)
s = Series(vals, index=idx)
# Now try by passing the 3 days as a relative time delta directly.
rm = rolling_mean(s, window=relativedelta(days=3))
>>> rm
Out[2]: 
2011-02-07 00:00:00    0.0
2011-02-07 00:01:00    0.5
2011-02-08 00:01:30    1.0
2011-02-09 00:02:00    1.5
2011-02-10 00:04:00    3.0
2011-02-11 00:05:00    4.0
2011-02-12 00:05:10    5.0
2011-02-12 00:06:00    5.5
2011-02-13 00:08:00    6.5
2011-02-14 00:09:00    7.5
Name: 0, dtype: float64
person InterwebIsGreat    schedule 14.05.2015

Убедитесь, что ваш индекс действительно datetime, а не str. Может быть полезно:

data.index = pd.to_datetime(data['Index']).values
person evgps    schedule 25.12.2018

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

  df=pd.read_csv('poll.csv',parse_dates=['enddate'],dtype={'favorable':np.float,'unfavorable':np.float,'other':np.float})

  df.set_index('enddate')
  df=df.fillna(0)

 fig, axs = plt.subplots(figsize=(5,10))
 df.plot(x='enddate', ax=axs)
 plt.show()


 df.rolling(window=3,min_periods=3).mean().plot()
 plt.show()
 print("The larger the window coefficient the smoother the line will appear")
 print('The min_periods is the minimum number of observations in the window required to have a value')

 df.rolling(window=6,min_periods=3).mean().plot()
 plt.show()
person Golden Lion    schedule 25.01.2021