Positive & Negitive Log10 Scale Y axis in Matlab - matlab

Hi i'm having a problem where I have a dataset which ranges between -10^3 to 10^3
I need to be able to plot this as with a log scale but semilogy cannot plot negative values
Say for example my data is:
x = [-3,-2,-1,0,1,2,3];
y = [-1000,-100,-10,1,10,100,1000];
(or in general y=sign(x).*10.^abs(x);)
How can I plot this in MATLAB with a log scale? If possible It would be great if the log scale ticks could be on the Y-axis too

Use your actual data as labels, but scale the plotted data with log10.
% data
x = -3:0.1:3;
y = sign(x).*10.^abs(x);
% scaling function
scale = #(x) sign(x).*log10(abs(x));
N = 7; % number of ticks desired
% picking of adequate values for the labels
TickMask = linspace(1,numel(y),N);
YTickLabels = y(TickMask);
% scale labels and plotdata, remove NaN ->inconsistency, do you really want that?
YTick = scale( YTickLabels );
Y = scale(y);
YTick(isnan(YTick)) = 0;
Y(isnan(Y)) = 0;
% plot
plot(x,Y)
set(gca,'YTick',YTick,'YTickLabels',YTickLabels)
grid on
For N = 7:
For N = 11
How to find a valid value for N?
The following function (thanks to gnovice) will return all possible values you could choose for N:
n = numel(x);
N = find(rem(n./(1:n), 1) == 0) + 1;
about the semilogy-style labels: by adding the following line before the plot:
YTickLabels = cellfun(#(x) ['10^' num2str(x)], num2cell(YTick),'UniformOutput',false)
you could at least achieve something like this:
not beautiful and not generic, but a good point to start for you.

The reason you can't make a logarithmic axis that crosses zero, is that it doesn't make sense!
Since a logarithmic scale is generally displayed as eg. 100 - 10 - 1 - 1/10 - 1/100 - ..., you would need an infinite amount of space to make the axis cross zero.

How about this:
x=logspace(-3,3);
y=sign(x).*10.^abs(x);
loglog(x,y)

#thewaywewalk has already given a beautiful solution to it. The one I'm suggesting is an epsilon improvement on it. If you make two changes
(a) Define a new MATLAB function signia that basically extracts the sign before a number.
function value = signia(x)
if(x>=0)
value = '';
else
value = '-';
end
and (b) make this little change that instead of
YTickLabels = cellfun(#(x) ['10^' num2str(x)], num2cell(YTick),'UniformOutput',false)
you use
YTickLabels = cellfun(#(x) [signia(x) '10^{' num2str(x) '}'], num2cell(YTick),'UniformOutput',false);
(notice the presence of curly braces), you'll get an improvement in the Y ticks display. I got the following.
enter image description here

Related

How to make multiple graphs with varying x-axis?

I need to plot multiple graphs in the same y-axis, but the x-axis is a bit tricky for me:
Assuming it goes from 0 to a, increasing by one, it needs to have an overall of a-1 different intervals.
Each one of them should finish at a, but it also has to have a different initialisation point. Only the first starts at 0, while each next one, starts by the previous plus one, as in the shape below. The two dashed lines, I used to visualise my thoughts as clear as I could, are not necessary.
I would appreciate any help!
Replicate your original interval 0 ... a a times (from my understanding, you'll have a intervals, not a-1), such that you get a matrix X of size [a x a+1]. Set the lower left triangle of X to NaN, so that the rows now represent your (shortening) intervals. Do your calculations on X. Pay attention, these have to support/neglect NaN values properly.
After that, you need to adjust the values in X properly, so that the intervals are plotted subsequently. Basically, we add some fixed value to each row.
Last, we need the proper xticks and xticklabels. Therefore, we extract all values from X and the modified X and get rid of the NaN values.
Here's a complete code snippet:
% Parameter
a = 7;
% Initialize intervals
X = repmat(0:a, a, 1);
X = X .* (ones(size(X)) + tril(nan(size(X)), -1));
% Calculation on these intervals; attention: there are NaN in X
Y = sin(X / a * 2 * pi);
% Modify X for plotting
X_plot = X;
X_plot(2:end, :) = X_plot(2:end, :) + cumsum(a:-1:2).';
% Get xticks
xt = X_plot.';
xt = xt(:);
xt(isnan(xt)) = [];
% Get xticklabels
xtl = X.';
xtl = xtl(:);
xtl(isnan(xtl)) = [];
% Plot
plot(X_plot.', Y.');
xticks(xt);
xticklabels(xtl);
The output (Octave 5.1.0, also tested with MATLAB Online) looks like this:
If you only want for example the start and end of each interval, you must further pre-process xt and xtl.
Hope that helps!

Best method of removing flattish areas of a signal in MatLab?

Say I have a signal that looks a bit like this:
that I am processing in MatLab, what functions would I have to use to get rid of the flattish area in the middle? is there any functions that can do that, or do I need to program it in myself? Currently I just have a blank function as I don't know where to start:
function removala = removal(a, b)
end
Is there any quick functions that can remove it or do I just have to search for all values below a threshold and remove them myself? For reference a and b are vectors of amplitude points.
use findpeaks:
% generating signal
x = 1:0.1:10;
y = rand(size(x))*0.5;
y([5,25,84]) = [6,-5.5,7.5];
z = y;
thresh = 0.75; % height threshold
% find peaks
[pks,locs] = findpeaks(z,'MinPeakProminence',thresh);
% remove signal noise between peaks
for ii = 1:length(locs)-1
zz = z(locs(ii)+1:locs(ii+1)-1);
zz(abs(zz) < thresh) = 0;
z(locs(ii)+1:locs(ii+1)-1) = zz;
end
% plot
plot(x,y);
hold on
plot(x,z);
plot(x(locs),pks,'og');
legend('original signal','modified signal','peaks')
You probably want to remove the signal whose absolute power is less than a certain threshold.
So the two input of your function would be the raw signal, and the threshold. The function will output a variable "cleanSignal"
function cleanSignal = removal(rawSignal,threshold)
simplest implementation. remove the data below threshold. If rawSignal is a matrix the resulting variable will be a vector concatenating all the epochs above threshold.
ind = abs(rawSignal)<threshold;
rawSignal(ind) = [];
cleanSignal = rawSignal;
This might not be the behavior that you want, since you want to preserve the original shape of your rawSignal matrix. So you can just "nan" the values below threshold.
ind = abs(rawSignal)<threshold;
rawSignal(ind) = nan;
cleanSignal = rawSignal;
end
Notice that this does not really removes flat signal, but signal which is close to 0.
If you really want to remove flat signal you should use
ind = abs(diff(rawSignal))<threshold;

Matlab Plotting roots (zeros and poles) of Z-Transform

I need to plot the roots onto a transfer function H(z) overlaying a unit circle, giving enough room to see all points. I'm able to get the roots from H(z) when it is given in the form zeros = [z0 z1 z2...], poles = [p0 p1 p2]. Using Matlab's roots function, I'm able to get the pole and zero locations. My Matlab code so far is
function zplot(b, a)
b_roots = roots(b);
a_roots = roots(a);
hold on
rectangle('Position',[-1 -1 2 2],'Curvature',[1 1]);
plot(b_roots,'x blue');
plot(a_roots,'o blue');
axis %need axis to be equal and +10percent of maximum value
hold off
end
So far, I can plot the roots and the unit circle, but I need help with adjusting the axes so that they are 1) equal to each other and 2) 10% more than the highest value. I'm not sure how to go about doing this part. I tried making a variable lim_max = max(b_roots,a_roots) but it ended up being an array, and wouldn't work in the axis([-lim_max lim_max -lim_max lim_max]) function. I need the plot to scale to +10% with the inputs as they change.
Side note (not necessary): is there a way for it to look like a circle when I plot it, because right now it ends up looking like an oval most of the time. I can readjust the screen, which is fine, but if there's an easy way to do that, I'd also like to know.
Set axis equal and calculate min/max:
function zplot(b, a)
b_roots = roots(b);
a_roots = roots(a);
xlimits = [min(min([real(a_roots);real(b_roots)])), max(max([real(a_roots);real(b_roots)]))];
ylimits = [min(min([imag(a_roots);imag(b_roots)])), max(max([imag(a_roots);imag(b_roots)]))];
hold on
rectangle('Position',[-1 -1 2 2],'Curvature',[1 1]);
plot(b_roots,'x black');
plot(a_roots,'o blue');
axis equal;
xlim(1.1*xlimits);
ylim(1.1*ylimits);
hold off
end
Use the following code. This will 1) find the maximum overall limits of x and y axes 2) set those limits equal to each other 3) plot those limits +10%
b_roots = roots(b);
a_roots = roots(a);
x_min = min(min([real(a_roots);real(b_roots)]));
x_max = max(max([real(a_roots);real(b_roots)]));
y_min = min(min([imag(a_roots);imag(b_roots)]));
y_max = max(max([imag(a_roots);imag(b_roots)]));
%get the magnitude of the overall minimum value
min_lim = abs(min(x_min,y_min));
%abs may not be necessary
max_lim = abs(max(x_max,y_max));
%set high and low limits equal to each other from negative to positive
eq_limit = [-max(min_lim,max_lim),max(min_lim,max_lim)];
hold on
rectangle('Position',[-1 -1 2 2],'Curvature',[1 1]);
plot(b_roots,'x black');
plot(a_roots,'o blue');
axis equal;
xlim(1.1*eq_limit);
ylim(1.1*eq_limit);
hold off
Thanks to #M.S. for their answer and help.

Interpolate/fill in missing cells in truth-value grid in MATLAB

Consider the 37x101 matrix below:
Each black cell has value 1, the rest of the cells value 0. I would like to fill in the "gaps" by means of cubic spline interpolation, as well as scaling the y axis from 37 to 181. The latter can be done by using the interp1 function, as in:
interp1(37,matrix,181,'pchip')
However, the outcome is now interpolated along the y-axis, but the gaps remain:
I don't want to interpolate along the x-axis, because I want the final matrix to have dimension 181 x 101. I just want to fill in the gaps using the existing cells (of the 181 x 101 matrix).
How can the original matrix (top), be scaled from 37 x 101 to 181 x 101 (without the "smoothing" in the second image), as well as filling in the gaps using some kind of spline interpolation as if this was a proper function?
It appears that your truth value grid has a single one where the true value is in each row. If the true/1 values do in fact create a line through the image, I would recommend parametrize the line with respect to t so that y = fy(t) and x = fx(t). If you're not familiar with this you can find some parametrization info on youtube tutorials or google. The main idea is that if you have , say a truth table that looks like this:
Then you could plot the the location of each pixel with respect to another variable, t and then use interp1(...) on each of these individually. In my case I defined the x and y values as follows:
n = 32;
rand('seed', 1982);
y_orig = 1:n;
x_orig = ceil(n*sin(y_orig/n*pi));
So I can plot as:
t1 = linspace(0,1, n);
plot(t1,x_orig, 'r', 'linewidth', 3);
hold all
plot(t1,y_orig, 'b', 'linewidth', 3);
legend('X', 'Y')
Note that I can get any truth value I want just by using interp1 like this (if you wanted to find the value half way between the 5th and 6th row):
desiredY = 5.5;
t= 1:n;
truthValue= interp1(t, x_orig, desiredY, 'cubic')
But we are looking to make a new image so I chose a more convenient parametrization of t between zero and one. Unfortunately, you may not have x and y off hand, so we need to pull them out of the image. Assuming you have a single true/1 value in each row we can yank out the values with max(...):
[maxVals, x1] = max(data,[],2);
x1(maxVals == 0) = [];
y1 = find(maxVals ~= 0);
Some form of find on each row would also work. If you have a truth value in each row then y1 should equal 1:n. The max function returns the index of the max in dimension 2 in the second return value. I use the next two lines to remove any entries where there truth table was empty (max is zero) and then y1 = 1:n minus those entries that were empty.
A quick and dirty way to get lots of points along this line is:
t2 = linspace(0,1,1024);
x2 = interp1(t1, x1, t2, 'cubic');
y2 = interp1(t1, y1, t2, 'cubic');
I can then plot the original points/image and this newly discovered finer line together like this:
imagesc(data);
hold all;
plot(x2,y2, 'linewidth', 2);
axis image
colormap(flipud(colormap(gray)));
To get this:
Finally, you can quickly turn this into a new image by scaling the parametrization up. My method is not particularly efficient for clarity:
y2_scaled = floor((y2(:)-1)*scaleValue + 1);
x2_scaled = floor((x2(:)-1)*scaleValue + 1);
scaleValue = 2;
data2 = zeros(n*scaleValue);
for ind = 1:length(x2_scaled)
data2(y2_scaled(ind),x2_scaled(ind)) = 1;
end
Which results in:
Note that this table has connected all the points and you now have multiple true/1's in each row. This is because I chose a very small step size for t2. You could fix this by either choosing t2 smarter, skipping multiple values in each row, or average the location of each indices in each row. Or ignoring this issue.
To fix t2 with the scaling value, you could use
t2 = linspace(0,1,n*scaleValue);
to get only one true/1 per row in the above code.
Also, if you want to only scale one dimension, you could do it like this:
y2_scaled = floor((y2(:)-1)*scaleValue + 1);
x2_scaled = floor((x2(:)-1) + 1);
scaleValue = 2;
data2 = zeros(n*scaleValue,n);
for ind = 1:length(x2_scaled)
data2(y2_scaled(ind),x2_scaled(ind)) = 1;
end
I see this as a bitmap, so why not a clamped blur?
I=yourmatrixhere
%gaussian blur
% it looks like bump-to-bump distance is 3 empties
% therefore hsize should be about 7
% it looks like bump vertical size is about 4
% therefore simga should be about 10
hsize=[3 3];
sigma = 10;
h=fspecial('gaussian',hsize,sigma)
I2=imfilter(I,h,'replicate');
At this point you have spread information to adjacent columns, but you need to "tidy up" from continuous to binary.
%threshold
th = 0.25;
I3=zeros(size(I));
ind=find(I>=th);
I3(ind)=1;
At this point, I3 is your matrix of interest to do the "erode" or interpolation.

How to fit a curve by a series of segmented lines in Matlab?

I have a simple loglog curve as above. Is there some function in Matlab which can fit this curve by segmented lines and show the starting and end points of these line segments ? I have checked the curve fitting toolbox in matlab. They seems to do curve fitting by either one line or some functions. I do not want to curve fitting by one line only.
If there is no direct function, any alternative to achieve the same goal is fine with me. My goal is to fit the curve by segmented lines and get locations of the end points of these segments .
First of all, your problem is not called curve fitting. Curve fitting is when you have data, and you find the best function that describes it, in some sense. You, on the other hand, want to create a piecewise linear approximation of your function.
I suggest the following strategy:
Split manually into sections. The section size should depend on the derivative, large derivative -> small section
Sample the function at the nodes between the sections
Find a linear interpolation that passes through the points mentioned above.
Here is an example of a code that does that. You can see that the red line (interpolation) is very close to the original function, despite the small amount of sections. This happens due to the adaptive section size.
function fitLogLog()
x = 2:1000;
y = log(log(x));
%# Find section sizes, by using an inverse of the approximation of the derivative
numOfSections = 20;
indexes = round(linspace(1,numel(y),numOfSections));
derivativeApprox = diff(y(indexes));
inverseDerivative = 1./derivativeApprox;
weightOfSection = inverseDerivative/sum(inverseDerivative);
totalRange = max(x(:))-min(x(:));
sectionSize = weightOfSection.* totalRange;
%# The relevant nodes
xNodes = x(1) + [ 0 cumsum(sectionSize)];
yNodes = log(log(xNodes));
figure;plot(x,y);
hold on;
plot (xNodes,yNodes,'r');
scatter (xNodes,yNodes,'r');
legend('log(log(x))','adaptive linear interpolation');
end
Andrey's adaptive solution provides a more accurate overall fit. If what you want is segments of a fixed length, however, then here is something that should work, using a method that also returns a complete set of all the fitted values. Could be vectorized if speed is needed.
Nsamp = 1000; %number of data samples on x-axis
x = [1:Nsamp]; %this is your x-axis
Nlines = 5; %number of lines to fit
fx = exp(-10*x/Nsamp); %generate something like your current data, f(x)
gx = NaN(size(fx)); %this will hold your fitted lines, g(x)
joins = round(linspace(1, Nsamp, Nlines+1)); %define equally spaced breaks along the x-axis
dx = diff(x(joins)); %x-change
df = diff(fx(joins)); %f(x)-change
m = df./dx; %gradient for each section
for i = 1:Nlines
x1 = joins(i); %start point
x2 = joins(i+1); %end point
gx(x1:x2) = fx(x1) + m(i)*(0:dx(i)); %compute line segment
end
subplot(2,1,1)
h(1,:) = plot(x, fx, 'b', x, gx, 'k', joins, gx(joins), 'ro');
title('Normal Plot')
subplot(2,1,2)
h(2,:) = loglog(x, fx, 'b', x, gx, 'k', joins, gx(joins), 'ro');
title('Log Log Plot')
for ip = 1:2
subplot(2,1,ip)
set(h(ip,:), 'LineWidth', 2)
legend('Data', 'Piecewise Linear', 'Location', 'NorthEastOutside')
legend boxoff
end
This is not an exact answer to this question, but since I arrived here based on a search, I'd like to answer the related question of how to create (not fit) a piecewise linear function that is intended to represent the mean (or median, or some other other function) of interval data in a scatter plot.
First, a related but more sophisticated alternative using regression, which apparently has some MATLAB code listed on the wikipedia page, is Multivariate adaptive regression splines.
The solution here is to just calculate the mean on overlapping intervals to get points
function [x, y] = intervalAggregate(Xdata, Ydata, aggFun, intStep, intOverlap)
% intOverlap in [0, 1); 0 for no overlap of intervals, etc.
% intStep this is the size of the interval being aggregated.
minX = min(Xdata);
maxX = max(Xdata);
minY = min(Ydata);
maxY = max(Ydata);
intInc = intOverlap*intStep; %How far we advance each iteraction.
if intOverlap <= 0
intInc = intStep;
end
nInt = ceil((maxX-minX)/intInc); %Number of aggregations
parfor i = 1:nInt
xStart = minX + (i-1)*intInc;
xEnd = xStart + intStep;
intervalIndices = find((Xdata >= xStart) & (Xdata <= xEnd));
x(i) = aggFun(Xdata(intervalIndices));
y(i) = aggFun(Ydata(intervalIndices));
end
For instance, to calculate the mean over some paired X and Y data I had handy with intervals of length 0.1 having roughly 1/3 overlap with each other (see scatter image):
[x,y] = intervalAggregate(Xdat, Ydat, #mean, 0.1, 0.333)
x =
Columns 1 through 8
0.0552 0.0868 0.1170 0.1475 0.1844 0.2173 0.2498 0.2834
Columns 9 through 15
0.3182 0.3561 0.3875 0.4178 0.4494 0.4671 0.4822
y =
Columns 1 through 8
0.9992 0.9983 0.9971 0.9955 0.9927 0.9905 0.9876 0.9846
Columns 9 through 15
0.9803 0.9750 0.9707 0.9653 0.9598 0.9560 0.9537
We see that as x increases, y tends to decrease slightly. From there, it is easy enough to draw line segments and/or perform some other kind of smoothing.
(Note that I did not attempt to vectorize this solution; a much faster version could be assumed if Xdata is sorted.)