2012-12-20 52 views
12

我刚刚开始从Matlab转到Python 2.7,并且在阅读我的.mat文件时遇到了一些麻烦。时间信息以Matlab的datenum格式存储。对于那些不熟悉它的人:将Matlab的datenum格式转换为Python

序列日期数字表示一个日历日期表示一个日历日期为自固定基准日以来经过的天数。在MATLAB中,序列日期编号1是0000年1月1日。

MATLAB还使用连续时间来表示从午夜开始的几分之几;例如下午6点。等于连续0.75天。所以MATLAB中的字符串'31 -Oct-2003,6:00 PM'是日期号731885.75。

(从MATLAB资料为准)

我想将其转换为蟒蛇时间格式,我发现this tutorial。总之,笔者指出

如果你分析这个使用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文件,因为它们中有很多和我的同事需要与他们一起工作。

如果有帮助,someone else asked for the other way round。可悲的是,我对Python太陌生,不能真正理解那里正在发生的事情。

/编辑(2012-11-01):这已在上面发布的教程中修复。

回答

13

您链接到解决方案,它有一个小问题。它是这样的:

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

更详细的解释可以发现here

+1

我转换'matlab_datenum'到'int'馈入'fromordinal'之前。 – Blender

+1

@Blender:你说得对。固定。 – carlosdc

+0

或更简单:'python_datetime = datetime.fromordinal(int(matlab_datenum) - 366)+ timedelta(days = matlab_datenum%1)':) –

7

以防万一它是有用的人,这里是一个Matlab垫文件加载时间序列数据的完整的例子,转换的矢量Matlab的datenums为datetime对象的列表使用carlosdc的回答(定义为一个功能),然后绘制时间序列与熊猫:

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

+0

绝对有用,感谢您分享您的代码。 –

2

只是建立和添加到以前的评论。关键在于日间计数,方法toordinal和构造函数fromordinal在类别datetime和相关的子类中执行。例如,从Python Library Reference for 2.7,一个读取fromordinal

返回对应的公历proleptic序,其中1月1日1年的有顺序1的日期。除非1 < =序号< = 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 

希望这有助于和高兴能纠正。

1

下面是使用numpy.datetime64而不是datetime转换这些的方法。

origin = np.datetime64('0000-01-01', 'D') - np.timedelta64(1, 'D') 
date = serdate * np.timedelta64(1, 'D') + origin 

这适用于serdate单个整数或整数数组。

0

使用熊猫,一个可以转换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),它是的datenum值默认originpd.to_datetime()

我用下面的Matlab代码进行此设置:

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