Lecture 4, ACM 11

Contents

Making a plot with error bars

This is easy: use "errorbar" (see "help specgraph")

clear;clf;clc
x = 1:10;
randn('state',5);
y = 3*x.^2 + 20*randn(1,10);
err = 30*randn(1,10);
errorbar(x,y,err,':');  % these are just as an example; they have no real meaning
text(1,250,'Data generated via 3*x^2 + noise');

Basic regression

Using the data from the error-bar plot, let's fit it to a polynomial

p = polyfit(x,y,2);     % we want a degree-2 polynomial

estimate = polyval(p, 1:.1:10 );
hold on
plot(1:.1:10,estimate,'r')
legend('data','fit')
text(4,10, ['via polyfit: ' poly2str(p,'x')])
title('Error bar plot')

% You can also do this graphically; in the figure window, go to "Tools" and
% pick "Basic Fitting".

Better regression by doing it ourselves

First, we recreate the results of polyfit "by hand" For a general quadratic fit, we do the following:

x = x(:); y = y(:);   % make sure x and y are column vectors
A = [x.^0, x, x.^2 ]
coefficients = A\y    % the backslash solver minimizes the l_2 norm of the residual
% This is the same as what polyfit gave us!
A =

     1     1     1
     1     2     4
     1     3     9
     1     4    16
     1     5    25
     1     6    36
     1     7    49
     1     8    64
     1     9    81
     1    10   100


coefficients =

   19.8233
   -8.2761
    3.6702

Now, suppose we know that the polynomial that best explains our data has no constant term. We can use this info to improve our estimate Now, we get rid of the 1's column:

coefficients = A(:,2:3)\y
coefficients = [flipud(coefficients);0];
text(4,-10,['forcing a zero intercept: ' poly2str(coefficients,'x')])

% And if we know it's exactly a quadratic, we would do this:
coefficients = A(:,3:3)\y
coefficients = [flipud(coefficients);0;0];
text(4,-30,['forcing a quadratic: ' poly2str(coefficients,'x')])
coefficients =

   -0.7528
    3.0732


coefficients =

    2.9833

3D plotting

Suppose we want a 3D plot of sin(x)*cos^2*(y). The simplest way to set this up is using "meshgrid".

clc;clear; N = 5;
f = @(x,y) sin(x).*(cos(y).^2);
x = linspace(0, 2*pi, N );
y = linspace(0, 2*pi, N );
[xGrid,yGrid] = meshgrid(x,y)
xGrid =

         0    1.5708    3.1416    4.7124    6.2832
         0    1.5708    3.1416    4.7124    6.2832
         0    1.5708    3.1416    4.7124    6.2832
         0    1.5708    3.1416    4.7124    6.2832
         0    1.5708    3.1416    4.7124    6.2832


yGrid =

         0         0         0         0         0
    1.5708    1.5708    1.5708    1.5708    1.5708
    3.1416    3.1416    3.1416    3.1416    3.1416
    4.7124    4.7124    4.7124    4.7124    4.7124
    6.2832    6.2832    6.2832    6.2832    6.2832

Here are four types of plots that are useful for 2D functions

N = 100;
x = linspace(0, 2*pi, N );
y = linspace(0, 2*pi, N );
[xGrid,yGrid] = meshgrid(x,y);
F = f(xGrid,yGrid);
% For 3D plotting tools, type "help graph3d"
figure(1); clf; subplot(2,2,1)
plot3(xGrid,yGrid,F,'o'); title('plot3')

subplot(2,2,2)
mesh(xGrid,yGrid,F); title('mesh') % better!

subplot(2,2,3)
surf(xGrid,yGrid,F); title('surf')
colorbar('location','westoutside')  % this adds a legend describing the
                                    % significance of each color
subplot(2,2,4)
contour(xGrid,yGrid,F,20);  title('contour');
% "contour" is listed in "help specgraph" along with many other useful
% functions

% In the figure window, click the "Rotate 3D" icon (let the mouse hover
% over the icons to see their names), and you can rotate these objects.

Here are some cool 3D plots that give an idea of what Matlab is capable of.

clf; xpklein
teapotdemo

transpdemo % this crashed my computer!

volvec % this behaves funny too, but gives an idea of how to display 3D stream lines. type "edit volvec" for details.

vector field plots

If you have the values of the vectors, then something like "quiver" will do what you want. Take our function "f" from earlier, and find the gradient field.

dfdx = @(x,y) cos(x).*(cos(y).^2);
dfdy = @(x,y) -2*cos(x).*cos(y).*sin(y);
% let's make a smaller grid so that it's not too crowded
close;clf;[xGrid,yGrid] = meshgrid(x(1:5:end),y(1:5:end));
quiver(xGrid,yGrid,dfdx(xGrid,yGrid), dfdy(xGrid,yGrid) )
title('Gradient field, using "quiver" command')
xlim([0,2*pi]); ylim([0,2*pi]);

displaying images

Matlab can display most common image file formats, including jpeg. To see all the availabe functions, type "help specgraph" and see the "Images display and file I/O" section. The basic command to load an image is "imread()". This will open the file and convert it to either a 2D array, or a 3D array if it has color. The 3D array will basically have 3 copies of the 2D array, with each copy representing a different color (these do not have to be colors -- they could be chrominance, luminance, etc.; often, these are referred to as "channels"). The data in the array will probably be an unsigned integer, and not a floating point number; this is done to keep the RAM requirements as small as possible. It's always possible to convert back to to a floating point number using "double()", but if you just want to display the image, this is unnecessary, because the display functions expect unsigned integer datatypes.

% This file should be on everyone's computer
% (type "which street1.jpg" to see where it's located)
clear; X = imread('street1','jpeg');
% X = imread('street1.jpg');    % this command work work also
whos X
% We see that X is a 3D array, with type "unsigned integer, 8 bits"
  Name      Size                           Bytes  Class

  X       480x640x3                       921600  uint8 array

Grand total is 921600 elements using 921600 bytes

To actually view the image, we can use "image" or "imagesc". "imagesc" is identical to "image", except that the values will be scaled to take up the full range of colors available. If you need to convert something back to an integer form, use "uint8()". To see all possible conversions, type "help datatypes" and "help isa"

imagesc(X); title(' file ''street1.jpg'', found in the ''demos'' directory');

We can view only one channel of the image, if we wish

imagesc( X(:,:,1) ); title('displaying only one channel');

Importing data the very easy way

Use Matlab's graphical importer:

uiimport

Also, here's a useful function to see what variables are inside a .mat file (in this example, there's a file called "myFile.mat")

whos -file myFile
% and a nice way to see (and modify) the values if a variable is with the
% "openvar" command.
s = randn(9,1);
openvar s       % the form   openvar('s')   also works
  Name            Size                    Bytes  Class

  variable1       1x1                         8  double array
  variable2       1x3                        24  double array

Grand total is 4 elements using 32 bytes

Sparse matrices

In a few fields, the term "sparse" has become a buzzword. What does it mean? There's no quantitative definition, but the qualitative definition is easy: a vector or matrix is "sparse" when it has many zero entries. In Matlab, there are two ways to store a matrix. The first way, which we've been using so far, is to store all entries. If we know the dimensions of the matrix, and have a convention of how the entries are stored, then there is no need to store the indices. The second method, which Matlab calls the "sparse" datatype, is to only store the nonzero entries. But, with this scheme, we need to store the indices also, so there's a penalty in memory. It is very important to realize that Matlab's definition of "sparse" only refers to the way a matrix is stored. The same matrix can be sparse, or non-sparse (aka "full"). Here's an example:

clc; clear;
A = randn(5)        % Method 1 of storing a matrix, aka "full" storage
B = sparse(A)       % Method 2 of storing a matrix, aka "sparse" storage
% The "sparse" function converts a matrix to the sparse storage format.  We
% can go the other direction using the "full" command, e.g.
% A = full(B);
A =

    0.5777    0.7453   -0.6938    0.2237   -1.1142
    1.6701   -0.5550    0.7174    1.4295   -0.2797
   -0.2803   -0.0587   -0.5504    1.5706    0.9893
    0.9819    0.8630   -1.7900    1.4769   -0.7902
   -0.7271   -1.3078    1.3686   -1.1266   -1.1000


B =

   (1,1)       0.5777
   (2,1)       1.6701
   (3,1)      -0.2803
   (4,1)       0.9819
   (5,1)      -0.7271
   (1,2)       0.7453
   (2,2)      -0.5550
   (3,2)      -0.0587
   (4,2)       0.8630
   (5,2)      -1.3078
   (1,3)      -0.6938
   (2,3)       0.7174
   (3,3)      -0.5504
   (4,3)      -1.7900
   (5,3)       1.3686
   (1,4)       0.2237
   (2,4)       1.4295
   (3,4)       1.5706
   (4,4)       1.4769
   (5,4)      -1.1266
   (1,5)      -1.1142
   (2,5)      -0.2797
   (3,5)       0.9893
   (4,5)      -0.7902
   (5,5)      -1.1000

If we look at the memory of the two variables, we see that the sparse datatype takes more memory, because we also have to store the coordinates.

whos
  Name      Size                    Bytes  Class

  A         5x5                       200  double array
  B         5x5                       324  double array (sparse)

Grand total is 50 elements using 524 bytes

Now, suppose we have a matrix that really is sparse. THEN the sparse matrix format has the advantage:

clear; A = zeros(100);
A(45,60) = 1;  % method 1
B = sparse(A)  % method 2
whos
B =

  (45,60)       1

  Name      Size                    Bytes  Class

  A       100x100                   80000  double array
  B       100x100                     416  double array (sparse)

Grand total is 10001 elements using 80416 bytes

We can create a sparse matrix on its own, i.e. we don't have to convert from a full matrix first. The simplest method is the following:

clear;
B = sparse(100,100); % makes an empty 100 x 100 sparse matrix
% Note: B = sparse(100) is NOT what you want.  This would make a 1 x 1 sparse
% matrix, with the (1,1) entry being 100.

B(45,60) = 4
B =

  (45,60)       4

Just like with the default matrix storage, we can "expand" a sparse matrix after we define it, and this also penalizes us in terms of memory (since we have to re-allocate space in RAM every time we extend the matrix). We can allocate memory and define a sparse matrix all at once, again using the "sparse" command. Here's an example:

clear;
A = rand(45,50);   % uniform random numbers in [0,1]
[i,j,s] = find( A .* (A < .01)  );
B = sparse(i,j,s,45,50)
B =

  (31,2)       0.0021
  (28,3)       0.0060
   (6,6)       0.0092
   (3,10)      0.0096
  (36,13)      0.0081
  (35,22)      0.0020
  (23,27)      0.0100
  (16,28)      0.0046
   (1,31)      0.0079
  (17,37)      0.0053
  (11,42)      0.0043
  (24,44)      0.0007
  (37,44)      0.0024
  (14,46)      0.0018
   (6,47)      0.0097
  (13,47)      0.0024
   (3,48)      0.0014
  (31,48)      0.0090

Some useful functions for sparse matrices (type "help sparfun" for a full list).

% Visualize the sparsity pattern.
spy(B); title('Sparsity pattern of our matrix B');
% How many non-zero entries:
fprintf('matrix B has %d nonzero entries\n', nnz(B) );
% Is the matrix stored in the sparse format?  (note: this does NOT imply
% that the matrix really is sparse, only that it is stored in the sparse
% format).
fprintf('matrix A is sparse?  %d\n', issparse(A) );
fprintf('matrix B is sparse?  %d\n', issparse(B) );
matrix B has 18 nonzero entries
matrix A is sparse?  0
matrix B is sparse?  1

Eigenvalues

Matlab has a good eigenvalue solver called "eig". This is different than "eigs" (which we will cover in a minute; the "s" stands for "sparse", and not to pluralize "eig"). "eig" does different things, depending on the number of outputs. If we only have one output, it just returns the eigenvalues in a vector. If we have two outputs, it gives the eigenvalues still (this time, as the diagonal of a matrix), as well as the eigenvectors.

clear; A = randn(4);
disp('The eigenvalues of A are:')
eig(A)
% (recall that eigenvalues of a real matrix are allowed to be complex; if
% the matrix is symmetric/Hermitian, then we're guaranteed to get real
% eignevalues).
disp('The eigenvalues of A are on the diagonal of matrix D');
disp('  and the eigenvectors are the columns of matrix V');
[V,D] = eig(A)

% If you don't need the eigenvectors, don't ask for them!  It will slow
% down the computation.
The eigenvalues of A are:

ans =

   3.0443          
  -2.1144          
  -0.2020 + 0.5391i
  -0.2020 - 0.5391i

The eigenvalues of A are on the diagonal of matrix D
  and the eigenvectors are the columns of matrix V

V =

  -0.4563             0.3481            -0.2545 - 0.1124i  -0.2545 + 0.1124i
   0.8764             0.6943            -0.0719 - 0.1372i  -0.0719 + 0.1372i
  -0.1517             0.6295             0.8222             0.8222          
   0.0242             0.0240             0.4003 - 0.2497i   0.4003 + 0.2497i


D =

   3.0443                  0                  0                  0          
        0            -2.1144                  0                  0          
        0                  0            -0.2020 + 0.5391i        0          
        0                  0                  0            -0.2020 - 0.5391i

The command "eigs" finds only the few largest (or smallest) eigenvalues of a matrix. It uses an entirely different kind of algorithm; this algorithm is very well-suited to sparse matrices. Here, we'll demonstrate with a very large sparse matrix.

B = sprandsym(1000,.01);    % this is a 1000 x 1000 sparse symmetric matrix
                            % 1% of its entries are randomly selected to
                            % be nonzero; on those entries, the value is
                            % chosen from a uniform distribution.
spy(B); title('Sparsity pattern of the random symmetric matrix');
% Now, ask for the top and bottom 8 eigenvalues:
% (by "top", we mean those with the greatest magnitued; remember, the
% singular values may be complex)
opts = []; opts.disp = 0;  % tell the algorithm to be quiet!
opts.maxiter = 1500;       % tell the algorithm to be patient
disp('largest eigenvalues:')
D = eigs(B,8,'LM',opts)
disp('smallest eigenvalues:')
D = eigs(B,8,'SM',opts)

% Note: you can use this command to also get the eigenvectors, not just the
% eigenvalues
largest eigenvalues:

D =

    7.3641
   -7.1307
   -6.9764
    6.9301
    6.8860
   -6.8477
    6.7235
   -6.7186

smallest eigenvalues:

D =

    0.0207
    0.0149
    0.0030
   -0.0018
   -0.0073
   -0.0129
   -0.0181
   -0.0204

To see the biggest/smallest eigenvalues, in terms of the number and not the absolute value (this only works of eigenvalues are real, i.e. the matrix is symmetric), we use "LA" and "SA" instead of "LM" and "SM"

disp('largest eigenvalues:')
D = eigs(B,8,'LA',opts)
disp('smallest eigenvalues:')
D = eigs(B,8,'SA',opts)
largest eigenvalues:

D =

    7.3641
    6.9301
    6.8860
    6.7235
    6.6404
    6.5884
    6.5498
    6.5121

smallest eigenvalues:

D =

   -7.1307
   -6.9764
   -6.8477
   -6.7186
   -6.6361
   -6.5858
   -6.5508
   -6.5229

Singular Value Decomposition (SVD)

The SVD is vaguely like a a generalization of the eigenvalue decomposition; it gives slightly different information, and it is also more general (i.e. it applies to non-square matrices). We factor a matrix A as the product of three matrices: A = U * S * V'. S need not be square, but it is zero except on the diagonal. There are important variations, called the "thin" or "compact" svd. The diagonal entries of S are called the singular values, and they are always real and positive. By convention, they are always listed in decreasing order. In Matlab, the function to generate the SVD is called "svd()", and there is also a sparse version ("svds()"). Like "eig", we can ask for the singular values only, or the singular values and the singular vectors (the vectors refer to U and V).

clear; A = randn(5);
disp('The singular values of A are:')
s = svd(A)
disp('The singular values and singular vectors of A are:');
[U,S,V] = svd(A)
fprintf('Norm of A - U*S*V'' is %.3e\n', norm( A - U*S*V') )
The singular values of A are:

s =

    3.2378
    2.5077
    1.4542
    0.7395
    0.3905

The singular values and singular vectors of A are:

U =

    0.3517    0.1627   -0.1112    0.5941   -0.6961
   -0.0600    0.3884   -0.4855   -0.6567   -0.4225
    0.8330   -0.4254   -0.1979   -0.2638    0.1280
    0.1207   -0.0322    0.8235   -0.3800   -0.4024
    0.4052    0.8004    0.1862    0.0425    0.3983


S =

    3.2378         0         0         0         0
         0    2.5077         0         0         0
         0         0    1.4542         0         0
         0         0         0    0.7395         0
         0         0         0         0    0.3905


V =

    0.0228   -0.2760   -0.1907   -0.9390   -0.0720
    0.7186    0.6327    0.0598   -0.1630   -0.2304
   -0.5086    0.5408   -0.6232   -0.0260   -0.2446
   -0.2752    0.4663    0.3626   -0.2716    0.7082
   -0.3855    0.1168    0.6635   -0.1312   -0.6167

Norm of A - U*S*V' is 2.221e-15

The SVD is a robust method to determine the rank of a matrix. The rank is simply the number of nonzero singular values. In practice, there is a little difficulty because we may find tiny singular valies (1e-15) that are not quite zero; do these count or not? In general, Matlab will apply some thresholding (probably after normalizing by the largest singular value).

clear; T = randn(5,2);  A = T*T';  % A is a 5 x 5 matrix, with rank 2
disp('The singular values of A are:')
s = svd(A)
disp('The rank of A is:')
r = rank(A)
The singular values of A are:

s =

    3.8765
    1.5813
    0.0000
    0.0000
    0.0000

The rank of A is:

r =

     2

The sparse version, "svds", works very similarly to "eigs". It is not one of Matlab's finest algorithms, from a numerical analysis point-of-view. It simply calls "eigs" on a modified version of the matrix.

If you are interested in how the SVD or eigenvalue decomposition is performed, for the non-sparse case, Cleve Moler has a fantastic visual demonstration (for ACM 106, this is vey useful): http://www.mathworks.com/moler/ncm/eigsvdgui.m

The SVD is a key ingredient in many techniques, such as Principal Component Analysis (PCA) and Latent Semantic Analysis (LSA). Cleve Moler's textbook includes a very nice routine that demonstrates the use of the SVD in approximating matrices (in this case, the matrices are pictures). This could lead to a good project... The following file can be found at: http://www.mathworks.com/moler/ncm/imagesvd.m

imagesvd

Numerical Integration, aka quadrature

Numerical integration is the process of approximating the value of an integral. We'll only cover definite integrals with finite endpoints, but it can be adapted for infinite endpoints. The general technique, called quadrature (not to be confused with other meanings of "quadrature"), is to sample the function f at a few points, interpolate these points piece-wise with a polynomial of degree n, integrate this polynomial exactly, and return this as our approximation. The interpolation and exact integration can be combined into one step, so no actual interpolation really happens. It is also not necessary to interpolate with a plain polynomial, but we will not cover this.

The simplest case is using polynomials of degree n = 0, i.e. use a piece-wise constant approximation. This is basically a Riemann sum. We can use linear polynomials to get the midpoint rule.

Matlab can do all this for us, and it will chose gridpoints adaptively. The workhorse routine is "quad()", but there are other variants (type "help funfun" and see the quadrature section). Simpler methods are "trapz" and using "sum". Here's an example:

f = @(x) exp(-x);   % this is the integrand
a = 0;              % The integral is from a to b
b = 3;
integralExact = 1 - exp(-3);
fprintf('Exact integral is %.4f\n', integralExact );
% Use a very basic method, with N points
for N = [50, 100, 200, 1000, 10000]
    RiemannSum = sum( f( linspace(a,b,N) ) ) *(b-a) / N;
    fprintf('Riemann sum, with N = %5d, gives %.4f\n',N,RiemannSum);
end
% Use another very basic method, with N points
for N = [50, 100, 200, 1000, 10000]
    trapRule = trapz( f( linspace(a,b,N) ) ) *(b-a) / N;
    fprintf('Trapezoidal Rule, with N = %5d, gives %.4f\n',N,trapRule);
end
% and use a smarter method:
[adaptiveSimpson,N] = quad(f,a,b);
fprintf('Using matlab''s "quad", with N = %d, gives %.4f\n',N,adaptiveSimpson);
Exact integral is 0.9502
Riemann sum, with N =    50, gives 0.9630
Riemann sum, with N =   100, gives 0.9565
Riemann sum, with N =   200, gives 0.9534
Riemann sum, with N =  1000, gives 0.9508
Riemann sum, with N = 10000, gives 0.9503
Trapezoidal Rule, with N =    50, gives 0.9315
Trapezoidal Rule, with N =   100, gives 0.9408
Trapezoidal Rule, with N =   200, gives 0.9455
Trapezoidal Rule, with N =  1000, gives 0.9493
Trapezoidal Rule, with N = 10000, gives 0.9501
Using matlab's "quad", with N = 25, gives 0.9502

dialog boxes, etc.

Matlab can popup dialog boxes (see "dialog" or "msgbox"). Here's an example of a waitbar -- it graphically displays a sliding counter.

h = waitbar(0,'Computing...');
for i = 1:300
    pause(.01);         % wait .01 seconds
    % (usually, a lengthy computation goes here)
    waitbar(i/300,h);
end
close(h);

For other length computations that update a graph at every step, use the command "drawnow" afer a "plot" command to force the graph to refresh, otherwise Matlab might wait to draw the graph until all the computation is done. For the most efficient way to update a graph, change the XData using handle graphics, or link the variables (see last lecture for how to do this).

"while" statement; "break"

The "while" statement is very similar to a "for" statement, and usually you can interchange the two (with appropriate modifications), but sometimes one or the other makes more sense. The idea is that the code is executed UNTIL some condition stops. The format is:

while [test] [body of code] end

and it will execute until the test if false. The simplest example is like a "for" loop:

clc;
j = 1;
while j < 10
    j = j + 1;
end
fprintf('j is now %d\n', j)
j is now 10

The example above doesn't take full advantage of a "while" statement. In general, the body of the code would something very complicated. Here's an example

seed = 0;
mn = 0;
while (mn < .6) && (seed < 10000)
    seed = seed + 1;
    rand('state',seed);
    mn = mean( rand(15,1) );
end
fprintf('Took %d tries to find a random vector with mean >= .6\n',seed);
Took 44 tries to find a random vector with mean >= .6

Here's how we can do the same using a "for" loop, with a "break" statement. Whenever a "break" statement is called, the most local "for" loop will quite.

for seed = 1:10000
    rand('state',seed);
    mn = mean( rand(15,1) );
    if mn >= .6
        break;
    end
	seed = seed + 1;
end
fprintf('Took %d tries to find a random vector with mean >= .6\n',seed);
Took 44 tries to find a random vector with mean >= .6

Dealing with errors

There are two things we'd like to do with errors: (1) when we write our own code, we might want to "throw" an error, and (2) when Matlab generates an error, we might want to do something special. First, let's cover (1). When we throw an error, execution will stop. The simplest way to throw an error is just using "error('message')"

% error('This is an error message')

We can also give an ID to the message (this is useful if some program is going to do something about the error):

% error('lectureNotes:ErrMsg1','Another error message')

For objective (2), here's how to see some information about an error. We use the function "lasterror" as follows:

errInfo = lasterror
% The "stack" field is itself a structure,and contains information about
% where the error occurred (e.g. which file, which function, and which
% line).  This doesn't work when the code was executed in "cell mode"!
errInfo = 

       message: ''
    identifier: ''
         stack: [0x1 struct]

Also as a part of objectve (2), we can handle Matlab's errors using "try" and "catch". Here's how:

try
    size( thisVariableDoesNotExist );   % because the variable doesn't exist,
                                        % Matlab will generate an error.
catch
    % If an error is thrown, then the code here will be executed:
    errInfo = lasterror;
    fprintf('Message identifier is: %s\n',errInfo.identifier);
    fprintf('Message string is: %s\n',errInfo.message);
    % and if we want to le the error continue, we can "rethrow" it:
    rethrow( lasterror );
end
Error using ==> evalin
Undefined function or variable 'thisVariableDoesNotExist'.