From matrix to insight Tobin A. Driscoll Richard J. Braun
Any two-dimensional array of numbers may be interpreted as a matrix. Whether or not this is the only point of view that matters to a particular application, it does lead to certain types of analysis. The related mathematical and computational tools are universally applicable and find diverse uses.
7.1.1 Tables as matrices ¶ Tables are used to represent variation of a quantity with respect to two variables. These variables may be encoded as the rows and columns of a matrix.
A corpus is a collection of text documents. A term-document matrix has one column for each document and one row for each unique term appearing in the corpus. The ( i , j ) (i,j) ( i , j ) entry of the matrix is the number of times term i i i appears in document j j j . That is, column j j j of the matrix is a term-frequency vector quantifying all occurrences of the indexed terms. A new document could be represented by its term-frequency vector, which is then comparable to the columns of the matrix. Or, a new term could be represented by counting its appearances in all of the documents and be compared to the rows of the matrix.
It turns out that by finding the singular value decomposition of the term-document matrix, the strongest patterns within the corpus can be isolated, frequently corresponding to what we interpret as textual meaning. This is known as latent semantic analysis.
Each vote cast in the U. S. Congress is available for download . We can put members of Congress along the columns of a matrix and bills along the rows, recording a number that codes for “yea,” “nay,” “none,” etc. The singular value decomposition can reveal an objective, reproducible analysis of the partisanship and cooperation of individual members.
In 2006 the online video service Netflix started an open competition for a $1 million prize. They provided a data set of 100,480,507 ratings (one to five stars) made by 480,189 users for 17,770 movies. Each rating is implicitly an entry in a 17,770-by-480,189 matrix. The object of the prize was to predict a user’s ratings for movies they had not rated. This is known as a matrix completion problem. (It took 6 days for a contestant to improve on Netflix’s private algorithm, and in 2009 the million-dollar prize was awarded to a team that had improved the performance by over 10%.)
7.1.2 Graphs as matrices ¶ Definition 7.1.1 (Graphs and adjacency matrices)
A graph or network consists of a set V V V of nodes and a set E E E of edges , each of which is an ordered pair of nodes. If there is an edge ( v i , v j ) (v_i,v_j) ( v i , v j ) , then we say that node i i i is adjacent to node j j j . The graph is undirected if for every edge ( v i , v j ) (v_i,v_j) ( v i , v j ) , the pair ( v j , v i ) (v_j,v_i) ( v j , v i ) is also an edge; otherwise the graph is directed .
The adjacency matrix of a graph with n n n nodes V V V and edge set E E E is the n × n n\times n n × n matrix whose elements are
A i j = { 1 if ( v i , v j ) ∈ E (i.e., node i is adjacent to node j ) , 0 otherwise . A_{ij} =
\begin{cases}
1 & \text{if $(v_i,v_j)\in E$ (i.e., node $i$ is adjacent to node $j$)},\\
0 & \text{otherwise}.
\end{cases} A ij = { 1 0 if ( v i , v j ) ∈ E (i.e., node i is adjacent to node j ) , otherwise . In an undirected graph, the edges ( v i , v j ) (v_i,v_j) ( v i , v j ) and ( v j , v i ) (v_j,v_i) ( v j , v i ) are equivalent and may be identified as a single edge, depending on the context.
Graphs are a useful way to represent the link structure of social networks, airline routes, power grids, sports teams, and web pages, to name a few examples. The natural interpretation is that the edge ( v i , v j ) (v_i,v_j) ( v i , v j ) denotes a link from node i i i to node j j j , in which case we say that node i i i is adjacent to node j j j . One usually visualizes small graphs by drawing points for nodes and arrows or lines for the edges.
Here are some elementary results about adjacency matrices.
Part 1 follows immediately from the definitions. Part 2 is clearly true for k = 1 k=1 k = 1 . Assume inductively that it is true for k − 1 k-1 k − 1 . Each walk of length k k k from node i i i to node j j j must be a walk of length k − 1 k-1 k − 1 from i i i to node p p p , then a walk of length 1 from node p p p to node j j j . The total number of such walks is therefore
∑ p = 1 n [ A k − 1 ] i p ⋅ A p j , \sum_{p=1}^n [\mathbf{A}^{k-1}]_{ip} \cdot A_{pj}, p = 1 ∑ n [ A k − 1 ] i p ⋅ A p j , which is the ( i , j ) (i,j) ( i , j ) element of A k \mathbf{A}^k A k .
Example 7.1.4 (Adjacency matrix)
Example 7.1.4 Here we create an adjacency matrix for a graph on four nodes.
A = [0 1 0 0; 1 0 0 0; 1 1 0 1; 0 1 1 0]
4×4 Matrix{Int64}:
0 1 0 0
1 0 0 0
1 1 0 1
0 1 1 0
The graphplot
function makes a visual representation of this graph.
using Plots, GraphRecipes
graphplot(A, names=1:4, markersize=0.2, arrow=6)
Since this adjacency matrix is not symmetric, the edges are all directed, as indicated by the arrows. Here are the counts of all walks of length 3 in the graph:
4×4 Matrix{Int64}:
0 1 0 0
1 0 0 0
3 2 0 1
1 3 1 0
If the adjacency matrix is symmetric, the result is an undirected graph: all edges connect in both directions.
A = [0 1 1 0; 1 0 0 1; 1 0 0 0; 0 1 0 0]
graphplot(A, names=1:4, markersize=0.2)
Example 7.1.4 Here we create an adjacency matrix for a graph on four nodes.
A = [0 1 0 0; 1 0 0 0; 1 1 0 1; 0 1 1 0]
Since this adjacency matrix is not symmetric, the edges are all directed. We use digraph
to create a directed graph.
Here are the counts of all walks of length 3 in the graph:
If the adjacency matrix is symmetric, the result is an undirected graph: all edges connect in both directions.
A = [0 1 1 0; 1 0 0 1; 1 0 0 0; 0 1 0 0];
plot(graph(A))
A “buckyball” is an allotrope of carbon atoms with the same connection structure as a soccer ball.
Example 7.1.4 Here we create an adjacency matrix for a graph on four nodes.
A = array([[0, 1, 0, 0], [1, 0, 0, 0], [1, 1, 0, 1], [0, 1, 1, 0]])
print(A)
[[0 1 0 0]
[1 0 0 0]
[1 1 0 1]
[0 1 1 0]]
The networkx
package has many functions for working with graphs. Here, we instruct it to create a directed graph from the adjacency matrix, then make a drawing of it.
import networkx as nx
G = nx.from_numpy_array(A, create_using=nx.DiGraph)
nx.draw(G, with_labels=True, node_color="yellow")
Here are the counts of all walks of length 3 in the graph:
print(linalg.matrix_power(A, 3))
[[0 1 0 0]
[1 0 0 0]
[3 2 0 1]
[1 3 1 0]]
If the adjacency matrix is symmetric, the result is an undirected graph: all edges connect in both directions.
A = array([[0, 1, 1, 0], [1, 0, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0]])
G = nx.from_numpy_array(A, create_using=nx.Graph)
nx.draw(G, with_labels=True, node_color="yellow")
The representation of a graph by its adjacency matrix opens up the possibility of many kinds of analysis of the graph. One might ask whether the nodes admit a natural partition into clusters, for example. Or one might ask to rank the nodes in order of importance to the network as determined by some objective criteria—an application made famous by Google’s PageRank algorithm, and one which is mathematically stated as an eigenvalue problem.
7.1.3 Images as matrices ¶ Computers typically represent images as rectangular arrays of pixels, each of which is colored according to numerical values for red (R), green (G), and blue (B) components of white light. Most often, these are given as integers in the range from zero (no color) to 255 (full color). Thus, an image that is m m m -by-n n n pixels can be stored as an m m m -by-n n n -by-3 array of integer values. In Julia, we can work with an m × n m\times n m × n matrix of 3-vectors representing entire colors.
Example 7.1.5 (Images as matrices)
Example 7.1.5 The Images
package has many functions for image manipulation, and TestImages
has some standard images to play with.
using Images, TestImages
img = testimage("mandrill")
The variable img
is a matrix.
However, its entries are colors, not numbers.
You can use eltype
to find out the type of the elements of any array.
It’s possible to extract matrices of red, green, and blue intensities, scaled from 0 to 1.
R, G, B = red.(img), green.(img), blue.(img);
@show minB, maxB = extrema(B);
(minB, maxB) = extrema(B) = (0.0N0f8, 1.0N0f8)
Or we can convert the pixels to gray, each pixel again scaled from 0 to 1.
In order to do our usual operations, we need to tell Julia that we want to interpret the elements of the image matrix as floating-point values.
A = Float64.(Gray.(img))
A[1:4, 1:5]
4×5 Matrix{Float64}:
0.568627 0.219608 0.192157 0.34902 0.537255
0.454902 0.396078 0.156863 0.262745 0.352941
0.301961 0.447059 0.180392 0.180392 0.384314
0.278431 0.533333 0.372549 0.188235 0.32549
We can use Gray
to reinterpret a matrix of floating-point values as grayscale pixels.
Gray.(reverse(A, dims=1))
Example 7.1.5 MATLAB ships with a few test images to play with.
A = imread('peppers.png');
color_size = size(A)
Use imshow
to display the image.
The image has three layers or channels for red, green, and blue. We can deal with each layer as a matrix, or (as below) convert it to a single matrix indicating shades of gray from black (0) to white (255). Either way, we have to explicitly convert the entries to floating-point values rather than integers.
A = im2gray(A); % collapse from 3 dimensions to 2
gray_size = size(A)
imshow(A)
Before we can do any numerical computation, we need to convert the image to a matrix of floating-point numbers.
Example 7.1.5 We will use a test image from the well-known scikit-image
package.
from skimage import data as testimages
img = getattr(testimages, "coffee")()
imshow(img)
The variable img
is a matrix.
However, its entries are colors, not numbers.
print(f"image has shape {img.shape}")
print(f"first pixel has value {img[0, 0]}")
image has shape (400, 600, 3)
first pixel has value [21 13 8]
The three values at each pixel are for intensities of red, green, and blue. We can convert each of those layers into an ordinary matrix of values between 0 and 255, which is maximum intensity.
R = img[:, :, 0]
print("upper left corner of the red plane is:")
print(R[:5, :5])
print(f"red channel values range from {R.min()} to {R.max()}")
upper left corner of the red plane is:
[[21 21 20 21 21]
[21 21 20 22 20]
[21 23 20 21 21]
[21 21 21 21 20]
[21 21 21 21 21]]
red channel values range from 0 to 255
It may also be convenient to convert the image to grayscale, which has just one layer of values from zero (black) to one (white).
from skimage.color import rgb2gray
A = rgb2gray(img)
A[:5, :5]
print("upper left corner of grayscale:")
print(A[:5, :5])
print(f"gray values range from {A.min()} to {A.max()}")
upper left corner of grayscale:
[[0.05623333 0.05651608 0.04978902 0.05708157 0.05903882]
[0.05595059 0.05651608 0.05792275 0.05987216 0.05511725]
[0.05875608 0.05846549 0.05848824 0.05651608 0.06212706]
[0.05566784 0.05651608 0.05623333 0.05679882 0.05174627]
[0.0553851 0.05595059 0.05510235 0.05595059 0.05623333]]
gray values range from 0.0002827450980392157 to 1.0
imshow(A, cmap='gray')
axis('off');
Some changes we make to the grayscale matrix are easy to interpret visually.
imshow(flipud(A), cmap='gray')
axis('off');
Representation of an image as a matrix allows us to describe some common image operations in terms of linear algebra. For example, in Singular value decomposition we will use the singular value decomposition to compress the information, and in Matrix-free iterations we will see how to apply and remove blurring effects.
7.1.4 Exercises ¶ ✍ Consider the terms numerical , analysis , and fun . Write out the term-document matrix for the following statements:
(a) Numerical analysis is the most fun type of analysis.
(b) It’s fun to produce numerical values for the digits of pi.
(c) Complex analysis is a beautiful branch of mathematics.
✍ Here is a graph adjacency matrix.
[ 0 1 0 1 0 1 0 1 0 1 0 0 1 0 0 1 0 0 1 0 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0 ] \begin{bmatrix}
0 & 1 & 0 & 1 & 0 & 1 & 0 \\
1 & 0 & 1 & 0 & 0 & 1 & 0 \\
0 & 1 & 0 & 0 & 1 & 0 & 1 \\
1 & 0 & 0 & 0 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 & 0 & 1 & 0 \\
1 & 1 & 0 & 0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 & 0 & 0 & 0
\end{bmatrix} ⎣ ⎡ 0 1 0 1 0 1 0 1 0 1 0 0 1 0 0 1 0 0 1 0 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0 ⎦ ⎤ (a) How many vertices are adjacent to vertex 5? (Assume node numbering starts at 1.)
(b) Is the graph directed or undirected?
(c) How many edges are in the graph?
(d) Draw the graph.
⌨ Refer to Example 7.1.5 on loading and displaying images. Choose a test image of your liking.
(a) Display the test image upside-down.
(b) Display it mirror-reversed from left to right.
(c) Display the image so that it is cropped to isolate a part of the subject.
⌨ For this problem you need to download and import data:
Download actors.mat by clicking the link and saving (you may need to fix the file name).
using MAT
A = matread("actors.mat")["A"]
Download actors.mat by clicking the link and saving (you may need to fix the file name).
Download actors.mtx by clicking the link and saving (you may need to fix the file name).
import scipy.io as spio
A = spio.mmread("actors.mtx")
Based on data provided by the Self-Organized Networks Database at the University of Notre Dame, the matrix A
contains information about the appearances of 392,400 actors in 127,823 movies, as given by the Internet Movie Database . It has A i j = 1 A_{ij}=1 A ij = 1 if actor j j j appeared in movie i i i and zero elements elsewhere.
(a) What is the maximum number of actors appearing in any one movie?
(b) How many actors appeared in exactly three movies?
(c) Define C = A T A \mathbf{C}=\mathbf{A}^T\mathbf{A} C = A T A . How many nonzero entries does C \mathbf{C} C have? What is the interpretation of C i j C_{ij} C ij ?