Correlação

Entendimento de relação entre dados.

Resultados Esperados

  1. Entender como sumarizar dados em duas dimensões.
  2. Entender correlação e covariância.
  3. Entender o paradoxo de simpson.
  4. Sumarização de dados em duas dimensões.
  5. Correlação não é causalidade.

Sumário

  1. Correlação
    1. Introdução
    2. Dados Sintéticos
    3. Dados Reais
      1. Olhando para os Dados
      2. Dispersão
    4. Covariânça
    5. Spearman
      1. Spearman com Dados Sintéticos
    6. Valores-p de Correlações
      1. Spearman com Dados Reais
    7. Entendendo uma correlação
    8. Algumas Outras Advertências Correlacionais
    9. Correlação e Causalidade
    10. Dados Categóricos
    11. Paradoxo de Simpson
      1. Paradoxo em Dados Categóricos

#In: 
# -*- coding: utf8

from scipy import stats as ss

import seaborn as sns

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
#In: 
plt.style.use('seaborn-colorblind')
plt.rcParams['figure.figsize']  = (16, 10)
plt.rcParams['axes.labelsize']  = 20
plt.rcParams['axes.titlesize']  = 20
plt.rcParams['legend.fontsize'] = 20
plt.rcParams['xtick.labelsize'] = 20
plt.rcParams['ytick.labelsize'] = 20
plt.rcParams['lines.linewidth'] = 4
#In: 
plt.ion()
#In: 
def despine(ax=None):
    if ax is None:
        ax = plt.gca()
    # Hide the right and top spines
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)

    # Only show ticks on the left and bottom spines
    ax.yaxis.set_ticks_position('left')
    ax.xaxis.set_ticks_position('bottom')

Introdução

Lembrando das aulas anteriores, podemos usar estatísticas para sumarizar dados e suas tendências centrais. Embora seja um campo rico, ainda não exploramos a ideia de tendências centrais em dados de duas dimensões. Nesta aula, teremos uma abordagem de exploração.

Dados Sintéticos

Vamos inicial entendendo os gráficos de dispersão para pares de colunas. Inicialmente, queremos ter alguma indicação visual da correlação entre nossos dados. Sendo $X = {x_1, x_2, \cdots}$ e $Y = {y_1, y_2, \cdots}$ um par de colunas, o gráfico mostra um ponto em cada coordenada ($x_i$, $y_i$).

No primeiro vamos mostrar um plot de números aleatórios de uma normal. Para cada linha, vamos gerar de forma independente outra normal. Como seria um formato esperado do gráfico?

#In: 
x = np.random.randn(1000)
y = np.random.randn(1000)
plt.scatter(x, y, edgecolor='k', alpha=0.6)
plt.xlabel('Random Normal X')
plt.ylabel('Random Normal Y')
despine()

png

No segundo vamos mostrar um plot no eixo x números aleatórios de uma normal. No eixo y, vamos plotar o valor de x adicionados de outra normal. Qual é o valor esperado?

#In: 
x = np.random.randn(1000)
y = x + np.random.randn(1000)
plt.scatter(x, y, edgecolor='k', alpha=0.6)
plt.xlabel('Random Normal X')
plt.ylabel('Random Normal Y + Outra Normal')
despine()

png

Agora vamos fazer $-x - Normal(0, 1)$.

#In: 
x = np.random.randn(1000)
y = -x - np.random.randn(1000)
plt.scatter(x, y, edgecolor='k', alpha=0.6)
plt.xlabel('Random Normal X')
plt.ylabel('-X - Outra Normal')
despine()

png

Por fim, um caso quadrático.

#In: 
x = np.random.randn(1000)
y = x * x + np.random.randn(1000) + 10
plt.scatter(x, y, edgecolor='k', alpha=0.6)
plt.xlabel('Random Normal X')
plt.ylabel('X * X + Random')
despine()

png

Dados Reais

Nesta aula vamos utilizados dados de preços de carros híbridos. Nos EUA, um carro híbrido pode rodar tanto em eletricidade quanto em combustível. A tabela contém as vendas de 1997 até 2003.

Uma máxima dessa aula será: Sempre visualize seus dados.

As colunas são:

  1. vehicle: model of the car
  2. year: year of manufacture
  3. msrp: manufacturer’s suggested retail price in 2013 dollars
  4. acceleration: acceleration rate in km per hour per second
  5. mpg: fuel econonmy in miles per gallon
  6. class: the model’s class.

Olhando para os Dados

Vamos iniciar olhando para cada coluna dos dados.

#In: 
df = pd.read_csv('https://media.githubusercontent.com/media/icd-ufmg/material/master/aulas/15-Correlacao/hybrid.csv')
df['msrp'] = df['msrp'] / 1000
df.head()
vehicleyearmsrpaccelerationmpgclass
0Prius (1st Gen)199724.509747.4641.26Compact
1Tino200035.354978.2054.10Compact
2Prius (2nd Gen)200026.832257.9745.23Compact
3Insight200018.936419.5253.00Two Seater
4Civic (1st Gen)200125.833387.0447.04Compact

A coluna MSRP é o preço médio de venda. Cada linha da tabela é um carro.

#In: 
plt.hist(df['acceleration'], bins=20, edgecolor='k')
plt.title('Histograma de Aceleração')
plt.xlabel('Aceleração em Milhas por Hora')
plt.ylabel('Num. Carros')
despine()

png

A coluna Year é o ano de fabricação.

#In: 
bins = np.arange(1997, 2013) + 0.5
plt.hist(df['year'], bins=bins, edgecolor='k')
plt.title('Histograma de Modelos por Ano')
plt.xlabel('Ano')
plt.ylabel('Num. Carros')
despine()

png

A coluna MSRP é o preço do carro.

#In: 
plt.hist(df['msrp'], bins=20, edgecolor='k')
plt.title('Histograma de Modelos por Ano')
plt.xlabel('Preço em Mil. Dólar')
plt.ylabel('Num. Carros')
despine()

png

A coluna MPG captura as milhas por hora.

#In: 
plt.hist(df['mpg'], bins=20, edgecolor='k')
plt.title('Histograma de Modelos por Ano')
plt.xlabel('Milhas por Hora')
plt.ylabel('Num. Carros')
despine()

png

Os gráficos acima nos dão uma visão geral dos dados. Note que, como esperado, cada coluna tem uma faixa diferente de valores no eixo-x. Além do mais, a concentração (lado esquerdo/direito) diferente entre as colunas. Como que podemos comparae as colunas? Cada uma está representada em uma unidade diferente.

Vamos fazer os gráficos de dispersão para todos os pares.

Dispersão

#In: 
plt.scatter(df['acceleration'], df['msrp'], edgecolor='k', alpha=0.6, s=80)
plt.xlabel('MSRP')
plt.ylabel('Acc.')
plt.title('Consumo vs Acc')
despine()

png

#In: 
plt.scatter(df['mpg'], df['acceleration'], edgecolor='k', alpha=0.6, s=80)
plt.xlabel('MPG')
plt.ylabel('Acc.')
plt.title('Consumo vs Aceleração')
despine()

png

#In: 
plt.scatter(df['msrp'], df['mpg'], edgecolor='k', alpha=0.6, s=80)
plt.xlabel('MSRP')
plt.ylabel('MPG')
plt.title('Preço vs Consumo')
despine()

png

Covariânça

Agora analisaremos a covariância, o análogo pareado da variância. Enquanto a variância mede como uma única variável se desvia de sua média, a covariância mede como duas variáveis $X = {x_1, \cdots, x_n}$ e $Y = {y_1, \cdots, y_n}$ variam em conjunto a partir de suas médias $\bar{x}$ e $\bar{y}$:

\[cov(X, Y) = \frac{\sum_{i=1}^{n}{(x_i - \bar{x})(y_i - \bar{y})}}{n-1},\] \[(x_i - \bar{x})(y_i - \bar{y})\]
#In: 
def covariance(x, y):
    n = len(x)
    x_m = x - np.mean(x)
    y_m = y - np.mean(y)
    return (x_m * y_m).sum() / (n - 1)
#In: 
covariance(df['acceleration'], df['msrp'])
43.809528657120744
#In: 
covariance(df['msrp'], df['mpg'])
-125.00248062016253

Entendendo a estatística. Quando os elementos correspondentes de x e y estão ambos acima de suas médias ou ambos abaixo de suas médias, um número positivo entra na soma. Quando um está acima de sua média e o outro abaixo, um número negativo entra na soma. Assim, uma covariância positiva “grande” significa que x tende a ser grande quando y é grande, e pequeno quando y é pequeno. Uma covariância negativa “grande” significa o oposto - que x tende a ser pequeno quando y é grande e vice-versa. Uma covariância próxima de zero significa que não existe tal relação.

Para entender, veja a tabela abaixo que mostra três colunas novas. Inicialmente podemos ver a diferença de cada coluna com sua média. Por fim, podemos ver também uma coluna impacto. A mesma tem valor 1 quando o sinal é o mesmo das colunas subtraídas da média. Uma métrica de correlação vai ser proporcional ao valor da soma deste impacto.

#In: 
df_n = df[['msrp', 'mpg']].copy()
df_n['msrp_menos_media'] = df_n['msrp'] - df['msrp'].mean()
df_n['mpg_menos_media'] = df_n['mpg'] - df['mpg'].mean()
df_n['impacto'] = np.zeros(len(df_n)) - 1
df_n['impacto'][(df_n['mpg_menos_media'] > 0) & (df_n['msrp_menos_media'] > 0)] = 1
df_n['impacto'][(df_n['mpg_menos_media'] < 0) & (df_n['msrp_menos_media'] < 0)] = 1
df_n.head(n=20)
msrpmpgmsrp_menos_mediampg_menos_mediaimpacto
024.5097441.26-14.8096956.462549-1.0
135.3549754.10-3.96446519.302549-1.0
226.8322545.23-12.48718510.432549-1.0
318.9364153.00-20.38302518.202549-1.0
425.8333847.04-13.48605512.242549-1.0
519.0367153.00-20.28272518.202549-1.0
619.1370153.00-20.18242518.202549-1.0
738.0847740.46-1.2346655.662549-1.0
819.1370153.00-20.18242518.202549-1.0
914.0719241.00-25.2475156.202549-1.0
1036.6761031.99-2.643335-2.8074511.0
1119.2373152.00-20.08212517.202549-1.0
1220.3556446.00-18.96379511.202549-1.0
1330.0896417.00-9.229795-17.7974511.0
1458.5211428.2319.201705-6.567451-1.0
1526.3544439.99-12.9649955.192549-1.0
1629.1862129.40-10.133225-5.3974511.0
1719.3877652.00-19.93167517.202549-1.0
1818.2363341.00-21.0831056.202549-1.0
1919.3225629.00-19.996875-5.7974511.0
#In: 
def corr(x, y):
    n = len(x)
    x_m = x - np.mean(x)
    x_m = x_m / np.std(x, ddof=1)
    y_m = y - np.mean(y)
    y_m = y_m / np.std(y, ddof=1)
    return (x_m * y_m).sum() / (n - 1)

No entanto, a covariância pode ser difícil de interpretar por duas razões principais:

  • Suas unidades são o produto das unidades das entradas. Como interpretar o produto de aceleração por preço?
  • A métrica não é normalizada. Cov(X, Y) de -125 é um valor alto? Note que ao multiplica X * 2 a mesma duplica, mas uma relação linear não muda muito neste caso.

Por esse motivo, é mais comum observar a correlação de Pearson. Uma forma de estimar a mesma é padronizar as variáveis. Assim, vamos fazer uma transformada $Z$:

\[\hat{X} = \frac{X - \bar{x}}{s_x}\] \[\hat{Y} = \frac{Y - \bar{y}}{s_y}\]

Lembrando que $\bar{x}$ é a média e $s_x$ o desvio padrão. Podemos estimar os mesmos dos dados. Ao computar a covariânça com os novos valores normalizados, teremos um resultado final entre 0 e 1.

\[corr(X, Y) = \frac{\sum_i \frac{x_i - \bar{x}}{s_x} \frac{y_i - \bar{x}}{s_y}}{n-1}\]

Ou de forma equivalente:

\(corr(X, Y) = \frac{cov(X, Y)}{s_x s_y}\).

#In: 
corr(df['acceleration'], df['msrp'])
0.6955778996913979
#In: 
corr(df['msrp'], df['mpg'])
-0.5318263633683785

Por fim, a biblioteca seaborn permite observar todas as correlações em um único plot!

#In: 
sns.pairplot(df, diag_kws={'edgecolor':'k'}, plot_kws={'alpha':0.5, 'edgecolor':'k'})
<seaborn.axisgrid.PairGrid at 0x7f570e8dfa00>

png

Spearman

Para casos não lineares você pode usar um coeficiente de correlação de posto (rank correlation coefficient).

O coeficiente de correlação de Spearman $\rho$ é definido como o coeficiente de correlação de Pearson para os postos das variáveis. Para uma amostra de tamanho $n$, os valores originais $X_{i},Y_{i}$ das variáveis $X$ e $Y$ são convertidos em postos (ranks) $\operatorname {rg} X_{i},\operatorname {rg} Y_{i}$, sendo $\rho$ calculado como:

\[\rho = r({\operatorname {rg} _{X},\operatorname {rg} _{Y}})={\frac {\operatorname {cov} (\operatorname {rg} _{X},\operatorname {rg} _{Y})}{\sigma _{\operatorname {rg} _{X}}\sigma _{\operatorname {rg} _{Y}}}}\]

em que

$r(.)$ denota o coeficiente de correlação de Pearson usual, mas calculado sobre os postos das variáveis;

$\operatorname {cov} (\operatorname {rg} _{X},\operatorname {rg} _{Y})$ são as covariâncias dos postos das variáveis;

$\sigma _{\operatorname {rg} _{X}}$ and $\sigma _{\operatorname {rg} _{Y}}$ são os desvios padrões dos postos das variáveis.

Para computar o posto vamos usar a função abaixo.

#In: 
def rank(x):
    aux = x.argsort()
    return aux.argsort()

Primeiro, fazemos um argsort no vetor. Esta operação retorna, para cada elemento do vetor, sua posição quando o vetor for ordenado. Isto é:

x[x.argsort()] == x.sort()

#In: 
x = np.array([7, 8, 9, 10, 1])
x
array([ 7,  8,  9, 10,  1])
#In: 
x.argsort()
array([4, 0, 1, 2, 3])
#In: 
x[x.argsort()]
array([ 1,  7,  8,  9, 10])

Quando chamamos argsort duas vezes:

  1. Retorna a posição dos elementos quando o vetor for ordenado.
  2. Ao ordenar as posições, qual é o posto (rank) do item? Isto é, é o primeiro, segundo, terceiro menor.
#In: 
x.argsort().argsort()
array([1, 2, 3, 4, 0])
#In: 
x
array([ 7,  8,  9, 10,  1])

Observe no resultado acima que:

  1. 7 é o segundo elemento
  2. 8 é o terceiro
  3. 9 é o quarto
  4. 0 é o primeiro

Assim, para computar a correlação de spearman basta correlacionar o vetor de postos.

Spearman com Dados Sintéticos

#In: 
x = np.random.normal(10, 100, 1000)
x = x[x > 0]
y = np.log2(x) + np.random.normal(0, 0.1, len(x))
plt.scatter(x, y, edgecolor='k', alpha=0.6)
plt.xlabel('X')
plt.ylabel('Y')
despine()

png

Comparando os postos (ranqueamento).

#In: 
x_p = rank(x)
y_p = rank(y)
plt.scatter(x_p, y_p, edgecolor='k', alpha=0.6)
plt.xlabel('X')
plt.ylabel('Y')
despine()

png

#In: 
corr(x, y)
0.8167497869189468
#In: 
corr(x_p, y_p)
0.9956422157250499

Como sempre, as funções já existem em Python:

#In: 
rho, p_val = ss.pearsonr(x, y)
print(rho)
print(p_val)
0.8167497869189468
2.603256400260837e-131
#In: 
rho, p_val = ss.spearmanr(x, y)
print(rho)
print(p_val)
0.9956422157250497
0.0

Valores-p de Correlações

Note que os resultados de scipy vêm com um Valor-p. Lembrando das aulas anteriores, como seria uma hipótese nula para uma correlação?

  1. H0: A correlação observada é estatisticamente explicada por permutações.
  2. H1: A correlação observada é mais extrema do que permutações

Observe como os dados abaixo tem uma correlação quase que perfeita!

#In: 
x = np.array([7.1, 7.1, 7.2, 8.3, 9.4])
y = np.array([2.8, 2.9, 2.8, 2.6, 3.5])
plt.scatter(x, y, edgecolor='k', alpha=0.6, s=80)
despine()

png

#In: 
corr(x, y)
0.6732254696830964

Temos uma correlação até que OK! Agora vamos permutar X 10,000 vezes.

#In: 
x_perm = x.copy()
perm_corr = []
for _ in range(10000):
    np.random.shuffle(x_perm)
    perm_corr.append(corr(x_perm, y))
perm_corr = np.array(perm_corr)
#In: 
plt.hist(perm_corr, edgecolor='k')
plt.xlabel('Correlação na Permutação')
plt.ylabel('Quantidade de Permutações')
plt.vlines(corr(x, y), 0, 1500, color='r')
plt.text(0.68, 1000, 'Observado')
despine()

png

#In: 
sum(perm_corr > corr(x, y)) / len(perm_corr)
0.1614

A mesma não é significativa :-(

Spearman com Dados Reais

Note como a correlação de spearman é um pouco melhor nos dados abaixo:

#In: 
x = df['mpg']
y = df['msrp']

plt.scatter(x, y, edgecolor='k', alpha=0.6, s=80)
plt.xlabel('MPG')
plt.ylabel('MSRP')
despine()

png

#In: 
x_p = rank(x)
y_p = rank(y)
#In: 
corr(x, y)
-0.5318263633683785
#In: 
corr(x_p, y_p)
-0.5772419015453071

Entendendo uma correlação

Nas próximas aulas vamos explorar o conceito de regressão linear. As nossas correlações até o momento já estão explorando tal correlação. Abaixo vemos 4 bases de dados com a melhor regressão.

#In: 
anscombe = sns.load_dataset('anscombe')
sns.lmplot(x='x', y='y', col='dataset', hue='dataset', data=anscombe, ci=None)
<seaborn.axisgrid.FacetGrid at 0x7f570e859af0>

png

#In: 
for data in ['I', 'II', 'IV', 'V']:
    sub = anscombe.query(f'dataset == "{data}"')
    if sub.values.any():
        print('spearman', ss.spearmanr(sub['x'], sub['y'])[0])
        print('pearson', ss.pearsonr(sub['x'], sub['y'])[0])
        print()
spearman 0.8181818181818182
pearson 0.8164205163448399

spearman 0.690909090909091
pearson 0.8162365060002427

spearman 0.5
pearson 0.8165214368885029

Algumas Outras Advertências Correlacionais

Uma correlação de zero indica que não há relação linear entre as duas variáveis. No entanto, outros tipos de relacionamentos podem existir. Por exemplo, se:

#In: 
x1 = np.random.normal(5, 1, 10000)
x2 = np.random.normal(-5, 1, 10000)
y1 = np.random.normal(10, 1, 10000)
y2 = np.random.normal(-10, 1, 10000)
x = np.concatenate((x1, x2, x2, x1))
y = np.concatenate((y1, y2, y1, y2))
plt.scatter(x, y, alpha=0.6, edgecolors='k')
despine()

png

então x e y têm correlação perto de zero. Mas eles certamente têm um relacionamento. Observe os quatro grupos!

Correlação e Causalidade

Lembrando de que que “correlação não é causalidade”, provavelmente por alguém que olha dados que representam um desafio para partes de sua visão de mundo que ele estava relutante em questionar. No entanto, este é um ponto importante - se x e y estão fortemente correlacionados, isso pode significar que x causa y, y causa x, que cada um causa o outro, que algum terceiro fator causa ambos, ou pode não significar nada.

Nos exemplos acima:

  1. Quanto maior o consumo de um carro, mais o mesmo acelera. Podemos concluir causalidade?
  2. Quanto maior a aceleração, maior o preço. Podemos concluir causalidade?

No primeiro caso temos uma relação física, para obter uma maior velocidade precisamos queimar mais combustível. Isto é esperado dado a mecânica do carro. E no segundo caso?! Pode existir uma série de variáveis que levam a um maior preço. Não podemos dizer que é apenas a aceleração. Pode ser que os carros que aceleram mais são carros esportivos. O mesmo vai ter um preço alto mesmo se não acelerar muito.

Dados Categóricos

Para explorar dados categóricos vamos fazer um da estatística $\chi^2$. Para uma tabela de dados, a estatística $\chi^2$ é definida como:

\[\chi^2=\sum_{i=1}^n \frac{(O_i-E_i)^2}{E_i}\]

Aqui, $O_i$ é um valor observado e $E_i$ é um valor esperado. Note que quanto maior este valor, mais o Observado difere do Esperado. O problema é, como definir $E_i$? É aqui que podemos levantar uma hipótese nula. Qual é ao valor esperado caso a seleção fosse uniforme?

Vamos explorar essa ideia abaixo. Para tal, vamos fazer um teste de permutações.

#In: 
def permuta(df, coluna):
    '''
    Permuta um dataframe com base e uma coluna categórica.
    Este código é mais lento pois cria uma cópia.
    
    Parâmetros
    ----------
    df: o dataframe
    coluna: uma coluna categórica
    
    Retorna
    -------
    um novo df permutado
    '''
    
    novo = df.copy()            # Cópia dos dados
    dados = df[coluna].copy()   # Copia da coluna, evitar um warning pandas. Deve ter forma melhor de fazer.
    np.random.shuffle(dados)    # Faz o shuffle
    novo[coluna] = dados        # Faz overwrite da coluna
    return novo

Aqui leio os dados do Titanic.

  1. class: a classe, quanto maior mais cara a passage. O caso especial 0 é a classe de tripulantes.
  2. Age, Sex e Survived. Valor um quando presente (sobreviveu), zero quando ausente (não sobreviveu). Para age, 1 e 0 captura crianças (0) e adultos (1).
#In: 
df = pd.read_csv('https://media.githubusercontent.com/media/icd-ufmg/material/master/aulas/15-Correlacao/survival_titanic.csv')
df.tail()
classAgeSexSurvived
21960101
21970101
21980100
21990100
22000100

Vamos montar uma tabela de contigência na mão. Para tal, vou inidicar contando a fração de pessoas que sobreviveram por classe. Podemos aplicar o group-by na classe e tirar a média. Lembrando que a média de 1s e 0s captura uma fração.

#In: 
df[['class', 'Survived']].groupby('class').mean()
Survived
class
00.239548
10.624615
20.414035
30.252125

Agora montar o dataframe novo.

ps: existe uma função crosstab do pandas que faz tudo isso. Estamos fazendo na mão para aprender.

#In: 
cont = df[['class', 'Survived']].groupby('class').mean() # taxa survival
cont['Died'] = 1 - cont['Survived'] # não survival, nova coluna 1-
cont['Count'] = df[['class']].groupby('class').size() # número em cada classe
cont
SurvivedDiedCount
class
00.2395480.760452885
10.6246150.375385325
20.4140350.585965285
30.2521250.747875706

Agora, vamos computar a taxa de sobrevência global.

#In: 
df['Survived'].mean()
0.3230349840981372

Com a taxa acima posso criar um novo dataframe do que seria esperado em um mundo uniforme.

#In: 
unif = cont.copy()
unif['Survived'] = df['Survived'].mean()
unif['Died'] = 1 - df['Survived'].mean()
unif
SurvivedDiedCount
class
00.3230350.676965885
10.3230350.676965325
20.3230350.676965285
30.3230350.676965706

E por fim, computar:

\[\chi^2=\sum_{i=1}^n \frac{(O_i-E_i)^2}{E_i}\]
#In: 
chi_sq = (cont - unif) ** 2
chi_sq = chi_sq / unif
chi_sq
SurvivedDiedCount
class
00.0215770.0102960.0
10.2815510.1343510.0
20.0256350.0122330.0
30.0155660.0074280.0

Por coluna

#In: 
chi_sq.sum()
Survived    0.344328
Died        0.164307
Count       0.000000
dtype: float64

Tudo

#In: 
t_obs = chi_sq.sum().sum()
t_obs
0.5086353797871764

Daqui para frente é só fazer o teste de permutação! Observe uma permutação abaixo.

#In: 
permut = permuta(df, 'class')
cont_p = permut[['class', 'Survived']].groupby('class').mean()
cont_p['Died'] = 1 - cont_p['Survived']
cont_p['Count'] = df[['class']].groupby('class').size()
cont_p
SurvivedDiedCount
class
00.3299440.670056885
10.3384620.661538325
20.3052630.694737285
30.3144480.685552706

Como o valor muda

#In: 
chi_sq = (cont_p - unif) ** 2
chi_sq = chi_sq / unif
chi_sq.sum().sum()
0.0030879681358975675

1000 permutações. 10000 demora muito. Mas neste caso com 1000 já podemos ver que t_obs=0.5 é bem raro.

#In: 
chi_sqs = []
for _ in range(1000):
    permut = permuta(df, 'class')
    cont_p = permut[['class', 'Survived']].groupby('class').mean()
    cont_p['Died'] = 1 - cont_p['Survived']
    cont_p['Count'] = df[['class']].groupby('class').size()
    chi_sq = (cont_p - unif) ** 2
    chi_sq = chi_sq / unif
    stat = chi_sq.sum().sum()
    chi_sqs.append(stat)
#In: 
plt.hist(chi_sqs, edgecolor='k')
plt.xlabel(r'$\chi^2$')
plt.ylabel('Num Amostras Permutadas')
Text(0, 0.5, 'Num Amostras Permutadas')

png

Paradoxo de Simpson

Correlação está medindo a relação entre suas duas variáveis sendo todo o resto igual. Ou seja, não controlamos por nenhum outro efeito. Assuma que os dados agoram vêm de grupos diferentes. Se seus grupos de dados são atribuídos uniforme, como em um experimento bem projetado, sendo todo o resto igual pode não ser uma suposição terrível. Mas quando há um padrão mais profundo para atribuições de grupos, sendo todo o resto igual pode ser uma suposição terrível.

Considere os dados sintéticos abaixo. Parece que temos uma correlação linear!

#In: 
x1 = np.random.normal(10, 1, 1000)
y1 = -x1 + np.random.normal(0, 2, 1000)

x2 = np.random.normal(12, 1, 1000)
y2 = -x2 + np.random.normal(6, 2, 1000)

x3 = np.random.normal(14, 1, 1000)
y3 = -x3 + np.random.normal(12, 2, 1000)

x = np.concatenate([x1, x2, x3])
y = np.concatenate([y1, y2, y3])
plt.scatter(x, y, edgecolor='k', alpha=0.6)
plt.xlabel('Excercise')
plt.ylabel('Cholesterol')
plt.xticks([])
plt.yticks([])
despine()

png

Porém eu, Flavio, escolhi os grupos de forma que afetam o resultado. Embora sejam dados sintéticos, tal efeito já foi observado em estudos sobre colesterol.

  1. Existe um crescimento no colesterol com a idade.
  2. Porém, existe uma redução com atividade física.

O paradoxo: Não podemos ver o segundo ponto, pois o crescimento com a idade domina.

#In: 
x1 = np.random.normal(10, 1, 1000)
y1 = -x1 + np.random.normal(0, 2, 1000)

x2 = np.random.normal(12, 1, 1000)
y2 = -x2 + np.random.normal(6, 2, 1000)

x3 = np.random.normal(14, 1, 1000)
y3 = -x3 + np.random.normal(12, 2, 1000)

plt.scatter(x1, y1, edgecolor='k', alpha=0.6, label='Children')
plt.scatter(x2, y2, edgecolor='k', alpha=0.6, label='Teenagers')
plt.scatter(x3, y3, edgecolor='k', alpha=0.6, label='Adults')
plt.xlabel('Excercise')
plt.ylabel('Cholesterol')
plt.xticks([])
plt.yticks([])
plt.legend()
despine()

png

Paradoxo em Dados Categóricos

Por fim, temos dados reais de contratações em Berkeley nos anos 70. Os dados estão estratificados por departamento. Quebrei os mesmos por gênero.

#In: 
df = pd.read_csv('https://media.githubusercontent.com/media/icd-ufmg/material/master/aulas/15-Correlacao/berkeley.csv', index_col=0)
male = df[['Admitted_Male', 'Denied_Male']]
female = df[['Admitted_Female', 'Denied_Female']]
male.head()
Admitted_MaleDenied_Male
Department
A512313
B313207
C120205
D138279
E53138
#In: 
male.head()
Admitted_MaleDenied_Male
Department
A512313
B313207
C120205
D138279
E53138

No geral, mulheres são admitidas com uma taxa menor!

#In: 
male.sum() / male.sum().sum()
Admitted_Male    0.436816
Denied_Male      0.563184
dtype: float64
#In: 
female.sum() / female.sum().sum()
Admitted_Female    0.303542
Denied_Female      0.696458
dtype: float64

Só que por departamento, a taxa é maior! Qual o motivo? Estamos computando frações. Mulheres aplicavam para os departamentos mais concorridos!

#In: 
male.T / male.sum(axis=1)
DepartmentABCDEF
Admitted_Male0.6206060.6019230.3692310.3309350.2774870.058981
Denied_Male0.3793940.3980770.6307690.6690650.7225130.941019
#In: 
female.T / female.sum(axis=1)
DepartmentABCDEF
Admitted_Female0.8240740.680.3406410.3493330.2391860.070381
Denied_Female0.1759260.320.6593590.6506670.7608140.929619