Te van a enseñar esto empezando por el determinante. Yo no 🌸
La forma clásica de enseñar autovalores es plantear una ecuación con un determinante, resolver un polinomio y sacar unas raíces. Se puede hacer y no sirve para entender nada.
Vamos por el otro lado: qué pregunta contesta.
La pregunta
Una matriz, cuando multiplica a un vector, normalmente lo gira y lo estira. Le cambia la dirección y el largo.
La pregunta es: ¿hay alguna dirección que esta matriz no gire? ¿Alguna donde el vector salga apuntando exactamente igual, solo más largo o más corto?
Casi siempre las hay. Esas direcciones son los autovectores, y cuánto se estira cada una es su autovalor.
un autovector es una dirección que la matriz no gira: la deja apuntando igual y solo la estira o la encoge, y cuánto la estira es el autovalor
| Símbolo | Qué es |
|---|---|
| A | la matriz |
| v | el autovector: la dirección que no gira |
| λ | el autovalor, la letra lambda: cuánto se estira esa dirección |
Léela así: "multiplicar por la matriz hace lo mismo que multiplicar por un número". Eso es todo lo que dice, y es rarísimo que pase. Por eso las direcciones donde pasa son especiales.
Abrimos el archivo con más columnas
Este bloque necesita una tabla más ancha. Con las tres de siempre no hay nada que descomponer, porque están casi sin relación entre ellas. Así que meto dos más: el descuento y el monto final facturado.
import pandas as pd import numpy as np COLS = ['unidades', 'monto', 'descuento', 'satisfaccion', 'monto_final_facturado'] df = pd.read_csv('ventas-miss-yera.csv') for c in COLS: df[c] = pd.to_numeric(df[c], errors='coerce') M = df[COLS].dropna().to_numpy() Z = (M - M.mean(axis=0)) / M.std(axis=0) print('M.shape:', M.shape)
M.shape: (1793, 5)
Esa Z es la tabla escalada: a cada columna le resté su promedio
y la dividí por su desviación. Después de eso todas tienen media cero y
desviación uno, o sea que ninguna manda por tener números más grandes. En el capítulo 7 viste por qué eso hace falta.
La matriz que nos importa
C = np.cov(Z, rowvar=False) print(np.round(C, 4))
[[ 1.0006e+00 5.0000e-04 -1.2200e-02 -3.5600e-02 1.0000e-03] [ 5.0000e-04 1.0006e+00 -4.0000e-02 -1.4600e-02 7.3770e-01] [-1.2200e-02 -4.0000e-02 1.0006e+00 -8.4000e-03 -6.2200e-02] [-3.5600e-02 -1.4600e-02 -8.4000e-03 1.0006e+00 8.0200e-02] [ 1.0000e-03 7.3770e-01 -6.2200e-02 8.0200e-02 1.0006e+00]]
Es la matriz de covarianza: en la diagonal está cuánto varía cada columna, y fuera cuánto se mueven juntas dos columnas. Como escalamos, la diagonal sale toda cerca de uno.
Y hay un número que salta: 0.7377, entre el monto y el monto final facturado. Esas dos columnas se mueven juntas, y tiene todo el sentido del mundo, porque una es casi la otra con el descuento aplicado.
Es simétrica, como te adelanté en el capítulo 6. Guárdate eso porque es lo que hace que todo lo que viene funcione.
Sacamos las direcciones
vals, vecs = np.linalg.eigh(C) orden = np.argsort(vals)[::-1] vals, vecs = vals[orden], vecs[:, orden] print('autovalores:', np.round(vals, 4)) print('autovectores, uno por columna:') print(np.round(vecs, 4))
autovalores: [1.7482 1.0382 0.9982 0.9617 0.2565] autovectores, uno por columna: [[-0. 0.7011 0.1877 -0.6879 0.0045] [ 0.6996 0.065 -0.113 0.0399 0.7014] [-0.0969 -0.112 -0.9189 -0.365 -0.0203] [ 0.0631 -0.701 0.3252 -0.6251 0.09 ] [ 0.7051 -0.0171 -0.0432 -0.0338 -0.7068]]
Uso eigh y no eig porque la matriz es simétrica.
La versión con hache lo aprovecha, va más rápido y devuelve números reales
garantizados. Con eig a veces salen complejos por redondeo y
empieza el circo.
eigh te los da de menor a mayor, así que hay que darles la
vuelta. Eso hace el [::-1].
Comprobamos que la fórmula se cumple
v0 = vecs[:, 0] print('C @ v0 :', np.round(C @ v0, 6)) print('lambda * v0 :', np.round(vals[0] * v0, 6)) print('son iguales :', np.allclose(C @ v0, vals[0] * v0))
C @ v0 : [-8.300000e-05 1.223104e+00 -1.693240e-01 1.102990e-01 1.232683e+00] lambda * v0 : [-8.300000e-05 1.223104e+00 -1.693240e-01 1.102990e-01 1.232683e+00] son iguales : True
Multiplicar por la matriz de cinco por cinco dio exactamente lo mismo que multiplicar por el número 1.7482. Eso es un autovector.
Y ahora léelo, que es lo bueno
Mira la primera columna de los autovectores, que es la dirección más importante:
| Columna | Cuánto pesa en la dirección 1 |
|---|---|
| unidades | -0.0000 |
| monto | 0.6996 |
| descuento | -0.0969 |
| satisfaccion | 0.0631 |
| monto_final_facturado | 0.7051 |
Dos números grandes y tres cerca de cero. La dirección donde más varían tus datos es plata: monto y monto final facturado, casi con el mismo peso, y lo demás no participa.
Eso no lo sabía nadie de antemano. Lo encontró la matriz sola 🌟
Tres propiedades que hacen falta después
print('largo del autovector :', round(float(np.linalg.norm(v0)), 6)) print('v0 . v1 :', round(float(vecs[:, 0] @ vecs[:, 1]), 10)) print('traza de C :', round(float(np.trace(C)), 4)) print('suma de autovalores :', round(float(vals.sum()), 4))
largo del autovector : 1.0 v0 . v1 : -0.0 traza de C : 5.0028 suma de autovalores : 5.0028
Uno. Los autovectores vienen con largo 1. Solo marcan dirección, no distancia.
Dos. El producto punto entre dos de ellos da cero, o sea que son perpendiculares entre sí, en el sentido del capítulo 9. Cada uno mira a un sitio que los otros no miran. Eso pasa porque la matriz es simétrica, y es la razón de que la simetría importara.
Tres. Los autovalores suman lo mismo que la diagonal. La varianza total no se creó ni se destruyó: se repartió entre direcciones. Y esa frase es PCA entero, que es el capítulo que viene.
Lo que te llevas
- Un autovector es una dirección que la matriz no gira.
- El autovalor es cuánto la estira.
- Av = λv: multiplicar por la matriz hace lo mismo que multiplicar por un número.
- Para matrices simétricas usa
eigh, noeig. - Salen con largo 1 y perpendiculares entre sí.
- Los autovalores suman la varianza total: la reparten, no la inventan.
Comprueba que se entendió
Comprueba que lo tienes
La matriz de covarianza de tus 5 columnas escaladas tiene autovalores [1.75, 1.04, 1.00, 0.96, 0.26]. ¿Qué te está diciendo el último?
- Que hay una dirección donde tus datos casi no varían, o sea información repetida entre columnas
- Que una columna tiene menos datos que las otras
- Que la quinta columna es la menos importante
- Que hay un error de escala en los datos
Ejercicios
1. Una dirección cualquiera SÍ gira
Para que veas que ser autovector es raro.
C = np.cov(Z, rowvar=False) vals, vecs = np.linalg.eigh(C) cualquiera = np.array([1.0, 0.0, 0.0, 0.0, 0.0]) salida = C @ cualquiera print('entra :', cualquiera) print('sale :', np.round(salida, 4)) print('mismo sentido:', np.allclose(salida / np.linalg.norm(salida), cualquiera))
entra : [1. 0. 0. 0. 0.] sale : [ 1.0006e+00 5.0000e-04 -1.2200e-02 -3.5600e-02 1.0000e-03] mismo sentido: False
Entró apuntando solo a las unidades y salió con un poco de todo. Eso es lo normal. Los autovectores son las pocas direcciones donde no pasa.
2. El autovalor más chico y qué columnas lo forman
La dirección donde no pasa nada es la más informativa.
COLS = ['unidades', 'monto', 'descuento', 'satisfaccion', 'monto_final_facturado'] vals, vecs = np.linalg.eigh(np.cov(Z, rowvar=False)) orden = np.argsort(vals)[::-1] vals, vecs = vals[orden], vecs[:, orden] ultimo = vecs[:, -1] print('autovalor mas chico:', round(float(vals[-1]), 4)) for nombre, peso in zip(COLS, ultimo): print(f' {nombre:24} {peso:8.4f}')
autovalor mas chico: 0.2565 unidades 0.0045 monto 0.7014 descuento -0.0203 satisfaccion 0.0900 monto_final_facturado -0.7068
Léelo: monto menos monto final facturado. En esa dirección tus datos casi no se mueven, y eso quiere decir que esas dos columnas dicen casi lo mismo. Es el detector de redundancia del capítulo 8, pero fino: no te dice si sobra, te dice cuánto sobra.
3. Vuelve a armar la matriz con sus piezas
Autovalores y autovectores contienen la matriz entera.
C = np.cov(Z, rowvar=False) vals, vecs = np.linalg.eigh(C) rearmada = vecs @ np.diag(vals) @ vecs.T print('vuelve a salir C:', np.allclose(rearmada, C))
vuelve a salir C: True
Girar a las direcciones buenas, estirar cada una por su autovalor, girar de vuelta. Eso se llama diagonalizar, y es la forma de la que salen PCA y SVD. Fíjate en que no se perdió nada: es la misma matriz escrita en otro idioma.
Practica este capítulo 📓
Todo el código de arriba en un cuaderno que corre de principio a fin, y los ejercicios con una celda vacía para que los hagas tú. Se abre en Google Colab de un clic y no hay que instalar nada. Donde veas %%revisa, escribe tu respuesta y el cuaderno te dice si te salió.
¿Prefieres trabajar en tu máquina? Bájate el cuaderno de práctica o el de soluciones. Todos están también en github.com/soymissyera/MissYeraEjercicios.