Gấu trúc: trung bình luân phiên theo khoảng thời gian


85

Tôi mới làm quen với Pandas .... Tôi có một loạt dữ liệu thăm dò ý kiến; Tôi muốn tính toán giá trị trung bình luân phiên để có được ước tính cho mỗi ngày dựa trên thời lượng ba ngày. Theo tôi hiểu từ câu hỏi này , các hàm Roll_ * tính toán cửa sổ dựa trên một số giá trị được chỉ định chứ không phải một phạm vi ngày giờ cụ thể.

Có một chức năng khác thực hiện chức năng này không? Hay tôi đang mắc kẹt trong việc viết của riêng mình?

BIÊN TẬP:

Dữ liệu đầu vào mẫu:

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

Đầu ra sẽ chỉ có một hàng cho mỗi ngày.

EDIT x2: sửa lỗi đánh máy


2
Đã xảy ra sự cố mở trong trình theo dõi lỗi Pandas yêu cầu chức năng này: github.com/pydata/pandas/issues/936 . Chức năng chưa tồn tại. Các câu trả lời cho câu hỏi này mô tả một cách để đạt được hiệu quả mong muốn, nhưng nó thường khá chậm so với các rolling_*chức năng tích hợp sẵn.
BrenBarn

Câu trả lời:


73

Trong thời gian chờ đợi, khả năng cửa sổ thời gian đã được thêm vào. Xem liên kết này .

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

Đây phải là câu trả lời hàng đầu.
Ivan

6
Tài liệu cho các đối số offset (như '2s') rollingcó thể lấy ở đây: pandas.pydata.org/pandas-docs/stable/user_guide/…
Guilherme Salomé

2
Điều gì sẽ xảy ra nếu có nhiều cột trong khung dữ liệu; làm thế nào để chúng tôi chỉ định các cột cụ thể?
Brain_overflowed

@Brain_overflowed được đặt làm chỉ mục
jamfie

Min_period có vẻ không đáng tin cậy với phương pháp này. Đối với min_period> 1, bạn có thể nhận được NaN mà bạn không mong đợi do độ chính xác của dấu thời gian / tỷ lệ lấy mẫu thay đổi
Albert James Teddy

50

Những gì về một cái gì đó như thế này:

Đầu tiên lấy mẫu lại khung dữ liệu thành các khoảng 1D. Điều này lấy giá trị trung bình cho tất cả các ngày trùng lặp. Sử dụng fill_methodtùy chọn để điền vào các giá trị ngày còn thiếu. Tiếp theo, chuyển khung đã lấy mẫu lại vào pd.rolling_meanvới cửa sổ 3 và 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

CẬP NHẬT : Như Ben đã chỉ ra trong các bình luận, với gấu trúc 0.18.0, cú pháp đã thay đổi . Với cú pháp mới, đây sẽ là:

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

xin lỗi, Pandas newb, chính xác thì ffill sử dụng làm quy tắc nào để cung cấp các giá trị bị thiếu?
Anov

1
Có một số tùy chọn điền. ffilllà viết tắt của điền chuyển tiếp và chỉ đơn giản đề xuất giá trị không bị thiếu gần đây nhất. Tương tự bfillđối với điền ngược, thực hiện tương tự theo thứ tự ngược lại.
Zelazny7

9
Có lẽ tôi là nhầm lẫn ở đây, nhưng bạn đang bỏ qua nhiều bài đọc từ trong cùng một ngày (khi chụp các cán có nghĩa bạn mong muốn hai bài đọc để thực hiện trọng lượng nhiều hơn một ...)
Andy Hayden

4
Câu trả lời chính xác. Chỉ cần lưu ý rằng trong pandas 0.18.0, cú pháp đã thay đổi . Cú pháp mới là:df.resample("1D").ffill(limit=0).rolling(window=3, min_periods=1).mean()
Ben

1
Để sao chép kết quả của câu trả lời ban đầu trong phiên bản gấu trúc 0.18.1, tôi đang sử dụng: df.resample("1d").mean().rolling(window=3, min_periods=1).mean()
JohnE

33

Tôi vừa có câu hỏi tương tự nhưng với các điểm dữ liệu cách nhau không đều. Resample không thực sự là một tùy chọn ở đây. Vì vậy, tôi đã tạo ra chức năng của riêng mình. Có thể nó cũng sẽ hữu ích cho những người khác:

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

Bạn có thể bao gồm các nhập khẩu có liên quan?
Bryce Drennan

Bạn có thể vui lòng cung cấp khung dữ liệu đầu vào ví dụ sẽ hoạt động nếu tính toán cửa sổ trượt khoảng thời gian không, cảm ơn
joshlk

Đã thêm một ví dụ vào bài viết gốc.
user2689410

5
Tương tự bây giờ có thể được thực hiện bằng cách sử dụngs.rolling('2min', min_periods=1).mean()
kampta

8

Mã của user2689410 là chính xác những gì tôi cần. Cung cấp phiên bản của tôi (tín dụng cho user2689410), nhanh hơn do tính toán giá trị trung bình cùng một lúc cho toàn bộ các hàng trong DataFrame.

Hy vọng các quy ước về hậu tố của tôi có thể đọc được: _s: string, _i: int, _b: bool, _ser: Series và _df: DataFrame. Khi bạn tìm thấy nhiều hậu tố, loại có thể là cả hai.

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

Ví dụ này dường như gọi một giá trị trung bình có trọng số như được đề xuất trong nhận xét của @ andyhayden. Ví dụ, có hai cuộc thăm dò vào ngày 25/10 và một cuộc thăm dò vào ngày 26/10 và 27/10. Nếu bạn chỉ lấy lại mẫu và sau đó lấy giá trị trung bình, điều này thực sự mang lại trọng số cho các cuộc thăm dò vào ngày 26/10 và 27/10 so với các cuộc thăm dò vào ngày 25/10.

Để có trọng lượng bằng nhau cho mỗi cuộc thăm dò thay vì trọng lượng bằng nhau cho mỗi ngày , bạn có thể làm như sau.

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

Điều đó cung cấp cho bạn các thành phần thô để thực hiện trung bình dựa trên cuộc thăm dò thay vì trung bình dựa trên ngày. Như trước đây, các cuộc thăm dò được tính trung bình vào ngày 25/10, nhưng trọng số của 10/25 cũng được lưu trữ và gấp đôi trọng số vào ngày 26/10 hoặc 27/10 để phản ánh rằng hai cuộc thăm dò đã được thực hiện vào ngày 25/10.

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

Lưu ý rằng giá trị trung bình luân phiên cho 10/27 hiện là 0,51500 (tính theo trọng số cuộc thăm dò) thay vì 52,1667 (tính theo ngày).

Cũng lưu ý rằng đã có những thay đổi đối với API cho resamplerollingkể từ phiên bản 0.18.0.

lăn (có gì mới trong gấu trúc 0.18.0)

lấy lại mẫu (có gì mới trong gấu trúc 0.18.0)


3

Để giữ cho nó cơ bản, tôi đã sử dụng một vòng lặp và một cái gì đó như thế này để giúp bạn bắt đầu (chỉ mục của tôi là 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

và sau đó bạn có thể chạy các chức năng trên lát cắt đó. Bạn có thể thấy cách thêm một trình lặp để bắt đầu cửa sổ khác với giá trị đầu tiên trong chỉ mục khung dữ liệu của bạn, sau đó sẽ cuộn cửa sổ (ví dụ: bạn có thể sử dụng quy tắc> cho phần bắt đầu).

Lưu ý, điều này có thể kém hiệu quả hơn đối với dữ liệu SUPER lớn hoặc số gia rất nhỏ vì việc cắt lát của bạn có thể trở nên vất vả hơn (đối với tôi, đủ tốt cho hàng trăm nghìn hàng dữ liệu và một số cột mặc dù đối với cửa sổ hàng giờ trong vài tuần)


2

Tôi thấy rằng mã user2689410 bị hỏng khi tôi thử với window = '1 triệu' vì delta vào tháng làm việc đã gây ra lỗi này:

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

Tôi đã thêm tùy chọn để vượt qua trực tiếp một đồng bằng thời gian tương đối, vì vậy bạn có thể làm những việc tương tự trong khoảng thời gian do người dùng xác định.

Cảm ơn vì những gợi ý, đây là nỗ lực của tôi - hy vọng nó sẽ được sử dụng.

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

Và ví dụ với khoảng thời gian 3 ngày để tính giá trị trung bình:

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

Kiểm tra xem chỉ mục của bạn có thực sự datetimekhông str Có thể hữu ích:

data.index = pd.to_datetime(data['Index']).values
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.