# Normal Equations ### Constructing Normal Equations Consider the fixed linear model $$ \mathbf{y} = \mathbf{X\beta} + \mathbf{e}, $$ where $\mathbf{y}$ is a $n\times1$ vector of observations, $\mathbf{X}$ is a $n\times p$ known matrix, $\mathbf{\beta}$ is a $p\times 1$ vector of fixed effects, and $\mathbf{e}$ is an $n\times 1$ vector of residuals that are often assumed to be identically and independently distributed with null mean and variance $\sigma^2_e$. The normal equations for this model are $$ (\mathbf{X}'\mathbf{X})\hat{\mathbf{\beta}} = \mathbf{X}'\mathbf{y}, $$ where $\mathbf{X'X}$ is $p\times p$ and $\mathbf{X'y}$ is $p\times 1$. In breeding applications, $p$ may be large, but $\mathbf{X'X}$ is often very sparse, i.e., has very few non-zero elements. Efficient algorithms take advantage of this sparse structure to reduce computing time and storage requirements. In this section, we will consider how to compute a **full-stored** $\mathbf{X'X}$ matrix and $\mathbf{X'y}$ vector. * One way to proceed would be to first build $\mathbf{X}$ and then get $\mathbf{X'X}$ by matrix multiplication. * Next, we will look more closely at a one-way model and write a simple program for constructing the normal equations for this model. #### Data Consider the following data from a hypothetical one-way experiment with four levels of one factor. ```julia using DataFrames data = DataFrame(x=[1,1,2,2,2,2,3,3,4,1],y=[1.1,1.2,1.9,1.2,2.0,1.7,1.0,1.7,1.1,1.7]) ``` <table class="data-frame"><thead><tr><th></th><th>x</th><th>y</th></tr><tr><th></th><th>Int64</th><th>Float64</th></tr></thead><tbody><p>10 rows × 2 columns</p><tr><th>1</th><td>1</td><td>1.1</td></tr><tr><th>2</th><td>1</td><td>1.2</td></tr><tr><th>3</th><td>2</td><td>1.9</td></tr><tr><th>4</th><td>2</td><td>1.2</td></tr><tr><th>5</th><td>2</td><td>2.0</td></tr><tr><th>6</th><td>2</td><td>1.7</td></tr><tr><th>7</th><td>3</td><td>1.0</td></tr><tr><th>8</th><td>3</td><td>1.7</td></tr><tr><th>9</th><td>4</td><td>1.1</td></tr><tr><th>10</th><td>1</td><td>1.7</td></tr></tbody></table> ### One way model The $\mathbf{X}$ matrix for the one-way model $$ y_{ij} = \mu + \alpha_i + e_{ij} $$ is $$ \mathbf{X} = \begin{bmatrix} 1 & 1 & 0 & 0 & 0 \\ 1 & 1 & 0 & 0 & 0 \\ 1 & 0 & 1 & 0 & 0 \\ 1 & 0 & 1 & 0 & 0 \\ 1 & 0 & 1 & 0 & 0 \\ 1 & 0 & 1 & 0 & 0 \\ 1 & 0 & 0 & 1 & 0 \\ 1 & 0 & 0 & 1 & 0 \\ 1 & 0 & 0 & 0 & 1 \\ 1 & 1 & 0 & 0 & 0 \\ \end{bmatrix} $$ Note $\mathbf{X'X}$ is singular, but a solution can be obtained as follows. ### Computing X'X as the product of full-stored X' and X ```julia n = size(data,1) p = length(unique(data[:x])) X = zeros(n,p); ``` ```julia @time for i = 1:n j = data[:x][i] X[i,j] = 1.0 end ``` 0.000011 seconds (11 allocations: 352 bytes) ```julia X = [ones(n) X] ``` 10×5 Array{Float64,2}: 1.0 1.0 0.0 0.0 0.0 1.0 1.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 1.0 1.0 1.0 0.0 0.0 0.0 ```julia y = data[:y]; ``` ```julia lhs = X'X rhs = X'y; ``` ```julia sol = lhs\rhs ``` 5-element Array{Float64,1}: -0.04583333333333304 1.3791666666666667 1.745833333333333 1.3958333333333333 1.1458333333333333 #### Verify solution ```julia [lhs*sol rhs] ``` 5×2 Array{Float64,2}: 14.6 14.6 4.0 4.0 6.8 6.8 2.7 2.7 1.1 1.1 In this section, we will consider how to compute a **sparse-stored** $\mathbf{X'X}$ matrix and $\mathbf{X'y}$ vector. * One way to proceed would be to first build $\mathbf{X}$ as a sparse matrix and then get $\mathbf{X'X}$ by matrix multiplication. * Now we will use the same one-way model and write a simple program for constructing the normal equations for this model. ### Computing X'X as the product of sparse-stored X' and X ```julia using SparseArrays X = sparse(1:n,data[:x],1.0); ``` ```julia X =[ones(n) X]; ``` ```julia y = sparse(y); ``` ```julia lhs = X'X rhs = X'y; ``` ### [Supplemental Note: Computing full-stored or sparse-stored X'X without matrix multiplication](SupplementalNote/1.SupplementalNote.ipynb) # Exercise This exercise will provide an opportunity to practice your matrix calculation skills using Julia in the context of comparison of efficiencies of different ways to calculate the left-hand-side of the normal equations. Ues the @time macro to measure performance of functions below 1. Compute the left-hand-side of the normal equations as the product of full-stored $\mathbf{X}^{T}$ and $\mathbf{X}$ 2. Compute the left-hand-side of the normal equations as the product of sparse-stored $\mathbf{X}^{T}$ and $\mathbf{X}$ #### Data ```julia using Distributions,DataFrames ``` ```julia nobs,nlevels = 1_000_000,1000 x = sample(1:nlevels,nobs) α = randn(nlevels) y = [α[i] .+ randn() for i in x]; ``` ```julia data = DataFrame(x=x,y=y) first(data,5) ``` <table class="data-frame"><thead><tr><th></th><th>x</th><th>y</th></tr><tr><th></th><th>Int64</th><th>Float64</th></tr></thead><tbody><p>5 rows × 2 columns</p><tr><th>1</th><td>740</td><td>0.140606</td></tr><tr><th>2</th><td>419</td><td>-0.197064</td></tr><tr><th>3</th><td>368</td><td>-0.109038</td></tr><tr><th>4</th><td>426</td><td>-0.478222</td></tr><tr><th>5</th><td>531</td><td>0.00339639</td></tr></tbody></table> #### Answer 1. Computing X'X as product of full-stored X ```julia X = zeros(nobs,nlevels); ``` ```julia @time for i = 1:nobs j = data[:x][i] X[i,j] = 1.0 end ``` 2.637775 seconds (3.51 M allocations: 69.172 MiB, 4.34% gc time) ```julia @time lhs = X'X; ``` 10.474817 seconds (943.22 k allocations: 52.955 MiB, 0.55% gc time) #### Answer 2. Computing X'X as product of sparse-stored X ```julia using SparseArrays ii = 1:nobs @time X = sparse(ii,data[:x],1.0); ``` 0.137251 seconds (351.10 k allocations: 62.857 MiB, 8.09% gc time) ```julia @time lhs = X'X; ``` 0.322128 seconds (290.65 k allocations: 48.350 MiB, 1.94% gc time) <!--stackedit_data: eyJoaXN0b3J5IjpbNzkyNDcxMDA4XX0= -->