【问题标题】:Python regularise irregular time series with linear interpolationPython用线性插值正则化不规则时间序列
【发布时间】:2014-10-03 18:56:47
【问题描述】:

我在 pandas 中有一个时间序列,如下所示:

                     Values
1992-08-27 07:46:48    28.0  
1992-08-27 08:00:48    28.2  
1992-08-27 08:33:48    28.4  
1992-08-27 08:43:48    28.8  
1992-08-27 08:48:48    29.0  
1992-08-27 08:51:48    29.2  
1992-08-27 08:53:48    29.6  
1992-08-27 08:56:48    29.8  
1992-08-27 09:03:48    30.0

我想将其重新采样为具有 15 分钟步长的常规时间序列,其中值是线性插值的。基本上我想得到:

                     Values
1992-08-27 08:00:00    28.2  
1992-08-27 08:15:00    28.3  
1992-08-27 08:30:00    28.4  
1992-08-27 08:45:00    28.8  
1992-08-27 09:00:00    29.9

但是使用 Pandas 的重采样方法 (df.resample('15Min')) 我得到:

                     Values
1992-08-27 08:00:00   28.20  
1992-08-27 08:15:00     NaN  
1992-08-27 08:30:00   28.60  
1992-08-27 08:45:00   29.40  
1992-08-27 09:00:00   30.00  

我尝试过使用不同的“how”和“fill_method”参数的重采样方法,但从未得到我想要的结果。我是不是用错了方法?

我认为这是一个相当简单的查询,但我已经在网上搜索了一段时间并没有找到答案。

提前感谢我能得到的任何帮助。

【问题讨论】:

    标签: python pandas time-series linear-interpolation


    【解决方案1】:

    我最近不得不重新采样非均匀采样的加速度数据。它通常以正确的频率进行采样,但会间歇性地累积延迟。

    我发现了这个问题,并使用纯 pandas 和 numpy 结合了 mstringer 和 Alberto Garcia-Rabosco 的答案。此方法在所需频率处创建一个新索引,然后进行插值,而无需在较高频率处进行插值的间歇步骤。

    # from Alberto Garcia-Rabosco above
    import io
    import pandas as pd
    
    data = io.StringIO('''\
    Values
    1992-08-27 07:46:48,28.0  
    1992-08-27 08:00:48,28.2  
    1992-08-27 08:33:48,28.4  
    1992-08-27 08:43:48,28.8  
    1992-08-27 08:48:48,29.0  
    1992-08-27 08:51:48,29.2  
    1992-08-27 08:53:48,29.6  
    1992-08-27 08:56:48,29.8  
    1992-08-27 09:03:48,30.0
    ''')
    s = pd.read_csv(data, squeeze=True)
    s.index = pd.to_datetime(s.index)
    

    进行插值的代码:

    import numpy as np
    # create the new index and a new series full of NaNs
    new_index = pd.DatetimeIndex(start='1992-08-27 08:00:00', 
        freq='15 min', periods=5, yearfirst=True)
    new_series = pd.Series(np.nan, index=new_index)
    
    # concat the old and new series and remove duplicates (if any) 
    comb_series = pd.concat([s, new_series])
    comb_series = comb_series[~comb_series.index.duplicated(keep='first')]
    
    # interpolate to fill the NaNs
    comb_series.interpolate(method='time', inplace=True)
    

    输出:

    >>> print(comb_series[new_index])
    1992-08-27 08:00:00    28.188571
    1992-08-27 08:15:00    28.286061
    1992-08-27 08:30:00    28.376970
    1992-08-27 08:45:00    28.848000
    1992-08-27 09:00:00    29.891429
    Freq: 15T, dtype: float64
    

    和以前一样,您可以使用 scipy 支持的任何插值方法,并且该技术也适用于 DataFrame(这就是我最初使用它的目的)。最后,请注意 interpolate 默认为“线性”方法,该方法忽略索引中的时间信息,并且不适用于非均匀间隔的数据。

    【讨论】:

    • 我得到了E TypeError: __new__() got an unexpected keyword argument 'start'pd.DatetimeIndex(start='1992-08-27 08:00:00' 行。我发现使用 pd.date_range() 代替工作 - Python 3.8 和 pandas 1.3.5
    【解决方案2】:

    @mstringer 获得的相同结果完全可以在 pandas 中实现。诀窍是首先按秒重新采样,使用插值填充中间值 (.resample('s').interpolate()),然后以 15 分钟为周期进行上采样 (.resample('15T').asfreq())。

    import io
    import pandas as pd
    
    data = io.StringIO('''\
    Values
    1992-08-27 07:46:48,28.0  
    1992-08-27 08:00:48,28.2  
    1992-08-27 08:33:48,28.4  
    1992-08-27 08:43:48,28.8  
    1992-08-27 08:48:48,29.0  
    1992-08-27 08:51:48,29.2  
    1992-08-27 08:53:48,29.6  
    1992-08-27 08:56:48,29.8  
    1992-08-27 09:03:48,30.0
    ''')
    s = pd.read_csv(data, squeeze=True)
    s.index = pd.to_datetime(s.index)
    
    res = s.resample('s').interpolate().resample('15T').asfreq().dropna()
    print(res)
    

    输出:

    1992-08-27 08:00:00    28.188571
    1992-08-27 08:15:00    28.286061
    1992-08-27 08:30:00    28.376970
    1992-08-27 08:45:00    28.848000
    1992-08-27 09:00:00    29.891429
    Freq: 15T, Name: Values, dtype: float64
    

    【讨论】:

    • 效率低下但仍然聪明有用。
    • 如果我有一个“字符串”列并且我想在 8:00:00 到 8 的时间段内复制 8:00:00 的值,请问我该怎么做:45:00 到 8:15:00 和 8:30:00 之间的值?
    【解决方案3】:

    您可以使用traces 执行此操作。首先,创建一个TimeSeries,使用您的不规则测量值,就像使用字典一样:

    ts = traces.TimeSeries([
        (datetime(1992, 8, 27, 7, 46, 48), 28.0),
        (datetime(1992, 8, 27, 8, 0, 48), 28.2),
        ...
        (datetime(1992, 8, 27, 9, 3, 48), 30.0),
    ])
    

    然后使用sample方法进行正则化:

    ts.sample(
        sampling_period=timedelta(minutes=15),
        start=datetime(1992, 8, 27, 8),
        end=datetime(1992, 8, 27, 9),
        interpolate='linear',
    )
    

    这导致以下正则化版本,其中灰色点是原始数据,橙色是线性插值的正则化版本。

    插值是:

    1992-08-27 08:00:00    28.189 
    1992-08-27 08:15:00    28.286  
    1992-08-27 08:30:00    28.377
    1992-08-27 08:45:00    28.848
    1992-08-27 09:00:00    29.891
    

    【讨论】:

    • 谢谢,我用xmgrace 做到了——老派:)
    • @mstringer 这个方法是神送!感谢您与我们分享这个!
    • @mstringer 谢谢你的踪迹!这种方法对于间隔不均匀的时间序列非常有用。你能告诉我你是如何制作上图的吗?你用过xmgrace吗?您是否知道任何可以帮助我在 Python 中重新创建上述内容的库?
    【解决方案4】:

    这需要一些工作,但请尝试一下。基本思想是找到最接近每个重采样点的两个时间戳并进行插值。 np.searchsorted 用于查找最接近重采样点的日期。

    # empty frame with desired index
    rs = pd.DataFrame(index=df.resample('15min').iloc[1:].index)
    
    # array of indexes corresponding with closest timestamp after resample
    idx_after = np.searchsorted(df.index.values, rs.index.values)
    
    # values and timestamp before/after resample
    rs['after'] = df.loc[df.index[idx_after], 'Values'].values
    rs['before'] = df.loc[df.index[idx_after - 1], 'Values'].values
    rs['after_time'] = df.index[idx_after]
    rs['before_time'] = df.index[idx_after - 1]
    
    #calculate new weighted value
    rs['span'] = (rs['after_time'] - rs['before_time'])
    rs['after_weight'] = (rs['after_time'] - rs.index) / rs['span']
    # I got errors here unless I turn the index to a series
    rs['before_weight'] = (pd.Series(data=rs.index, index=rs.index) - rs['before_time']) / rs['span']
    
    rs['Values'] = rs.eval('before * before_weight + after * after_weight')
    

    毕竟,希望是正确的答案:

    In [161]: rs['Values']
    Out[161]: 
    1992-08-27 08:00:00    28.011429
    1992-08-27 08:15:00    28.313939
    1992-08-27 08:30:00    28.223030
    1992-08-27 08:45:00    28.952000
    1992-08-27 09:00:00    29.908571
    Freq: 15T, Name: Values, dtype: float64
    

    【讨论】:

    • 太棒了!我刚刚将最后一行更改为: rs['Values'] = rs.eval('after * before_weight + before * after_weight') 现在它正在按照我想要的方式进行线性插值。谢谢。
    猜你喜欢
    • 2015-08-12
    • 2020-07-19
    • 1970-01-01
    • 2021-01-08
    • 2019-10-10
    • 2011-04-23
    • 1970-01-01
    • 2019-03-18
    • 2016-04-07
    相关资源
    最近更新 更多