Test du Khi-2

Je reprends ici en partie le calcul manuel réalisé sur ce MOOC : https://openclassrooms.com/fr/courses/4525266-decrivez-et-nettoyez-votre-jeu-de-donnees/4775616-analysez-deux-variables-qualitatives-avec-le-chi-2

Le but est de montrer, une fois le calcul manuel maîtrisé, comment obtenir une pvaleur et pouvoir réaliser un test d'indépendance entre deux variables qualitatives grâce à scipy.stats.

Le notebook est divisé en deux parties : une partie appelée "manuelle" et une partie appelée "scipy.stats".

La partie manuelle du notebook permet de calculer :

  • Le tableau de contingence (matrice des valeurs observées)
  • Le tableau des fréquences (matrice des valeurs attendues à partir des sommes marginales)
  • Le tableau des écarts au carré normalisé entre les deux précédentes matrices, dont la somme totale suit une loi du khi2 à k degrés de liberté s'il y a indépendance.

Merci à Nicolas Rangeon pour son excellent cours sur cette partie

La partie utilisant scipy.stats permet :

  • D'obtenir notre valeur de Khi2 et constater que l'on obtient exactement la même à la main
  • D'obtenir notre matrice des fréquences attendues et constater que l'on obtient exactement la même à la main

Mais surtout :

  • De calculer le nombre de degrés de liberté
  • D'obtenir la pvaleur et donc réaliser un test d'indépendance
In [1]:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import scipy.stats as st
In [2]:
X = ["Chez Luc"]*10
X.extend(["Au café Dembas"]*20)
X.extend(["Au café Ducoing"]*40)
X.extend(["Chez Sarah"]*30)
In [3]:
Y = ["Café"]*1
Y.extend(["Thé"]*9)
Y.extend(["Autre"]*0)

Y.extend(["Café"]*9)
Y.extend(["Thé"]*6)
Y.extend(["Autre"]*5)

Y.extend(["Café"]*20)
Y.extend(["Thé"]*10)
Y.extend(["Autre"]*10)

Y.extend(["Café"]*20)
Y.extend(["Thé"]*5)
Y.extend(["Autre"]*5)
In [4]:
data = {
    'bar':X,
    'boisson':Y
}
df = pd.DataFrame(data)
In [5]:
df.head()
Out[5]:
bar boisson
0 Chez Luc Café
1 Chez Luc Thé
2 Chez Luc Thé
3 Chez Luc Thé
4 Chez Luc Thé

1. Partie manuelle

Création de la matrice "valeurs observées" (tableau de contingence)

On pivote, on remplit les NaN, on calcule la ligne Total et la colonne Total.

Ce tableau nous sera également utile pour la partie 2, car c'est cela qui est attendu en entrée par la méthode de scipy.stats que l'on utilisera.

In [6]:
X = "bar"
Y = "boisson"

cont = df[[X, Y]].pivot_table(index=X, columns=Y, aggfunc=len).fillna(0).copy()

tx = df[X].value_counts()
ty = df[Y].value_counts()

cont = cont.astype(int)
In [7]:
cont
Out[7]:
boisson Autre Café Thé
bar
Au café Dembas 5 9 6
Au café Ducoing 10 20 10
Chez Luc 0 1 9
Chez Sarah 5 20 5

Création de la matrice "valeurs attendues"

In [8]:
tx_df = pd.DataFrame(tx)
tx_df.columns = ["c"]
ty_df = pd.DataFrame(ty)
ty_df.columns = ["c"]

# Valeurs totaleso observées
n = len(df)

# Produit matriciel. On utilise pd.T pour pivoter une des deux séries.
indep = (tx_df.dot(ty_df.T) / n)
In [9]:
indep
Out[9]:
Café Thé Autre
Au café Ducoing 20.0 12.0 8.0
Chez Sarah 15.0 9.0 6.0
Au café Dembas 10.0 6.0 4.0
Chez Luc 5.0 3.0 2.0

Calcul de la matrice "écart au carré normalisé de la valeur attendue VS valeur observée"

In [10]:
# Matrice
freq = (cont-indep)**2/indep
In [11]:
freq
Out[11]:
Autre Café Thé
Au café Dembas 0.250000 0.100000 0.000000
Au café Ducoing 0.500000 0.000000 0.333333
Chez Luc 2.000000 3.200000 12.000000
Chez Sarah 0.166667 1.666667 1.777778

Calcul du Chi2

Somme des valeurs de la précédente matrice. Cette somme suit une loi du Chi2 à k degrés de liberté.

k = observed.size - sum(observed.shape) + observed.ndim - 1 (D'après la documentation de scipy.stats)

In [12]:
chi2 = freq.sum().sum()
In [13]:
chi2
Out[13]:
21.994444444444444

2. Partie scipy.stats

Documentation : https://docs.scipy.org/doc/scipy-0.15.1/reference/generated/scipy.stats.chi2_contingency.html

Calcul du khi2 et de la pvalue à partir de la matrice des valeurs observées

Via scipy.stats.chi2_contingency

In [14]:
st_chi2, st_p, st_dof, st_exp = st.chi2_contingency(cont)

Chi2

On retrouve bien la même valeur que calculée manuellement

In [15]:
st_chi2
Out[15]:
21.994444444444447

Degrés de liberté

C'est ce nombre de degrés de liberté qui sera utilisé par scipy.stats pour calculer la loi suivie et donc calculer la pvaleur (en confrontant notre valeur de khi2 à la courbe de la loi obtenue).

In [16]:
st_dof
Out[16]:
6

P-Value

Cette valeur nous permet de décider si deux variables sont indépendantes ou non en se fixant un seuil de décision.

Scipy.stats calcule lui-même le nombre de degrés de liberté à partir du tableau de contingence. Cela permet ensuite de calculer la pvaleur, en "observant" la courbe de densité de la loi du khi2 à k degrés de liberté.

L'hypothèse H0 est que les variables sont indépendantes entre elles.

A noter qu'une loi du khi2 est suivie par une somme de k lois normales centrées réduites et indépendantes.

In [17]:
st_p
Out[17]:
0.001213683312816688

A un seuil de 0.1%, nous pouvons rejeter H0. On rejette donc l'indépendance des veux variables.

Tableau des fréquences attendues

On constate alors qu'il s'agit bien de la même matrice que nous avions calculé à la main (seules la position des index et des colonnes a changé).

In [18]:
st_exp
Out[18]:
array([[ 4., 10.,  6.],
       [ 8., 20., 12.],
       [ 2.,  5.,  3.],
       [ 6., 15.,  9.]])