Pandas: rollierender Mittelwert nach Zeitintervall

85

Ich bin neu bei Pandas ... Ich habe eine Reihe von Umfragedaten. Ich möchte einen rollierenden Mittelwert berechnen, um eine Schätzung für jeden Tag basierend auf einem dreitägigen Fenster zu erhalten. Wie ich aus dieser Frage verstehe , berechnen die Funktionen rolling_ * das Fenster basierend auf einer bestimmten Anzahl von Werten und nicht auf einem bestimmten Datums- / Uhrzeitbereich.

Gibt es eine andere Funktion, die diese Funktionalität implementiert? Oder stecke ich fest, meine eigenen zu schreiben?

BEARBEITEN:

Beispiel für Eingabedaten:

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

Die Ausgabe würde nur eine Zeile für jedes Datum haben.

EDIT x2: Tippfehler behoben

Anov
quelle
2
Es gibt ein offenes Problem im Pandas-Bug-Tracker, das diese Funktionalität anfordert: github.com/pydata/pandas/issues/936 . Die Funktionalität ist noch nicht vorhanden. Antworten auf diese Frage beschreiben einen Weg, um den gewünschten Effekt zu erzielen, der jedoch im Vergleich zu integrierten rolling_*Funktionen normalerweise recht langsam ist .
BrenBarn

Antworten:

73

In der Zwischenzeit wurde eine Zeitfensterfunktion hinzugefügt. Siehe diesen 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
Martin
quelle
Dies sollte die beste Antwort sein.
Ivan
6
Die Dokumentation für die Offset-Argumente (wie '2s') rollingkann hier sein: pandas.pydata.org/pandas-docs/stable/user_guide/…
Guilherme Salomé
2
Was ist, wenn der Datenrahmen mehrere Spalten enthält? Wie spezifizieren wir bestimmte Spalten?
Brain_overflowed
@Brain_overflowed als Index festgelegt
Jamfie
Die min_period scheint mit dieser Methode nicht zuverlässig zu sein. Für min_periods> 1 erhalten Sie möglicherweise NaNs, bei denen Sie sie aufgrund der Zeitstempelgenauigkeit / variablen Abtastrate nicht erwarten
Albert James Teddy,
50

Was ist mit so etwas:

Abtasten Sie zuerst den Datenrahmen in 1D-Intervallen. Dies ist der Mittelwert der Werte für alle doppelten Tage. Verwenden Sie die fill_methodOption, um fehlende Datumswerte einzugeben. Übergeben Sie als Nächstes den neu abgetasteten Frame pd.rolling_meanmit einem Fenster von 3 und 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

UPDATE : Wie Ben in den Kommentaren betont , hat sich bei Pandas 0.18.0 die Syntax geändert . Mit der neuen Syntax wäre dies:

df.resample("1d").sum().fillna(0).rolling(window=3, min_periods=1).mean()
Zelazny7
quelle
Entschuldigung, Pandas Newb, was genau verwendet ffill in der Regel, um fehlende Werte zu liefern?
Anov
1
Es gibt einige Fülloptionen. ffillsteht für Forward Fill und propagiert einfach den neuesten nicht fehlenden Wert. Ähnliches gilt bfillfür das Rückwärtsfüllen in umgekehrter Reihenfolge.
Zelazny7
9
Vielleicht irre ich mich hier, aber ignorieren Sie mehrere Messwerte vom selben Tag (wenn Sie den rollenden Mittelwert nehmen, würden Sie erwarten, dass zwei Messwerte mehr Gewicht haben als einer ...)
Andy Hayden
4
Gute Antwort. Ich habe nur bemerkt, dass sich in Pandas 0.18.0 die Syntax geändert hat . Die neue Syntax lautet:df.resample("1D").ffill(limit=0).rolling(window=3, min_periods=1).mean()
Ben
1
Um die Ergebnisse der ursprünglichen Antwort in Pandas Version 0.18.1 zu replizieren, verwende ich: df.resample("1d").mean().rolling(window=3, min_periods=1).mean()
JohnE
33

Ich hatte nur die gleiche Frage, aber mit unregelmäßig verteilten Datenpunkten. Resample ist hier eigentlich keine Option. Also habe ich meine eigene Funktion erstellt. Vielleicht ist es auch für andere nützlich:

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
quelle
Könnten Sie die relevanten Importe einbeziehen?
Bryce Drennan
Können Sie bitte einen Beispiel-Eingabedatenrahmen angeben, der funktionieren würde, wenn ein Zeitintervall-Schiebefenster
berechnet wird
Dem ursprünglichen Beitrag wurde ein Beispiel hinzugefügt.
user2689410
5
Gleiches kann jetzt mits.rolling('2min', min_periods=1).mean()
kampta
8

Der Code von user2689410 war genau das, was ich brauchte. Bereitstellung meiner Version (Credits für Benutzer 2689410), die schneller ist, da der Mittelwert für ganze Zeilen im DataFrame sofort berechnet wird.

Hoffe, meine Suffix-Konventionen sind lesbar: _s: string, _i: int, _b: bool, _ser: Series und _df: DataFrame. Wenn Sie mehrere Suffixe finden, kann der Typ beides sein.

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

Dieses Beispiel scheint einen gewichteten Mittelwert zu erfordern, wie in @ andyhaydens Kommentar vorgeschlagen. Zum Beispiel gibt es zwei Umfragen am 25.10. Und jeweils eine am 26.10. Und 27.10. Wenn Sie nur eine Neuabtastung durchführen und dann den Mittelwert ermitteln, werden die Umfragen am 26.10. Und 27.10. Im Vergleich zu den Umfragen am 25.10. Effektiv doppelt so gewichtet.

Um jeder Umfrage das gleiche Gewicht zu geben und nicht jedem Tag das gleiche Gewicht , können Sie Folgendes tun.

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

Das gibt Ihnen die Rohstoffe für die Erstellung eines umfragebasierten Mittelwerts anstelle eines tagesbasierten Mittelwerts. Nach wie vor werden die Umfragen am 25.10. Gemittelt, aber das Gewicht für 25.10. Wird ebenfalls gespeichert und ist doppelt so hoch wie am 26.10. Oder 27.10., Um anzuzeigen, dass am 25.10. Zwei Umfragen durchgeführt wurden.

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

Beachten Sie, dass der rollierende Mittelwert für 10/27 jetzt 0,51500 (umfragegewichtet) und nicht 52,1667 (taggewichtet) beträgt.

Beachten Sie auch, dass Änderungen an den APIs für resampleund rollingab Version 0.18.0 vorgenommen wurden.

Rollen (was ist neu in Pandas 0.18.0)

Resample (was ist neu in Pandas 0.18.0)

JohnE
quelle
3

Um es einfach zu halten, habe ich eine Schleife und so etwas verwendet, um Ihnen den Einstieg zu erleichtern (mein Index ist datetimes):

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

und dann können Sie Funktionen auf diesem Slice ausführen. Sie können sehen, wie das Hinzufügen eines Iterators, um den Start des Fensters zu einem anderen Wert als dem ersten Wert in Ihrem Datenrahmenindex zu machen, das Fenster dann rollt (Sie könnten beispielsweise auch eine> -Regel für den Start verwenden).

Beachten Sie, dass dies für SUPER große Datenmengen oder sehr kleine Schritte möglicherweise weniger effizient ist, da Ihr Slicing möglicherweise anstrengender wird (funktioniert für mich gut genug für Hunderttausende von Datenzeilen und mehrere Spalten, jedoch für stündliche Fenster über einige Wochen).

Vlox
quelle
2

Ich habe festgestellt, dass der Code von user2689410 beim Versuch mit window = '1M' fehlerhaft war, da das Delta im Geschäftsmonat diesen Fehler verursachte:

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

Ich habe die Option hinzugefügt, ein relatives Zeitdelta direkt zu übergeben, damit Sie ähnliche Dinge für benutzerdefinierte Zeiträume tun können.

Vielen Dank für die Hinweise, hier ist mein Versuch - hoffe, es ist von Nutzen.

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

Und das Beispiel mit einem 3-Tage-Zeitfenster zur Berechnung des Mittelwerts:

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
InterwebIsGreat
quelle
0

Überprüfen Sie, ob Ihr Index wirklich datetimenicht ist. str Kann hilfreich sein:

data.index = pd.to_datetime(data['Index']).values
evgps
quelle