【问题标题】:How do you interpolate from an array containing datetime objects?如何从包含日期时间对象的数组中进行插值?
【发布时间】:2015-03-11 16:13:40
【问题描述】:

我正在寻找一个类似于np.interp 的函数,它可以与datetime 对象一起使用。

例如:

import datetime, numpy as np
arr1 = np.array([datetime.datetime(2008,1,d) for d in range(1,10)])
arr2 = np.arange(1,10)

np.interp(datetime.datetime(2008,1,5,12),arr1,arr2)

理想情况下会返回5.5,但numpy 会引发TypeError: array cannot be safely cast to required type。有没有一种很好的 Pythonic 方式来解决这个问题?

【问题讨论】:

  • 使用什么时区?是当地时间还是UTC?本地时间不是单调的,它可能是模棱两可的,例如,在 DST 过渡结束期间。
  • 我感兴趣的具体应用是卫星数据,所以是UTC。

标签: python arrays datetime numpy


【解决方案1】:

您可以将它们转换为时间戳(编辑以反映calendar.timegm 的使用以避免与时区相关的陷阱)。

# Python 2.7
import datetime, numpy as np
import calendar

def toTimestamp(d):
  return calendar.timegm(d.timetuple())

arr1 = np.array([toTimestamp(datetime.datetime(2008,1,d)) for d in range(1,10)]) 
arr2 = np.arange(1,10)

result = np.interp(toTimestamp(datetime.datetime(2008,1,5,12)),arr1,arr2)
print result # Prints 5.5

【讨论】:

  • mktime() 假定 d 是本地时间。 mktime() may fail to get the correct timestamp。 OP 应该使用 UTC 或感知日期时间对象。
  • OP input is UTC。除非本地时区是 UTC,否则使用 time.mktime() 是不正确的,请改用 calendar.timegm()。 More options.
  • @J.F.Sebastian 一如既往的好,答案已更新为改用calendar.timegm。
  • @rchang 我有一个类似的problem,你能帮帮我吗?
【解决方案2】:

numpy.interp() function 期望 arr1 和 arr2 是浮点数的一维序列,即,如果您想使用 np.interp(),您应该将 datetime 对象的序列转换为一维浮点数序列。

如果输入数据对所有日期时间对象使用相同的 UTC 偏移量,那么您可以通过从所有值中减去参考日期来获得浮点数。如果your input is UTC 是真的(偏移量总是为零):

from datetime import datetime
import numpy as np

arr1 = np.array([datetime(2008, 1, d) for d in range(1, 10)])
arr2 = np.arange(1, 10)

def to_float(d, epoch=arr1[0]):
    return (d - epoch).total_seconds()

f = np.interp(to_float(datetime(2008,1,5,12)), map(to_float, arr1), arr2)
print f # -> 5.5

【讨论】:

    【解决方案3】:

    对于那些想要在 Pandas 中完成这一切的人,我将提供此作为对 @rchang 的回答的补充。此函数采用包含日期的 pandas 系列并返回一个新系列,其值在指定日期后转换为“天数”。

    def convert_dates_to_days(dates, start_date=None, name='Day'):
        """Converts a series of dates to a series of float values that
        represent days since start_date.
        """
    
        if start_date:
            ts0 = pd.Timestamp(start_date).timestamp()
        else:
            ts0 = 0
    
        return ((dates.apply(pd.Timestamp.timestamp) - 
                ts0)/(24*3600)).rename(name)
    

    不确定它是否适用于时间,或者它是否不受上述时区陷阱的影响。但我认为,只要您提供同一时区的开始日期,从所有时间戳值中减去,就可以了。

    这是我的使用方法:

    from scipy.interpolate import interp1d
    
    data = pd.DataFrame({
        'Date': pd.date_range('2018-01-01', '2018-01-22', freq='7D'),
        'Value': np.random.randn(4)
    })
    
    x = convert_dates_to_days(data.Date, start_date='2018-01-01')
    y = data.Value
    f2 = interp1d(x, y, kind='cubic')
    
    all_dates = pd.Series(pd.date_range('2018-01-01', '2018-01-22'))
    x_all = convert_dates_to_days(all_dates, start_date='2018-01-01')
    
    plt.plot(all_dates, f2(x_all), '-')
    data.set_index('Date')['Value'].plot(style='o')
    plt.grid()
    plt.savefig("interp_demo.png")
    plt.show()
    

    它似乎工作......

    【讨论】:

      【解决方案4】:

      如果您的时间戳具有/需要亚秒级精度,这里是 rchang 的 answer 的略微编辑版本(基本上只是不同的 toTimestamp 方法)

      import datetime, numpy as np
      
      def toTimestamp(d):
        return d.timestamp()
      
      arr1 = np.array([toTimestamp(datetime.datetime(2000,1,2,3,4,5) + datetime.timedelta(0,d)) for d in np.linspace(0,1,9)]) 
      arr2 = np.arange(1,10) # 1, 2, ..., 9
      
      result = np.interp(toTimestamp(datetime.datetime(2000,1,2,3,4,5,678901)),arr1,arr2)
      print(result) # Prints 6.431207656860352
      

      我不能说任何关于时区的问题,因为我没有用其他时区测试过。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-09-22
        • 1970-01-01
        • 1970-01-01
        • 2016-09-01
        • 2014-02-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多