Capítulo 1 de 21 16 secciones 13 min

Compartir

Tus datos ya eran una matriz y nadie te lo había dicho

Qué es una matriz, qué es una fila, qué es una columna, y por qué el archivo de ventas que abres todos los días ya es una.

Una matriz es una tabla de números con filas y columnas, y nada más. Si abriste alguna vez un Excel de ventas, ya trabajaste con una: cada fila es una venta y cada columna es algo que mediste. En machine learning se escribe X, sus filas son los ejemplos y sus columnas son las características.

Hola! Empezamos por lo que ya sabes 🌸

Te voy a hacer una promesa antes de nada: en este libro no hay ni un ejemplo de peras y manzanas. Todo sale del mismo archivo de ventas que usamos en los otros libros, porque lo que quiero es que veas que la matemática ya estaba ahí, metida en lo que haces todos los días, y nadie te lo había dicho con ese nombre.

Vamos a abrirlo.

Abre el archivo y mira la forma

import pandas as pd
import numpy as np

df = pd.read_csv('ventas-miss-yera.csv')
print('shape:', df.shape)
shape: (3037, 14)

Ese (3037, 14) es lo primero que quiero que mires. Son 3.037 filas y 14 columnas. Y eso, dicho en el idioma de este libro, es una matriz de 3.037 por 14.

Ya está. Ese es el concepto entero. Una matriz es una tabla de números con filas y columnas 🙂

Nos quedamos con los números

Digo "de números" a propósito, porque tu archivo tiene columnas de texto como la ciudad y el canal. Una matriz de verdad es solo números, así que nos quedamos con tres columnas:

for c in ('unidades', 'monto', 'satisfaccion'):
    df[c] = pd.to_numeric(df[c], errors='coerce')

X = df[['unidades', 'monto', 'satisfaccion']].dropna().to_numpy()
print('shape:', X.shape, '| dtype:', X.dtype, '| ndim:', X.ndim)
shape: (2238, 3) | dtype: float64 | ndim: 2

Fíjate en el to_numeric. Sin esa línea el monto llega como texto, porque el archivo trae suciedad a propósito, y entonces dtype sale object en vez de float64. Eso lo trabajamos entero en el capítulo de auditoría de tipos del libro de machine learning; acá solo lo arreglamos para poder seguir.

El ndim: 2 es la palabra técnica: una matriz tiene dos dimensiones. Una sola columna sería de una dimensión y se llama vector, que es el capítulo que viene. Tres dimensiones sería un tensor, y eso lo vas a ver cuando llegues a las imágenes en Deep learning.

Una matriz X de 2.238 filas por 3 columnas: cada fila es una venta con sus unidades, su monto y su satisfacción, cada columna es una de esas tres cosas medida en todas las ventas, y el cruce de una fila con una columna es un solo número.
Una fila es un ejemplo y una columna es una característica. En machine learning ese acuerdo se da por hecho y casi nunca se dice, y es el que hay que tener claro antes de nada.

Cómo se escribe esto en un paper

Cuando abras cualquier libro de machine learning te vas a topar con esto:

Xn×d

X es una tabla de n filas por d columnas, y todo lo que hay dentro son números reales

Y ahora ya lo puedes leer. La X es tu tabla. La n son tus 2.238 ventas. La d son tus 3 columnas. Lo de solo dice que dentro hay números reales, o sea números con decimales, no palabras.

SímboloQué es
Xla matriz entera, tu tabla de datos
ncuántas filas tiene, o sea cuántos ejemplos
dcuántas columnas tiene, o sea cuántas características
xijun número suelto: el de la fila i, columna j

Y una cosa que a mí me costó y por eso te la digo: en machine learning casi siempre las filas son los ejemplos y las columnas son las características. No es una ley del universo, es un acuerdo. Pero si te encuentras un paper donde está al revés, te lo va a decir.

Las tres formas de cortar una matriz: X[0] devuelve la fila entera con forma (3,), X[:, 1] devuelve la columna entera con forma (2238,), y X[0, 1] devuelve un solo número.
Primero la fila y después la columna, siempre. Los dos puntos quieren decir "todas", y por eso X[:, 1] es una columna entera y no un número suelto.

Sacar un pedazo, que es lo que vas a hacer todo el tiempo

print('una venta entera  X[0]   :', np.round(X[0], 2))
print('todos los montos  X[:,1] :', np.round(X[:5, 1], 2), '...')
print('un solo numero    X[0,1] :', X[0, 1])
una venta entera  X[0]   : [ 10.   480.37   4.  ]
todos los montos  X[:,1] : [ 480.37  524.9   154.83 2480.84  379.4 ] ...
un solo numero    X[0,1] : 480.37

Tres formas de meter la mano en la misma tabla:

  • X[0] te da la fila completa. Una venta: 10 unidades, 480.37 soles, satisfacción 4.
  • X[:, 1] te da la columna completa. Los dos puntos quieren decir "todas las filas".
  • X[0, 1] te da un número. Fila 0, columna 1.
xij=el valor de la fila i en la columna j

el elemento que está en el cruce: primero se dice la fila, después la columna, siempre en ese orden

El orden importa y es el que se te va a olvidar: primero la fila, después la columna. Siempre. Si alguna vez tu código explota con un "index out of bounds", nueve de cada diez veces es que los cambiaste de sitio.

Cuánto es una matriz, de verdad

print('filas x columnas =', X.shape[0], 'x', X.shape[1], '=', X.size, 'numeros')
print('cuanto ocupa en memoria:', X.nbytes, 'bytes')
filas x columnas = 2238 x 3 = 6714 numeros
cuanto ocupa en memoria: 53712 bytes

6.714 números. 53 kilobytes. Nada.

Te pongo el dato porque cuando llegues a Deep learning vas a ver matrices de otro tamaño, y quiero que tengas con qué comparar. Una imagen de 224 por 224 en color son 150.528 números por foto. Un lote de 32 fotos son casi cinco millones. Ahí es donde empieza a importar que esto sean matrices y no bucles 🐣

La traspuesta, que es mirarla al revés

print('X.T shape:', X.T.shape)
print('X.T[1][:5]:', np.round(X.T[1][:5], 2))
X.T shape: (3, 2238)
X.T[1][:5]: [ 480.37  524.9   154.83 2480.84  379.4 ]

La traspuesta gira la tabla: lo que era una columna pasa a ser una fila. Se escribe con esa T pequeñita arriba:

(X)ij=xji

la traspuesta cambia filas por columnas, así que el elemento que estaba en la fila 2 columna 5 pasa a estar en la fila 5 columna 2

Y no es un truco de programador, es una operación con nombre propio que vas a ver constantemente. Cuando en Deep learning veas W, ya sabes qué está pasando: alguien necesitaba que las dimensiones encajaran para poder multiplicar, y eso lo vemos en el capítulo de multiplicación de matrices.

Tres matrices que vas a ver por todos lados

Hay tres formas de matriz con nombre propio. Te las presento ahora porque cuando aparezcan en un capítulo más adelante quiero que ya las reconozcas.

print(np.eye(3))
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

Esa es la identidad. Unos en la diagonal y ceros en todo lo demás. Es el 1 de las matrices: multiplicar por ella no cambia nada. Cuando llegues al capítulo de la inversa la vas a necesitar, porque la inversa se define justo como "la que multiplicada por la original te da la identidad".

print(np.zeros((2, 3)))
[[0. 0. 0.]
 [0. 0. 0.]]

La de ceros. Parece inútil y no lo es: así es como se arranca casi cualquier acumulador, y así empiezan los pesos de algunas capas en deep learning.

print(np.diag([2., 0.5, 10.]))
[[ 2.   0.   0. ]
 [ 0.   0.5  0. ]
 [ 0.   0.  10. ]]

Una diagonal. Solo tiene números en la diagonal. Esta la vas a ver en el capítulo de autovalores, y también es lo que hay por debajo cuando escalas cada columna por un número distinto.

Operar con toda la tabla de una vez

Esto es lo que hace que NumPy valga la pena, y es donde mucha gente que viene de Excel dice "ah, ya".

print(np.round(X[:2], 2))
print(np.round(X[:2] * 2, 2))
print(np.round(X[:2] + 100, 2))
[[ 10.   480.37   4.  ]
 [ 10.   524.9    3.  ]]
[[  20.    960.74    8.  ]
 [  20.   1049.8     6.  ]]
[[110.   580.37 104.  ]
 [110.   624.9  103.  ]]

Sin un solo bucle. Escribes X * 2 y se multiplica cada número de la tabla. Eso se llama operación elemento a elemento, y es lo que hace que entrenar una red sobre millones de números sea posible.

Y ojo con esto, que es la trampa del capítulo que viene: X * 2 no es multiplicar matrices. Es otra cosa completamente distinta y tiene otro símbolo. Lo vemos en su momento 🐣

La diferencia entre axis igual a cero y axis igual a uno: el primero recorre las filas hacia abajo y deja un número por columna, que tiene sentido; el segundo recorre las columnas de lado y mezcla unidades con soles y con satisfacción.
axis=0 deja un número por columna y es el que vas a querer casi siempre. Con axis=1 el código no da error: devuelve un número que no es una cantidad de nada, y ese es el problema.

El eje, que es lo que más se confunde

Cuando resumes una tabla tienes que decir en qué dirección. Y aquí es donde todo el mundo se equivoca al menos una vez:

print('media de cada COLUMNA (axis=0):', np.round(X.mean(axis=0), 2))
print('media de cada FILA    (axis=1):', np.round(X.mean(axis=1)[:5], 2), '...')
print('sin axis (todo junto)         :', round(X.mean(), 2))
media de cada COLUMNA (axis=0): [ 11.68 803.39   3.02]
media de cada FILA    (axis=1): [164.79 179.3   56.28 829.28 127.13] ...
sin axis (todo junto)         : 272.69

Mira los tres resultados y cuál tiene sentido:

  • axis=0 da tres números, uno por columna. La venta media es de 11.68 unidades y 803.39 soles, con satisfacción media de 3.02. Esto significa algo.
  • axis=1 da un número por venta, y es el promedio de mezclar unidades con soles y con satisfacción. Eso no significa nada. Sumar soles con estrellas no es una cantidad de nada.
  • sin axis promedia los 6.714 números juntos. Peor todavía.

La regla para acordarte: axis=0 recorre las filas hacia abajo y te deja un número por columna. Es el que vas a querer el 90% de las veces, porque las columnas son las que tienen unidades comparables.

El error de forma, que te va a pasar

Te lo enseño ahora para que cuando te salga ya sepas leerlo:

a = X[:, :2]
b = X[:, :3]
print('a.shape', a.shape, ' b.shape', b.shape)
a.shape (2238, 2)  b.shape (2238, 3)

Y ahora las sumas:

a + b
ValueError: operands could not be broadcast together with shapes (2238,2) (2238,3)

Dos tablas con distinto número de columnas no se pueden sumar. Obvio dicho así, pero cuando llevas cuarenta líneas de código y una de ellas cambió la forma sin que te dieras cuenta, este error aparece muy lejos de donde estuvo la culpa.

El hábito que te va a salvar horas: cuando algo no cuadre, imprime los .shape antes de tocar nada más. En mi experiencia, la mitad de los errores de un proyecto de machine learning son de forma, no de matemática.

Lo que te llevas

  • Una matriz es una tabla de números. Tu archivo de ventas ya lo era.
  • Se escribe X, tiene n filas y d columnas, y en machine learning las filas son los ejemplos.
  • X[fila, columna], en ese orden y no al revés.
  • .shape te dice la forma y es lo primero que hay que mirar cuando algo no cuadra.
  • La traspuesta X cambia filas por columnas.

Comprueba que se entendió

Comprueba que lo tienes

Tienes X con forma (2238, 3) y escribes X.mean(axis=0). ¿Cuántos números te salen y qué es cada uno?

  • Tres números: el promedio de cada columna
  • 2.238 números: el promedio de cada venta
  • Un solo número: el promedio de toda la tabla
  • Depende de si la matriz tiene decimales

Ejercicios

1. La forma antes que nada

Antes de hacer ninguna cuenta, mira la forma. Es el hábito que más errores te va a ahorrar.

print('X       :', X.shape)
print('X.T     :', X.T.shape)
print('X[0]    :', X[0].shape)
print('X[:, 1] :', X[:, 1].shape)
X       : (2238, 3)
X.T     : (3, 2238)
X[0]    : (3,)
X[:, 1] : (2238,)

Mira las dos últimas: (3,) y (2238,), con esa coma suelta. Eso quiere decir una sola dimensión, o sea que al sacar una fila o una columna ya no tienes una matriz, tienes un vector. Ese es el capítulo que viene.

2. Traspón dos veces y mira qué pasa

Sin correrlo, ¿qué crees que sale? Después córrelo.

print(np.array_equal(X.T.T, X))
True

Girar la tabla dos veces te devuelve la de partida. Obvio dicho así, pero es una propiedad con nombre y la vas a usar para simplificar expresiones largas: (X) = X.

3. Saca la última fila sin saber cuántas hay

Los índices negativos cuentan desde el final.

print('ultima fila   :', np.round(X[-1], 2))
print('lo mismo, feo :', np.round(X[X.shape[0] - 1], 2))
ultima fila   : [ 17.   993.57   4.  ]
lo mismo, feo : [ 17.   993.57   4.  ]

Las dos dan lo mismo. La primera se lee mejor y no se rompe si mañana el archivo tiene más ventas.

4. El error que vas a cometer

Pide una columna que no existe y lee el error entero, que para eso está.

try:
    print(X[0, 7])
except IndexError as e:
    print('IndexError:', e)
IndexError: index 7 is out of bounds for axis 1 with size 3

El mensaje te lo dice todo si sabes leerlo: axis 1 son las columnas, size 3 es que hay tres. Pediste la número 7. Cuando esto te salga en un proyecto de verdad, mira el .shape antes de tocar nada más.

Preguntas frecuentes

¿Qué es una matriz?

Una tabla de números con filas y columnas. Tu archivo de ventas ya es una: cada fila es una venta y cada columna una cosa medida.

¿Qué son las filas y las columnas de una matriz?

Las filas son los casos y las columnas las variables. Cuando alguien dice que una matriz es "de 3037 por 14", el primer número son las filas y el segundo las columnas, siempre en ese orden.

¿Qué es la matriz identidad?

La que tiene unos en la diagonal y ceros en todo lo demás. Multiplicar por ella no cambia nada, igual que multiplicar por uno, y sirve para comprobar si una inversa está bien calculada.

¿Qué es la transpuesta de una matriz?

La misma matriz con las filas puestas como columnas. Se usa mucho más de lo que parece, porque es lo que hace que dos formas encajen para poder multiplicarlas.

Ya sabes qué pasa por debajo. Ahora que corra

Sabes qué mide una derivada, hacia dónde apunta un gradiente y por qué la regla de la cadena es lo que hace que una red aprenda. Eso no lo tiene la mayoría de la gente que entrena modelos 🎓

Lo que la matemática no te da es lo que pasa después: que el sistema corra solo, que alguien se entere cuando empiece a fallar y que puedas demostrar con números que sirve. Eso es el curso de ingeniería de IA en producción.

Son cuatro semanas y sales con un sistema tuyo, sobre un proceso real de tu trabajo. Con lo que acabas de leer vas a entender por qué se toma cada decisión en vez de copiarla.

Y si por ahora te quedas con la teoría, tranqui: el capítulo de referencias te deja por dónde seguir sola 🌸

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.

¿Le sirve a alguien que conoces?

Pásale el libro. Es gratis, está entero y no pide registro 🐣

Instagram y TikTok no dejan compartir enlaces desde la web: esos dos copian la URL para que la pegues en tu historia.

¿Tienes alguna duda o consulta?