# Las métricas

URL canónica: https://missyera.com/guias/machine-learning-desde-cero/metricas/
Última actualización: 2026-11-14
Autora: Gera Flores (Miss Yera)

> **Respuesta corta**
>
> Me pasé siete capítulos diciendo "exactitud 0,6733" como si eso fuera el modelo, y no lo es. Un modelo se equivoca de dos maneras que casi nunca cuestan lo mismo, y la exactitud las mezcla en un número solo. La precisión contesta cuánto le creo cuando avisa, y el recall a cuántos se me escapan. Elegir cuál miras es una decisión de negocio, no de estadística 📐

Capítulo 14 de 29 del libro [Machine learning desde cero](https://missyera.com/guias/machine-learning-desde-cero.md).

Llevamos siete capítulos diciendo "exactitud 0,6733" como si eso fuera el modelo. No lo es 📐

Antes de seguir, contéstate esto: **¿qué te duele más, llamar a diez clientes que no iban a comprar, o dejar escapar a uno que sí?** De esa respuesta sale toda la decisión de este capítulo 💭

La exactitud es un número solo, y un modelo se equivoca de dos maneras distintas que casi nunca cuestan lo mismo. En este capítulo vamos a separarlas, y al final vas a ver un modelo con **92% de exactitud que es peor que no hacer nada**.

## El punto de partida

```
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'

def carga_limpia(url):
    v = pd.read_csv(url).drop_duplicates()
    v['ciudad'] = (v['ciudad'].str.strip().str.lower()
                   .str.normalize('NFKD')
                   .str.encode('ascii', 'ignore').str.decode('utf-8'))
    v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.'))
    for col in ['fecha', 'fecha_ultima_compra']:
        f = pd.to_datetime(v[col], format='%Y-%m-%d', errors='coerce')
        falta = f.isna() & v[col].notna()
        f[falta] = pd.to_datetime(v.loc[falta, col], format='%d/%m/%Y', errors='coerce')
        v[col] = f
    return v

def prepara(v):
    v = v.sort_values(['cliente_id', 'fecha']).copy()
    v['sin_compra_previa'] = v['fecha_ultima_compra'].isna().astype(int)
    v['sin_descuento'] = v['descuento'].isna().astype(int)
    v['sin_satisfaccion'] = v['satisfaccion'].isna().astype(int)
    v['precio_unitario'] = v['monto'] / v['unidades']
    v['visita_numero'] = v.groupby('cliente_id').cumcount() + 1
    return v

NUMERICAS = ['unidades', 'monto', 'descuento', 'satisfaccion', 'precio_unitario',
             'sin_compra_previa', 'sin_descuento', 'sin_satisfaccion', 'visita_numero']
CATEGORICAS = ['ciudad', 'segmento', 'canal', 'categoria']

def arma(numericas, categoricas):
    pre = ColumnTransformer([
        ('num', Pipeline([('r', SimpleImputer(strategy='median')),
                          ('e', StandardScaler())]), numericas),
        ('cat', Pipeline([('r', SimpleImputer(strategy='most_frequent')),
                          ('c', OneHotEncoder(handle_unknown='ignore'))]), categoricas),
    ])
    return Pipeline([('pre', pre),
                     ('mod', LogisticRegression(max_iter=1000, random_state=42))])

datos = prepara(carga_limpia(URL))
X = datos[NUMERICAS + CATEGORICAS]
y = datos['compro']
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25,
                                          random_state=42, stratify=y)
modelo = arma(NUMERICAS, CATEGORICAS).fit(X_tr, y_tr)
print('exactitud:', round(modelo.score(X_te, y_te), 4))
```

```text
exactitud: 0.6733
```

![Matriz de confusión de dos por dos con la realidad en las filas y la predicción en las columnas: 86 verdaderos positivos, 14 falsos negativos, 9 falsos positivos y 91 verdaderos negativos.](https://missyera.com/static/diagramas/webp/g-ml-confusion.webp)

*Precisión y recall salen de aquí y por eso conviene leer la tabla antes que las métricas. La precisión se lee por columnas y el recall por filas: de los que marqué, cuántos eran, contra de los que había, cuántos pillé.*

## Las cuatro casillas

Todo empieza aquí, y esta tabla es la que hay que saber de memoria.

```
from sklearn.metrics import confusion_matrix

prediccion = modelo.predict(X_te)
print(confusion_matrix(y_te, prediccion))
```

```text
[[157 160]
 [ 85 348]]
```

Se lee así: las filas son **la verdad** y las columnas **lo que dijo el modelo**.

|  | Dijo que no | Dijo que sí |
|---|---|---|
| **No compró** | 157 aciertos | 160 falsas alarmas |
| **Sí compró** | 85 se le escaparon | 348 aciertos |

Los nombres formales, que vas a ver en todos lados:

- ✅ **Verdaderos positivos** (348): dijo sí y era sí.
- ✅ **Verdaderos negativos** (157): dijo no y era no.
- 🔔 **Falsos positivos** (160): dijo sí y era no. La falsa alarma. Aquí, una llamada que no valía la pena.
- 💸 **Falsos negativos** (85): dijo no y era sí. El que se escapó. Aquí, una venta perdida.

**La pregunta que hay que hacer siempre: ¿cuál de los dos errores duele más?** Aquí una llamada de más cuesta quince minutos y una venta perdida cuesta S/800. No son ni parecidos, y sin embargo la exactitud los cuenta igual 😳

## Los dos números que separan los dos errores

`Precisión = (VP)/(VP + FP), Recall = (VP)/(VP + FN)`

de los que marcaste cuántos acertaste, y de los que había cuántos cazaste, que son dos preguntas distintas y casi siempre en conflicto

```
from sklearn.metrics import precision_score, recall_score, f1_score

print('precisión:', round(precision_score(y_te, prediccion), 4))
print('recall   :', round(recall_score(y_te, prediccion), 4))
print('F1       :', round(f1_score(y_te, prediccion), 4))
```

```text
precisión: 0.685
recall   : 0.8037
F1       : 0.7396
```

Y en cristiano:

- 🎯 **Precisión 0,685.** De los que dije que sí, acerté el 68,5%. Contesta: *cuando el modelo avisa, ¿cuánto le creo?*
- 🕸️ **Recall 0,8037.** De todos los que sí compraron, atrapé el 80,4%. Contesta: *¿a cuántos de los buenos se me escapan?*
- ⚖️ **F1 0,7396.** El promedio de los dos, para cuando quieres un solo número. Yo casi nunca lo uso para decidir: prefiero mirar los dos.

La forma de acordarse: **la precisión mira la columna del "dijo que sí"; el recall mira la fila del "sí compró"**.

Y siempre están en tensión. Si quieres atrapar a más compradores, tienes que avisar más veces y te vas a equivocar más. Si quieres equivocarte menos, avisas menos y se te escapan más. Eso se maneja con el umbral, que es el capítulo 15 🎚️

```
from sklearn.metrics import classification_report

print(classification_report(y_te, prediccion, digits=4))
```

```text
              precision    recall  f1-score   support

           0     0.6488    0.4953    0.5617       317
           1     0.6850    0.8037    0.7396       433

    accuracy                         0.6733       750
   macro avg     0.6669    0.6495    0.6507       750
weighted avg     0.6697    0.6733    0.6644       750
```

Ese informe es el que yo pego en el correo. Fíjate en que da los números **para las dos clases**: el recall de la clase 0 es 0,4953, o sea que de los que no iban a comprar solo detecta la mitad. La exactitud sola te escondía eso 🔍

![Curva ROC del modelo del libro, que se despega de la diagonal de tirar una moneda pero sin llegar a la esquina superior izquierda, con el AUC anotado dentro de la figura.](https://missyera.com/static/diagramas/webp/graf-ml-roc.webp)

*La diagonal es lo que consigue tirar una moneda. Cuanto más se despega la curva de esa diagonal, mejor ordena el modelo, y el área que queda debajo es exactamente el AUC.*

![Tres curvas ROC con áreas de 0,58, 0,70 y 0,94 sobre la diagonal del azar, que vale 0,50. Cuanto más se despega la curva de la diagonal hacia la esquina superior izquierda, mayor es el área.](https://missyera.com/static/diagramas/webp/g-ml-roc.webp)

*La diagonal es tirar una moneda. Cada punto de una curva es un umbral distinto, así que el área resume el modelo entero sin comprometerse con ninguno: te dice si sirve, no dónde cortar. Un AUC de 0,58 no es un modelo flojo, es prácticamente azar.*

## El AUC, que ya venía apareciendo

`AUC = P(p^_+ > p^_−)`

la probabilidad de que el modelo le dé más puntaje a un caso positivo que a uno negativo tomados al azar, y por eso 0,5 es tirar una moneda

```
from sklearn.metrics import roc_auc_score, average_precision_score

probabilidad = modelo.predict_proba(X_te)[:, 1]
print('AUC ROC:', round(roc_auc_score(y_te, probabilidad), 4))
print('AUC PR :', round(average_precision_score(y_te, probabilidad), 4))
```

```text
AUC ROC: 0.7214
AUC PR : 0.7542
```

Las tres métricas de arriba dependen de dónde pongas el corte. El AUC no: mide si el modelo **ordena** bien, o sea si a los que compran les da más probabilidad que a los que no.

Se lee fácil: **0,5 es tirar una moneda y 1,0 es perfecto**. Y hay una interpretación preciosa: 0,7214 significa que si tomas al azar un comprador y un no comprador, el 72% de las veces el modelo le da más probabilidad al comprador.

El **AUC PR** (0,7542) es su primo para cuando la clase positiva es rara. Lo vamos a necesitar en la sección siguiente 📐

## El 92% que es peor que no hacer nada

`L = −1/n∑_(i = 1)^n[y_ilogp_i + (1−y_i)log(1−p_i)]`

castiga estar seguro y equivocarse mucho más que estar dudando, que es justo lo que se quiere de una probabilidad

Y ahora el ejemplo que vale el capítulo entero.

Cambiamos la pregunta a una más útil para el negocio: *¿esta oportunidad va a cerrar por más de S/2.000?* Y le quitamos al modelo las columnas de monto, porque si no estaría copiando la respuesta.

```
y_grande = ((datos['compro'] == 1) & (datos['monto'] > 2000)).astype(int)
print('qué porcentaje son:', round(y_grande.mean(), 4))

SIN_MONTO = [c for c in NUMERICAS if c not in ('monto', 'precio_unitario')]
Xg = datos[SIN_MONTO + CATEGORICAS]
Xg_tr, Xg_te, yg_tr, yg_te = train_test_split(Xg, y_grande, test_size=0.25,
                                              random_state=42, stratify=y_grande)

grande = arma(SIN_MONTO, CATEGORICAS).fit(Xg_tr, yg_tr)
print('exactitud:', round(grande.score(Xg_te, yg_te), 4))
```

```text
qué porcentaje son: 0.076
exactitud: 0.9227
```

**92,27% de exactitud** 🎉 Un número que en una diapositiva se ve espectacular.

Ahora el listón:

```
from sklearn.dummy import DummyClassifier

tonto = DummyClassifier(strategy='most_frequent').fit(Xg_tr, yg_tr)
print('el listón:', round(tonto.score(Xg_te, yg_te), 4))
```

```text
el listón: 0.924
```

92,40%. **El modelo es PEOR que decir "no" a todo el mundo** 😱

Y la matriz explica por qué:

```
pred_g = grande.predict(Xg_te)
print(confusion_matrix(yg_te, pred_g))
print('recall:', round(recall_score(yg_te, pred_g, zero_division=0), 4))
```

```text
[[692   1]
 [ 57   0]]
recall: 0.0
```

De las 57 ventas grandes de verdad, **encontró cero**. El modelo aprendió que decir "no" siempre acierta el 92% de las veces, y hace exactamente eso.

Ese es el modelo con 92% de exactitud que no sirve para nada, y no es un ejemplo inventado: es lo que pasa cada vez que alguien mide un problema desbalanceado con exactitud.

## Pero el modelo no es malo, y esto es lo bonito

```
prob_g = grande.predict_proba(Xg_te)[:, 1]
print('AUC ROC:', round(roc_auc_score(yg_te, prob_g), 4))
print('AUC PR :', round(average_precision_score(yg_te, prob_g), 4))
```

```text
AUC ROC: 0.92
AUC PR : 0.3741
```

**AUC 0,92.** El modelo ordena estupendamente: sabe perfectamente quién tiene más pinta de venta grande.

Y mira el AUC PR: 0,3741 cuando la clase positiva es solo el 7,6%. Ese número se compara contra la tasa base, así que 0,3741 contra 0,076 es **cinco veces mejor que el azar**. Ahí se ve para qué sirve esta métrica: el ROC dice 0,92 y suena a perfecto, y el PR pone el logro en su tamaño 📐

Lo que está mal no es el modelo, es **el corte en 0,5**. Con un 7,6% de positivos, casi nadie llega a 0,5 de probabilidad, así que todo cae del lado del "no".

Dos conclusiones y las dos son de las importantes del libro:

- 📏 **La exactitud sola no se reporta nunca.** Siempre con el listón al lado.
- 🎚️ **Un modelo con buen AUC y mal recall no está roto: está mal cortado.** Eso se arregla y es el capítulo 15.

![Cómo elegir métrica y umbral según qué error cuesta más: si duele el falso negativo se prioriza recall bajando el umbral, y si duele el falso positivo se prioriza precisión subiéndolo. La exactitud sola miente con clases desbalanceadas.](https://missyera.com/static/diagramas/webp/guia-ml-04-metrica.webp)

*El umbral 0,5 que trae el modelo por defecto no sale de tu negocio, sale de la librería. Elegirlo es una decisión de costos, y es de las pocas partes del proyecto donde manda quien conoce el negocio y no quien programa.*

## Cuál mirar según el caso

`F_1 = 2 (Precisión·Recall)/(Precisión + Recall)`

la media armónica de las dos, que se hunde en cuanto una de ellas es mala en vez de disimularlo como haría un promedio normal

| Si tu problema es... | Mira | Por qué |
|---|---|---|
| Clases parecidas y los dos errores cuestan igual | Exactitud, con listón | Es la más fácil de explicar |
| Que no se te escape ninguno (fraude, enfermedad) | Recall | Un falso negativo es lo caro |
| Que no molestes a quien no toca (correo, llamadas) | Precisión | Un falso positivo es lo caro |
| Comparar modelos sin decidir el corte | AUC ROC | No depende del umbral |
| La clase positiva es rara (menos del 10%) | AUC PR | El ROC se ve bien aunque el modelo sirva poco |
| Solo puedes atender N casos | Precisión en el top N | Es la que corresponde a la capacidad del capítulo 2 |

## Ejercicios

Siete. Intenta antes de abrir 💛

**1. La matriz en soles**

Pon precio a las cuatro casillas: una llamada de más cuesta S/15 y una venta perdida S/800.

```
tn, fp, fn, tp = confusion_matrix(y_te, prediccion).ravel()
print('falsas alarmas :', fp, '->', f'S/{fp * 15:,}')
print('ventas perdidas:', fn, '->', f'S/{fn * 800:,}')
print('costo total    :', f'S/{fp * 15 + fn * 800:,}')
```

```text
falsas alarmas : 160 -> S/2,400
ventas perdidas: 85 -> S/68,000
costo total    : S/70,400
```

S/2.400 de llamadas de más contra S/68.000 de ventas perdidas. **El error caro es veintiocho veces el barato**, y la exactitud los contaba igual.

Esa cuenta de tres líneas es la que convierte una métrica en una conversación de negocio, y es la base del capítulo 15 💰

**2. La precisión en el top 200**

Del capítulo 2: el equipo hace 200 llamadas. ¿Cuántas de esas 200 cierran?

```
import numpy as np

orden = np.argsort(probabilidad)[::-1]
top = orden[:200]
print('cierran en el top 200:', int(y_te.iloc[top].sum()), 'de 200')
print('precisión en el top  :', round(y_te.iloc[top].mean(), 4))
print('tasa general         :', round(y_te.mean(), 4))
```

```text
cierran en el top 200: 159 de 200
precisión en el top  : 0.795
tasa general         : 0.5773
```

Llamando a los 200 mejores según el modelo se cierra el 79,5% (159 ventas), contra el 57,7% de llamar al azar. **Veintidós puntos de mejora**, y esa es la métrica que de verdad le importa a quien paga 📞

Traducido: las mismas 200 llamadas traen 159 ventas en vez de 115. Cuarenta y cuatro ventas más por el mismo trabajo, y ahí sí se puede poner un número de soles en la propuesta 💰

Fíjate en que no aparece por ningún lado la palabra exactitud.

**3. La curva ROC, en tres puntos**

Qué pasa con precisión y recall en tres umbrales distintos.

```
for u in (0.3, 0.5, 0.7):
    p = (probabilidad >= u).astype(int)
    print(f'umbral {u}  precisión {precision_score(y_te, p, zero_division=0):.4f}  '
          f'recall {recall_score(y_te, p):.4f}  avisa {p.sum():4d} veces')
```

```text
umbral 0.3  precisión 0.6122  recall 0.9700  avisa  686 veces
umbral 0.5  precisión 0.6850  recall 0.8037  avisa  508 veces
umbral 0.7  precisión 0.7945  recall 0.4018  avisa  219 veces
```

Ahí está la tensión en una tabla. Con el umbral en 0,3 el recall sube a 0,97 y la precisión baja a 0,61, y avisa 686 veces. Con 0,7 la precisión sube a 0,79 y el recall se cae a 0,40, avisando solo 219 veces.

**El modelo es el mismo en las tres filas.** Lo único que cambió es dónde pusimos la raya 🎚️

**4. El listón, para las dos preguntas**

Compara modelo y listón en los dos problemas del capítulo, uno al lado del otro.

```
t1 = DummyClassifier(strategy='most_frequent').fit(X_tr, y_tr)
print('cierra          modelo', round(modelo.score(X_te, y_te), 4),
      '| listón', round(t1.score(X_te, y_te), 4))
print('cierra > S/2000 modelo', round(grande.score(Xg_te, yg_te), 4),
      '| listón', round(tonto.score(Xg_te, yg_te), 4))
```

```text
cierra          modelo 0.6733 | listón 0.5773
cierra > S/2000 modelo 0.9227 | listón 0.924
```

En el primero ganamos diez puntos. En el segundo perdemos por poquito, con un número más alto.

**Un 92% peor que un 67%.** Si alguna vez dudas de por qué el listón es obligatorio, esta es la tabla 📏

**5. Los dos errores de medir**

Pásale a `f1_score` las probabilidades en vez de las predicciones.

```
f1_score(y_te, probabilidad)
```

```
ValueError: Classification metrics can't handle a mix of binary and continuous targets
```

*can't handle a mix of binary and continuous targets*. Las métricas de clasificación quieren **etiquetas** (0 y 1), no probabilidades.

La regla para no confundirse: `predict()` para precisión, recall, F1 y la matriz; `predict_proba()` solo para el AUC y para la curva. Confundirlos es de los errores más frecuentes, y menos mal que este avisa.

El otro error de medir es peor porque **no** avisa:

```
print('al derecho:', round(precision_score(y_te, prediccion), 4))
print('al revés  :', round(precision_score(prediccion, y_te), 4))
```

```text
al derecho: 0.685
al revés  : 0.8037
```

0,685 y 0,8037. Los dos son números creíbles y solo uno es la precisión: el segundo es en realidad el recall, porque al cambiar el orden le dijiste a scikit-learn que la predicción era la verdad 🔄

El orden es siempre `(verdad, predicción)`, en todas las métricas y sin excepción.

**6. Cuando la clase rara es la que importa**

Sobre el problema de las ventas grandes, mira el informe completo en vez de la exactitud.

```
print(classification_report(yg_te, pred_g, digits=4, zero_division=0))
```

```text
              precision    recall  f1-score   support

           0     0.9239    0.9986    0.9598       693
           1     0.0000    0.0000    0.0000        57

    accuracy                         0.9227       750
   macro avg     0.4619    0.4993    0.4799       750
weighted avg     0.8537    0.9227    0.8868       750
```

Mira la fila de la clase 1: precisión 0, recall 0, F1 0, y 57 casos de soporte. El informe te lo dice en la cara.

Y mira el `macro avg`, que promedia las dos clases sin pesarlas: 0,48. Ese número sí refleja lo que pasa, y por eso en problemas desbalanceados se mira el macro y no el `weighted` 🎯

**7. Tu informe de una línea**

Una función que devuelva todo lo que hay que reportar de un modelo, para no olvidarte nada nunca más.

```
def informe(m, X, y_verdad, nombre=''):
    """Todo lo que va en el correo, con el listón incluido."""
    p = m.predict(X)
    prob = m.predict_proba(X)[:, 1]
    tonto = y_verdad.value_counts(normalize=True).max()
    print(f'--- {nombre}')
    print(f'  listón     {tonto:.4f}')
    print(f'  exactitud  {m.score(X, y_verdad):.4f}')
    print(f'  precisión  {precision_score(y_verdad, p, zero_division=0):.4f}')
    print(f'  recall     {recall_score(y_verdad, p, zero_division=0):.4f}')
    print(f'  AUC ROC    {roc_auc_score(y_verdad, prob):.4f}')
    print(f'  AUC PR     {average_precision_score(y_verdad, prob):.4f}')

informe(modelo, X_te, y_te, 'cierra la venta')
informe(grande, Xg_te, yg_te, 'cierra por más de S/2000')
```

```text
--- cierra la venta
  listón     0.5773
  exactitud  0.6733
  precisión  0.6850
  recall     0.8037
  AUC ROC    0.7214
  AUC PR     0.7542
--- cierra por más de S/2000
  listón     0.9240
  exactitud  0.9227
  precisión  0.0000
  recall     0.0000
  AUC ROC    0.9200
  AUC PR     0.3741
```

Seis números y el listón arriba del todo, que es donde tiene que estar.

Con esos seis, cualquiera que lea tu correo puede juzgar el modelo sin confiar en ti, y eso es exactamente lo que quieres 💛

Comprueba que lo tienes

Tu modelo tiene 92% de exactitud y el 92% de los casos son de una sola clase. ¿Es bueno?

- No se sabe: decir siempre la clase mayoritaria da lo mismo
- Sí, 92% es alto
- No, porque 92% es poco para producción
- Depende de la velocidad del modelo

## La métrica que se elige mirando el resultado

La trampa

Calculas las cuatro métricas como manda el capítulo, miras la tabla y escribes el informe con la que mejor describe al modelo. Suena razonable.

```
print(accuracy_score(y_te, pred))   # 0.67
print(precision_score(y_te, pred))  # 0.71
print(recall_score(y_te, pred))     # 0.58
print(f1_score(y_te, pred))         # 0.64

# al informe va la precisión, 0.71
```

**Qué está mal**

Elegiste la métrica **después** de ver los números, y a partir de ahí ya no estás midiendo el modelo: estás buscando la foto en la que sale favorecido 📸

Y no hace falta mala fe, pasa solo. Si el recall hubiera salido 0,78 habrías escrito recall, con la misma sensación de estar siendo objetiva.

La métrica se elige **antes** de entrenar y sale de una pregunta que no es estadística: cuál de los dos errores duele más. Si llamar de más cuesta S/15 y perder una venta cuesta S/800, la respuesta estaba decidida desde el primer día y no depende de lo que salga. Escribe cuál vas a mirar en el contrato, y así el número que salga no puede moverte la vara.

## Lo que te llevas

- 🔢 La matriz de confusión primero: 348 aciertos, 160 falsas alarmas y 85 que se escaparon.
- 🎯 Precisión es "cuando avisa, cuánto le creo" (0,685). Recall es "a cuántos buenos se me escapan" (0,8037).
- 📐 El AUC no depende del umbral: mide si el modelo ordena. 0,7214 significa que acierta el orden el 72% de las veces.
- 😱 El modelo de ventas grandes tiene 92,27% de exactitud, el listón 92,40%, y encuentra cero de 57. La exactitud sola no se reporta nunca.
- 🎚️ Ese mismo modelo tiene 0,92 de AUC: no está roto, está mal cortado.
- 💰 Las cuatro casillas se convierten en soles y ahí se ve que el error caro es veintiocho veces el barato.
- 📞 Con capacidad limitada, la métrica es la precisión en el top N: 84% en las 200 mejores contra 57,7% al azar.

Y si de todo el capítulo te llevas una sola frase, que sea esta:

La métrica no se elige mirando el resultado. Se elige sabiendo cuál de los dos errores duele más.

Precisión, recall y curvas ROC vienen de la estadística clásica, y están explicadas despacio en el [libro de estadística desde cero](https://missyera.com/guias/estadistica-desde-cero/) 📐

En el capítulo 15 movemos el umbral y ponemos precio a cada error, que es donde el modelo se convierte en una decisión de negocio.

Que tengas lindo día! 🌸

## Seguir leyendo

- Anterior: [Ocho modelos](https://missyera.com/guias/machine-learning-desde-cero/los-modelos.md)
- Siguiente: [El umbral](https://missyera.com/guias/machine-learning-desde-cero/umbral-y-costo.md)

## Contacto

- Diagnóstico de 30 minutos con Miss Yera: https://calendly.com/soymissyera/reunion-con-yera
- Correo: hola@missyera.com
- Esta página en HTML: https://missyera.com/guias/machine-learning-desde-cero/metricas/
- Índice de todas las páginas en texto: https://missyera.com/llms-index.txt
