Панда: середнє значення за часовим інтервалом


85

Я новачок у 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

Результат мав би лише один рядок для кожної дати.

EDIT x2: виправлена ​​помилка


2
У програмі відстеження помилок Pandas є відкрита проблема із запитом на цю функцію: github.com/pydata/pandas/issues/936 . Функціонал ще не існує. Відповіді на це запитання описують спосіб досягнення бажаного ефекту, але він, як правило, буде досить повільним у порівнянні з вбудованими rolling_*функціями.
BrenBarn

Відповіді:


72

Тим часом була додана можливість часового вікна. Дивіться це посилання .

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

Це має бути головною відповіддю.
Іван

6
Документацію для аргументів зсуву (наприклад, "2s") rollingможна взяти тут: pandas.pydata.org/pandas-docs/stable/user_guide/…
Guilherme Salomé

2
Що робити, якщо у фреймі даних є кілька стовпців; як ми вказуємо конкретні стовпці?
Brain_ overflowed

@Brain_overflowed встановлено як індекс
jamfie

З цим методом період min_per не здається надійним. Для min_periods> 1 ви можете отримати NaN там, де їх не очікуєте, через точність мітки часу / змінну частоту дискретизації
Альберт Джеймс Тедді,

50

Що можна сказати про щось подібне:

Спочатку перепробовуйте кадр даних на 1D інтервали. Це приймає середнє значення значень для всіх повторюваних днів. Використовуйте 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()

вибачте, Pandas newb, що саме використовує ffill як правило для подання відсутніх значень?
Анов

1
Є кілька варіантів заповнення. ffillрозшифровується як заповнення вперед і просто пропонує останнє невідсутнє значення. Аналогічно bfillдля заповнення назад, робить те саме у зворотному порядку.
Zelazny7,

9
Можливо, я помиляюся тут, але чи ви ігноруєте кілька показань того самого дня (якщо брати до уваги, ви очікуєте, що два показники матимуть більшу вагу, ніж одне ...)
Енді Хайден,

4
Чудова відповідь. Тільки зазначивши, що в пандах 0.18.0 синтаксис змінився . Новий синтаксис:df.resample("1D").ffill(limit=0).rolling(window=3, min_periods=1).mean()
Бен,

1
Для тиражування результатів оригінальної відповіді у pandas версії 0.18.1 я використовую: df.resample("1d").mean().rolling(window=3, min_periods=1).mean()
JohnE

33

У мене просто було те саме запитання, але з нерегулярно розташованими точками даних. Перепробовування тут насправді не є варіантом. Тож я створив власну функцію. Можливо, це буде корисно і для інших:

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')

Не могли б ви включити відповідний імпорт?
Bryce Drennan

Чи можете ви надати приклад вхідного кадру даних, який би працював, якщо обчислюється розсувне вікно часового інтервалу, дякую
joshlk

Додав приклад до оригінальної публікації.
user2689410

5
Тепер те саме можна зробити, використовуючиs.rolling('2min', min_periods=1).mean()
kampta

8

Код user2689410 був саме тим, що мені потрібно. Надання моєї версії (кредити 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

3

Здається, цей приклад вимагає зваженого середнього значення, як пропонується в коментарі @ andyhayden. Наприклад, є два опитування 25.10.10 та по одному 10.10.26. Якщо ви просто зробите вибірку, а потім скористаєтеся середнім значенням, це фактично дає вдвічі більшу вагу виборчих дільниць 26.10.

Щоб надати рівну вагу кожному опитуванню, а не однакову вагу кожному дню , ви можете зробити щось на зразок наступного.

>>> 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

Це дає вам вихідні інгредієнти для того, щоб робити середнє значення на основі опитування замість денного. Як і раніше, середні показники опитувань 10/25, але вага 10/25 також зберігається і вдвічі перевищує вагу 10/26 або 10/27, щоб відобразити, що два опитування були проведені 10/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 (зважене за день).

Також зауважте, що в API були змінені версії 0.18.0 resampleта rollingстаном на них.

прокат (що нового у pandas 0.18.0)

передискретизувати (що нового у pandas 0.18.0)


3

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

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

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

Зауважте, це може бути менш ефективним для СУПЕР великих даних або дуже малих приростів, оскільки ваша нарізка може стати більш напруженою (працює для мене досить добре для сотень тисяч рядків даних та кількох стовпців, хоча для погодинних вікон протягом декількох тижнів)


2

Я виявив, що код 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

0

Переконайтеся, що ваш індекс справді datetime, а не str Може бути корисним:

data.index = pd.to_datetime(data['Index']).values
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.