Minimal-code to get axes limits of log-log plots, but using gscatter

I used to specify scatter plots using loglog(X,Y,'o') to plot little circles at the data points. Now, gscatter(X,Y,GroupVector,'br','o') allows me to designate different colours for different data points. In my case, I only had two groups, and with logical GroupVector above designating which points belongs to which group. The 'br' says to use blue and red for the two groups.
Unfortunately, gscatter doesn't have a log scale option. I can set(gca,'xscale','log','yscale','log'), but I get complaints that negative axis limits are ignored. Sure enough, that is because the axis limits do include negative numbers, even if the data does not. As a result of this, the log scale scatter plot does not *automatically* have the nice axis limits that loglog(X,Y,'o') has.
My nonideal solution has been to create both plots, then copy the axes limits from the loglog plot to the gscatter plot. It seems heavily redundant. It sure would be nice to have a log scale option for gscatter. In the meantime, what is the most minimal code alternative of replicating loglog axes limits for gscatter? My method *might* be "minimal code", but I mean without duplicating plots.

Answers (3)

You can remove the negative part of the axis before setting the log scale
set(gca,'XLim',[0 max(xlim)],'YLim',[0 max(ylim)]);
set(gca,'XScale','log','YScale','log')

3 Comments

Thank you, Fabio. Unfortunately, that doesn't replicate the axes limits of loglog (though it does avoid the warning about negative axis limits). I'm hoping to avoid the manual finagling of axes limits, which is dependent on the data set. The loglog function seems to do a good job of choosing good axis limits (and XTicks/YTicks as well, but that's another question).
Is this working for you
set(gca,'XLim',[10^floor(log10(max([min(xlim) 0]))) 10^ceil(log10(max(xlim)))],'YLim',[10^floor(log10(max([min(ylim) 0]))) 10^ceil(log10(max(ylim)))]);
set(gca,'XScale','log','YScale','log')

Sign in to comment.

Hi, Fabio,
Here is the set of data that seems to reveal differences between "loglog" layout and your two suggestions for "gscattter", totalling three scatter plots.
At the end of each of the three set of plotting instructions are 2 lines that make the "x" and "y" axes symmetrical. That hides the above discrepancies. I put them there because that's eventually what I want. But I'm still interested in how the "loglog" layout decisions can be emulated. I thought it might be easy, but it looks like it involves guessing about the process that it uses.
Thanks.
% Close figure windows and set up data
%-------------------------------------
close all;
x=[
0.3531 0.3531 12.9305 12.9305 0.4744 0.4744 0.3649 0.3649 0.3770 ...
0.3770 0.4318 0.4318 0.2568 0.2568 0.3288 0.3288 0.4668 0.4668 0.3228 ...
0.3228 0.2664 0.2664 1.4536 1.4536 0.2041 0.2934 0.3591 0.3591 0.8932 ...
0.8932 0.3095 0.3095 0.3800 0.3800 0.5411 0.5411 0.3390 0.3390 1.0329 ...
1.0329 0.4423 0.4423 0.5537 0.5537 0.5048 0.5048 0.5338 0.5338 0.8937 ...
0.8937 0.1257 0.1257 0.7695 0.7695 10.5885 10.5885 0.2753 0.2753 ...
0.7879 0.7879 0.2630 0.2630 0.5923 0.5923 0.6481 0.6481 0.3127 0.3127 ...
0.3159 0.3159 10.9593 10.9593 0.4369 0.4369 1.1650 1.1650 0.3901 ...
0.3901 0.2631 0.2631 0.1317 0.1317 13.5365 13.5365 0.3627 0.3627 ...
12.5942 12.5942 0.9374 0.9374 0.3617 0.3617 0.5765 0.5765 0.5280 ...
0.5280 0.4550 0.4550 0.4141 0.4141 0.4400 0.4400 0.5764 0.5764 0.3103 ...
0.3103 0.5172 0.2633 1.5205 1.1162 0.5018 0.5567 0.2402 0.2581 0.5132 ...
1.4910 0.4151 0.7040 12.2202 10.2918 0.3412 0.9795 0.3006 0.4063 ...
0.4293 0.3683 0.4776 10.6593 0.3107 12.9954 0.4495 0.4852 0.4539 ...
0.5873 9.7542 11.7193 0.5353 0.4711 0.3711 0.6086 0.8484 0.3802 0.2874 ...
0.2883 10.3625 0.1769 0.6156 0.2677 12.0048 0.3398 0.7914 13.2012 ...
0.4201 0.3387 ];
y=[
0.3893 0.3695 0.3893 0.3695 0.5019 0.4650 0.5019 0.4650 0.4889 0.5750 ...
0.4889 0.5750 0.2664 0.5354 0.2664 0.5354 0.4596 0.3731 0.4596 0.3731 ...
0.3385 0.3823 0.3385 0.3823 0.3239 0.3239 0.5162 0.7474 0.5162 0.7474 ...
0.4392 0.4705 0.4392 0.4705 0.6075 0.2430 0.6075 0.2430 0.5813 0.4279 ...
0.5813 0.4279 2.0488 0.4991 2.0488 0.4991 0.8421 0.6363 0.8421 0.6363 ...
0.8016 0.5145 0.8016 0.5145 0.4891 0.3124 0.4891 0.3124 0.4811 0.3389 ...
0.4811 0.3389 0.4555 0.3411 0.4555 0.3411 0.8957 0.5301 0.8957 0.5301 ...
0.3736 0.3827 0.3736 0.3827 0.3790 0.5523 0.3790 0.5523 0.6001 0.2787 ...
0.6001 0.2787 0.3654 0.4716 0.3654 0.4716 0.7382 0.3084 0.7382 0.3084 ...
0.6224 0.5768 0.6224 0.5768 0.5205 0.8075 0.5205 0.8075 0.3892 0.7064 ...
0.3892 0.7064 1.0712 0.4087 1.0712 0.4087 0.3074 0.6415 1.0017 0.7258 ...
0.3847 0.9258 0.3310 0.2532 0.9183 0.3233 0.2444 0.6016 0.7064 0.4829 ...
0.6532 0.5670 1.0520 0.8499 0.4388 0.7136 0.3934 0.4591 1.5558 0.9160 ...
0.4748 0.4851 0.4481 0.6678 0.6283 0.6660 0.7489 0.6644 0.3003 0.6246 ...
0.4611 0.7549 0.5237 0.3981 0.3973 0.5200 0.6287 0.3837 0.9782 0.3594 ...
0.8034 0.3673 1.5584 0.3934 ];
% "loglog" layout
%----------------
loglog(x,y,'o');
ax=gca;
% xlim([ min( [ ax.XLim(1) ax.YLim(1) ] ) max( [ ax.XLim(2) ax.YLim(2) ] ) ]);
% ylim(ax.XLim);
% Fabio's first suggestion
%-------------------------
figure
gscatter(x,y)
set(gca,'XLim',[0 max(xlim)],'YLim',[0 max(ylim)]); % <------------
set(gca,'XScale','log','YScale','log')
% xlim([ min( [ ax.XLim(1) ax.YLim(1) ] ) max( [ ax.XLim(2) ax.YLim(2) ] ) ]);
% ylim(ax.XLim);
% Fabio's second suggestion
%--------------------------
figure
gscatter(x,y)
set(gca,'XLim',[10^floor(log10(max([min(xlim) 0]))) 10^ceil(log10(max(xlim)))],'YLim',[10^floor(log10(max([min(ylim) 0]))) 10^ceil(log10(max(ylim)))]);
set(gca,'XScale','log','YScale','log')
% xlim([ min( [ ax.XLim(1) ax.YLim(1) ] ) max( [ ax.XLim(2) ax.YLim(2) ] ) ]);
% ylim(ax.XLim);
P.S. This was meant to be a comment in response to Fabio (under the originally posted question). I apologize for having posted it as an answer, as it certainly is not. I don't want to delete it because it contains Fabio's comments and insights, which I found to be very helpful in formulating my own solution.

4 Comments

@FM: thank you for the data. However, I really don't get your original question: why do you think that the first plot has nicer axis limits than the second one? I see that some points lies on the xmax, ymin and ymax boundaries. In the second picture points touch xmin and ymin boundary. This is due to the fact that the the lower limits are chosen starting from 0. In any case, isn't it an slight improvement with respect to the first plot?
I agree, it is subjective. In my view, in the "loglog" plot, a reader of a paper can more easily determine where the points lie relative to 0.1. In contrast, it's hard to know what the left limit correspond to in any of the other graphs. It doesn't seem that it aligns with a minor tick.
I realize that it could be pure coincidence that "loglog" did this. For example, in the y-axis, the lower limit doesn't align with a minor tick. But it's close enough to a minor tick that readers can mentally use it as a reference point.
I thought that "loglog" had a process that determines the best axes limits based on lots of wise heuristics. I wanted to leverage that to avoid manual finagling. But maybe it doesn't.
This code gives an extra space between the data and the boundaries
% Close figure windows and set up data
%-------------------------------------
close all;
x=[
0.3531 0.3531 12.9305 12.9305 0.4744 0.4744 0.3649 0.3649 0.3770 ...
0.3770 0.4318 0.4318 0.2568 0.2568 0.3288 0.3288 0.4668 0.4668 0.3228 ...
0.3228 0.2664 0.2664 1.4536 1.4536 0.2041 0.2934 0.3591 0.3591 0.8932 ...
0.8932 0.3095 0.3095 0.3800 0.3800 0.5411 0.5411 0.3390 0.3390 1.0329 ...
1.0329 0.4423 0.4423 0.5537 0.5537 0.5048 0.5048 0.5338 0.5338 0.8937 ...
0.8937 0.1257 0.1257 0.7695 0.7695 10.5885 10.5885 0.2753 0.2753 ...
0.7879 0.7879 0.2630 0.2630 0.5923 0.5923 0.6481 0.6481 0.3127 0.3127 ...
0.3159 0.3159 10.9593 10.9593 0.4369 0.4369 1.1650 1.1650 0.3901 ...
0.3901 0.2631 0.2631 0.1317 0.1317 13.5365 13.5365 0.3627 0.3627 ...
12.5942 12.5942 0.9374 0.9374 0.3617 0.3617 0.5765 0.5765 0.5280 ...
0.5280 0.4550 0.4550 0.4141 0.4141 0.4400 0.4400 0.5764 0.5764 0.3103 ...
0.3103 0.5172 0.2633 1.5205 1.1162 0.5018 0.5567 0.2402 0.2581 0.5132 ...
1.4910 0.4151 0.7040 12.2202 10.2918 0.3412 0.9795 0.3006 0.4063 ...
0.4293 0.3683 0.4776 10.6593 0.3107 12.9954 0.4495 0.4852 0.4539 ...
0.5873 9.7542 11.7193 0.5353 0.4711 0.3711 0.6086 0.8484 0.3802 0.2874 ...
0.2883 10.3625 0.1769 0.6156 0.2677 12.0048 0.3398 0.7914 13.2012 ...
0.4201 0.3387 ];
y=[
0.3893 0.3695 0.3893 0.3695 0.5019 0.4650 0.5019 0.4650 0.4889 0.5750 ...
0.4889 0.5750 0.2664 0.5354 0.2664 0.5354 0.4596 0.3731 0.4596 0.3731 ...
0.3385 0.3823 0.3385 0.3823 0.3239 0.3239 0.5162 0.7474 0.5162 0.7474 ...
0.4392 0.4705 0.4392 0.4705 0.6075 0.2430 0.6075 0.2430 0.5813 0.4279 ...
0.5813 0.4279 2.0488 0.4991 2.0488 0.4991 0.8421 0.6363 0.8421 0.6363 ...
0.8016 0.5145 0.8016 0.5145 0.4891 0.3124 0.4891 0.3124 0.4811 0.3389 ...
0.4811 0.3389 0.4555 0.3411 0.4555 0.3411 0.8957 0.5301 0.8957 0.5301 ...
0.3736 0.3827 0.3736 0.3827 0.3790 0.5523 0.3790 0.5523 0.6001 0.2787 ...
0.6001 0.2787 0.3654 0.4716 0.3654 0.4716 0.7382 0.3084 0.7382 0.3084 ...
0.6224 0.5768 0.6224 0.5768 0.5205 0.8075 0.5205 0.8075 0.3892 0.7064 ...
0.3892 0.7064 1.0712 0.4087 1.0712 0.4087 0.3074 0.6415 1.0017 0.7258 ...
0.3847 0.9258 0.3310 0.2532 0.9183 0.3233 0.2444 0.6016 0.7064 0.4829 ...
0.6532 0.5670 1.0520 0.8499 0.4388 0.7136 0.3934 0.4591 1.5558 0.9160 ...
0.4748 0.4851 0.4481 0.6678 0.6283 0.6660 0.7489 0.6644 0.3003 0.6246 ...
0.4611 0.7549 0.5237 0.3981 0.3973 0.5200 0.6287 0.3837 0.9782 0.3594 ...
0.8034 0.3673 1.5584 0.3934 ];
% "loglog" layout
%----------------
loglog(x,y,'o');
ax=gca;
% xlim([ min( [ ax.XLim(1) ax.YLim(1) ] ) max( [ ax.XLim(2) ax.YLim(2) ] ) ]);
% ylim(ax.XLim);
% Fabio's third suggestion
figure
gscatter(x,y)
set(gca,'XLim',[min(x)-10^(floor(log10(min(x)))-1) max(x)+10^(floor(log10(max(x)))-1)]);
set(gca,'YLim',[min(y)-10^(floor(log10(min(y)))-1) max(y)+10^(floor(log10(max(y)))-1)]);
set(gca,'XScale','log','YScale','log')
Thanks, Fabio. I used your logic as a starting point and cobbled together an alternative process for choosing axis limits, in the form of function "Dat2LogLim.m":
% Dat2LogLim.m
%--------------
% Calculates loose axis limits if input data is
% plotted in log10 scale
function Lim2 = Dat2LogLim( Dat )
DatMin = min(Dat);
FloorLog10min = floor(log10(DatMin));
if DatMin >= 2*10^FloorLog10min
Lim2(1) = DatMin - 10^FloorLog10min;
else
Lim2(1) = 0.9*10^FloorLog10min;
end %
DatMax = max(Dat);
FloorLog10max = floor(log10(DatMax));
Lim2(2) = DatMax + 10^FloorLog10max;
return
Of course, I don't need to repeatedly raise quantities to the 10th power, but I want the code to be clear. Computation here is not high volume, so this doesn't act as a bottleneck.
As an example, assume that the data minimum is between 10 and just under 100. If the minimum is between 20 and 100, I simply subtract 10 to get the lower axis limit. If it is between 10 and 20, choose the lower axis limit to be 9, since that is where I expect the next minor tick to be. Of course, it assumes a common pattern of axis ticks. If the lower limit is between 100 and just under 1000, All the figures above get increased 10x. Similar scaling for when the lower limit is between 1 and just under 10, etc.
The upper axis limit is easier to calculate. If the maximum is between 10 and 100, I just add 10. Even if the maximum is 95, that puts the upper axis limit at 105, which then encompasses 100, which is what I want.
Thanks for setting me on this path. Given the above data for variables "x" and "y", here is the code to utilize "Dat2LogLim" and the resulting graph.
gscatter(x,y)
set(gca,'XLim',Dat2LogLim(x));
set(gca,'YLim',Dat2LogLim(y));
set(gca,'XScale','log','YScale','log')
grid on

Sign in to comment.

Based on Fabio's approach, here is my final solution, using a utility function "Dat2LogLim.m":
% Dat2LogLim.m
%--------------
% Calculates loose axis limits if input data is
% plotted in log10 scale
function Lim2 = Dat2LogLim( Dat )
Dat = Dat( ~isnan(Dat) & isreal(Dat) & isfinite(Dat) & Dat>0 );
DatMin = min(Dat);
TickStep = 10^floor(log10(DatMin));
if DatMin >= 2*TickStep
Lim2(1) = floor(DatMin/TickStep)*TickStep - 0.1*TickStep;
else
Lim2(1) = 0.9*TickStep;
end % if DatMin
DatMax = max(Dat);
TickStep = 10^floor(log10(DatMax));
if DatMax <= 9*TickStep
Lim2(2) = ceil(DatMax/TickStep)*TickStep + 0.2*TickStep;
else
Lim2(2) = 11*TickStep;
end % if DatMax
return
This assumes that the minor ticks progress as (say) [...0.8 0.9 1.0 1.1 1.2...]. The idea is to choose loose enough exis limits so that the next minor tick is included. If there are too few orders of magnitude, or too many, then the minor ticks will not progress as above, and the axis limits will be too loose or too tight.
Here is the test driver script "Test.m":
clear
close all;
Xs={[0.15 0.95] [0.25 0.85] [0.45 0.65]}; % 3 test plots
for ix=1:length(Xs)
SymmetricScatter(Xs{ix});
exportgraphics(gcf,"LogLog"+ix+".png");
end % for x
return
function SymmetricScatter(x)
figure
gscatter(x,x)
set(gca,'XLim',Dat2LogLim(x));
set(gca,'YLim',Dat2LogLim(x));
set(gca,'XScale','log','YScale','log')
grid on
return
end % function SymmetricScatter
Here are the resulting plots.

Products

Release

R2022a

Asked:

FM
on 2 Jun 2022

Edited:

FM
on 6 Jun 2022

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!