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.
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.
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
- Estatísticas descritivas e teste de normalidade
- Testes t para comparar médias
- Teste qui-quadrado de independência
- ANOVA e testes pós-hoc