from pathlib import Path
import pandas as pd
import numpy as np
from scipy.stats import trim_mean
from statsmodels import robust
import wquantiles
import seaborn as sns
import matplotlib.pylab as plt
O diretório DATA contém os arquivos .csv utilizados nos exemplos.
DATA = './'
Se você não mantiver seus dados no mesmo diretório que o código, adapte os nomes dos caminhos.
AIRLINE_STATS_CSV = DATA + 'airline_stats.csv'
KC_TAX_CSV = DATA + 'kc_tax.csv.gz'
LC_LOANS_CSV = DATA + 'lc_loans.csv'
AIRPORT_DELAYS_CSV = DATA + 'dfw_airline.csv'
SP500_DATA_CSV = DATA + 'sp500_data.csv.gz'
SP500_SECTORS_CSV = DATA + 'sp500_sectors.csv'
STATE_CSV = DATA + 'state.csv'
Exemplo: Estimativas de Localização da População e Taxas de Homicídios.
Tabela 1-2:
state = pd.read_csv(STATE_CSV)
print(state.head(8))
Calcule a média, a média aparada e a mediana para a população.
Os métodos mean() e median() do dataframe Pandas calculam a média a mediana para a população, e o método trim_mean em scipy.stats calcula a média aparada.
print(state['Population'].mean())
print(trim_mean(state['Population'], 0.1))
print(state['Population'].median())
O pacote especializado wquantiles (https://pypi.org/project/wquantiles/) permite calcular a média ponderada.
print(state['Murder.Rate'].mean())
print(np.average(state['Murder.Rate'], weights=state['Population']))
print(wquantiles.median(state['Murder.Rate'], weights=state['Population']))
Tabela 1-2: Desvio padrão
# Desvio padrão
#
print(state['Population'].std())
#
# O intervalo interquartil é calculado como a diferença do quantil 75% e 25%.
#
print(state['Population'].quantile(0.75) - state['Population'].quantile(0.25))
#
# O desvio absoluto da mediana pode ser calculado com um método em <i>statsmodels</i>
#
print(robust.scale.mad(state['Population']))
print(abs(state['Population'] - state['Population'].median()).median() / 0.6744897501960817)
Pandas tem o método quantile para dataframes.
Tabela 1.4:
print(state['Murder.Rate'].quantile([0.05, 0.25, 0.5, 0.75, 0.95]))
percentages = [0.05, 0.25, 0.5, 0.75, 0.95]
df = pd.DataFrame(state['Murder.Rate'].quantile(percentages))
df.index = [f'{p * 100}%' for p in percentages]
print(df.transpose())
O Pandas fornece vários gráficos exploratórios básicos; um deles são boxplots.
ax = (state['Population']/1_000_000).plot.box(figsize=(3, 4))
ax.set_ylabel('Population (millions)')
plt.tight_layout()
plt.show()
O método cut dos dataframes Pandas divide o conjunto de dados em compartimentos.
Existem vários argumentos para o método.
O código a seguir cria compartimentos de tamanhos iguais.
O método value_counts retorna uma tabela de frequência.
binnedPopulation = pd.cut(state['Population'], 10)
print(binnedPopulation.value_counts())
Tabela 1.5:
binnedPopulation.name = 'binnedPopulation'
df = pd.concat([state, binnedPopulation], axis=1)
df = df.sort_values(by='Population')
groups = []
for group, subset in df.groupby(by='binnedPopulation'):
groups.append({
'BinRange': group,
'Count': len(subset),
'States': ','.join(subset.Abbreviation)
})
print(pd.DataFrame(groups))
Pandas também suporta histogramas para análise exploratória de dados.
ax = (state['Population'] / 1_000_000).plot.hist(figsize=(4, 4))
ax.set_xlabel('Population (millions)')
plt.tight_layout()
plt.show()
A densidade é uma alternativa aos histogramas que pode fornecer mais informações sobre a distribuição dos pontos de dados.
Use o argumento bw_method para controlar a suavidade da curva de densidade.
ax = state['Murder.Rate'].plot.hist(
density=True, xlim=[0, 12],
bins=range(1,12), figsize=(4, 4))
state['Murder.Rate'].plot.density(ax=ax)
ax.set_xlabel('Murder Rate (per 100,000)')
plt.tight_layout()
plt.show()
Tabela 1-6:
dfw = pd.read_csv(AIRPORT_DELAYS_CSV)
print(100 * dfw / dfw.values.sum())
Pandas também suporta gráficos de barras para exibir uma única variável categórica.
ax = dfw.transpose().plot.bar(figsize=(4, 4), legend=False)
ax.set_xlabel('Cause of delay')
ax.set_ylabel('Count')
plt.tight_layout()
plt.show()
Primeiro leia os conjuntos de dados necessários.
Tabela 1-7:
sp500_sym = pd.read_csv(SP500_SECTORS_CSV)
sp500_px = pd.read_csv(SP500_DATA_CSV, index_col=0)
#
# Determinar símbolos de telecomunicações
telecomSymbols = sp500_sym[sp500_sym['sector'] == 'telecommunications_services']['symbol']
# Filtrar dados para datas de julho de 2012 a junho de 2015
telecom = sp500_px.loc[sp500_px.index >= '2012-07-01', telecomSymbols]
telecom.corr()
print(telecom)
Em seguida, focamos nos fundos negociados nas principais bolsas (sector == 'etf').
etfs = sp500_px.loc[
sp500_px.index > '2012-07-01',
sp500_sym[sp500_sym['sector'] == 'etf']['symbol']]
print(etfs.head())
Devido ao grande número de colunas nesta tabela, observar a matriz de correlação é complicado e é mais conveniente plotar a correlação como um mapa de calor.
O pacote Seaborn fornece uma implementação conveniente para mapas de calor.
fig, ax = plt.subplots(figsize=(5, 4))
ax = sns.heatmap(
etfs.corr(), vmin=-1, vmax=1, ax=ax,
cmap=sns.diverging_palette(20, 220, as_cmap=True))
plt.tight_layout()
plt.show()
O mapa de calor acima funciona quando você tem cores.
Para as imagens em escala de cinza, conforme usadas no livro, precisamos visualizar a direção também.
O código a seguir mostra a força da correlação usando elipses.
from matplotlib.collections import EllipseCollection
from matplotlib.colors import Normalize
#
def plot_corr_ellipses(data, figsize=None, **kwargs):
''' https://stackoverflow.com/a/34558488 '''
M = np.array(data)
if not M.ndim == 2:
raise ValueError('data must be a 2D array')
fig, ax = plt.subplots(1, 1, figsize=figsize, subplot_kw={'aspect':'equal'})
ax.set_xlim(-0.5, M.shape[1] - 0.5)
ax.set_ylim(-0.5, M.shape[0] - 0.5)
ax.invert_yaxis()
#
# xy locations of each ellipse center
xy = np.indices(M.shape)[::-1].reshape(2, -1).T
#
# set the relative sizes of the major/minor axes according to the strength of
# the positive/negative correlation
w = np.ones_like(M).ravel() + 0.01
h = 1 - np.abs(M).ravel() - 0.01
a = 45 * np.sign(M).ravel()
#
ec = EllipseCollection(widths=w, heights=h, angles=a, units='x', offsets=xy,
norm=Normalize(vmin=-1, vmax=1),
transOffset=ax.transData, array=M.ravel(), **kwargs)
ax.add_collection(ec)
#
# if data is a DataFrame, use the row/column names as tick labels
if isinstance(data, pd.DataFrame):
ax.set_xticks(np.arange(M.shape[1]))
ax.set_xticklabels(data.columns, rotation=90)
ax.set_yticks(np.arange(M.shape[0]))
ax.set_yticklabels(data.index)
#
return ec, ax
#
m, ax = plot_corr_ellipses(etfs.corr(), figsize=(5, 4), cmap='bwr_r')
#cb = fig.colorbar(m, ax=ax)
#cb.set_label('Correlation coefficient')
#
plt.tight_layout()
plt.show()
O Pandas suporta gráficos de dispersão simples.
Especificar o marcador como $\u25EF$ usa um círculo aberto para cada ponto.
ax = telecom.plot.scatter(x='T', y='VZ', figsize=(4, 4), marker='$\u25EF$')
ax.set_xlabel('ATT (T)')
ax.set_ylabel('Verizon (VZ)')
ax.axhline(0, color='grey', lw=1)
ax.axvline(0, color='grey', lw=1)
#
plt.tight_layout()
plt.show()
T x VZ:
ax = telecom.plot.scatter(
x='T',
y='VZ',
figsize=(4, 4),
marker='$\u25EF$',
alpha=0.5)
ax.set_xlabel('ATT (T)')
ax.set_ylabel('Verizon (VZ)')
ax.axhline(0, color='grey', lw=1)
print(ax.axvline(0, color='grey', lw=1))
Carregue o conjunto de dados kc_tax e filtre com base em vários critérios.
kc_tax = pd.read_csv(KC_TAX_CSV)
kc_tax0 = kc_tax.loc[
(kc_tax.TaxAssessedValue < 750000) &
(kc_tax.SqFtTotLiving > 100) &
(kc_tax.SqFtTotLiving < 3500), :]
print(kc_tax0.shape)
Se o número de pontos de dados for grande, os gráficos de dispersão não serão mais significativos.
Aqui os métodos que visualizam as densidades são mais úteis.
O método hexbin para dataframes pandas é uma abordagem poderosa.
ax = kc_tax0.plot.hexbin(x='SqFtTotLiving', y='TaxAssessedValue',
gridsize=30, sharex=False, figsize=(5, 4))
ax.set_xlabel('Finished Square Feet')
ax.set_ylabel('Tax Assessed Value')
#
plt.tight_layout()
plt.show()
O método kdeplot do Seaborn é uma extensão bidimensional do gráfico de densidade.
O cálculo da densidade 2D para o conjunto de dados completo leva vários minutos.
É suficiente criar a visualização com uma amostra menor do conjunto de dados.
Com 10.000 pontos de dados, a criação do gráfico leva apenas alguns segundos.
Embora alguns detalhes possam ser perdidos, a forma geral é preservada.
fig, ax = plt.subplots(figsize=(4, 4))
sns.kdeplot(data=kc_tax0.sample(10000), x='SqFtTotLiving', y='TaxAssessedValue', ax=ax)
ax.set_xlabel('Finished Square Feet')
ax.set_ylabel('Tax Assessed Value')
#
plt.tight_layout()
plt.show()
Carregue o conjunto de dados lc_loans.
lc_loans = pd.read_csv(LC_LOANS_CSV)
Tabela 1-8(1):
crosstab = lc_loans.pivot_table(index='grade', columns='status',
aggfunc=lambda x: len(x), margins=True)
print(crosstab)
Tabela 1-8(2):
df = crosstab.copy().loc['A':'G',:]
df.loc[:,'Charged Off':'Late'] = df.loc[:,'Charged Off':'Late'].div(df['All'], axis=0)
df['All'] = df['All'] / sum(df['All'])
perc_crosstab = df
print(perc_crosstab)
As caixas de uma coluna podem ser agrupadas por uma coluna diferente com método boxplot do Pandas.
airline_stats = pd.read_csv(AIRLINE_STATS_CSV)
airline_stats.head()
ax = airline_stats.boxplot(
by='airline',
column='pct_carrier_delay',
figsize=(5, 5))
ax.set_xlabel('')
ax.set_ylabel('Daily % of Delayed Flights')
plt.suptitle('')
#
plt.tight_layout()
plt.show()
Pandas também suporta uma variação de gráficos de caixas chamada violinplot.
fig, ax = plt.subplots(figsize=(5, 5))
sns.violinplot(
data=airline_stats,
x='airline',
y='pct_carrier_delay',
ax=ax,
inner='quartile',
color='white')
ax.set_xlabel('')
ax.set_ylabel('Daily % of Delayed Flights')
#
plt.tight_layout()
plt.show()
zip_codes = [98188, 98105, 98108, 98126]
kc_tax_zip = kc_tax0.loc[kc_tax0.ZipCode.isin(zip_codes),:]
kc_tax_zip
#
def hexbin(x, y, color, **kwargs):
cmap = sns.light_palette(color, as_cmap=True)
plt.hexbin(x, y, gridsize=25, cmap=cmap, **kwargs)
g = sns.FacetGrid(kc_tax_zip, col='ZipCode', col_wrap=2)
g.map(
hexbin,
'SqFtTotLiving',
'TaxAssessedValue',
extent=[0, 3500, 0, 700000])
g.set_axis_labels('Finished Square Feet', 'Tax Assessed Value')
g.set_titles('Zip code {col_name:.0f}')
#
plt.tight_layout()
plt.show()