Matlab科学数据处理:如何补上缺失的时间序列

Matlab科学数据处理:如何补上缺失的时间序列

Zhuotong Nan (南卓铜, [email protected])

问题提出

假设我们有一个时间序列文件,内容如下

286880 19480223 -5.7

286880 19480227 10.8

286880 19480229 7.7

286880 19480301 -15.7

286880 19480302 3.2

286880 19480303 7.7

286880 19480305 21.2

第一列是站点名,第二列是时间信息,第三列是温度值。时间并不连接,我们经常需要把时间补全,这些时间上的观测以缺失值(比如999.9)替代。即,我们希望是,

286880 19480223 -5.7

286880 19480224 999.9

286880 19480225 999.9

286880 19480226 999.9

286880 19480227 10.8

286880 19480228 999.9

286880 19480229 7.7

286880 19480301 -15.7

286880 19480302 3.2

286880 19480303 7.7

286880 19480304 999.9

286880 19480305 21.2

那么在matlab里要如何实现呢。

基本思路

取得此数据的起始时期和终止日期,我们额外构造一个完整的日期序列,与此数据文件中的进行对比,取得缺失的时间序列,将这些序列补充进来。

实现

%main.m

missing_value = 999.9; %指定缺失代替数据

data = load(‘data.txt’); %把数据装载进来

timeser = data(:,2); %取得第二列时间数据

minDate = min(timeser ); %取得起始时间

maxDate = max(timeser); %取得结束时间

entiretime = getEntireTime(minDate,maxDate); %取得完整的时间序列,这是我们写的函数

tm_missed = setdiff(entiretime,timeser); %与数据中的时间序列进行对比,得到缺的序列

line_missed = data(1,:); %以第一行为原型

line_missed(3) = missing_value; %第三个数据是丢失数据

rows_missed = repmat(line_missed, length(tm_missed), 1); %补充进去的行,对应于每个丢失的时间

rows_missed(:,2)=tm_missed; %把第二列换成丢失时间序列

data = [data; rows_missed]; %把缺失时间的各行补充进来

data = sortrows(data,2); %根据时间列对整个数据进行升序排序

%输到文件保存

fid=fopen(‘data_out.txt’,’w’);

fprintf(fid, ‘%dt%dt%6.1frn’,data’);

fclose(fid);

%getEntireTime.m

function yearmonth = getEntireTime(minp, maxp)

mind = datenum(num2str(minp),’yyyymmdd’); %起始时间,转换为数值形式
maxd = datenum(num2str(maxp),’yyyymmdd’); %结束时间,转换为数值形式
yearmonth = [];
cd=mind;
%通过循环,构造每个时间
while(cd<maxd)
yearmonth=[yearmonth;cd];
cd = addtodate(cd,1,’day’); %增加一天
end
yearmonth=[yearmonth;maxd];
yearmonth = datevec(yearmonth); %将数值形式的时间转为矢量形式

%将矢量形式的时间转为 yyyymmdd 的数值形式
yearmonth = yearmonth(:,1)*10000 + yearmonth(:,2)*100 + yearmonth(:,3);

end

讨论

构造时间序列时,通过addtodate可以方便的修改时间。如果自已处理,需要考虑各月天数的不同以及闰年等情形,比较复杂。

时间序列变量yearmonth每次循环都增加一个要素,性能开支大,但考虑到时间序列并不会很长,这点性能开支可以忽略。如果要改进这点,通过判断起点终止之间的时间差,比如使用etime函数得到时间差,再转换为需要的时间步长数,预先对yearmonth变量进行容量分配。

注意构造完整时间循环时,并不使用cd <= maxd,而是使用cd < maxd,然后在结束后将maxd(结束时间)补充进来。这是考虑到一些特殊情况,比如我们现在需要构造的是逐月序列(addtodate的参数选month),mind是比如是 1987年10月12日,maxd是同年12月9日。这时,如果cd<=maxd,则构造了10月12日,11月12日。当发现12月12日比maxd大,即退出循环,因此12月份不在构造序列里。这是不对的。使用我们的方法,则可以回避这个问题。

但我们的方法带来别的问题,即如果mind是1987年10月2日,maxd是同年12月9日,即么cd<maxd为条件,会出现10月2日、11月2日、12月2日,在来年1月2日时因为超过maxd退出循环。然后我们在循环外又追加了maxd即12月9日。当我们需要逐月时,会出现两个12月。

结合到我们这个具体的例子里,重复的月份不会带来问题,这是因为setdiff会将重复的月份排除掉(因为已经存在于数据文件的时间列里)。

setdiff是另一个很有用的集合函数,setdiff(A, B) 会将A矢量中的但不属于B矢量的元素找出来。

很多人在编程实现类似的功能时,首先想到的是循环,这在使用其它语言时正确,但在Matlab里应当尽量避免,尽量使用集合函数对整个集合进行操作。这时因为Matlab对集合操作进行了极为有效的优化,当处理大数据量时,会发现性能提升十分大。

注意Matlab里的日期如果转换为数值形式,是起始于0000年0月0日0时0分0秒,因此如19871012这样的日期用datenum得到的数值形式并非19871012,我们在这里使用datevec进行转换为矢量,即[年 月 日 时 分 秒],用年乘以10000加月乘以100加日的形式得到yyyymmdd的数值形式。在处理日期时,Matlab的几个函数如datenum, datevec, datastr, etime等都十分有用,加以灵活使用可以解决全部的时间有关问题。

附件代码和数据文件

a31ee4c8ffa9084f75fd4c0a0f447c7e  matlabcase_09112014_01.zip

下载 (~1KiB)

Leave a Reply

Your email address will not be published. Required fields are marked *