<jupyter_start><jupyter_text>Matrix Operation<jupyter_code>import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import sparse

# https://learning.oreilly.com/library/view/machine-learning-with/9781491989371/ch01.html#<jupyter_output><empty_output><jupyter_text>Creating a MatrixTo create a matrix we can use a NumPy two-dimensional array. In our solution, the matrix contains three rows and two columns (a column of 1s and a column of 2s).**Pros and Cons:** the matrix data structure is not recommended for two reasons. First, arrays are the de facto standard data structure of NumPy. Second, the vast majority of NumPy operations return arrays, not matrix objects<jupyter_code>matrix_object = np.mat([[1, 2],
                        [1, 2],
                        [1, 2]])
matrix_object
matrix = np.array([[1, 2, 3, 1],
                   [2, 4, 6, 1],
                   [3, 8, 9, 1],
                   [10,11,12,1]])
matrix<jupyter_output><empty_output><jupyter_text>Describing Matrix<jupyter_code># View number of rows and columns
print(matrix.shape)

# View number of elements (rows * columns)
print(matrix.size)

# View number of dimensions
print(matrix.ndim)<jupyter_output>(4, 4)
16
2<jupyter_text>Determinant<jupyter_code>print(matrix)

# Determi# Return determinant of matrix
np.linalg.det(matrix)<jupyter_output>[[ 1  2  3  1]
 [ 2  4  6  1]
 [ 3  8  9  1]
 [10 11 12  1]]<jupyter_text>Diagonal Elements<jupyter_code>print(matrix)

# Return diagonal elements
matrix.diagonal()<jupyter_output>[[ 1  2  3  1]
 [ 2  4  6  1]
 [ 3  8  9  1]
 [10 11 12  1]]<jupyter_text>NumPy makes getting the diagonal elements of a matrix easy with diagonal. It is also possible to get a diagonal off from the main diagonal by using the offset parameter:<jupyter_code>print(matrix)

# Return diagonal one above the main diagonal
print(matrix.diagonal(offset=1))

# Return diagonal one below the main diagonal
print(matrix.diagonal(offset=-1))
# Another Way
np.diag(matrix)<jupyter_output><empty_output><jupyter_text>Create a Diagonal Matrix<jupyter_code>diagonal_mat1 = np.diag([1,1,1])
diagonal_mat2 = np.diag([1,2,3])<jupyter_output><empty_output><jupyter_text>Some Other Operation with Diagonal Matrix<jupyter_code># Both Produce the same result
np.inner(diagonal_mat1, diagonal_mat2)
diagonal_mat1.dot(diagonal_mat2)
np.outer(diagonal_mat1, diagonal_mat2)<jupyter_output><empty_output><jupyter_text>Trace of matrixThe trace of a matrix is the sum of the diagonal elements and is often used under the hood in machine learning methods. Given a NumPy multidimensional array, we can calculate the trace using trace. We can also return the diagonal of a matrix and calculate its sum.<jupyter_code>print(matrix)
# Return trace
print(matrix.trace())

# Return diagonal and sum elements
sum(matrix.diagonal())<jupyter_output>[[ 1  2  3  1]
 [ 2  4  6  1]
 [ 3  8  9  1]
 [10 11 12  1]]
15<jupyter_text>Transposing a Vector or MatrixTransposing is a common operation in linear algebra where the column and row indices of each element are swapped. One nuanced point that is typically overlooked outside of a linear algebra class is that, technically, a vector cannot be transposed because it is just a collection of values<jupyter_code>print(matrix.T)

vector = np.array([1, 2, 3, 4, 5, 6]).T   # no change
print(vector)

vector = np.array([[1, 2, 3, 4, 5, 6]]).T
print(vector)<jupyter_output>[[ 1  2  3 10]
 [ 2  4  8 11]
 [ 3  6  9 12]
 [ 1  1  1  1]]
[1 2 3 4 5 6]
[[1]
 [2]
 [3]
 [4]
 [5]
 [6]]<jupyter_text>Rank of a Matrix<jupyter_code>print(matrix)

# Return matrix rank
np.linalg.matrix_rank(matrix)<jupyter_output>[[ 1  2  3  1]
 [ 2  4  6  1]
 [ 3  8  9  1]
 [10 11 12  1]]<jupyter_text>Eigenvalues and Eigenvectors<jupyter_code># Calculate eigenvalues and eigenvectors
eigenvalues, eigenvectors = np.linalg.eig(matrix)
print("eigenvalues: ",eigenvalues)
print(eigenvectors)<jupyter_output>eigenvalues:  [16.69813223  1.37262985 -2.42236393 -0.64839816]
[[-0.19136692 -0.33786015  0.27061639  0.13964786]
 [-0.33730665 -0.01927195  0.15062268 -0.61113392]
 [-0.52364691  0.27123591 -0.0937311   0.52314184]
 [-0.75854917 -0.90106059 -0.9461998  -0.57735295]]<jupyter_text>Eigenvectors are widely used in machine learning libraries. Intuitively, given a linear transformation represented by a matrix, A, eigenvectors are vectors that, when that transformation is applied, change only in scale (not direction). More formally: **Av=λv**where A is a square matrix, λ contains the eigenvalues and v contains the eigenvectors. In NumPy’s linear algebra toolset, eig lets us calculate the eigenvalues, and eigenvectors of any square matrix.  Calculating Dot Products<jupyter_code># Create matrix
matrix_a = np.array([[1, 1, 1],
                     [1, 1, 1],
                     [1, 1, 2]])

# Create matrix
matrix_b = np.array([[1, 3, 1],
                     [1, 3, 1],
                     [1, 3, 8]])


# Calculate dot product
np.dot(matrix_a, matrix_b)<jupyter_output><empty_output><jupyter_text>Adding and Subtracting Matrices<jupyter_code># Add two matrices
print(np.add(matrix_a, matrix_b))

print(matrix_a + matrix_b)
# Subtract two matrices
print(np.subtract(matrix_a, matrix_b))

print(matrix_a - matrix_b)<jupyter_output>[[ 0 -2  0]
 [ 0 -2  0]
 [ 0 -2 -6]]
[[ 0 -2  0]
 [ 0 -2  0]
 [ 0 -2 -6]]<jupyter_text>Inverting a MatrixThe inverse of a square matrix, A, is a second matrix $A^{-1}$, such that: ${AA}^{-1} = I$where $I$ is the identity matrix. In NumPy we can use linalg.inv to calculate $A^{-1}$ if it exists. To see this in action, we can multiply a matrix by its inverse and the result is the identity matrix:<jupyter_code># Calculate inverse of matrix
np.linalg.inv(matrix)<jupyter_output><empty_output><jupyter_text>Creating a Sparse Matrix**Sparse matrices** only store nonzero elements and assume all other values will be zero, leading to significant computational savings. Here, we created a NumPy array with two nonzero values, then converted it into a sparse matrix. If we view the sparse matrix we can see that only the nonzero values are stored.There are a number of types of sparse matrices. However, in compressed sparse row (CSR) matrices, (1, 1) and (2, 0) represent the (zero-indexed) indices of the non-zero values 1 and 3, respectively. For example, the element 1 is in the second row and second column. We can see the advantage of sparse matrices if we create a much larger matrix with many more zero elements and then compare this larger matrix with our original sparse matrix:<jupyter_code>from scipy import sparse

matrix = np.array([[0, 0],
                   [0, 1],
                   [3, 0]])

# Create compressed sparse row (CSR) matrix
matrix_sparse = sparse.csr_matrix(matrix)

# final sparse matrix
print(matrix_sparse)
print('############ For Large Matrix ##############')

# another example
# Create larger matrix
matrix_large = np.array([[0, 0, 3, 0, 0, 0, 0, 0, 4, 0],
                         [0, 1, 0, 0, 0, 0, 0, 0, 4, 0],
                         [3, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

# Create compressed sparse row (CSR) matrix
matrix_large_sparse = sparse.csr_matrix(matrix_large)
print(matrix_large_sparse)
# Matrix Multiplications<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10401989225031082, 'fraction_numerical': 0.06147257908550905, 'mean_word_length': 4.059399021663173, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 57, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '16', 'dir': 'jupyter-structured-clean-dedup', 'id': '198280', 'license': 'MIT', 'n_tokens_mistral': 2701, 'n_tokens_neox': 2380, 'n_words': 865, 'path': 'Libraries/Numpy/matrix_operations.ipynb', 'repo_name': 'sharif-42/All_About_DS', 'size': '7239'}