Test chi2 para problemas de clasificación#
Ultima modificación: 2023-03-11 | YouTube
En esta prueba, la hipótesis nula es que no hay una diferencia estadísticamente significativa entre la frecuencia observada y la frecuencia esperada en una o más categorías.
Se tienen n observaciones en una muestra aleatoria, que están clasificadas en k clases mutuamente exclusivas, cada una con una probabilidad p_i.
\sum_{i=1}^k p_i = 1
Cada clase tiene un número observado z_i, con i=1,...,k. Este es asimilable a la cantidad de observaciones de la clase y se usa para determinar la frecuencia observada de cada clase.
El número esperado para cada clase es m_i = n * p_i (la cantidad de observaciones por la probabilidad de cada clase).
El estadístico
\sum_{i=1}^k \frac{(z_i - m_i)^2}{m_i} = \sum_{i=1}^k \frac{z_i^2}{m_i} - n
sigue una distribución \chi^2
[1]:
#
# Datos de ejemplo
#
import numpy as np
from sklearn.datasets import make_blobs
X, y = make_blobs(
n_samples=150,
n_features=2,
centers=3,
cluster_std=0.8,
shuffle=False,
random_state=12345,
)
#
# Note que x0 y x1 son significativas, mientras que x2 es una variable
# aleatoria no explicativa
#
X = np.hstack((X, 2 * np.random.random((X.shape[0], 2))))
#
# Esta parte garantiza que todos los valores de X son positivos
#
x_min = np.where(X < 0, X, 0).min()
X = X - x_min + 1
X.shape
[1]:
(150, 4)
[2]:
from sklearn.preprocessing import LabelBinarizer
#
# Transformación tipo one-hot-encoder para las clases de y. Cada columna
# representa pertenece/no pertenece a la clase.
#
Y = LabelBinarizer().fit_transform(y)
Y[:5, :]
[2]:
array([[1, 0, 0],
[1, 0, 0],
[1, 0, 0],
[1, 0, 0],
[1, 0, 0]])
[3]:
#
# Computa la frecuencia observada en características continuas como
# la sumatoria de X para los x que pertenecen a la misma clase
#
observed = np.dot(Y.T, X)
observed
[3]:
array([[881.83302613, 274.30036282, 508.49815421, 507.92819818],
[140.43480966, 159.1087139 , 511.68209303, 508.14465453],
[514.41956328, 549.23014322, 506.66896684, 501.06467143]])
[4]:
#
# Calcula la suma de cada feature para aproximar la frecuencia esperada
#
sum_x = X.sum(axis=0).reshape(1, -1)
sum_x
[4]:
array([[1536.68739907, 982.63921993, 1526.84921408, 1517.13752414]])
[5]:
#
# Probabilidad para cada una de las clases
#
class_prob = Y.mean(axis=0).reshape(1, -1)
class_prob
[5]:
array([[0.33333333, 0.33333333, 0.33333333]])
[6]:
#
# La probabilidad esperada para cada clase es computada como la suma sobre cada
# feature para una clase
#
expected = np.dot(class_prob.T, sum_x)
expected
[6]:
array([[512.22913302, 327.54640664, 508.94973803, 505.71250805],
[512.22913302, 327.54640664, 508.94973803, 505.71250805],
[512.22913302, 327.54640664, 508.94973803, 505.71250805]])
[7]:
from scipy.stats import chisquare
chisq, p = chisquare(
# -------------------------------------------------------------------------
# Observed frequencies in each category.
f_obs=observed,
# -------------------------------------------------------------------------
# Expected frequencies in each category.
f_exp=expected,
# -------------------------------------------------------------------------
# “Delta degrees of freedom”: adjustment to the degrees of freedom for the
# p-value.
ddof=0,
# -------------------------------------------------------------------------
# The axis of the broadcast result of f_obs and f_exp along which to apply
# the test. If axis is None, all values in f_obs are treated as a single
# data set.
axis=0,
)
display(
chisq,
p,
)
array([5.36562325e+02, 2.45308985e+02, 2.52905308e-02, 6.41214208e-02])
array([3.06882045e-117, 5.39300358e-054, 9.87434350e-001, 9.68447785e-001])
[8]:
#
# --- Verificación ------------------------------------------------------------
#
from sklearn.feature_selection import chi2
chi2_statistics, p_values = chi2(
# -------------------------------------------------------------------------
# Sample vectors
X,
# -------------------------------------------------------------------------
# Target vector (class labels).
y,
)
display(
chi2_statistics,
p_values,
)
array([5.36562325e+02, 2.45308985e+02, 2.52905308e-02, 6.41214208e-02])
array([3.06882045e-117, 5.39300358e-054, 9.87434350e-001, 9.68447785e-001])