extend matrix of time elements in matlab - matlab

I've got 1xn matrix called time that I imported from a csv file. Is there any way to extend this matrix by following the time pattern (so that the days per month work)? For example, if I start with.
time =
'"2013-05-01"'
'"2013-05-02"'
'"2013-05-03"'
'"2013-05-04"'
'"2013-05-05"'
And somehow add 5 observations, my matrix becomes:
time =
'"2013-05-01"'
'"2013-05-02"'
'"2013-05-03"'
'"2013-05-04"'
'"2013-05-05"'
'"2013-05-06"'
'"2013-05-07"'
'"2013-05-08"'
'"2013-05-09"'
'"2013-05-10"'

If time is a char matrix:
N = 5; %// how many dates to add
lastdate = datenum(strrep(time(end,:),'"',''),29); %// last available date
time = [time; [repmat('"',N,1) datestr(lastdate+(1:N),29) repmat('"',N,1)] ];
If time is a cell array, just replace last line by
time = [time; mat2cell([repmat('"',N,1) datestr(lastdate+(1:N),29) repmat('"',N,1)],ones(1,N)) ];
This works by reading the last string date, converting to numerical date with datenum, generating N new consecutive dates, and then converting back to string with datestr. The double quotes are dealt with separately.
Example:
>>time = ['"2013-05-04"'; '"2013-05-05"']
time =
"2013-05-04"
"2013-05-05"
gives
>> N = 5; %// how many dates to add
lastdate = datenum(strrep(time(end,:),'"',''),29); %// last available date
time = [time; [repmat('"',N,1) datestr(lastdate+(1:N),29) repmat('"',N,1)] ]
time =
"2013-05-04"
"2013-05-05"
"2013-05-06"
"2013-05-07"
"2013-05-08"
"2013-05-09"
"2013-05-10"

I'm assuming time is a cell array here:
t = cell2mat(time);
n = 5;
t = datenum(t,'"yyyy-mm-dd"'); % using custom format
tdiff = t(end)-t(end-1); % assuming
l = length(t);
newtime = zeros(l+n,1);
newtime(1:l)=t;
newtime(l+1:end) = (t(end)+tdiff):tdiff:(t(end)+tdiff*n);
You can use datestr to convert back to the date format of your choice.

Related

Vectorization instead of nested for loops in matlab

I am having trouble vectorizing this for loop in matlab which is really slow.
tvec and data are N×6 and N×4 arrays respectively, and they are the inputs to the function.
% preallocate
sVec = size(tvec)
tvec_ab = zeros(sVec(1),6);
data_ab = zeros(sVec(1),4);
inc = 0;
for i = 1:12
for j = 1:31
inc = inc +1;
[I,~] = find(tvec(:,3)==i & tvec(:,2)== j,1,'first');
if(I > 0)
tvec_ab(inc,:) = tvec(I,:);
data_ab(inc,:) = sum(data( (tvec(:,3) == j) & (tvec(:,2)==i) ,:));
end
end
end
% set output values
tvec_a = tvec_ab(1:inc,:);
data_a = data_ab(1:inc,:);
Every row in tvec represents the timestamp where the data was taken in the same row in the data matrix. Below you can see how a row would look like:
tvec:
[year, month, day, hour, minute, second]
data:
[dataA, dataB, dataC, dataD]
In the main program we can choose to "aggregate" after month, day or hour.
The code above is an example of how the aggregation for the option 'DAY' could happen.
the first time stamp of the day is the time stamp we want our output tvec_a to have in the row for that day.
The data output for that day (row in this case) would then be the sum of all the data for that day. Example:
data:
[data1ADay1, data1BDay1, data1CDay1, data1DDay1;
data2ADay1, data2BDay1, data2CDay1, data2DDay1]
aggregated data:
[data1ADay1 + data2ADay1, data1BDay1 + data2BDay1, data1CDay1+ data2CDay1,
data1DDay1+data2DDay1]
A vectorized version (not fully tested)
[x y] = meshgrid(1:12,1:31);
XY=[x(:) Y(:)];
[I,loc]=ismember(XY,tvec(:,2:3),'rows');
tvec_ab(I)=tvec(loc(loc>0),:);
acm = accumarray(tvec(:,2:3),data);
data_ab(I) = acm(sub2ind(size(acm),tvec(:,2),tvec(:,3)));
I actually found a way to do it myself:
%J is the indexes of the first unique days ( eg. if there is multiple
%data from january 1., the first time stamp from january 1. will be
%the time samp for our output)
[~,J,K] = unique(tvec(:,2:3),'rows');
%preallocate
tvec_ab = zeros(length(J),6);
data_ab = zeros(length(J),4);
tvec_ab = tvec(J,:);
%sum all data from the same days together column wise.
for i = 1:4
data_ab(:,i) = accumarray(K,data(:,i));
end
%set output
data_a = data_ab;
tvec_a = tvec_ab;
Thanks for your responses though

Changing seconds in the date vector

I would like to keep the seconds uniform in my time vector using Matlab.
for example in my data the time vector is
01-01-2007 14:18:30; 01-01-2007 14:19:30;01-01-2007 14:20:38;01-01-2007 14:21:28
and so on..
I want it to keep it uniform to
01-01-2007 14:18:30; 01-01-2007 14:19:30;01-01-2007 14:20:30;01-01-2007 14:21:30.
Any help would be greatly appreciated
Thanks.
S.S
So it depends on how your dates are stored
As a string array:
s = ['01-01-2007 14:18:30';
'01-01-2007 14:19:30';
'01-01-2007 14:20:38';
'01-01-2007 14:21:28']
s(:,end-1:end)= repmat('30', size(s,1),1)
or if for some reason you don't want to use repmat (this might be faster), you can take advantage of the fact that Matlab automatically broadcasts scalars:
s(:,end-1)='3';
s(:,end)= '0';
or as a cell array of strings:
c = {'01-01-2007 14:18:30';
'01-01-2007 14:19:30';
'01-01-2007 14:20:38';
'01-01-2007 14:21:28'}
cellfun(#(x)([x(1:end-2),'30']), c, 'uni', false)
or if you have your dates as numbers then
s = ['01-01-2007 14:18:30';
'01-01-2007 14:19:30';
'01-01-2007 14:20:38';
'01-01-2007 14:21:28']
n = datenum(s, 'dd-mm-yyyy HH:MM:SS')
%// Note that n will store numbers in units of days, so to round off to the nearest 30 seconds we must round to multiples of 1/(# of 30 seconds per day)
k = 2*60*24; %//The number of 30 seconds per day
round(n*k)/k
or with any of them, you can covert to a date vector:
v = datevec(s, 'dd-mm-yyyy HH:MM:SS')
or
v = datevec(n)
where s or n are defined as above. Now you can simply go
v(:,end) = 30;
and then use datestr(v) or datenum(v) to go back.
In the data strings the last the seconds are always the last two places in the string so you can just replace them by using:
date_string='01-01-2007 14:18:28';
uni_seconds = '30';
date_string(end-1:end)=uni_seconds
The last line states that the last two places (end-1 and end) are to be replaced by the variable uni_seconds which is the string containing the seconds you want to set.

find the range for specific data from a time series

I have a time series:
d = [transpose(floor(1+1/24:1/24:366)),1+(30-1).*rand(8760,1)];
Where the first column refers to day of year and the second column refers to the data. The data are measured in hours but are floored here to represent the day of measurement.
I want to split the data into different cells, where each cell shows the data for the different days where the start time of the data varies. For example, if I select one specific day from the example:
dat = d(d(:,1)==2,:);
And I want to split this into different cells according to the following statement:
Res = 1:11;
starti = arrayfun(#(x)dat(x:end,:),Res,'un',0);
which gives me the same series but with different starting points. I then find the range of values by:
rng = cellfun(#(x)range(x(:,2)),starti,'un',0);
How would I perform the same method but for the entire series i.e.
dat = d;
thanks for your help
Why not just use a for loop?
R = d(1,1):d(end,1);
rng = cell(numel(R),1);
for ii = R
dat = d(d(:,1)==ii,:)
Res = 1:11;
starti = arrayfun(#(x)dat(x:end,:),Res,'un',0)
if ~any(cellfun('isempty', starti))
rng{ii} = cellfun(#(x)range(x(:,2)),starti); end
end

MATLAB: Dividing a year-length varying-resolution time vector into months

I have a time series in the following format:
time data value
733408.33 x1
733409.21 x2
733409.56 x3
etc..
The data runs from approximately 01-Jan-2008 to 31-Dec-2010.
I want to separate the data into columns of monthly length.
For example the first column (January 2008) will comprise of the corresponding data values:
(first 01-Jan-2008 data value):(data value immediately preceding the first 01-Feb-2008 value)
Then the second column (February 2008):
(first 01-Feb-2008 data value):(data value immediately preceding the first 01-Mar-2008 value)
et cetera...
Some ideas I've been thinking of but don't know how to put together:
Convert all serial time numbers (e.g. 733408.33) to character strings with datestr
Use strmatch('01-January-2008',DatesInChars) to find the indices of the rows corresponding to 01-January-2008
Tricky part (?): TransformedData(:,i) = OriginalData(start:end) ? end = strmatch(1) - 1 and start = 1. Then change start at the end of the loop to strmatch(1) and then run step 2 again to find the next "starting index" and change end to the "new" strmatch(1)-1 ?
Having it speed optimized would be nice; I am going to apply it on data sampled ~2 million times.
Thanks!
I would use histc with a list a list of last days of the month as the second parameter (Note: use histc with the two return functions).
The edge list can easily be created with datenum or datevec.
This way you don't have operation on string and you that should be fast.
EDIT:
Example with result in a simple data structure (including some code from #Rody):
% Generate some test times/data
tstart = datenum('01-Jan-2008');
tend = datenum('31-Dec-2010');
tspan = tstart : tend;
tspan = tspan(:) + randn(size(tspan(:))); % add some noise so it's non-uniform
data = randn(size(tspan));
% Generate list of edge
edge = [];
for y = 2008:2010
for m = 1:12
edge = [edge datenum(y, m, 1)];
end
end
% Histogram
[number, bin] = histc(tspan, edge);
% Setup of result
result = {};
for n = 1:length(edge)
result{n} = [tspan(bin == n), data(bin == n)];
end
% Test
% 04-Aug-2008 17:25:20
datestr(result{8}(4,1))
tspan(data == result{8}(4,2))
datestr(tspan(data == result{8}(4,2)))
Assuming you have sorted, non-equally-spaced date numbers, the way to go here is to put the relevant data in a cell array, so that each entry corresponds to the next month, and can hold a different amount of elements.
Here's how to do that quite efficiently:
% generate some test times/data
tstart = datenum('01-Jan-2008');
tend = datenum('31-Dec-2010');
tspan = tstart : tend;
tspan = tspan(:) + randn(size(tspan(:))); % add some noise so it's non-uniform
data = randn(size(tspan));
% find month numbers
[~,M] = datevec(tspan);
% find indices where the month changes
inds = find(diff([0; M]));
% extract data in columns
sz = numel(inds)-1;
cols = cell(sz,1);
for ii = 1:sz-1
cols{ii} = data( inds(ii) : inds(ii+1)-1 );
end
Note that it can be difficult to determine which entry in cols belongs to which month, year, so here's how to do it in a more human-readable way:
% change this line:
[y,M] = datevec(tspan);
% and change these lines:
cols = cell(sz,3);
for ii = 1:sz-1
cols{ii,1} = data( inds(ii) : inds(ii+1)-1 );
% also store the year and month
cols{ii,2} = y(inds(ii));
cols{ii,3} = M(inds(ii));
end
I'll assume you have a timeVals an Nx1 double vector holding the time value of each datum. Assuming data is also an Nx1 array. I also assume data and timeVals are sorted according to time: that is, the samples you have are ordered according to the time they were taken.
How about:
subs = #(x,i) x(:,i);
months = subs( datevec(timeVals), 2 ); % extract the month of year as a number from the time
r = find( months ~= [months(2:end), months(end)+1] );
monthOfCell = months( r );
r( 2:end ) = r( 2:end ) - r( 1:end-1 );
dataByMonth = mat2cell( data', r ); % might need to transpose data or r here...
timeByMonth = mat2cell( timeVal', r );
After running this code, you have a cell array dataByMonth each cell contains all data relevant to a specific month. The corresponding cell of timeByMonth holds the sampling times of the data of the respective month. Finally, monthOfCell tells you what is the month's number (1-12) of each cell.

For command + interpolation: need some tips

I have a matrix A with three columns: daily dates, prices, and hours - all same size vector - there are multiple prices associated to hours in a day.
sample data below:
A_dates = A_hours= A_prices=
[20080902 [9.698 [24.09
20080902 9.891 24.59
200080902 10.251 24.60
20080903 9.584 25.63
200080903 10.45 24.96
200080903 12.12 24.78
200080904 12.95 26.98
20080904 13.569 26.78
20080904] 14.589] 25.41]
Keep in my mind that I have about two years of daily data with about 10 000 prices per day that covers almost every minutes in a day from 9:30am to 16:00pm. Actually my initial dataset time was in milliseconds. I then converted my milliseconds in hours. I have some hours like 14.589 repeated three times with 3 different prices. Hence I did the following:
time=[A_dates,A_hours,A_prices];
[timeinhr,price]=consolidator(time,A_prices,'mean'); where timeinhr is both vector A_dates and A_hours
to take an average price at each say 14.589hours.
then for any missing hours with .25 .50 .75 and integer hours - I wish to interpolate.
For each date, hours repeat and I need to interpolate linearly prices that I don't have for some "wanted" hours. But of course I can't use the command interp1 if my hours repeats in my column because I have multiple days. So say:
%# here I want hours in 0.25unit increments (like 9.5hrs)
new_timeinhr = 0:0.25:max(A_hours));
day_hour = rem(new_timeinhour, 24);
%# Here I want only prices between 9.5hours and 16hours
new_timeinhr( day_hour <= 9.2 | day_hour >= 16.1 ) = [];
I then create a unique vectors of day and want to use a for and if command to interpolate daily and then stack my new prices in a vector one after the other:
days = unique(A_dates);
for j = 1:length(days);
if A_dates == days(j)
int_prices(j) = interp1(A_hours, A_prices, new_timeinhr);
end;
end;
My error is:
In an assignment A(I) = B, the number of elements in B and I must be the same.
How can I write the int_prices(j) to the stack?
I recommend converting your input to a single monotonic time value. Use the MATLAB datenum format, which represents one day as 1. There are plenty of advantages to this: You get the builtin MATLAB time/date functions, you get plot labels formatted nicely as date/time via datetick, and interpolation just works. Without test data, I can't test this code, but here's the general idea.
Based on your new information that dates are stored as 20080902 (I assume yyyymmdd), I've updated the initial conversion code. Also, since the layout of A is causing confusion, I'm going to refer to the columns of A as the vectors A_prices, A_hours, and A_dates.
% This datenum vector matches A. I'm assuming they're already sorted by date and time
At = datenum(num2str(A_dates), 'yyyymmdd') + datenum(0, 0, 0, A_hours, 0, 0);
incr = datenum(0, 0, 0, 0.25, 0, 0); % 0.25 hour
t = (At(1):incr:At(end)).'; % Full timespan of dataset, in 0.25 hour increments
frac_hours = 24*(t - floor(t)); % Fractional hours into the day
t_business_day = t((frac_hours > 9.4) & (frac_hours < 16.1)); % Time vector only where you want it
P = interp1(At, A_prices, t_business_day);
I repeat, since there's no test data, I can't test the code. I highly recommend testing the date conversion code by using datestr to convert back from the datenum to readable dates.
Converting days/hours to serial date numbers, as suggested by #Peter, is definitely the way to go. Based on his code (which I already upvoted), I present below a simple example.
First I start by creating some fake data resembling what you described (with some missing parts as well):
%# three days in increments of 1 hour
dt = datenum(num2str((0:23)','2012-06-01 %02d:00'), 'yyyy-mm-dd HH:MM'); %#'
dt = [dt; dt+1; dt+2];
%# price data corresponding to each hour
p = cumsum(rand(size(dt))-0.5);
%# show plot
plot(dt, p, '.-'), datetick('x')
grid on, xlabel('Date/Time'), ylabel('Prices')
%# lets remove some rows as missing
idx = ( rand(size(dt)) < 0.1 );
hold on, plot(dt(idx), p(idx), 'ro'), hold off
legend({'prices','missing'})
dt(idx) = [];
p(idx) = [];
%# matrix same as yours: days,prices,hours
ymd = str2double( cellstr(datestr(dt,'yyyymmdd')) );
hr = str2double( cellstr(datestr(dt,'HH')) );
A = [ymd p hr];
%# let clear all variables except the data matrix A
clearvars -except A
Next we interpolate the price data across the entire range in 15 minutes increments:
%# convert days/hours to serial date number
dt = datenum(num2str(A(:,[1 3]),'%d %d'), 'yyyymmdd HH');
%# create a vector of 15 min increments
t_15min = (0:0.25:(24-0.25))'; %#'
tt = datenum(0,0,0, t_15min,0,0);
%# offset serial date across all days
ymd = datenum(num2str(unique(A(:,1))), 'yyyymmdd');
tt = bsxfun(#plus, ymd', tt); %#'
tt = tt(:);
%# interpolate data at new datetimes
pp = interp1(dt, A(:,2), tt);
%# extract desired period of time from each day
idx = (9.5 <= t_15min & t_15min <= 16);
idx2 = bsxfun(#plus, find(idx), (0:numel(ymd)-1)*numel(t_15min));
P = pp(idx2(:));
%# plot interpolated data, and show extracted periods
figure, plot(tt, pp, '.-'), datetick('x'), hold on
plot([tt(idx2);nan(1,numel(ymd))], [pp(idx2);nan(1,numel(ymd))], 'r.-')
hold off, grid on, xlabel('Date/Time'), ylabel('Prices')
legend({'interpolated prices','period of 9:30 - 16:00'})
and here are the two plots showing the original and interpolated data:
I think I might have solved it this way:
new_timeinhr = 0:0.25:max(A(:,2));
day_hour = rem(new_timeinhr, 24);
new_timeinhr( day_hour <= 9.4 | day_hour >= 16.1 ) = [];
days=unique(data(:,1));
P=[];
for j=1:length(days);
condition=A(:,1)==days(j);
intprices = interp1(A(condition,2), A(condition,3), new_timeinhr);
P=vertcat(P,intprices');
end;