Proyecto de aplicación
Contents
Proyecto de aplicación¶
Cálculo de valores propios de una matriz¶
Como viste en la clase anterior, cuando tenemos un conjunto de datos con muchas variables, la matriz de covarianza es el objeto matemático que permite identificar cuáles variables están altamente correlacionadas.
Cuando dos variables están altamente correlacionadas, quiere decir que aportan básicamente la misma información del problema y eso podría indicar que solo necesitaríamos una sola de ellas. En la próxima clase veremos que esa reducción de variables se puede hacer con una técnica matemática denominada PCA (principal component analysis).
Esta técnica está basada en un concepto del álgebra de vectores y matrices, que llamamos el cálculo de los valores propios de una matriz, y en esta lectura profundizaremos sobre qué significa ese procedimiento.
Repaso de matrices¶
Las matrices en general son objetos matemáticos que tienen un cierto número de filas y columnas (estos números los denominamos dimensiones de la matriz).
Las matrices tienen una operación especial, que llamamos transponer, la cual consiste en ubicar cada fila como una columna en una nueva matriz, el resultado de una transposición se denomina matriz transpuesta y se ve así:
Así también, entre las matrices podemos definir reglas de suma y resta de forma similar a como hacemos con los números naturales o decimales, con una condición especial: ambas matrices deben tener las mismas dimensiones y cuando las dimensiones de una matriz son iguales entre ellas (# filas = # columnas) decimos que la matriz es cuadrada.
Con esto en mente resumimos lo anterior con un ejemplo de suma entre dos matrices cuadradas así:
Donde vemos que la suma de matrices se hace elemento a elemento para dar origen a otra matriz con las mismas dimensiones. Entonces para obtener el elemento de la primera fila y primera columna de la matriz suma, se suman los elementos correspondientes a cada matriz ubicados en la primera fila y primera columna:
\(1 + (-1) = 0\)
Ahora bien, las matrices también tienen una operación de multiplicación entre ellas que es más compleja de definir que la suma, sin embargo aquí vamos a desglosarla. Vamos a reemplazar los elementos numéricos de las matrices del ejemplo anterior por variables algebraicas, indicando que pueden ser cualquier número para así exponer el proceso con la mayor generalidad posible. De esta manera, vamos a definir el producto de dos matrices cuadradas de tal manera que el resultado sea otra matriz de las mismas dimensiones así:
Donde cada expresión algebraica tiene la forma de dos índices que denotan la fila y columna donde está posicionado el número, respectivamente. Y la manera de calcular cada elemento de la matriz resultante viene dado por una regla sencilla que ilustraremos con el siguiente ejemplo:
Que equivale a decir que el elemento de la fila 1 y columna 1 de la matriz resultado se calcula como la suma de los productos de los elementos de la primera fila de la primera matriz por los elementos de la primera columna de la segunda matriz
Veamos otro ejemplo para tenerlo más claro:
En este caso el elemento de la fila 1 y columna 2 de la matriz resultado se calcula como la suma de los productos de los elementos de la primera fila de la primera matriz por los elementos de la segunda columna de la segunda matriz.
En python el producto de matrices se calcula fácil usando la librería numpy:
np.matmul(A, B)
Donde, por ejemplo, una matriz:
Se escribe en python como:
A = np.array([[2, 4], [-1, 2]])
Esta definición se hizo para matrices de dos filas y dos columnas que denominamos matrices 2x2. Pero se aplica de la misma manera para matrices cuadradas de cualquier tamaño NxN. Por ahora solo nos interesará esta definición restringida para matrices cuadradas, pero en cualquier curso de álgebra de matrices podrás darte cuenta de que esta definición se puede hacer más general para matrices que no sean necesariamente cuadradas.
Ahora, así como cada número tiene su inverso, donde el inverso se define como aquel número tal que la multiplicación de ambos da 1:
En este caso ⅙ es el inverso de 6. Así también, las matrices también pueden tener su inversa (aunque no siempre), la matriz inversa A-1de una matriz dada A se define como aquella matriz donde (supongamos que A es 2x2):
El cálculo de matriz inversa se hace rápido usando NumPy nuevamente así:
A = np.array([[2, 4], [-1, 2]])
Ainversa = np.linalg.inv(A)
Donde, el resultado de Ainversa en este caso particular sería:
array([[ 0.25 , -0.5 ],
[ 0.125, 0.25 ]])
Para comprobar que esto está bien, puedes multiplicar ambas matrices para ver que da lo correcto:
np.matmul(A, Ainversa)
Repaso de Vectores¶
Como caso particular adicional a la definición anterior, consideremos el producto de una matriz cuadrada por un vector (aquí entendemos un vector como una matriz de una sola columna, también se le denomina por eso vectores columna en muchos libros de álgebra lineal) cuya longitud es igual al número de filas de la matriz así:
Donde definimos que el producto de una matriz por un vector resulta en otro vector de las mismas dimensiones (filas). Y la regla que consideramos para el caso de matrices cuadradas también aplica de manera que los elementos del vector resultante se obtendrían de multiplicar filas por columnas así:
También tenemos una operación entre vectores que denominamos el producto punto o producto interior. Normalmente al considerar esta definición se representa el primer vector como una sola fila y el segundo como una sola columna así (sigamos pensando con base en el ejemplo anterior de la matriz por el vector, pero ahora la matriz solo tiene una fila):
Y como ya te estás dando cuenta (teniendo en mente la misma definición de multiplicación de matrices) al solo haber una fila en la primera matriz y una columna en la segunda matriz, el resultado solo podrá tener un elemento y es por esto que el resultado de multiplicar dos vectores de esta manera es siempre un número:
Simplifiquemos la notación así:
Y esto nos recuerda la clásica regla de multiplicar vectores como x por x más y por y. Y si los vectores tienen más dimensiones, entonces … más z por z y así. Recuerda que la notación simplificada es porque ahora tenemos que la primera componente es el eje X del vector y la segunda componente el eje Y del vector cuando lo dibujamos en un plano cartesiano.
Ahora, cuando pensamos en multiplicar un vector por el mismo, la misma definición aplica:
Y nos damos cuenta de que esto se relaciona con el Teorema de Pitágoras al ver que el producto de un vector por el mismo nos da el cuadrado de la longitud de la flecha que representa al vector en el plano cartesiano:
Así vemos que la longitud de un vector, también conocida como norma del vector se calcula como:
Listo, con esto terminamos un repaso básico de lo mínimo de matrices y vectores para lo que viene.
Vectores y Valores propios de una matriz¶
En álgebra lineal podemos tener ecuaciones donde la incógnita es un vector, supongamos la siguiente ecuación:
Aquí A es una matriz cuadrada NxN cuyos elementos conocemos perfectamente y X es un vector columna cuyas componentes desconocemos. Aquí recordemos que multiplicar un vector por un número es simplemente multiplicar cada componente del vector por dicho número.
Entonces lo que esta ecuación nos pregunta es:
¿Existen vectores X tales que al multiplicarlos por la matriz A eso es equivalente a simplemente multiplicarlos por un número?
Si tal vector existe y está asociado a un valor específico de , entonces decimos que el vector X es un vector propio de la matriz A y x es su valor propio correspondiente.
Consideremos esto para el caso de una matriz 2 x 2, como la siguiente:
Esto se traduce en el sistema de ecuaciones (haciendo el producto matriz por vector):
Aquí entonces debemos encontrar las combinaciones de x, y e que satisfacen el sistema de ecuaciones. En general, hacer esto requiere otros conceptos más detallados del álgebra de matrices como el cálculo de determinantes y resolver ecuaciones polinomiales cuya explicación solo puede dejarse a un curso exclusivo de álgebra lineal. Pero no te preocupes, ya que podemos hacer este cálculo de manera rápida con python así:
import numpy as np
A = np.array([[1, 2], [1, 0]])
values, vectors = np.linalg.eig(A)
Donde la matriz A contiene los elementos exactos de la matriz anterior y el comando np.linalg.eig(A) lo que hace es calcular directamente los valores y vectores propios, llamados values y vectors en el código, respectivamente.
Verás que esta matriz tiene dos valores propios:
array([ 2., -1.])
Con sus respectivos vectores propios asociados:
array([[ 0.89442719, -0.70710678 ],
[ 0.4472136 , 0.70710678 ]])
Aquí es importante anotar que los vectores que entrega la función np.linalg.eig(A) son vectores columna de manera que los elementos de la primera columna de vectors corresponden con el primer valor de values y así sucesivamente. Entonces en nuestro lenguaje matemático usual, escribimos las dos soluciones como:
Puedes verificar que cada vector y su respectivo valor propio cumplen la ecuación original ejecutando cada parte así:
np.matmul(A, vectors.T[1])
Que te da como resultado:
array([ 0.70710678, -0.70710678])
Mientras que por otro lado calculando:
values[1]*vectors.T[1]
Resulta en lo mismo:
array([ 0.70710678, -0.70710678])
Donde hemos considerado el segundo vector y valor propio respectivamente tomando λ = -1 y el vector incógnita X igual a vectors.T[1].
Uno de los hechos más importantes de obtener los vectores y valores propios de una matriz es poder diagonalizarla. En general se define que una matriz A es diagonalizable si es posible escribirla como el producto de:
Donde D es una matriz diagonal (matriz donde todos los elementos por fuera de la diagonal son cero), un ejemplo de matriz diagonal sería:
Y aquí un resultado matemático bien conocido es que si una matriz es diagonalizable, la matriz D se construye colocando sus valores propios en la diagonal y la matriz P se construye colocando en cada columna el vector propio,siguiendo el mismo orden de valores propios correspondientes de la matriz D, así:
Lo importante de estudiar este procedimiento en nuestro curso, es que cuando aplicamos este cálculo de vectores y valores propios a una matriz de covarianza, los vectores representan las direcciones a lo largo de las cuales percibimos la mayor cantidad de varianza de ese conjunto de datos, donde la cantidad de varianza es proporcional al valor propio de cada vector propio.
Y es importante tener en cuenta que este procedimiento aplica para un conjunto de datos con N variables al que le corresponde una matriz de covarianza de tamaño NxN.
Ahora, el último factor importante de esta técnica es que para matrices de covarianza, sus vectores propios siempre son independientes unos de otros y esto es justamente lo que queremos en un proceso de reducción de variables, porque direcciones independientes implica que estos vectores representan nuevas variables cuya correlación es la más baja posible y así cada nueva variable es lo más representativa posible.
En álgebra lineal se dice más precisamente que los vectores propios de una matriz de covarianza son ortogonales y esto quiere decir que el producto interno de cualquier par de estos vectores siempre da como resultado cero:
Como consecuencia la matriz se denomina matriz ortogonal, y se sabe en matemáticas que la inversa de una matriz ortogonal es igual a la transpuesta, de manera que:
PCA (Principal Component Analysis)¶
Para entender el analisis de componentes principales recomendamos revisar los siguientes enlaces
2. Explicacion de vectores principales
3. Explicacion de PCA en ingles
4. Explicacion de PCA en español
5. Explicacion de PCA en ingles (2)
#Paso 1. Antes de crear las componentes principales es necesario scalar cada variable
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from sklearn.preprocessing import StandardScaler
iris = sns.load_dataset('iris')
scaler = StandardScaler()
#scalando variables del data set iris
scaled = scaler.fit_transform(
iris[['sepal_length','sepal_width','petal_length','petal_width']].values)
#necesitamos la matriz de covarianza
covariance_matrix = np.cov(scaled.T)
covariance_matrix
array([[ 1.00671141, -0.11835884, 0.87760447, 0.82343066],
[-0.11835884, 1.00671141, -0.43131554, -0.36858315],
[ 0.87760447, -0.43131554, 1.00671141, 0.96932762],
[ 0.82343066, -0.36858315, 0.96932762, 1.00671141]])
#2. Queremos ver visualmente cuales variables tienen buena correlacion
sns.pairplot(iris)
#podemos ver que petal_width vs petal_lenght tiene buena correlacion
<seaborn.axisgrid.PairGrid at 0x1b8af9382b0>
# 3. Veamos las dos variables con los datos originales y escalados (standarizados)
sns.jointplot(x=iris['petal_length'], y=iris['petal_width'])
sns.jointplot(x=scaled[:,2], y= scaled[:,3])
<seaborn.axisgrid.JointGrid at 0x1b8b6443250>
# 4. Cuando verificamos que estan escalados y tienen correlacion entonces sacamos los
# valores propios y vectores propios
eigen_values, eigen_vectors = np.linalg.eig(covariance_matrix)
#usamos la libreria de numpy que devuelve los dos (vect y valores)
eigen_values
array([2.93808505, 0.9201649 , 0.14774182, 0.02085386])
eigen_vectors
array([[ 0.52106591, -0.37741762, -0.71956635, 0.26128628],
[-0.26934744, -0.92329566, 0.24438178, -0.12350962],
[ 0.5804131 , -0.02449161, 0.14212637, -0.80144925],
[ 0.56485654, -0.06694199, 0.63427274, 0.52359713]])
Aca es importante volver a mencionar que estos son los valores que capturan la mayor cantidad de datos. Es a traves de estos vectores y valores propios que se pueden simplificar los valores iniciales. Recordemos que una matriz NxN devuelve N valores propios y una matriz NxN con vectores propios.
En resumen cada uno de estos vectores
# 5. Podemos verificar en porcentaje que el primer valor propio captura el 72% de los valores y el segundo valor propio
# captura el 22% por lo tanto con estos dos valores podemos captura aprox el 95% siendo suficiente estos dos
variance_explained =[]
for i in eigen_values:
variance_explained.append((i/sum(eigen_values)))
variance_explained
[0.7296244541329987,
0.22850761786701757,
0.03668921889282883,
0.0051787091071548025]
En conclusion estos valores propios nos dicen que podemos resumir en dos dimensiones nuestra matriz NxN; en esta ocasion es 4x4.
Ahora como sabemos que solo son necesario 2 componentes podemos usar Scikit Learn para reducir por PCA las dimensiones.
# 6. Fijamos el nivel de componentes deseadas es decir a cuantas dimensiones queremos resumir la original
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
#Aca utilizamos la matriz escalada
pca.fit(scaled)
PCA(n_components=2)
# Podemos verificar el ratio de porcentaje capturado que es semejante al comprobado anteriormente
#por lo tanto validamos lo doble validamos
pca.explained_variance_ratio_
array([0.72962445, 0.22850762])
# 7. Ahora si reducimos los datos
# Como vemos ahora solo tenemos dos dimensiones que capturan el 72 y 22 % del dataset original.
# Sobrepasamos el 90% por lo tanto es aceptable
reduced_scaled = pca.transform(scaled)
reduced_scaled
array([[-2.26470281, 0.4800266 ],
[-2.08096115, -0.67413356],
[-2.36422905, -0.34190802],
[-2.29938422, -0.59739451],
[-2.38984217, 0.64683538],
[-2.07563095, 1.48917752],
[-2.44402884, 0.0476442 ],
[-2.23284716, 0.22314807],
[-2.33464048, -1.11532768],
[-2.18432817, -0.46901356],
[-2.1663101 , 1.04369065],
[-2.32613087, 0.13307834],
[-2.2184509 , -0.72867617],
[-2.6331007 , -0.96150673],
[-2.1987406 , 1.86005711],
[-2.26221453, 2.68628449],
[-2.2075877 , 1.48360936],
[-2.19034951, 0.48883832],
[-1.898572 , 1.40501879],
[-2.34336905, 1.12784938],
[-1.914323 , 0.40885571],
[-2.20701284, 0.92412143],
[-2.7743447 , 0.45834367],
[-1.81866953, 0.08555853],
[-2.22716331, 0.13725446],
[-1.95184633, -0.62561859],
[-2.05115137, 0.24216355],
[-2.16857717, 0.52714953],
[-2.13956345, 0.31321781],
[-2.26526149, -0.3377319 ],
[-2.14012214, -0.50454069],
[-1.83159477, 0.42369507],
[-2.61494794, 1.79357586],
[-2.44617739, 2.15072788],
[-2.10997488, -0.46020184],
[-2.2078089 , -0.2061074 ],
[-2.04514621, 0.66155811],
[-2.52733191, 0.59229277],
[-2.42963258, -0.90418004],
[-2.16971071, 0.26887896],
[-2.28647514, 0.44171539],
[-1.85812246, -2.33741516],
[-2.5536384 , -0.47910069],
[-1.96444768, 0.47232667],
[-2.13705901, 1.14222926],
[-2.0697443 , -0.71105273],
[-2.38473317, 1.1204297 ],
[-2.39437631, -0.38624687],
[-2.22944655, 0.99795976],
[-2.20383344, 0.00921636],
[ 1.10178118, 0.86297242],
[ 0.73133743, 0.59461473],
[ 1.24097932, 0.61629765],
[ 0.40748306, -1.75440399],
[ 1.0754747 , -0.20842105],
[ 0.38868734, -0.59328364],
[ 0.74652974, 0.77301931],
[-0.48732274, -1.85242909],
[ 0.92790164, 0.03222608],
[ 0.01142619, -1.03401828],
[-0.11019628, -2.65407282],
[ 0.44069345, -0.06329519],
[ 0.56210831, -1.76472438],
[ 0.71956189, -0.18622461],
[-0.0333547 , -0.43900321],
[ 0.87540719, 0.50906396],
[ 0.35025167, -0.19631173],
[ 0.15881005, -0.79209574],
[ 1.22509363, -1.6222438 ],
[ 0.1649179 , -1.30260923],
[ 0.73768265, 0.39657156],
[ 0.47628719, -0.41732028],
[ 1.2341781 , -0.93332573],
[ 0.6328582 , -0.41638772],
[ 0.70266118, -0.06341182],
[ 0.87427365, 0.25079339],
[ 1.25650912, -0.07725602],
[ 1.35840512, 0.33131168],
[ 0.66480037, -0.22592785],
[-0.04025861, -1.05871855],
[ 0.13079518, -1.56227183],
[ 0.02345269, -1.57247559],
[ 0.24153827, -0.77725638],
[ 1.06109461, -0.63384324],
[ 0.22397877, -0.28777351],
[ 0.42913912, 0.84558224],
[ 1.04872805, 0.5220518 ],
[ 1.04453138, -1.38298872],
[ 0.06958832, -0.21950333],
[ 0.28347724, -1.32932464],
[ 0.27907778, -1.12002852],
[ 0.62456979, 0.02492303],
[ 0.33653037, -0.98840402],
[-0.36218338, -2.01923787],
[ 0.28858624, -0.85573032],
[ 0.09136066, -0.18119213],
[ 0.22771687, -0.38492008],
[ 0.57638829, -0.1548736 ],
[-0.44766702, -1.54379203],
[ 0.25673059, -0.5988518 ],
[ 1.84456887, 0.87042131],
[ 1.15788161, -0.69886986],
[ 2.20526679, 0.56201048],
[ 1.44015066, -0.04698759],
[ 1.86781222, 0.29504482],
[ 2.75187334, 0.8004092 ],
[ 0.36701769, -1.56150289],
[ 2.30243944, 0.42006558],
[ 2.00668647, -0.71143865],
[ 2.25977735, 1.92101038],
[ 1.36417549, 0.69275645],
[ 1.60267867, -0.42170045],
[ 1.8839007 , 0.41924965],
[ 1.2601151 , -1.16226042],
[ 1.4676452 , -0.44227159],
[ 1.59007732, 0.67624481],
[ 1.47143146, 0.25562182],
[ 2.42632899, 2.55666125],
[ 3.31069558, 0.01778095],
[ 1.26376667, -1.70674538],
[ 2.0377163 , 0.91046741],
[ 0.97798073, -0.57176432],
[ 2.89765149, 0.41364106],
[ 1.33323218, -0.48181122],
[ 1.7007339 , 1.01392187],
[ 1.95432671, 1.0077776 ],
[ 1.17510363, -0.31639447],
[ 1.02095055, 0.06434603],
[ 1.78834992, -0.18736121],
[ 1.86364755, 0.56229073],
[ 2.43595373, 0.25928443],
[ 2.30492772, 2.62632347],
[ 1.86270322, -0.17854949],
[ 1.11414774, -0.29292262],
[ 1.2024733 , -0.81131527],
[ 2.79877045, 0.85680333],
[ 1.57625591, 1.06858111],
[ 1.3462921 , 0.42243061],
[ 0.92482492, 0.0172231 ],
[ 1.85204505, 0.67612817],
[ 2.01481043, 0.61388564],
[ 1.90178409, 0.68957549],
[ 1.15788161, -0.69886986],
[ 2.04055823, 0.8675206 ],
[ 1.9981471 , 1.04916875],
[ 1.87050329, 0.38696608],
[ 1.56458048, -0.89668681],
[ 1.5211705 , 0.26906914],
[ 1.37278779, 1.01125442],
[ 0.96065603, -0.02433167]])
# 8. Lo que nos interesa con estas dos dimensiones es introducirlas a un modelo de ML para entrenamiento
# Pero entonces... como se evidencia los puntos graficamente por categorias
iris['pca_1'] = reduced_scaled[:,0]
iris['pca_2'] = reduced_scaled[:,1]
sns.jointplot(x=iris['pca_1'], y=iris['pca_2'], hue=iris['species'])
<seaborn.axisgrid.JointGrid at 0x1b8b788c0a0>
Con esto vemos un grafico de dispersion reducido de 4 dimensiones a 2 dimensiones con una perdida de informacion de solo el 5%. Y en esto consiste el metodo de PCA. Revisar por favor antes la literatura y recursos compartidos.