Pandas & NumPy Academy · Lezione

Test chi-quadrato di indipendenza

Verifichi se due variabili categoriche sono indipendenti usando chi2_contingency su una tabella di frequenza ottenuta con una crosstab.

Lezione 3 di 413 passaggi

Test chi-quadrato di indipendenza è una lezione Pandas & NumPy Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Pandas & NumPy Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Pandas & NumPy Academy include 4 lezioni in totale.

Verifica delle relazioni tra variabili categoriali

Il test chi-quadrato di indipendenza verifica se due variabili categoriali sono statisticamente indipendenti o se esiste un'associazione tra loro. Ad esempio: «L'abbandono da parte dei clienti è indipendente dal livello di abbonamento?» oppure «La preferenza per un prodotto è indipendente dalla fascia d'età?». A differenza dei t-test, che confrontano medie numeriche, i test chi-quadrato confrontano le frequenze osservate in una tabella di contingenza con le frequenze che ci aspetteremmo se le variabili fossero indipendenti.

Creazione di una tabella di contingenza con Pandas

Una tabella di contingenza (chiamata anche tabella a doppia entrata) mostra il numero di osservazioni per ogni combinazione di due variabili categoriali. pd.crosstab(df['var1'], df['var2']) crea direttamente questa tabella a partire da un DataFrame. Ogni cella contiene il numero di osservazioni in cui la categoria della riga e la categoria della colonna compaiono insieme. Questa è l'input per scipy.stats.chi2_contingency().

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'subscription': np.random.choice(['free', 'basic', 'pro'], 300),
    'churned': np.random.choice(['yes', 'no'], 300, p=[0.3, 0.7])
})

# Build contingency table
ct = pd.crosstab(df['subscription'], df['churned'])
print(ct)
print()
print('Row totals:', ct.sum(axis=1).to_dict())

Esecuzione di chi2_contingency()

scipy.stats.chi2_contingency(observed) riceve la tabella di contingenza osservata (come array NumPy o DataFrame Pandas) e restituisce quattro valori: la statistica chi-quadrato, il p-value, i gradi di libertà e la tabella delle frequenze attese. L'ipotesi nulla è H₀: the two variables are independent. Se p ≤ 0.05, rifiutiamo l'indipendenza e concludiamo che esiste un'associazione statisticamente significativa.

import pandas as pd
import numpy as np
from scipy import stats

np.random.seed(0)
df = pd.DataFrame({
    'tier': np.random.choice(['free', 'basic', 'pro'], 300),
    'churned': np.random.choice(['yes', 'no'], 300, p=[0.3, 0.7])
})

ct = pd.crosstab(df['tier'], df['churned'])
chi2, p, dof, expected = stats.chi2_contingency(ct)

print(f'Chi-squared: {chi2:.3f}')
print(f'p-value: {p:.4f}')
print(f'Degrees of freedom: {dof}')
print('Association significant?', 'Yes' if p < 0.05 else 'No')

Comprendere le frequenze attese

Le frequenze attese rappresentano l'aspetto che avrebbero i conteggi delle celle se le due variabili fossero perfettamente indipendenti. Per ogni cella, expected = (row_total × column_total) / grand_total. La statistica chi-quadrato misura la somma delle differenze al quadrato tra conteggi osservati e attesi, normalizzata rispetto ai conteggi attesi. Un chi-quadrato elevato indica che i conteggi osservati si discostano molto dall'indipendenza; un chi-quadrato basso indica che i dati sono compatibili con l'indipendenza.

import pandas as pd
import numpy as np
from scipy import stats

observed = pd.DataFrame({
    'converted': [50, 30],
    'not_converted': [150, 170]
}, index=['variant', 'control'])

chi2, p, dof, expected = stats.chi2_contingency(observed)
print('Observed:')
print(observed)
print()
print('Expected (under independence):')
print(pd.DataFrame(expected,
                   index=observed.index,
                   columns=observed.columns).round(1))

Gradi di libertà nel test chi-quadrato

Per una tabella di contingenza con r righe e c colonne, i gradi di libertà sono df = (r-1) × (c-1). Una tabella 2×2 ha df=1; una tabella 3×4 ha df=6. Il valore critico del chi-quadrato aumenta con i gradi di libertà: per df=1 con α=0.05, il valore critico è ≈ 3.84; per df=6 con α=0.05, è ≈ 12.6. La funzione chi2_contingency gestisce automaticamente questo calcolo, ma conoscere la formula aiuta a capire perché, per essere significativo, il chi-quadrato di tabelle più grandi richiede statistiche maggiori.

from scipy import stats

# Critical values of chi-squared for alpha=0.05
for df in [1, 2, 3, 4, 6, 9]:
    critical = stats.chi2.ppf(0.95, df=df)
    print(f'df={df}: critical value = {critical:.3f}')

L'assunzione sulla frequenza attesa minima

Il test chi-quadrato è affidabile solo quando tutte le frequenze attese sono almeno pari a 5 (alcune fonti indicano almeno 1, purché non più del 20% sia inferiore a 5). Conteggi attesi piccoli rendono inaccurata l'approssimazione chi-quadrato. Quando questa assunzione non è rispettata, usi il test esatto di Fisher (scipy.stats.fisher_exact()) per le tabelle 2×2, oppure accorpi le categorie rare per aumentare i conteggi attesi. Esamini sempre la matrice delle frequenze attese restituita da chi2_contingency.

import numpy as np
from scipy import stats

# Table with small expected counts
observed = np.array([[2, 3], [100, 95]])
chi2, p, dof, expected = stats.chi2_contingency(observed)

print('Expected frequencies:')
print(expected)
print('Min expected:', expected.min())

if expected.min() < 5:
    print('Warning: Expected frequency < 5. Use Fisher\'s exact test.')
    odds_ratio, p_fisher = stats.fisher_exact(observed)
    print(f'Fisher\'s exact p: {p_fisher:.4f}')

Test esatto di Fisher per campioni piccoli

scipy.stats.fisher_exact(table) calcola la probabilità esatta di osservare la tabella 2×2 indicata (o una ancora più estrema) sotto l'ipotesi nulla di indipendenza, senza alcuna approssimazione. È sempre valido, indipendentemente dalla dimensione del campione o dai conteggi delle celle: il p-value è esatto, non approssimato. Il costo è computazionale: enumera tutte le tabelle possibili, risultando lento per totali elevati. Per le tabelle 2×2 con un conteggio qualsiasi inferiore a 5, preferisca sempre il test esatto di Fisher al chi-quadrato.

import numpy as np
from scipy import stats

# Small clinical trial: treatment vs. outcome
observed = np.array([
    [3, 12],   # treated: 3 improved, 12 did not
    [1, 18]    # control: 1 improved, 18 did not
])

odds_ratio, p = stats.fisher_exact(observed, alternative='two-sided')
print(f'Odds ratio: {odds_ratio:.3f}')
print(f'p-value: {p:.4f}')
print('Association significant?', 'Yes' if p < 0.05 else 'No')

Misurare la forza dell'associazione: V di Cramér

Come la d di Cohen per i t-test, la sola statistica chi-quadrato non misura la forza dell'associazione: è influenzata dalla dimensione del campione. La V di Cramér normalizza il chi-quadrato su una scala da 0 a 1: 0 indica assenza di associazione, 1 indica associazione perfetta. V = sqrt(chi2 / (n × min(r-1, c-1))). Indicazioni generali: < 0.1 è debole, 0.1–0.3 è moderata, > 0.3 è forte. Per un quadro completo, riporti sempre la V di Cramér insieme al p-value.

import pandas as pd
import numpy as np
from scipy import stats

np.random.seed(0)
df = pd.DataFrame({
    'tier': np.random.choice(['free', 'basic', 'pro'], 500),
    'churn': np.random.choice(['yes', 'no'], 500, p=[0.3, 0.7])
})
ct = pd.crosstab(df['tier'], df['churn'])

chi2, p, dof, _ = stats.chi2_contingency(ct)
n = ct.sum().sum()
min_dim = min(ct.shape[0]-1, ct.shape[1]-1)
cramers_v = np.sqrt(chi2 / (n * min_dim))

print(f'chi2={chi2:.3f}, p={p:.4f}')
print(f'Cramer\'s V: {cramers_v:.4f}')
print('Association strength:', 'strong' if cramers_v > 0.3 else 'moderate' if cramers_v > 0.1 else 'weak')

Test chi-quadrato di bontà dell'adattamento

Un'altra applicazione del chi-quadrato è il test di bontà dell'adattamento: verifica se le frequenze osservate corrispondono a una distribuzione ipotizzata. Ad esempio: «I risultati del dado sono distribuiti uniformemente?» oppure «Il traffico del nostro sito web segue l'andamento previsto in base al giorno della settimana?». Usi scipy.stats.chisquare(f_obs, f_exp), dove f_exp rappresenta le frequenze attese. L'ipotesi nulla è che i dati seguano la distribuzione specificata.

import numpy as np
from scipy import stats

# Observed: counts for each weekday over 70 weeks
observed = np.array([980, 1050, 1020, 1080, 1120, 850, 900])
# Expected: uniform distribution
expected = np.full(7, observed.sum() / 7)

chi2, p = stats.chisquare(f_obs=observed, f_exp=expected)
print('Observed:', observed)
print('Expected (uniform):', expected.round(0))
print(f'chi2={chi2:.3f}, p={p:.4f}')
print('Uniform?', 'Yes' if p > 0.05 else 'No - some days significantly busier')

Uso di pd.crosstab con normalise

Quando riporta i risultati del chi-quadrato, è utile mostrare le proporzioni anziché i conteggi grezzi, in modo che i lettori possano vedere l'associazione relativa. pd.crosstab(var1, var2, normalize='index') mostra la proporzione per riga (quale frazione di ogni categoria di riga rientra in ciascuna colonna). normalize='columns' mostra le proporzioni per colonna. Confrontare visivamente queste proporzioni prima di eseguire il test aiuta ad anticipare la direzione dell'associazione e a interpretare il risultato nel contesto.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'tier': np.random.choice(['free', 'basic', 'pro'], 300),
    'churned': np.random.choice(['yes', 'no'], 300, p=[0.3, 0.7])
})

# Row-proportions: churn rate within each tier
prop_table = pd.crosstab(df['tier'], df['churned'], normalize='index')
print('Churn rate by tier:')
print((prop_table * 100).round(1))

Test chi-quadrato nei test A/B

Il test chi-quadrato è il test standard per i tassi di conversione nei test A/B. Crei una tabella di contingenza 2×2 con righe = (controllo, variante) e colonne = (convertito, non convertito). Il test chi-quadrato indica se il tasso di conversione differisce in modo significativo tra i gruppi. Per campioni numerosi, equivale a un test z per due proporzioni. Per campioni piccoli (con un conteggio atteso < 5), usi invece il test esatto di Fisher.

import numpy as np
from scipy import stats

# A/B test: control vs. variant, conversions vs. non-conversions
control_conv = 45
control_total = 500
variant_conv = 63
variant_total = 500

contingency = np.array([
    [control_conv, control_total - control_conv],
    [variant_conv, variant_total - variant_conv]
])

chi2, p, dof, expected = stats.chi2_contingency(contingency)
print(f'Control rate: {control_conv/control_total:.1%}')
print(f'Variant rate: {variant_conv/variant_total:.1%}')
print(f'p-value: {p:.4f}')
print('Variant significantly better?', 'Yes' if p < 0.05 else 'No')

Verifica rapida

Verifichi la Sua comprensione dei concetti di analisi dei dati trattati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: pd.crosstab() + chi2_contingency() verificano se due variabili categoriali sono indipendenti sulla base delle frequenze osservate e attese, il test esatto di Fisher è l'alternativa più sicura quando i conteggi attesi sono inferiori a 5 e la V di Cramér misura la forza dell'associazione indipendentemente dalla dimensione del campione. Nella prossima lezione confronteremo le medie di tre o più gruppi con l'ANOVA e i test post hoc.

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Test chi-quadrato di indipendenza» è gratuita?

Sì — il testo completo di «Test chi-quadrato di indipendenza» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Pandas & NumPy Academy, passa a CoddyKit PRO. Il corso Pandas & NumPy Academy include 4 lezioni in totale.

Cosa imparerò in «Test chi-quadrato di indipendenza»?

Verifichi se due variabili categoriche sono indipendenti usando chi2_contingency su una tabella di frequenza ottenuta con una crosstab. Eserciti Pandas & NumPy Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Pandas & NumPy Academy?

Non è richiesta alcuna esperienza precedente. Pandas & NumPy Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Test chi-quadrato di indipendenza»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Pandas & NumPy Academy?

Sì. Ogni lezione Pandas & NumPy Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Statistiche descrittive e test di normalità
  2. T-test per confrontare le medie
  3. Test chi-quadrato di indipendenza
  4. ANOVA e test post-hoc
← Torna a Pandas & NumPy Academy