Pandy: średnia krocząca według przedziału czasu

85

Jestem nowy w Pandas… Mam mnóstwo danych z ankiet; Chcę obliczyć średnią kroczącą, aby uzyskać oszacowanie dla każdego dnia na podstawie trzydniowego okna. Jak rozumiem z tego pytania , funkcje rolling_ * obliczają okno na podstawie określonej liczby wartości, a nie określonego zakresu dat i godzin.

Czy istnieje inna funkcja, która implementuje tę funkcję? A może utknąłem na pisaniu własnego?

EDYTOWAĆ:

Przykładowe dane wejściowe:

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

Dane wyjściowe miałyby tylko jeden wiersz dla każdej daty.

EDYCJA x2: poprawiona literówka

Anov
źródło
2
W narzędziu do śledzenia błędów Pandas jest otwarty problem z żądaniem tej funkcji: github.com/pydata/pandas/issues/936 . Funkcjonalność jeszcze nie istnieje. Odpowiedzi na to pytanie opisują sposób uzyskania pożądanego efektu, ale zazwyczaj będzie to dość powolne w porównaniu z rolling_*funkcjami wbudowanymi .
BrenBarn

Odpowiedzi:

73

W międzyczasie dodano możliwość okna czasowego. Zobacz ten link .

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
Jaskółka oknówka
źródło
To powinna być najlepsza odpowiedź.
Ivan
6
Dokumentacja dotycząca argumentów przesunięcia (takich jak „2s”), które rollingmogą być przyjmowane, znajduje się tutaj: pandas.pydata.org/pandas-docs/stable/user_guide/ ...
Guilherme Salomé
2
Co się stanie, jeśli w ramce danych jest wiele kolumn; jak określamy konkretne kolumny?
Brain_overflowed
@Brain_overflowed ustawiono jako index
jamfie
Min_period nie wydaje się wiarygodny w przypadku tej metody. Dla min_periods> 1 możesz otrzymać NaNs tam, gdzie się ich nie spodziewasz ze względu na precyzję znacznika czasu / zmienną częstotliwość próbkowania
Albert James Teddy
50

A co z czymś takim:

Najpierw ponownie próbkuj ramkę danych na interwały 1D. Jest to średnia wartości ze wszystkich zduplikowanych dni. Użyj fill_methodopcji, aby uzupełnić brakujące wartości dat. Następnie przekaż ponownie próbkowaną klatkę do pd.rolling_meanz oknem 3 i 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

AKTUALIZACJA : Jak Ben wskazuje w komentarzach, w pandach 0.18.0 składnia uległa zmianie . Przy nowej składni wyglądałoby to tak:

df.resample("1d").sum().fillna(0).rolling(window=3, min_periods=1).mean()
Żelazny7
źródło
przepraszam, Pandas newb, czego dokładnie używa ffill jako reguły do ​​podania brakujących wartości?
Anov
1
Istnieje kilka opcji wypełnienia. ffilloznacza wypełnienie do przodu i po prostu propaguje najnowszą, której nie brakuje. Podobnie w bfillprzypadku wypełnienia wstecznego, robi to samo w odwrotnej kolejności.
Zelazny7
9
Być może się mylę, ale czy ignorujesz wielokrotne odczyty tego samego dnia (biorąc pod uwagę, że spodziewasz się, że dwa odczyty będą ważniejsze niż jeden ...)
Andy Hayden
4
Świetna odpowiedź. Wystarczy zauważyć, że w pandach 0.18.0 zmieniła się składnia . Nowa składnia to:df.resample("1D").ffill(limit=0).rolling(window=3, min_periods=1).mean()
Ben
1
Aby powtórzyć wyniki oryginalnej odpowiedzi w pandach w wersji 0.18.1, używam: df.resample("1d").mean().rolling(window=3, min_periods=1).mean()
JohnE
33

Właśnie miałem to samo pytanie, ale z nieregularnymi punktami danych. Resample nie jest tutaj rozwiązaniem. Stworzyłem więc własną funkcję. Może przyda się też innym:

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')
user2689410
źródło
Czy możesz dołączyć odpowiedni import?
Bryce Drennan,
Czy możesz podać przykładową ramkę danych wejściowych, która działałaby przy obliczaniu przesuwnego okna przedziału czasu, dzięki
joshlk
Dodano przykład do oryginalnego postu.
user2689410
5
To samo można teraz zrobić za pomocąs.rolling('2min', min_periods=1).mean()
kampta
8

Kod użytkownika 2689410 był dokładnie tym, czego potrzebowałem. Podanie mojej wersji (kredyty dla użytkownika 2689410), co jest szybsze dzięki obliczaniu średniej dla całych wierszy w DataFrame.

Mam nadzieję, że moje konwencje sufiksów są czytelne: _s: string, _i: int, _b: bool, _ser: Series i _df: DataFrame. Jeśli znajdziesz wiele sufiksów, wpisz oba.

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
Mark Horvath
źródło
3

Ten przykład wydaje się wymagać średniej ważonej, jak sugeruje komentarz @ andyhayden. Na przykład, są dwie ankiety 25.10 i jedna 26.10 i 27.10. Jeśli po prostu ponownie spróbujesz, a następnie weźmiesz średnią, w rzeczywistości daje to dwa razy większą wagę ankietom z 26 października i 27 października w porównaniu do ankiet z 25 października.

Aby nadać równą wagę każdej ankiecie, a nie równą wagę każdemu dniu , możesz zrobić coś takiego.

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

To daje surowe składniki do wykonywania średniej opartej na ankiecie zamiast średniej dziennej. Tak jak poprzednio, sondaże są uśredniane 25 października, ale waga dla 10/25 jest również przechowywana i jest dwukrotnie większa niż waga z dnia 10/26 lub 10/27, aby odzwierciedlić, że dwie ankiety zostały przeprowadzone 25 października.

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

Zauważ, że średnia krocząca dla 10/27 wynosi teraz 0,51500 (ważona polem), a nie 52,1667 (ważona dzień).

Należy również pamiętać, że nastąpiły zmiany w interfejsach API dla resamplei rollingod wersji 0.18.0.

Rolling (co nowego w pandach 0.18.0)

ponowne próbkowanie (co nowego w pandach 0.18.0)

JohnE
źródło
3

Aby zachować prostotę, użyłem pętli i czegoś takiego, aby zacząć (mój indeks to czasy dat):

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

a następnie możesz uruchamiać funkcje na tym wycinku. Możesz zobaczyć, jak dodanie iteratora, który sprawi, że początek okna będzie inny niż pierwsza wartość w twoim indeksie dataframes, spowoduje przewinięcie okna (możesz na przykład użyć reguły> na początku).

Uwaga, może to być mniej wydajne w przypadku SUPER dużych danych lub bardzo małych przyrostów, ponieważ krojenie może stać się bardziej uciążliwe (działa dla mnie wystarczająco dobrze dla setek tysięcy wierszy danych i kilku kolumn, chociaż w przypadku okien godzinowych przez kilka tygodni)

Vlox
źródło
2

Zauważyłem, że kod user2689410 zepsuł się, gdy próbowałem z window = '1M', ponieważ różnica w miesiącu roboczym spowodowała ten błąd:

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

Dodałem opcję bezpośredniego przekazywania względnej delty czasu, dzięki czemu można robić podobne rzeczy dla okresów zdefiniowanych przez użytkownika.

Dzięki za wskazówki, oto moja próba - mam nadzieję, że się przyda.

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

I przykład z 3-dniowym oknem czasowym do obliczenia średniej:

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
InterwebIs Świetnie
źródło
0

Sprawdź, czy Twój indeks jest naprawdę datetime, nie str może być pomocny:

data.index = pd.to_datetime(data['Index']).values
evgps
źródło