Error: chol: input matrix must be positive definite - matlab

How can I fix this error?
mvnpdf.m
% y = mvnpdf(x,mu,Sigma)
% Compute multivariate normal pdf for x given mean mu and covariance matrix
% sigma. The dimension of x is d x p, mu is 1 x p and sigma is p x p.
function pdf = mvnpdf(x,mu,sigma)
[d,p] = size(x);
% mu can be a scalar, a 1xp vector or a nxp matrix
if nargin == 1, mu = 0; end
if all(size(mu) == [1,p]), mu = repmat(mu,[d,1]); end
if nargin < 3
pdf = (2*pi)^(-p/2) * exp(-sumsq(x-mu,2)/2);
else
r = chol(sigma);
pdf = (2*pi)^(-p/2) * exp(-sumsq((x-mu)/r,2)/2) / prod(diag(r));
end
pdfdep.m
function pdfmx = pdfdep(train, test)
% computes probability density for all classes
% assuming feature independence
% train - train set; the first column contains label
% used to compute mean and variation for all classes
% test - test set (without labels)
% pdfmx - matrix of probability density for all classes
% class with label idx is stored in pdfmx(:,idx)
classnb = rows(unique(train(:,1)));
pdfmx = ones(rows(test), classnb);
for cl=1:classnb
clidx = train(:,1) == cl;
mu = mean(train(clidx,2:end));
sigma = cov(train(clidx,2:end));
pdfmx(:,cl) = mvnpdf(test, mu, sigma);
end
mat.txt
1 2 3 4 5 6 7 8
2 3 4 5 6 7 8 1
3 4 5 6 7 8 1 2
4 5 6 7 8 1 2 3
1 8 7 6 5 4 3 2
2 7 6 5 4 3 2 9
3 6 5 4 3 2 9 8
4 5 4 3 2 9 8 7
1 8 7 6 5 4 3 2
3 6 5 4 3 2 9 8
Error message:
>> mat2 = mat;
>> pdfdep(mat, mat2)
error: chol: input matrix must be positive definite
error: called from
mvnpdf at line 13 column 7
pdfdep at line 20 column 15
>>

The error is quite self explanatory
input matrix must be positive definite
Means that your matrix (sigma) is not positive definite, thus you cannot run cholesky decomposition on it. There are many ways used to estimate covariance in a nice manner, simply computing the empirical estimate (what you do by calling cov) does not work when your data is degenerated (it lies in low dimensional manifold). One of the simplest solutions is to use "pulled estimator" of form:
cov(X) + eps * I
instead of
cov(X)
thus just change
sigma = cov(train(clidx,2:end));
to incorporate this additional + eps * I (where I is identity matrix of appropriate dimension).

Related

Merging sorted pairs

I have two (or more but if solved for two, it's solved for any number) 2-by-N matrices which represent points with an x (the first row) and y (the second row) coordinates. The points are always sorted in the increasing x coordinate. What I want to do is I want to merge these two matrices into one 3-by-N matrix so that if two points (one from each matrix) have the same x coordinate, they would form one column in the new matrix, the first row being the x coordinate and the second and third row being the two y coordinates. However, if there is a point in one matrix that has x coordinate different than all other points in the second matrix, I still want to have full 3-element column that is placed such that the x coordinates are still sorted and the missing value from the other matrix is replaced by the nearest value with lower x coordinate (or NaN if there is none).
Better to explain by example.
First matrix:
1 3 5 7 % x coordinate
1 2 3 4 % y coordinate
Second matrix:
2 3 4 7 8 % x coordinate
5 6 7 8 9 % y coordinate
Desired result:
1 2 3 4 5 7 8 % x coordinate
1 1 2 2 3 4 4 % y coordinate from first matrix
NaN 5 6 7 7 8 9 % y coordinate from second matrix
My question is, how can I do it effectively in matlab/octave and numpy? (Effectively because I can always do it "manually" with loops but that doesn't seem right.)
You can do it with interp1 and the keyword 'previous' for strategy (you can also choose 'nearest' if you do not care if it is larger or smaller) and 'extrap' for allowing extrapolation.
Define the matrices
a=[...
1 3 5 7;...
1 2 3 4];
b=[...
2 3 4 7 8;...
5 6 7 8 9];
Then find the interpolation points
x = unique([a(1,:),b(1,:)]);
And interpolate
[x ; interp1(a(1,:),a(2,:),x,'previous','extrap') ; interp1(b(1,:),b(2,:),x,'previous','extrap') ]
Timeit results:
I tested the algorithms on
n = 1e6;
a = cumsum(randi(3,2,n),2);
b = cumsum(randi(2,2,n),2);
and got:
Wolfie: 1.7473 s
Flawr: 0.4927 s
Mine: 0.2757 s
This verions uses set operations:
a=[...
1 3 5 7;...
1 2 3 4];
b=[...
2 3 4 7 8;...
5 6 7 8 9];
% compute union of x coordinates
c = union(a(1,:),b(1,:));
% find indices of x of a and b coordinates in c
[~,~,ia] = intersect(a(1,:),c);
[~,~,ib] = intersect(b(1,:),c);
% create output matrix
d = NaN(3,numel(c));
d(1,:) = c;
d(2,ia) = a(2,:);
d(3,ib) = b(2,:);
% fill NaNs
m = isnan(d);
m(:,1) = false;
i = find(m(:,[2:end,1])); %if you have multiple consecutive nans you have to repeat these two steps
d(m) = d(i);
disp(d);
Try it online!
Your example:
a = [1 3 5 7; 1 2 3 4];
b = [2 3 4 7 8; 5 6 7 8 9];
% Get the combined (unique, sorted) `x` coordinates
output(1,:) = unique([a(1,:), b(1,:)]);
% Initialise y values to NaN
output(2:3, :) = NaN;
% Add x coords from `a` and `b`
output(2, ismember(output(1,:),a(1,:))) = a(2,:);
output(3, ismember(output(1,:),b(1,:))) = b(2,:);
% Replace NaNs in columns `2:end` with the previous value.
% A simple loop has the advantage of capturing multiple consecutive NaNs.
for ii = 2:size(output,2)
colNaN = isnan(output(:, ii));
output(colNaN, ii) = output(colNaN, ii-1);
end
If you have more than 2 matrices (as suggested in your question) then I'd advise
Store them in a cell array, and loop over them to do the calls to ismember, instead of having one code line per matrix hardcoded.
The NaN replacement loop is already vectorised for any number of rows.
This is the generic solution for any number of matrices, demonstrated with a and b:
mats = {a, b};
cmats = horzcat(mats);
output(1, :) = unique(cmats(1,:));
output(2:numel(mats)+1, :) = NaN;
for ii = 1:size(mats)
output(ii+1, ismember(output(1,:), mats{ii}(1,:))) = mats{ii}(2,:);
end
for ii = 2:size(output,2)
colNaN = isnan(output(:,ii));
output(colNaN, ii) = output(colNaN, ii-1);
end

Independent random selection with replacement of elements per column in a matrix

I have a matrix A which is of size r1 x c. I'm trying to create a matrix B which is of size r2 x c where for each individual column, I would like to randomly sample with replacement.
I wrote this code that does what I am looking for:
%// Define a random index :
RO = randi(r1,r2,c);
%// Define an output matrix
B = zeros(r2,c);
%// Perform selection
for i1 = 1:c
for i2 = 1:r2
B(i2,i1) = A(RO(i2,i1),i1);
end
end
Is there an easier and/or faster way to do this in MATLAB without loops?
If I am interpreting this code correctly, you have a matrix and for each column, you consider this to be an individual signal and you want to randomly sample r2 elements from each signal to create another r2 signal that possibly has duplicates. You wish to stack these columns horizontally to generate an output matrix. A property with this matrix is that for each column, this random sampling is applied only for the corresponding column in the input matrix.
You can certainly do this vectorized. The matrix RO would be used as row coordinates and the column coordinates, which we will call RC, would simply be matrix of enumerations where each row is the linear range 1:c and there are r2 of these stacked on top of each other. This can be achieved with repmat.
First obtain the linear indices of the row and column coordinates via sub2ind then use this to index into your input matrix A.
RO = randi(r1,r2,c);
RC = repmat(1:c,r2,1);
ind = sub2ind(size(A), RO, RC);
B = A(ind);
To show that this works, I've created the following data:
rng(123);
r1 = 5;
r2 = 10;
c = 3;
A = randi(10, r1, c);
Running your code above gives me:
>> B
B =
6 10 8
7 10 5
7 10 4
3 10 4
3 5 5
6 5 1
8 7 4
6 7 8
6 7 5
6 7 4
Using the same matrix RO that was generated, the more optimized code that I wrote also gives:
>> B
B =
6 10 8
7 10 5
7 10 4
3 10 4
3 5 5
6 5 1
8 7 4
6 7 8
6 7 5
6 7 4

reconstruct time series from given matrix

suppose that we are creating following matrix from given signal
function [ x ]=create_matrix1(b,l)
n = length(b);
m = n-l+1;
x = zeros(m,l);
for i=1:m
x(i,:)=b(i:i+l-1);
end;
end
with some window length,for example
X=[2;1;3;4;5;7]
X =
2
1
3
4
5
7
>> B=create_matrix1(X,3)
B =
2 1 3
1 3 4
3 4 5
4 5 7
if we have given matrix and windows length ,how can i reconstruct original signal?let say i know that windows length is 3,thanks in advance,i think i should sum elements on anti diagonal and divide by number of elements in this anti diagonal ,but how can i do it by code?thanks in advance
Your original vector is located along the top and right edge of your matrix B and can be reconstructed like so:
>> X_reconstructed = [B(1,1:end-1).'; B(:,end)]
X_reconstructed =
2
1
3
4
5
7
In case the matrix B is some noisy matrix and you actually want to do the averages along the diagonals:
>> BB = fliplr(B);
>> X_mean = arrayfun(#(i) mean(diag(BB,i)), size(B,2)-1:-1:-size(B,1)+1).'
X_mean =
2
1
3
4
5
7

Impulse response function in matlab

There are examples for summation of a vector but not for matrix in Matlab. So please help solve the following:
How to write impulse response function in matlab?
I want program in Matlab for the equation:
hij(t) = ∑_(k=1)to n (φik*φjk*e-xwk*sin(wdk(t))/(M*wdk))
h is impulse response function
φ is mode shape
x is constant
wk is kth mode nat frequency
wdk is kth mode damped frequency
M is mass matrix.
Summing on a matrix, in general, looks like this:
>> A = randi(5,[3,6]) % Creating a random [3 x 6] integer matrix
A =
3 4 4 1 2 4
3 4 4 3 3 2
4 2 1 5 2 3
>> sum(A) % Sums on rows (dim=1 is default) so you get a [1 x 6] vector
ans =
10 10 9 9 7 9
>> sum(A,2) % Sums on columns (dim=2) so you get a [3 x 1] vector
ans =
18
19
17
And similarly if you had a 3D matrix V, then you could do sum(V,3) to sum on the slices.
If you want more specific help, please note the dimensions of each input (phi_i, phi_j, M, w, and wd)

What is the simplest way to create a weight matrix bases on how frequent each element appear in the matrix?

This is the input matrix
7 9 6
8 7 9
7 6 7
Based on the frequency their appearance in the matrix (Note. these values are for explanation purpose. I didn't pre-calculate them in advance. That why I ask this question)
number frequency
6 2
7 4
8 1
9 2
and the output I expect is
4 2 2
1 4 2
4 2 4
Is there a simple way to do this?
Here's a three-line solution. First prepare the input:
X = [7 9 6;8 7 9;7 6 7];
Now do:
[a m n] = unique(X);
b = hist(X(:),a);
c = reshape(b(n),size(X));
Which gives this value for c:
4 2 2
1 4 2
4 2 4
If you also wanted the frequency matrix, you can get it with this code:
[a b']
Here is a code with for-loop (a is input matrix, freq - frequency matrix with 2 columns):
weight = zeros(size(a));
for k = 1:size(freq,1)
weight(a==freq(k,1)) = freq(k,2);
end
Maybe it can be solved without loops, but my code looks like:
M = [7 9 6 ;
8 7 9 ;
7 6 7 ;];
number = unique(M(:));
frequency = hist(M(:), number)';
map = containers.Map(number, frequency);
[height width] = size(M);
result = zeros(height, width); %allocate place
for i=1:height
for j=1:width
result(i,j) = map(M(i,j));
end
end