Pandalar: zaman aralığına göre değişen ortalama


87

Pandalar'da yeniyim .... Elimde bir sürü anket verisi var; Üç günlük bir pencereye dayalı olarak her gün için bir tahmin elde etmek için değişen bir ortalama hesaplamak istiyorum. Bu sorudan anladığım kadarıyla , rolling_ * işlevleri pencereyi belirli bir tarih-saat aralığına değil, belirli bir değer sayısına göre hesaplar.

Bu işlevi uygulayan farklı bir işlev var mı? Yoksa kendi başıma mı yazıyorum?

DÜZENLE:

Örnek giriş verileri:

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

Çıktı, her tarih için yalnızca bir satıra sahip olacaktır.

EDIT x2: düzeltilmiş yazım hatası


2
Pandas hata izleyicide bu işlevselliği isteyen açık sorun var: github.com/pydata/pandas/issues/936 . İşlevsellik henüz mevcut değil. Bu sorunun yanıtları , istenen etkiyi elde etmenin bir yolunu açıklar, ancak bu, yerleşik rolling_*işlevlere kıyasla genellikle oldukça yavaş olacaktır .
BrenBarn

Yanıtlar:


75

Bu arada, bir zaman aralığı yeteneği eklendi. Bu bağlantıya bakın .

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

Bu en iyi cevap olmalı.
Ivan

6
Offset ('2s' gibi) argümanlarının rollingalabileceği belgeler burada: pandas.pydata.org/pandas-docs/stable/user_guide/…
Guilherme Salomé

2
Veri çerçevesinde birden fazla sütun varsa ne olur? belirli sütunları nasıl belirleriz?
Brain_overflowed

@Brain_overflowed dizin olarak ayarlandı
jamfie

Min_period bu yöntemle güvenilir görünmemektedir. Min_periods> 1 için, NaN'leri zaman damgası hassasiyeti / değişken örnekleme oranı nedeniyle beklemediğiniz yerlerde alabilirsiniz
Albert James Teddy

50

Böyle bir şeye ne dersin:

İlk önce veri çerçevesini 1 boyutlu aralıklarla yeniden örnekleyin. Bu, tüm yinelenen günler için değerlerin ortalamasını alır. fill_methodEksik tarih değerlerini doldurmak için seçeneği kullanın . Ardından, yeniden örneklenen çerçeveyi pd.rolling_mean3 ve min_periods = 1 penceresiyle içine geçirin:

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

GÜNCELLEME : Ben'in yorumlarda belirttiği gibi, 0.18.0 pandalarda sözdizimi değişti . Yeni sözdizimiyle bu şöyle olur:

df.resample("1d").sum().fillna(0).rolling(window=3, min_periods=1).mean()

pardon Pandas newb, eksik değerleri sağlamak için kural olarak doldurma tam olarak neyi kullanıyor?
Anov

1
Birkaç dolgu seçeneği vardır. ffillileriye doğru doldurma anlamına gelir ve en son eksik olmayan değeri bildirir. Benzer şekilde bfillgeriye doğru doldurma için de aynı işlemi ters sırada yapar.
Zelazny7

9
Belki (... haddeleme Eğer birden fazla ağırlık taşımak için iki okuma beklediğiniz anlamına çekerken) Burada yanlış değilim, ama aynı gün birden fazla okumalar görmezden
Andy Hayden

4
Mükemmel cevap. Sadece 0.18.0 pandalarında sözdiziminin değiştiğine dikkat edin . Yeni sözdizimi:df.resample("1D").ffill(limit=0).rolling(window=3, min_periods=1).mean()
Ben

1
Pandalar 0.18.1 sürümündeki orijinal cevabın sonuçlarını çoğaltmak için kullanıyorum: df.resample("1d").mean().rolling(window=3, min_periods=1).mean()
JohnE

33

Sadece aynı soruyu sormuştum ama düzensiz aralıklı veri noktaları ile. Yeniden örnekleme burada gerçekten bir seçenek değil. Bu yüzden kendi fonksiyonumu yarattım. Belki başkaları için de faydalı olacaktır:

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

İlgili ithalatı dahil edebilir misiniz?
Bryce Drennan

Bir zaman aralığı kayan penceresi hesaplanırsa işe yarayacak örnek bir girdi veri çerçevesi sağlayabilir misiniz, teşekkürler
joshlk

Orijinal gönderiye bir örnek eklendi.
user2689410

5
Aynısı şimdi kullanılarak yapılabilirs.rolling('2min', min_periods=1).mean()
kampta

8

user2689410'un kodu tam da ihtiyacım olan şeydi. DataFrame'deki tüm satırlar için ortalamanın bir kerede hesaplanması nedeniyle daha hızlı olan sürümümü (user2689410 kredisi) sağlamak.

Umarım sonek kurallarım okunabilir: _s: string, _i: int, _b: bool, _ser: Series ve _df: DataFrame. Birden çok son ek bulduğunuzda, yazım her ikisi de olabilir.

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

Bu örnek, @ andyhayden'ın yorumunda önerildiği gibi ağırlıklı bir ortalama gerektiriyor gibi görünüyor. Örneğin, 10/25 ve 10/26 ve 10/27 tarihlerinde birer anket vardır. Sadece yeniden örneklendirir ve sonra ortalamayı alırsanız, bu etkin bir şekilde 10/25 ve 10/27 tarihlerindeki anketlere 10/25 ile karşılaştırıldığında iki kat daha fazla ağırlık verir.

Her güne eşit ağırlık vermek yerine her ankete eşit ağırlık vermek için aşağıdaki gibi bir şey yapabilirsiniz.

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

Bu size, güne dayalı bir ortalama yerine ankete dayalı bir ortalama yapmak için ham maddeleri verir. Daha önce olduğu gibi, anketlerin ortalaması 10/25, ancak 10/25 için ağırlık da saklanıyor ve iki anketin 10/25 tarihinde yapıldığını yansıtmak için 10/26 veya 10/27 tarihinde ağırlığın iki katı.

>>> 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 için dönen ortalamanın 52.1667 (gün ağırlıklı) yerine artık 0.51500 (anket ağırlıklı) olduğuna dikkat edin.

Ayrıca için API'ler hiç değişiklikler gerektiğini unutmayın resampleve rollingsürümü 0.18.0 itibariyle.

yuvarlanma (pandalar 0.18.0'daki yenilikler)

yeniden örnekleme (Pandalar 0.18.0'daki yenilikler)


3

Temel tutmak için, başlamanızı sağlamak için bir döngü ve buna benzer bir şey kullandım (benim indeksim tarih saatleri):

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

ve sonra bu dilim üzerinde işlevleri çalıştırabilirsiniz. Pencerenin başlangıcını veri çerçevesi dizininizdeki ilk değerden farklı bir şey yapmak için bir yineleyici eklemenin daha sonra pencereyi nasıl döndürdüğünü görebilirsiniz (örneğin, başlangıç ​​için bir> kuralı da kullanabilirsiniz).

Bu, SÜPER büyük veriler için daha az verimli olabilir veya dilimlemeniz daha yorucu hale gelebileceğinden çok küçük artışlar olabilir (benim için yüz binlerce veri satırı ve birkaç sütun için yeterince iyi çalışır, ancak birkaç hafta boyunca saatlik pencereler için)


2

İş ayındaki delta şu hatayı attığından, window = '1M' ile denediğimde user2689410 kodunun kırıldığını buldum:

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

Doğrudan göreceli bir zaman delta geçirme seçeneğini ekledim, böylece kullanıcı tanımlı dönemler için benzer şeyler yapabilirsiniz.

Öneriler için teşekkürler, işte benim girişimim - umarım işe yarar.

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

Ve ortalamayı hesaplamak için 3 günlük zaman penceresine sahip örnek:

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

Dizininizin gerçekten datetimestr yararlı olup olmadığını :

data.index = pd.to_datetime(data['Index']).values
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.