Text file and Matlab - matlab

I have a problem doing som changes to a *.txt file.
The data have this format:
11,2003,1,1,9,38,40.38,1
11,2003,1,1,9,47,2.5,1
11,2003,1,1,10,34,43.88,1
11,2003,1,1,10,38,14.5,1
11,2003,1,1,12,47,13.2,1
Where the columns are station number,year, month, day, hour, minute, seconds and precipitation(1 = 0.1 mm)
The times that have precipitation = 0 are not included in the list. This results in hours without rainfall will be absent. For these cases I want to make one entry for the first minute of the hour without rainfall in the New file, to show that there has been made measurements. Like this:
50810,200301010938,0.1
50810,200301010947,0.1
50810,200301011034,0.1
50810,200301011038,0.1
50810,200301011100,0.0 <---- This is what I need to get in the New file
50810,200301011247,0.1
(New station number, date/time, precipitation)
For now I've come up With this:
clear all
data = load('jan-31des_2003.txt'); %opens file with data
fid=fopen('50810_2003','w'); %opens empty file to write
[nrow, ncol] = size(data); %size of data
fprintf(fid,'%5s %12s %5s \r\n','Snr','Dato - Tid','RR_01') %Header
for row = 1:nrow
y = data(row,2); %year
m = data(row,3); % month
d = data(row,4); % date
h = data(row,5); % hour
M = data(row,6); % minute
p = data(row,8); % precipitation
p = p*0.1
end
fclose(fid);

You can use an if command to check if the next hour you are looking at is more than a single hour ahead from the last one. If that's the case you can create a new entry at this point:
if data(row,5) > (data(row-1,5)+1)
y = data(row,2); %year
m = data(row,3); % month
d = data(row,4); % date
h = data(row,5)+1; % hour
M = 00; % minute
p = 0; % precipitation
end
after this part you will need to make check again if there is another 'skipped' hour and so on. You should also replace your for-loop, by a while-loop until you reach the last entry in your dataset.
Try to implement this idea and come back to us with your code in case it didn't work out.

Related

ind2sub from finding value in a 3D array

I have a dataset time_local that is size 144x91x8845 (lon x lat x hour). I want to find the indices at which a particular hour occurs.
data stores the data
time_local stores the hours that the data occurs at. Due to time zone differences, not all the hours in each 144x91 face is the same
% One year
first = datenum([num2str(years(y)),'-01-01 00:00:00']);
last = datenum([num2str(years(y)),'-12-31 23:00:00']);
dt=1/24;
subset = first:dt:last;
% Find where hour one occurs (want all hours, but starting with 1 hour)
ind = find(time_local == subset(1)); % Hour 1
% Want to save out a new data matrix with the data from hour 1
[a,b,~] = size(time_local);
ind = find(time_local == subset(1)); % Day 1
[x1,y1,z1] = ind2sub(size(time_local),ind);
ind1 = [x1,y1,z1];
data1 = NaN(a,b,length(subset)); % Preallocate new array
data1(:,:,1) = data(ind1(1,:)); % Pull out data where
ind gives me the linear indices, but I want to know the subscript indices so I can save data1 out where each 144x91 face is one hour. Right now, the ind2sub does not seem to be finding the right indices because the time_local that comes out from the indices is not correct.
Edit: I tried the following, which doesn't quite work because the time_local1 and data saved out isn't indexed correctly, but it's close. There must be a more efficient way though.
time_local1 = NaN(a,b,length(subset));
data = NaN(a,b,length(subset));
for a1 = 1:length(subset)
if isempty(time_local == subset(a1)) == 0
ind = find(time_local == subset(a1)); % Hour 1
[x1,y1,z1] = ind2sub(size(time_local),ind);
for a2 = 1:length(x1)
time_local1(x1(a2),y1(a2),a1) = time_local(x1(a2),y1(a2),z1(a2));
data(x1(a2),y1(a2),a1) = data1(x1(a2),y1(a2),z1(a2));
end
end
end
This probably isn't the most efficient one - using loops - but it works
[a,b,~] = size(data1); % Size of data file
first = datenum([num2str(years(y)),'-01-01 00:00:00']); % Hour one
last = datenum([num2str(years(y)),'-12-31 23:00:00']);
dt=1/24;
subset = first:dt:last;
%% Find where hour 1 occurs along the 3rd dimension for every gridcell (matching vector and vector instead of vector and matrix)
% Pre-allocate
time_local1 = NaN(a,b,length(subset));
data = NaN(a,b,length(subset));
count = 1:24:length(data1); % Set increases for beginning of each day
for a1 = 1:a % lon
disp(a1)
for b1 = 1:b % lat
ind(a1,b1) = find(time_local(a1,b1,:) == first); % Location of hour one in the 3rd dimension
ind1 = ind(a1,b1):24:length(data1); % Hours in each day starting from hour one
for c1 = 1:length(ind1)-2
%%% Ran once - Checking if the dates I am averaging across
%%% actually have no missing days
% if squeeze(time_local(a1,b1,ind1(c1):ind1(c1)+23)) ~= subset(count(c1):count(c1)+23)'
% disp([a1,b1])
% end
time_local1(a1,b1,count(c1):count(c1)+23) = time_local(a1,b1,ind1(c1):ind1(c1)+23); % Hourly data
data(a1,b1,c1) = nanmean(data1(a1,b1,ind1(c1):ind1(c1)+23)); % Average over one day
time(a1,b1,c1) = time_local1(a1,b1,count(c1)); % One entry per day
end
end
end

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

Create a text file using Matlab

I'm trying to make some changes in a list of precipitation data.
The data are in a *.txt file in this format:
50810,200301010600,0.0
50810,200301010601,0.0
50810,200301010800,0.0
50810,200301010938,0.1
50810,200301010947,0.1
50810,200301010957,0.1
Each file I have contains precipitation data per minute from 1 year.
The first number is just the station number, and this is equal for each line in the file.
In my original file, I have one line with data for every minute.
I want to make a new file that contains:
Only lines with precipitation that is not zero, so I want to remove
all the lines where the last number is zero. I've figured this out.
If there is one whole hour with no precipitation, I want to remove all
of the zero-lines and create a new line which says:
50810,200301010600,0.0. If there was no precipitation between 6 and 7
am. at the 01.01.
clear all
data = load('jan-31des_2003.txt'); %opens file with data
fid=fopen('50810_2003','w'); %opens empty file to write
[nrow, ncol] = size(data); %size of data
fprintf(fid,'%5s %12s %5s \r\n','Snr','Dato - Tid','RR_01') %Header
for row = 1:nrow
y = data(row,2); %year
m = data(row,3); % month
d = data(row,4); % date
h = data(row,5); % hour
M = data(row,6); % minute
p = data(row,8); % precipitation
if p > 0
fprintf(fid,'50810,%04d%02d%02d%02d%02d,%.1f \r\n',[y,m,d,h,M,p]);
end
if p==0
HERE I NEED SOME HELP
end
end
fclose(fid);
What is the code for my desired formatting within the if p==0 condition?
Assuming you've already imported your data as an M*3 array named infile and M is divisible by 60 and your data begins at the first minute of an hour, and you have an array outfile that will be written to a new file:
outfile = []
while ~isempty(infile)
block = infile(1:60,:); % Take one hour's worth of data
if sum (block(:,3)==0) == 60 % Check if the last column are all zero
outfile = cat(1, outfile, [block(1,1:2), 0]); % If so, put one line to new array
else
filter = block(:,3)~=0; % Otherwise, pick out only rows that the last column is not zeros
outfile = cat(1, outfile, block(filter,:)); % Put these rows into the new array
end
infile(1:60,:) = []; % Remove already processed data from original array
end
Then write the entire outfile array to file.

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;