Skip to content
Snippets Groups Projects
migram.m 8.15 KiB
Newer Older
marwan's avatar
marwan committed
function [c_out, l_out, t_out] = migram(varargin)
%MIGRAM Calculate windowed mutual information between two signals.
%   I = MIGRAM(A,B,MAXLAG,WINDOW,NOVERLAP) calculates the windowed mutual   
%   information between the signals in vector A and vector B. MIGRAM splits  
%   the signals into overlapping segments and forms the columns of I with 
%   their mutual information values up to maximum lag specified by scalar 
%   MAXLAG. Each column of I contains the mutual information function 
%   between the short-term, time-localized signals A and B.  Time increases
%   linearly across the columns of I, from left to right.  Lag increases 
%   linearly down the rows, starting at -MAXLAG. If lengths of A and B 
%   differ, the shorter signal is filled with zeros. If N is the length of 
%   the signals, I is a matrix with 2*MAXLAG+1 rows and 
%         k = fix((N-NOVERLAP)/(WINDOW-NOVERLAP)) 
%   columns.
%
%   I = MIGRAM(A,B,MAXLAG,WINDOW,NOVERLAP,NBINS) calculates the mutual
%   information based on histograms with the number of bins NBINS.
%
marwan's avatar
marwan committed
%   I = MIGRAM(...,'norm') calculates the renormalised mutual
%   information, which is I/log(NBINS) and ensures a value range [0 1].
%
marwan's avatar
marwan committed
%   [I,L,T] = MIGRAM(...) returns a column of lag L and one of time T
%   at which the mutual information is computed. L has length equal 
%   to the number of rows of I, T has length k.
%
%   I = MIGRAM(A,B) calculates windowed mutual information using defeault
%   settings; the defeaults are MAXLAG = floor(0.1*N), WINDOW = floor(0.1*N),
marwan's avatar
marwan committed
%   NOVERLAP = 0 and NBINS = 10. You can tell MIGRAM to use the defeault 
marwan's avatar
marwan committed
%   for any parameter by leaving it off or using [] for that parameter, e.g. 
%   MIGRAM(A,B,[],1000).
%
%   MIGRAM(A,B) with no output arguments plots the mutual information 
%   using the current figure.
%
marwan's avatar
marwan committed
%   Remark
%   Please note that the mutual information derived with MI slightly 
%   differs from the results derived with MIGRAM. The reason is that
%   MI also considers estimation errors. 
%
%   Example
marwan's avatar
marwan committed
%       x = cos(0:.01:10*pi)';
%       y = sin(0:.01:10*pi)' + .5 * randn(length(x),1);
%       migram(x,y)
%
%   See also MI, CORRGRAM.

marwan's avatar
marwan committed
% Copyright (c) 2008-2009
% Norbert Marwan, Potsdam Institute for Climate Impact Research, Germany
% http://www.pik-potsdam.de
%
% Copyright (c) 2007-2008
marwan's avatar
marwan committed
% Norbert Marwan, Potsdam University, Germany
% http://www.agnld.uni-potsdam.de
%
% $Date$
% $Revision$


marwan's avatar
marwan committed
error(nargchk(2,7,nargin))
marwan's avatar
marwan committed
verbose = 0;

x = varargin{1}; y = varargin{2};
x = x(:); y = y(:);


% check input and inital setting of parameters

nx = length(x); ny = length(y);
if nx < ny    % zero-pad x if it has length less than y
    x(ny) = 0; nx = ny;
end

if ny < nx    % zero-pad y if it has length less than x
    y(nx) = 0;
end

% check for NaNs
if any(isnan(x(:,1)) + isnan(y(:,1)))
    error('Data contains NaNs.')
end

marwan's avatar
marwan committed
maxlag = floor(nx/10); 
window = floor(nx/10);
noverlap = 0;
nbins = 10;
marwan's avatar
marwan committed
norm = 0;

i_num = find(cellfun('isclass',varargin,'double'));
i_char = find(cellfun('isclass',varargin,'char'));
marwan's avatar
marwan committed

marwan's avatar
marwan committed
if length(i_num) > 2 && ~isempty(varargin{i_num(3)})
marwan's avatar
marwan committed
    maxlag = varargin{i_num(3)};
marwan's avatar
marwan committed
    if maxlag < 0, error('Requires positive integer value for maximum lag.'), end
    if length(maxlag) > 1, error('Requires MAXLAG to be a scalar.'), end
end

marwan's avatar
marwan committed
if length(i_num) > 3 && ~isempty(varargin{i_num(4)})
marwan's avatar
marwan committed
    window = varargin{i_num(4)};
marwan's avatar
marwan committed
    if window <= 0, error('Requires positive integer value for window length.'), end
    if length(window) > 1, error('Requires WINDOW to be a scalar.'), end
end

marwan's avatar
marwan committed
if length(i_num) > 4 && ~isempty(varargin{i_num(5)})
marwan's avatar
marwan committed
    noverlap = varargin{i_num(5)};
marwan's avatar
marwan committed
    if noverlap < 0, error('Requires positive integer value for NOVERLAP.'), end
    if length(noverlap) > 1, error('Requires NOVERLAP to be a scalar.'), end
    if noverlap >= window, error('Requires NOVERLAP to be strictly less than the window length.'), end
end

marwan's avatar
marwan committed
if length(i_num) > 5 && ~isempty(varargin{i_num(6)})
marwan's avatar
marwan committed
    nbins = varargin{i_num(6)};
marwan's avatar
marwan committed
    if nbins <= 0, error('Requires positive integer value for NBINS.'), end
    if length(nbins) > 1, error('Requires NBINS to be a scalar.'), end
end

marwan's avatar
marwan committed
% normalise the result
for i = 1:length(i_char)
    if strcmpi(varargin(i_char(i)), 'norm'), norm = 1; end
end
marwan's avatar
marwan committed

% prepare time delayed signals
X = buffer(x,maxlag+1,maxlag)';
Y = fliplr(buffer(y,maxlag+1,maxlag)');

% divide the delayed signals into overlapping windows
% and compute the correlation coefficient
cnt = 1;

marwan's avatar
marwan committed
warning('off','MATLAB:divideByZero')

marwan's avatar
marwan committed
C = zeros(2*maxlag+1, fix((nx-noverlap)/(window-noverlap)));
if verbose, h = waitbar(0,'Compute mutual information'); end

% -MAXLAG:0
marwan's avatar
marwan committed
[Yi dummy] = buffer(Y(:,1),window,noverlap,'nodelay');
marwan's avatar
marwan committed
if exist('accumarray','builtin') == 5
    for i = 1:size(X,2), if verbose, waitbar(i/(2*size(X,2))), end
marwan's avatar
marwan committed
        [Xi dummy] = buffer(X(:,i),window,noverlap,'nodelay');
        C(i,:) = MI6(Xi, Yi, nbins);
        %cnt = cnt + 1;
marwan's avatar
marwan committed
    end
else
    for i = 1:size(X,2), if verbose, waitbar(cnt/(2*size(X,2))), end
        [Xi dummy] = buffer(X(:,i),window,noverlap,'nodelay');
        C(cnt,:) = MI5(Xi, Yi, nbins);
        cnt = cnt + 1;
    end
end
cnt = size(X,2)-1;
marwan's avatar
marwan committed
% 0:MAXLAG
[Xi dummy] = buffer(X(:,end),window,noverlap,'nodelay');
if exist('accumarray','builtin') == 5
    for i = 2:size(Y,2), if verbose, waitbar(cnt/(2*size(X,2))), end
        [Yi dummy] = buffer(Y(:,i),window,noverlap,'nodelay');
        C(i+cnt,:) = MI6(Xi, Yi, nbins);
       % cnt = cnt + 1;
marwan's avatar
marwan committed
    end
else
    for i = 2:size(Y,2), if verbose, waitbar(cnt/(2*size(X,2))), end
        [Yi dummy] = buffer(Y(:,i),window,noverlap,'nodelay');
        C(cnt,:) = MI5(Xi, Yi, nbins);
        cnt = cnt + 1;
    end
end

if verbose, delete(h), end

marwan's avatar
marwan committed
warning('on','MATLAB:divideByZero')
marwan's avatar
marwan committed

% create time scale for the windows
t = (1:nx/size(Xi,2):nx)';
l = (-maxlag:maxlag)';

marwan's avatar
marwan committed
% if result has to be normalised
if norm
    C = C / log(nbins);
end


marwan's avatar
marwan committed
% display and output result
if nargout == 0
    newplot
    imagesc(t, l, C)
    xlabel('Time'), ylabel('Lag'), axis xy
    title('Windowed mutual information', 'fontweight', 'bold')
    colorbar
elseif nargout == 1,
    c_out = C;
elseif nargout == 2,
    c_out = C;
    l_out = l;
elseif nargout == 3,
    c_out = C;
    t_out = t;
    l_out = l;
end


% mutual information for Matlab version >= 6
function Z = MI6(x, y, nbins)
    
    % normalise the data and replace the values with integers
    % in the range [1 nbins]
    x = x - repmat(min(x), size(x,1), 1); 
    y = y - repmat(min(y), size(y,1), 1);
    x = x ./ repmat(max(x) + eps, size(x,1), 1); 
    y = y ./ repmat(max(y) + eps, size(y,1), 1);
    
    x = floor(x * nbins) + 1;
    y = floor(y * nbins) + 1;
marwan's avatar
marwan committed
    % compute probabilities
    Z = zeros(1,size(x,2));
    for i = 1:size(x,2)
    
        Pxy = accumarray([x(:,i) y(:,i)] + 1, 1);
        Px = sum(Pxy,1);
        Py = sum(Pxy,2);

        Pxy = Pxy / sum(Pxy(:));
        Px = Px / sum(Px(:));
        Py = Py / sum(Py(:));

        % entropies
        Ix = -sum((Px(Px ~= 0)) .* log(Px(Px ~= 0)));
        Iy = -sum((Py(Py ~= 0)) .* log(Py(Py ~= 0)));
        Ixy = -sum(Pxy(Pxy ~= 0) .* log(Pxy(Pxy ~= 0)));                                                                    
        
        % mutual information
        Z(i) = Ix + Iy - Ixy;
    end    
    
    
% mutual information for Matlab version < 6
function Z = MI5(x, y, nbins)   
    
    % normalise the data and replace the values with integers
    % in the range [1 nbins]
    x = x - repmat(min(x), size(x,1), 1); 
    y = y - repmat(min(y), size(y,1), 1);
    x = x ./ repmat(max(x) + eps, size(x,1), 1); 
    y = y ./ repmat(max(y) + eps, size(y,1), 1);
    
    x = floor(x * nbins) + 1;
    y = floor(y * nbins) + 1;
    
    % compute probabilities
    Z = zeros(1,size(x,2));
    for i = 1:size(x,2)
    
        Pxy = full(sparse(x(:,i) + 1, y(:,i) + 1, 1));
        Px = sum(Pxy,1);
        Py = sum(Pxy,2);

        Pxy = Pxy / sum(Pxy(:));
        Px = Px / sum(Px(:));
        Py = Py / sum(Py(:));

        % entropies
        Ix = -sum((Px(Px ~= 0)) .* log(Px(Px ~= 0)));
        Iy = -sum((Py(Py ~= 0)) .* log(Py(Py ~= 0)));
        Ixy = -sum(Pxy(Pxy ~= 0) .* log(Pxy(Pxy ~= 0)));                                                                    
        
        % mutual information
        Z(i) = Ix + Iy - Ixy;
    end