3
votes

Here is my code:

for p = 1:length(id_unique)
    h=figure;
    hold on
    j = 1;
    for i = 1:1:length(id)
        if id_unique(p)==id(i)
            h=plot([startdate(i),enddate(i)],[j,j],'ok-');
            hold on
            j = j + 1;        
        end
    end
    grid on
    hold off
    savefig([plotname,'.fig'])
    print(plotname,'-djpeg','-r300')
    close
end

% id:           integer vector containing 50000 values
% id_unique:    sorted unique values from vector 'id'
% startdate:    datetime vector containing 50000 dates
% enddate:      datetime vector containing 50000 dates

Each elements/values in 'id' vector implies an event where the startdate and the enddate of the event is available in the corresponding position in 'startdate' and 'enddate' vectors. Thus an event id(1) has the start date in startdate(i) and end date in enddate(i).

The program takes a value from the 'id_unique' vector and for each matched value found in 'id', it draws a line in the plot, denoting the beginning and ending time of the event.

For example, suppose 55 is an id-value from vector 'id_unique' and we have this value 1000 times in id. So for 55 a plot is created depicting 1000 separate lines with a marker 'o' at the beginning of the event, a marker 'o' at the end of the event and a line connecting both markers.

enter image description here

Please look at the attached plot that is generated from this code block. If id_unique has 70 values, 70 such plots will be created from this code. In the image, many lines are too small because of small difference between start and end date, so the markers overlap each other and look like a single point.

Now the problem comes when for an id-value in 'id_unique', we have a lot of instances of it in the 'id' vector. When the program plots individual lines unto 100 it works quite fast, but after plotting 300 lines in the same figure the program gets slow. When the program plots 1000 lines in the same figure, it takes about 5-7 seconds for each line. So it takes many hours to generate a plot with many lines.

Is there a way to improve my code to make these plot generation faster.

3

3 Answers

5
votes

You don't need a loop:

you can use something like:

a = 1:0.1:4*pi;
b = sin(a); %vector that represent the start of each line
c = sin(a)+1; %vector that represent the end of each line
plot([b;c],[a;a],'-ko','markersize',2)

Result:

enter image description here

The function plot need 2 arguments: x and y, but, and this is why the world is beautiful, the function plot can manage several lines. If x and y are matrices, matlab interpret each column as a new line.

1
votes

Having thought about this more, I now would suggest doing the following - rather than creating multiple lines in a plot, which generates a lot of objects in the background, it is better to produce one line per plot, that is broken up into multiple segments. If startdate and enddate are vectors of 1xlength(id), then the following code will be much faster as it concatenates all of the lines together, with NaN values in between. This makes the plot break the line at that point. All very quick, without the need to mess about with xlim and ylim as I previously suggested...

Here's my code:

for p = 1:length(id_unique)
    h=figure;
    block=[startdate(id==id_unique(p));enddate(id==id_unique(p))];
    x_vals=reshape([block;nan(1,size(block,2))],1,[]);
    y_vals=reshape([(1:size(block,2));(1:size(block,2));nan(1,size(block,2))],1,[]);
    plot(x_vals,y_vals,'ok-');
    grid on
    savefig([plotname,'.fig'])
    print(plotname,'-djpeg','-r300')
    close
end

I hope this works better for you. (Of course, if your startdate and enddate vectors are length(id) by 1, then you should transpose them using .' in the 3rd line above).

I'm not sure what the purpose of the separate plots is, but it would be possible to plot them all on the same graph if that were useful. The code would then be:

h=figure;
hold on;
for p = 1:length(id_unique)
    block=[startdate(id==id_unique(p));enddate(id==id_unique(p))];
    x_vals=reshape([block;nan(1,size(block,2))],1,[]);
    y_vals=reshape([(1:size(block,2));(1:size(block,2));nan(1,size(block,2))],1,[]);
    plot(x_vals,y_vals,'Marker','o');
    grid on
end
savefig([plotname,'.fig'])
print(plotname,'-djpeg','-r300')
close

This lets Matlab set the line type and colour using its standard series. It also allows you to use the legend function to add labels. Of course, if the position within id were important, rather than using a sequence for y_vals, you could use the position information obtained from find, changing the 4th and 6th lines to be:

[~,index]=find(id==id_unique(p));
block=[startdate(index);enddate(index)];

y_vals=reshape(index;index;nan(1,size(block,2))],1,[]);

Then you see all of id on one plot, with the different values of id distinguished by colour and linetype. You can then use the legend function to generate a legend:

legend(num2str(id_unique.'));

(This assumes that id_unique is a row vector. If it is a column vector, remove the .').

1
votes

Here is way to also plot 2 different markers:

% some random data:
N = 50;
id = randi(5,N,1);
startdate = sort(randi(100,N,1));
enddate = startdate+randi(10,N,1);

% plotting:
ax = plot([startdate(:).'; enddate(:).'],[1:N; 1:N],'-k',...
    startdate,1:N,'k<',enddate,1:N,'k>')

The (:).' after the vectors is not really needed, it's just to make sure they will be given in the right direction to plot - 2 rows, startdate above enddate.

which gives (randomly):

2_mark


If you want to divide the data to groups by id, and color them this way, you can do the following:

N = 30;
id = randi(5,N,1);
startdate = datetime(sort(736000+randi(100,N,1)),'ConvertFrom','datenum');
enddate = startdate+randi(20,N,1);

% plotting:
ax = plot([startdate(:).'; enddate(:).'],[1:N; 1:N],'-',...
    startdate,1:N,'k<',enddate,1:N,'k>');

% coloring:
cmap = colormap('jet');
col = cmap(floor(linspace(1,64,numel(unique(id)))),:);
for k = 1:N
    ax(k).Color = col(id(k),:);
    ax(k).LineWidth = 2;
end

% set the legend:
c = 1;
leg_ent = zeros(numel(unique(id)),1);
for k = unique(id).'
    leg_ent(c) = find(id==k,1,'first');
    c = c+1;
end
legend(ax(leg_ent),num2str(unique(id)),'Location','SouthEast')

and you will get:

2mark_by_id