将Matlab的datenum格式转换为Python

20

我刚刚开始从Matlab转到Python 2.7,但我在读取.mat文件方面遇到了一些问题。时间信息存储在Matlab的datenum格式中,对于那些不熟悉它的人:

序列日期号表示日历日期距离固定基准日期过去了多少天。在MATLAB中,序列日期号1表示公元0000年1月1日。

MATLAB还使用串行时间来表示从午夜开始的一天之分数;例如,下午6点等于0.75个串行天。因此,在MATLAB中,“2003年10月31日,下午6:00”的字符串是日期号731885.75。

(摘自Matlab文档)

我想将其转换为Python的时间格式,并找到了这篇教程。简而言之,作者表示:

如果您使用python的datetime.fromordinal(731965.04835648148)进行解析,则结果可能看起来合理[...]

(在进行任何进一步的转换之前),但这对我不起作用,因为datetime.fromordinal期望一个整数:

>>> datetime.fromordinal(731965.04835648148) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: integer argument expected, got float

虽然我可以将它们舍入为每日数据,但实际上我需要导入每分钟时间序列。有人有这个问题的解决方案吗?我想避免重新格式化我的.mat文件,因为有很多文件,而且我的同事也需要使用它们。

如果有帮助的话,其他人询问了相反的方式。不幸的是,我太新手了,无法真正理解那里正在发生什么。

/编辑(2012年11月1日):已在上面发布的教程中修复此问题。

5个回答

25

您链接到的解决方案存在一个小问题。就是这个:

python_datetime = datetime.fromordinal(int(matlab_datenum)) + timedelta(days=matlab_datenum%1) - timedelta(days = 366)

更详细的解释可以在这里找到


1
在将 matlab_datenum 输入到 fromordinal 中之前,我会将其转换为 int - Blender
1
更简单的写法:python_datetime = datetime.fromordinal(int(matlab_datenum) - 366) + timedelta(days=matlab_datenum%1) :) - Marco Sulla
1
我必须将timedelta的输入也转换为int:timedelta(days=int(matlab_datenum%1))。否则我会得到:TypeError: unsupported type for timedelta days component: numpy.int32 - NMO
我不知道这是浮点精度问题还是实际上是不同的算法,但是这个答案比下面@jonas的pandas答案不够准确。请看我在那个答案下面的评论。 - Jim Hunziker

20

使用pandas,您可以将带有小数部分的整个datenum值数组进行转换:

import numpy as np
import pandas as pd
datenums = np.array([737125, 737124.8, 737124.6, 737124.4, 737124.2, 737124])
timestamps = pd.to_datetime(datenums-719529, unit='D')

数值719529是Unix纪元开始时间(1970-01-01)的日期数字表示形式,这也是pd.to_datetime()默认使用的origin

我使用以下Matlab代码进行设置:

datenum('1970-01-01')  % gives 719529
datenums = datenum('06-Mar-2018') - linspace(0,1,6)  % test data
datestr(datenums)  % human readable format

1
我不确定区别在哪里,但是这个答案给出的结果比被接受的答案更接近。对于输入 719529 + 1/24/12,应该是 UNIX 纪元后的 5 分钟,被接受的答案偏差 2 秒,而这个答案偏差 20 微秒。 - Jim Hunziker

13

以下是一个完整的示例,演示如何从Matlab mat文件中加载时间序列数据,使用carlosdc提供的答案(定义为函数)将Matlab datenum向量转换为datetime对象列表,并使用Pandas作为时间序列绘图:

from scipy.io import loadmat
import pandas as pd
import datetime as dt
import urllib

# In Matlab, I created this sample 20-day time series:
# t = datenum(2013,8,15,17,11,31) + [0:0.1:20];
# x = sin(t)
# y = cos(t)
# plot(t,x)
# datetick
# save sine.mat

urllib.urlretrieve('http://geoport.whoi.edu/data/sine.mat','sine.mat');

# If you don't use squeeze_me = True, then Pandas doesn't like 
# the arrays in the dictionary, because they look like an arrays
# of 1-element arrays.  squeeze_me=True fixes that.

mat_dict = loadmat('sine.mat',squeeze_me=True)

# make a new dictionary with just dependent variables we want
# (we handle the time variable separately, below)
my_dict = { k: mat_dict[k] for k in ['x','y']}

def matlab2datetime(matlab_datenum):
    day = dt.datetime.fromordinal(int(matlab_datenum))
    dayfrac = dt.timedelta(days=matlab_datenum%1) - dt.timedelta(days = 366)
    return day + dayfrac

# convert Matlab variable "t" into list of python datetime objects
my_dict['date_time'] = [matlab2datetime(tval) for tval in mat_dict['t']]

# print df
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 201 entries, 2013-08-15 17:11:30.999997 to 2013-09-04 17:11:30.999997
Data columns (total 2 columns):
x    201  non-null values
y    201  non-null values
dtypes: float64(2)

# plot with Pandas
df = pd.DataFrame(my_dict)
df = df.set_index('date_time')
df.plot()

enter image description here


5
这里有一种使用numpy.datetime64而不是datetime进行转换的方法。
origin = np.datetime64('0000-01-01', 'D') - np.timedelta64(1, 'D')
date = serdate * np.timedelta64(1, 'D') + origin

这适用于 serdate,可以是单个整数或整数数组。


谢谢!一直在寻找这个。为了获得更高的分辨率,timedelta64乘数可以更改为另一个单位,并乘以该单位在一天内的数量。以下是我能够在不溢出的情况下获得的最高分辨率示例(使用与您相同的“origin”):delta = np.timedelta64(1,'us') * 86400e6 t = datenum_array * delta + origin 可选: t = t.astype(dtype = 'datetime64[us]')其中datenum_array是从.mat文件导入的浮点数组。 - Simen91

2
仅在之前的评论上进行补充和添加。关键在于由类datetime及其相关子类中的方法toordinal和构造函数fromordinal执行的日期计数。例如,从Python Library Reference for 2.7,可以看到fromordinal的说明如下:

返回与普通公历序数对应的日期,其中公元1年1月1日的序数为1。除非1 <= ordinal <= date.max.toordinal(),否则会引发ValueError异常。

但是,公元0年仍然是要计算的一年(闰年),因此仍需要考虑366天。(像2016年一样,正好是504个四年周期之前的闰年。)

以下是我用于类似目的的两个函数:

import datetime 

def datetime_pytom(d,t):
'''
Input
    d   Date as an instance of type datetime.date
    t   Time as an instance of type datetime.time
Output
    The fractional day count since 0-Jan-0000 (proleptic ISO calendar)
    This is the 'datenum' datatype in matlab
Notes on day counting
    matlab: day one is 1 Jan 0000 
    python: day one is 1 Jan 0001
    hence an increase of 366 days, for year 0 AD was a leap year
'''
dd = d.toordinal() + 366
tt = datetime.timedelta(hours=t.hour,minutes=t.minute,
                       seconds=t.second)
tt = datetime.timedelta.total_seconds(tt) / 86400
return dd + tt

def datetime_mtopy(datenum):
'''
Input
    The fractional day count according to datenum datatype in matlab
Output
    The date and time as a instance of type datetime in python
Notes on day counting
    matlab: day one is 1 Jan 0000 
    python: day one is 1 Jan 0001
    hence a reduction of 366 days, for year 0 AD was a leap year
'''
ii = datetime.datetime.fromordinal(int(datenum) - 366)
ff = datetime.timedelta(days=datenum%1)
return ii + ff 

希望这能帮到你,也乐意接受纠正。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接