Pandas & NumPy Academy · Aula

Teste qui-quadrado de independência

Teste se duas variáveis categóricas são independentes usando chi2_contingency em uma tabela de frequências de tabulação cruzada.

Aula 3 de 413 etapas

Teste qui-quadrado de independência é uma aula grátis de Pandas & NumPy Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Pandas & NumPy Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Pandas & NumPy Academy inclui 4 aulas no total.

Testando relações categóricas

O teste do qui-quadrado de independência verifica se duas variáveis categóricas são estatisticamente independentes ou se existe uma associação entre elas. Por exemplo: “A rotatividade de clientes é independente do nível de assinatura?” ou “A preferência por produtos é independente da faixa etária?” Diferentemente dos testes t, que comparam médias numéricas, os testes do qui-quadrado comparam as frequências observadas em uma tabela de contingência com as frequências que esperaríamos se as variáveis fossem independentes.

Criando uma tabela de contingência com o Pandas

Uma tabela de contingência (também chamada de tabulação cruzada) mostra a contagem de observações para cada combinação de duas variáveis categóricas. pd.crosstab(df['var1'], df['var2']) cria essa tabela diretamente a partir de um DataFrame. Cada célula contém a contagem de observações em que a categoria da linha e a categoria da coluna aparecem juntas. Essa é a entrada para 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())

Executando chi2_contingency()

scipy.stats.chi2_contingency(observed) recebe a tabela de contingência observada (como uma matriz do NumPy ou um DataFrame do Pandas) e retorna quatro valores: a estatística do qui-quadrado, o valor de p, os graus de liberdade e a tabela de frequências esperadas. A hipótese nula é H₀: the two variables are independent. Se p ≤ 0.05, rejeitamos a independência e concluímos que existe uma associação estatisticamente 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')

Entendendo as frequências esperadas

As frequências esperadas representam como seriam as contagens das células se as duas variáveis fossem perfeitamente independentes. Para cada célula, expected = (row_total × column_total) / grand_total. A estatística do qui-quadrado mede a soma das diferenças ao quadrado entre as contagens observadas e esperadas, normalizada pelas contagens esperadas. Um qui-quadrado grande significa que as contagens observadas se desviam bastante da independência; um qui-quadrado pequeno significa que os dados são compatíveis com a independência.

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))

Graus de liberdade no qui-quadrado

Para uma tabela de contingência com r linhas e c colunas, os graus de liberdade são df = (r-1) × (c-1). Uma tabela 2×2 tem df=1; uma tabela 3×4 tem df=6. O valor crítico do qui-quadrado aumenta com os graus de liberdade: para df=1 com α=0,05, o valor crítico é ≈ 3,84; para df=6 com α=0,05, é ≈ 12,6. A função chi2_contingency trata disso automaticamente, mas conhecer a fórmula ajuda a entender por que o qui-quadrado de tabelas maiores precisa de estatísticas maiores para ser significativo.

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}')

O pressuposto da frequência esperada mínima

O teste do qui-quadrado só é confiável quando todas as frequências esperadas são de pelo menos 5 (algumas fontes afirmam que basta serem de pelo menos 1, desde que no máximo 20% sejam inferiores a 5). Contagens esperadas pequenas tornam imprecisa a aproximação do qui-quadrado. Quando esse pressuposto é violado, use o teste exato de Fisher (scipy.stats.fisher_exact()) para tabelas 2×2 ou agrupe categorias raras para aumentar as contagens esperadas. Inspecione sempre a matriz de frequências esperadas retornada por 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}')

Teste exato de Fisher para amostras pequenas

scipy.stats.fisher_exact(table) calcula a probabilidade exata de observar a tabela 2×2 fornecida (ou uma ainda mais extrema) sob a hipótese nula de independência, sem qualquer aproximação. Ele é sempre válido, independentemente do tamanho da amostra ou das contagens das células — o valor de p é exato, não aproximado. O custo é computacional: a função enumera todas as tabelas possíveis, o que a torna lenta para totais grandes. Para tabelas 2×2 com qualquer contagem de célula inferior a 5, prefira sempre o teste exato de Fisher ao qui-quadrado.

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')

Medindo a força da associação: V de Cramér

Assim como o d de Cohen nos testes t, a estatística do qui-quadrado, sozinha, não mede a força da associação — ela é inflada pelo tamanho da amostra. O V de Cramér normaliza o qui-quadrado para uma escala de 0 a 1: 0 significa nenhuma associação e 1 significa associação perfeita. V = sqrt(chi2 / (n × min(r-1, c-1))). Orientações: < 0,1 é fraca, 0,1–0,3 é moderada e > 0,3 é forte. Relate sempre o V de Cramér junto com o valor de p para obter uma visão completa.

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')

Ajuste pelo qui-quadrado

Outra aplicação do qui-quadrado é o teste de aderência: ele verifica se as frequências observadas correspondem a uma distribuição hipotética. Por exemplo: “Os resultados do dado são distribuídos uniformemente?” ou “O tráfego do nosso site segue o padrão esperado para os dias da semana?” Use scipy.stats.chisquare(f_obs, f_exp), em que f_exp são as frequências esperadas. A hipótese nula é que os dados seguem a distribuição especificada.

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')

Usando pd.crosstab com normalização

Ao relatar resultados do qui-quadrado, é útil mostrar proporções em vez de contagens brutas, para que os leitores possam ver a associação relativa. pd.crosstab(var1, var2, normalize='index') mostra a proporção por linha (qual fração de cada categoria de linha pertence a cada coluna). normalize='columns' mostra as proporções por coluna. Comparar visualmente essas proporções antes de executar o teste ajuda a antecipar a direção da associação e a interpretar o resultado no contexto.

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))

Teste do qui-quadrado em testes A/B

O teste do qui-quadrado é o teste padrão para taxas de conversão em testes A/B. Crie uma tabela de contingência 2×2 com linhas = (controle, variante) e colunas = (convertido, não convertido). O teste do qui-quadrado informa se a taxa de conversão difere significativamente entre os grupos. Para amostras grandes, isso equivale a um teste z de duas proporções. Para amostras pequenas (qualquer contagem esperada < 5), use o teste exato de 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ção rápida

Teste sua compreensão dos conceitos de Análise de Dados desta lição.

Recapitulação da lição

Nesta lição, você aprendeu que pd.crosstab() + chi2_contingency() testam se duas variáveis categóricas são independentes com base nas frequências observadas e esperadas, que o teste exato de Fisher é a alternativa segura quando as contagens esperadas são inferiores a 5 e que o V de Cramér mede a força da associação independentemente do tamanho da amostra. Em seguida, compararemos médias entre três ou mais grupos com ANOVA e testes pós-hoc.

Grátis para começar

Aprenda Python com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
30
Aulas
120

Perguntas Frequentes

A aula “Teste qui-quadrado de independência” é grátis?

Sim — o texto completo de “Teste qui-quadrado de independência” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Pandas & NumPy Academy, atualize para CoddyKit PRO. O curso de Pandas & NumPy Academy inclui 4 aulas no total.

O que vou aprender em “Teste qui-quadrado de independência”?

Teste se duas variáveis categóricas são independentes usando chi2_contingency em uma tabela de frequências de tabulação cruzada. Você pratica Pandas & NumPy Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Pandas & NumPy Academy?

Nenhuma experiência prévia é necessária. Pandas & NumPy Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “Teste qui-quadrado de independência”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Pandas & NumPy Academy?

Sim. Cada aula de Pandas & NumPy Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Estatísticas descritivas e teste de normalidade
  2. Testes t para comparar médias
  3. Teste qui-quadrado de independência
  4. ANOVA e testes pós-hoc
← Voltar para Pandas & NumPy Academy