Estatística para Cientistas de Dados
Carregando, aguarde alguns segundos.

4 - Experimentos estatísticos e testes de significância

O desenho de experimentos é uma pedra angular da prática da estatística, com aplicações em praticamente todas as áreas de pesquisa.

O objetivo é projetar um experimento para confirmar ou rejeitar uma hipótese.

Os cientistas de dados geralmente precisam realizar experimentos contínuos, particularmente em relação à interface do usuário e ao marketing do produto.

Este capítulo revisa o desenho experimental tradicional e discute alguns desafios comuns em ciência de dados.

Ele também cobre alguns conceitos frequentemente citados em inferência estatística e explica seu significado e relevância (ou falta de relevância) para a ciência de dados.

Sempre que você vê referências a significância estatística, testes t ou valores p, normalmente é no contexto do “pipeline” de inferência estatística clássica (veja a Figura 3-1).

Este processo começa com uma hipótese (“o medicamento A é melhor que o padrão existente medicamento” ou “o preço A é mais lucrativo do que o preço B existente”).

Um experimento (que pode ser um teste A/B) é projetado para testar a hipótese - projetado de tal forma que esperamos que produza resultados conclusivos.

Os dados são coletados e analisados, e então uma conclusão é tirada.

O termo inferência reflete a intenção de aplicar o resultados de experimentos, que envolvem um conjunto limitado de dados, para um processo maior ou população.

4.1 - Preparação dos dados

4.1.1 - Importação dos pacotes Python

from pathlib import Path
import random

import pandas as pd
import numpy as np

from scipy import stats
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats import power

import matplotlib.pylab as plt

4.1.2 - Diretório de dados

O diretório DATA contém os arquivos .csv utilizados nos exemplos.

DATA = './'

4.1.3 - Caminhos dos conjuntos de dados

Se você não mantiver seus dados no mesmo diretório que o código, adapte os nomes dos caminhos.

WEB_PAGE_DATA_CSV = DATA + 'web_page_data.csv'
FOUR_SESSIONS_CSV = DATA + 'four_sessions.csv'
CLICK_RATE_CSV = DATA + 'click_rates.csv'
IMANISHI_CSV = DATA + 'imanishi_data.csv'

4.2 - Reamostragem

A reamostragem em estatística significa amostrar repetidamente valores de dados observados, com um objetivo geral de avaliar a variabilidade aleatória em uma estatística.

Também pode ser usado para avaliar e melhorar a precisão de alguns modelos de aprendizado de máquina (por exemplo, as previsões de modelos de árvore de decisão construídos em vários conjuntos de dados bootstrap podem ser calculados em

um processo conhecido como ensacamento—consulte “Ensacando e a Floresta Aleatória” na página 259).

Existem dois tipos principais de procedimentos de reamostragem: o da alça de bota (bootstrap) e o de permutação de testes.

O bootstrap é usado para avaliar a confiabilidade de uma estimativa.

Os testes de permutação são usados para hipóteses de teste, geralmente envolvendo dois ou mais grupos.

session_times = pd.read_csv(WEB_PAGE_DATA_CSV)
session_times.Time = 100 * session_times.Time
#
ax = session_times.boxplot(
    by='Page',
    column='Time',
    figsize=(4, 4))
ax.set_xlabel('')
ax.set_ylabel('Time (in seconds)')
plt.suptitle('')
#
plt.tight_layout()
plt.show()
mean_a = session_times[session_times.Page == 'Page A'].Time.mean()
mean_b = session_times[session_times.Page == 'Page B'].Time.mean()
print(mean_b - mean_a)

Exemplo de teste de permutação com aderência:

def perm_fun(x, nA, nB):
    n = nA + nB
    idx_B = set(random.sample(range(n), nB))
    idx_A = set(range(n)) - idx_B
    return x.loc[list(idx_B)].mean() - x.loc[list(idx_A)].mean()
    
nA = session_times[session_times.Page == 'Page A'].shape[0]
nB = session_times[session_times.Page == 'Page B'].shape[0]
print(perm_fun(session_times.Time, nA, nB))

Em Python, podemos criar um gráfico usando matplotlib:

random.seed(1)
perm_diffs = [perm_fun(session_times.Time, nA, nB) for _ in range(1000)]

fig, ax = plt.subplots(figsize=(5, 5))
ax.hist(perm_diffs, bins=11, rwidth=0.9)
ax.axvline(x = mean_b - mean_a, color='black', lw=2)
ax.text(50, 190, 'Observed\ndifference', bbox={'facecolor':'white'})
ax.set_xlabel('Session time differences (in seconds)')
ax.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

Converta perm_diffs para matriz numpy para evitar problemas com algumas instalações do Python:

perm_diffs = np.array(perm_diffs)
print(np.mean(perm_diffs > mean_b - mean_a))

4.3 - Significância estatística e valores-P

random.seed(1)
obs_pct_diff = 100 * (200 / 23739 - 182 / 22588)
print(f'Observed difference: {obs_pct_diff:.4f}%')
conversion = [0] * 45945
conversion.extend([1] * 382)
conversion = pd.Series(conversion)
#
perm_diffs = [100 * perm_fun(conversion, 23739, 22588) 
              for _ in range(1000)]
#
fig, ax = plt.subplots(figsize=(5, 5))
ax.hist(perm_diffs, bins=11, rwidth=0.9)
ax.axvline(x=obs_pct_diff, color='black', lw=2)
ax.text(0.06, 200, 'Observed\ndifference', bbox={'facecolor':'white'})
ax.set_xlabel('Conversion rate (percent)')
ax.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

4.3.1 - Valor-P

Se np.mean for aplicado a uma lista de booleanos, fornece a porcentagem de quantas vezes True foi encontrado na lista (#True / #Total).

print(np.mean([diff > obs_pct_diff for diff in perm_diffs]))
#
survivors = np.array([[200, 23739 - 200], [182, 22588 - 182]])
chi2, p_value, df, _ = stats.chi2_contingency(survivors)
#
print(f'p-value for single sided test: {p_value / 2:.4f}')

4.4 - Testes-t

#
res = stats.ttest_ind(
    session_times[session_times.Page == 'Page A'].Time, 
    session_times[session_times.Page == 'Page B'].Time,
    equal_var=False)
#
print(f'p-value for single sided test: {res.pvalue / 2:.4f}')
#
tstat, pvalue, df = sm.stats.ttest_ind(
    session_times[session_times.Page == 'Page A'].Time, 
    session_times[session_times.Page == 'Page B'].Time,
    usevar='unequal', alternative='smaller')
#
print(f'p-value: {pvalue:.4f}')

4.5 - ANOVA

four_sessions = pd.read_csv(FOUR_SESSIONS_CSV)
print(pd.read_csv(FOUR_SESSIONS_CSV).head())
ax = four_sessions.boxplot(
    by='Page', 
    column='Time',
    figsize=(4, 4))
ax.set_xlabel('Page')
ax.set_ylabel('Time (in seconds)')
plt.suptitle('')
plt.title('')
#
plt.tight_layout()
plt.show()
observed_variance = four_sessions.groupby('Page').mean().var()[0]
print('Observed means:', four_sessions.groupby('Page').mean().values.ravel())
print('Variance:', observed_variance)

4.6 - Exemplo de teste de permutação com aderência

def perm_test(df):
    df = df.copy()
    df['Time'] = np.random.permutation(df['Time'].values)
    return df.groupby('Page').mean().var()[0]
#   
print(perm_test(four_sessions))
#
random.seed(1)
perm_variance = [perm_test(four_sessions) for _ in range(3000)]
print('Pr(Prob)', np.mean([var > observed_variance for var in perm_variance]))
fig, ax = plt.subplots(figsize=(5,5))
ax.hist(
    perm_variance,
    bins=11,
    rwidth=0.9)
ax.axvline(
    x = observed_variance,
    color='black', lw=2)
ax.text(
    60, 200, 'Observed\nvariance',
    bbox={'facecolor':'white'})
ax.set_xlabel('Variance')
ax.set_ylabel('Frequency')

plt.tight_layout()
plt.show()

4.6.1 - Estatística-F

Podemos calcular uma tabela ANOVA usando statsmodel.

model = smf.ols('Time ~ Page', data=four_sessions).fit()
#                
aov_table = sm.stats.anova_lm(model)
print(aov_table)
#
res = stats.f_oneway(
    four_sessions[four_sessions.Page == 'Page 1'].Time, 
    four_sessions[four_sessions.Page == 'Page 2'].Time,
    four_sessions[four_sessions.Page == 'Page 3'].Time,
    four_sessions[four_sessions.Page == 'Page 4'].Time)
print(f'F-Statistic: {res.statistic / 2:.4f}')
print(f'p-value: {res.pvalue / 2:.4f}')

4.6.1.1 - Anova bidirecional disponível apenas com statsmodels

# ```
# formula = 'len ~ C(supp) + C(dose) + C(supp):C(dose)'
# model = ols(formula, data).fit()
# aov_table = anova_lm(model, typ=2)
# ```

4.7 - Teste Qui-Quadrado

4.7.1 - Uma Abordagem de Reamostragem

Tabela 3-4:

click_rate = pd.read_csv(CLICK_RATE_CSV)
clicks = click_rate.pivot(index='Click', columns='Headline', values='Rate')
print(clicks)

Tabela 3-5:

row_average = clicks.mean(axis=1)
pd.DataFrame({
    'Headline A': row_average,
    'Headline B': row_average,
    'Headline C': row_average,
})
#
# Abordagem de reamostragem
#
box = [1] * 34
box.extend([0] * 2966)
random.shuffle(box)
#
def chi2(observed, expected):
    pearson_residuals = []
    for row, expect in zip(observed, expected):
        pearson_residuals.append([(observe - expect) ** 2 / expect
                                  for observe in row])
    # return sum of squares
    return np.sum(pearson_residuals)
#
expected_clicks = 34 / 3
expected_noclicks = 1000 - expected_clicks
expected = [34 / 3, 1000 - 34 / 3]
chi2observed = chi2(clicks.values, expected)
#
def perm_fun(box):
    sample_clicks = [sum(random.sample(box, 1000)),
                     sum(random.sample(box, 1000)),
                     sum(random.sample(box, 1000))]
    sample_noclicks = [1000 - n for n in sample_clicks]
    return chi2([sample_clicks, sample_noclicks], expected)
#
perm_chi2 = [perm_fun(box) for _ in range(2000)]
#
resampled_p_value = sum(perm_chi2 > chi2observed) / len(perm_chi2)
print(f'Observed chi2: {chi2observed:.4f}')
print(f'Resampled p-value: {resampled_p_value:.4f}')
#
chisq, pvalue, df, expected = stats.chi2_contingency(clicks)
print(f'Observed chi2: {chisq:.4f}')
print(f'p-value: {pvalue:.4f}')

4.7.2 - Figura distribuição qui-quadrado

x = [1 + i * (30 - 1) / 99 for i in range(100)]
#
chi = pd.DataFrame({
    'x': x,
    'chi_1': stats.chi2.pdf(x, df=1),
    'chi_2': stats.chi2.pdf(x, df=2),
    'chi_5': stats.chi2.pdf(x, df=5),
    'chi_10': stats.chi2.pdf(x, df=10),
    'chi_20': stats.chi2.pdf(x, df=20),
})
fig, ax = plt.subplots(figsize=(4, 2.5))
ax.plot(chi.x, chi.chi_1, color='black', linestyle='-', label='1')
ax.plot(chi.x, chi.chi_2, color='black', linestyle=(0, (1, 1)), label='2')
ax.plot(chi.x, chi.chi_5, color='black', linestyle=(0, (2, 1)), label='5')
ax.plot(chi.x, chi.chi_10, color='black', linestyle=(0, (3, 1)), label='10')
ax.plot(chi.x, chi.chi_20, color='black', linestyle=(0, (4, 1)), label='20')
ax.legend(title='df')
#
plt.tight_layout()
plt.show()

4.7.3 - Teste Exato de Fisher

# O Scipy possui apenas uma implementação do teste Exato de Fisher para matrizes 2x2.
# Existe um repositório github que fornece uma implementação Python que usa o mesmo código da versão R.
# A instalação requer um compilador Fortran.
# ```
# stats.fisher_exact(clicks)
# ```
# stats.fisher_exact(clicks.values)

4.7.3.1 - Fraude Científica

imanishi = pd.read_csv(IMANISHI_CSV)
imanishi.columns = [c.strip() for c in imanishi.columns]
ax = imanishi.plot.bar(x='Digit', y=['Frequency'], legend=False,
                      figsize=(4, 4))
ax.set_xlabel('Digit')
ax.set_ylabel('Frequency')
#
plt.tight_layout()
plt.show()

4.8 - Potência e tamanho da amostra

statsmodels tem vários métodos para cálculo de potência.

Veja, por exemplo: https://machinelearningmastery.com/statistical-power-and-power-analysis-in-python/

effect_size = sm.stats.proportion_effectsize(0.0121, 0.011)
analysis = sm.stats.TTestIndPower()
result = analysis.solve_power(
    effect_size=effect_size, 
    alpha=0.05,
    power=0.8,
    alternative='larger')
print('Sample Size: %.3f' % result)
#
effect_size = sm.stats.proportion_effectsize(0.0165, 0.011)
analysis = sm.stats.TTestIndPower()
result = analysis.solve_power(
    effect_size=effect_size, 
    alpha=0.05, power=0.8, alternative='larger')
print('Sample Size: %.3f' % result)
Arduino
Coautor
Betobyte
Autor
Autores
||| Áreas ||| Estatística ||| Python ||| Projetos ||| Dicas & Truques ||| Quantum ||| Estatística para Cientistas de Dados || Python para Iniciantes || Python Básico || Matplotlib || Numpy || Seaborn || Pandas || Django || Estatística para Cientistas de Dados || Python com ML Básico || Python com ML Básico || Aulas | 1 (Introdução) | 2 (Análise de dados exploratória) | 3 (Dados e exemplos de distribuições) | 4 (Experimentos estatísticos e testes de significância) | 5 (Regressão e previsão) | 6 (Regressão e previsão) | 7 (Aprendizado de máquina estatístico) | 8 (Aprendizado não supervisionado) |