Primeiro, vamos investigar manualmente alguns gastos dos deputados em 2015. Em seguida, usaremos uma técnica simples de Aprendizado de Máquina (Machine Learning) para buscar transações incomuns por todos os dados.
É um valor mensal recebido, além do salário, para custear os gastos dos deputados na atividade parlamentar. Em 2016, esse valor varia entre R\$ 30.788,66 para deputados do DF e R\$ 44.632,46 para os do Acre. Ainda há um adicional de R\$ 1.353,04 em alguns casos. Mais detalhes na assessoria de imprensa da Câmara dos Deputados.
Vamos carregar os dados de 2015 e olhar a primeira entrada. Os dados utilizados estão no formato CSV e foram convertidos do original em XML. O significado de cada coluna pode ser conferido no site de transparência da Câmara.
In [1]:
import pandas as pd
ceap = pd.read_csv('dados/ceap2015.csv.zip')
In [2]:
linhas, colunas = ceap.shape
print('Temos {} entradas com {} colunas cada.'.format(linhas, colunas))
print('Primeira entrada:')
ceap.iloc[0]
Out[2]:
Por curiosidade, vamos calcular quais foram os 3 parlamentares que mais gastaram em 2015:
In [3]:
colunas = ['txNomeParlamentar', 'sgPartido', 'sgUF', 'vlrLiquido']
grupo = ['txNomeParlamentar', 'sgPartido', 'sgUF']
ceap[colunas].groupby(grupo).sum().sort_values('vlrLiquido', ascending=False).head(3)
Out[3]:
O deputado que mais usou a cota parlamentar totalizou R\$ 516.027,24 em 2015, uma média de um pouco mais que R$ 43.000,00 mensais. Vamos verificar seu maior gasto:
In [4]:
nome = "JHONATAN DE JESUS"
ceap[ceap.txNomeParlamentar == nome].sort_values('vlrLiquido', ascending=False).iloc[0]
Out[4]:
Será que um pagamento de R$ 88.500,00 para divulgação da atividade parlamentar é muito alto? Vamos ver os 5 maiores pagamentos desse tipo, entre todos os deputados, ordenado do maior pro menor:
In [5]:
colunas = ['vlrLiquido', 'txNomeParlamentar', 'sgPartido', 'sgUF', 'txtDescricao']
ceap.query('numSubCota == 5')[colunas].sort_values('vlrLiquido', ascending=False).head()
Out[5]:
Descobrimos então que outros parlamentares gastaram ainda mais para divulgar suas atividades. Nesse momento, seu foco pode ter mudado dos R\$ 88.500,00 de Jhonatan de Jesus para os R\$ 189.600,00 de Arnaldo Faria de Sá. Comparando os gastos da tabela acima, o primeiro colocado se destoa a ponto de investigarmos melhor esse gasto? Note que começamos com uma ideia: o maior gasto do parlamentar que mais gastou no ano e, conforme investigamos, mudamos o rumo para o maior gasto com divulgação entre todos os deputados. Isso pode acontecer repetidas vezes até que de fato escolhamos um gasto para investigar mais a fundo.
Como você já deve ter percebido, a análise manual é muito trabalhosa nas mais de 350 mil entradas que temos. Vejamos agora como processá-las de forma mais objetiva e automatizada.
Vamos usar uma técnica simples de detecção de outliers lecionada no Coursera por Andrew Ng. Essa técnica diz a probabilidade de um valor específico ocorrer no grupo. Para que ela funcione, os valores devem seguir aproximadamente uma distribuição normal. Não pretendo entrar em detalhes sobre estatística, apenas o suficiente para nos certificarmos de que teremos bons resultados.
Primeiro, vamos considerar todos os gastos de uma só vez. Será que os valores possuem uma distribuição normal? Veja um exemplo de distribuição normal (padrão):
In [6]:
import matplotlib # gráficos
import numpy as np # cálculos
%matplotlib inline
matplotlib.style.use('ggplot')
In [7]:
positivos = ceap[ceap.vlrLiquido > 0].vlrLiquido
aleatorios = pd.Series(np.random.randn(len(positivos)), name='normal')
aleatorios.plot.hist(bins=75, ylim=(0, 35000));
Observe que os valores de x = 0 são mais frequentes e a frequência diminui para as laterais. Você pode conferir mais detalhes sobre a Distribuição Normal na Wikipedia.
Como é a distribuição dos valores da cota parlamentar?
In [8]:
positivos.plot.hist(bins=75);
Bem diferente da distribuição normal padrão, não é? Embora invisíveis nessa escala, há alguns poucos gastos muito altos à direita. Além disso, notamos muitos gastos próximo do zero e uma diminuição brusca da barra ao lado.
Para que os valores se aproximem da normal, vamos transformá-los aplicando logaritmo, subtraindo a média e dividindo pelo desvio padrão. Vamos ver o resultado e a curva normal sobrepostos:
In [9]:
def log_zscores(valores):
positivos = valores[valores > 0].dropna()
logs = np.log(positivos)
return (logs - logs.mean()) / logs.std()
vlrLiquido_z = log_zscores(ceap.vlrLiquido)
In [10]:
pd.concat([aleatorios, vlrLiquido_z], axis=1).plot.hist(bins=75, alpha=0.6);
Agora os gastos estão muito mais próximos da distribuição normal padrão. Segundo Andrew Ng, a distribuição não precisa ser muito igual à normal para obter bons resultados. Podemos seguir para o próximo passo: calcular a probabilidade da ocorrência de cada valor. Talvez você já tenha ouvido falar em "6 Sigma" ($6\sigma$) e esse nome vem do fato de que um intervalo entre $-3 \sigma$ e $3\sigma$ abrange quase 100% dos valores de uma distribuição normal. Na distribuição normal padrão, $\sigma = 1$.
Vamos então calcular a probabilidade de cada gasto, supondo que eles sigam uma distribuição normal e, em seguida, mostrar aqueles que possuem menor probabilidade de ocorrência (os 5 primeiros).
In [11]:
from scipy.stats import norm
def prob(valores):
probs = valores.copy()
probs[probs <= 0] = np.nan
z = log_zscores(probs)
probs[z.index] = norm.sf(z)
return probs
ceap['prob_geral'] = prob(ceap.vlrLiquido)
colunas = ['prob_geral', 'vlrLiquido', 'txNomeParlamentar', 'sgPartido', 'sgUF', 'txtDescricao']
ceap[colunas].sort_values('prob_geral').head()
Out[11]:
Os gastos com divulgação são os primeiros colocados. A tabela acima é a mesma que a última tabela da abordagem manual e sofre do mesmo problema: o catagoria com os maiores gastos é penalizada. Vamos corrigir isso a seguir.
Vamos calcular as probabilidades da mesma maneira, mas considerando apenas os valores do mesmo grupo.
In [12]:
colunas = ['numSubCota', 'vlrLiquido']
ceap['prob_grupo'] = ceap[colunas].groupby('numSubCota').transform(prob)
colunas = ['prob_grupo', 'vlrLiquido', 'txNomeParlamentar', 'sgPartido', 'sgUF', 'txtDescricao']
ceap[colunas].sort_values('prob_grupo').head()
Out[12]:
NaN significa que não há esse valor nos dados, mas é fácil entender o porquê pelo nome.
Na tabela acima, temos os gastos que mais destoam dentro de suas categorias. Entre os 5 primeiros, 4 são para cobrir alimentação e são cerca de 10 vezes menor que o gasto com serviços postais. Para se ter uma ideia do quanto eles se destacam em suas categorias, vamos ver o valor com alimentação abaixo do qual se encontram 99,865% dos gastos ($3 \sigma$ segundo a tabela que pode ser encontrada na Wikipedia):
In [13]:
alim = ceap.query('numSubCota == 13 and vlrLiquido > 0').vlrLiquido.dropna()
alim_log = np.log(alim)
média_log = alim_log.mean()
sigma_log = alim_log.std()
limite_log = média_log + 3 * sigma_log
limite = np.exp(limite_log)
print('Valor limite = R$ {:.2f}:'.format(limite))
valores_abaixo = len(alim[alim < limite])
valores_totais = len(alim)
print('{} valores abaixo, em um total de {} = {:.3f}%.'.format(
valores_abaixo, valores_totais, 100 * valores_abaixo/valores_totais))
A porcentagem está bem próxima da teórica. Mais de 99% dos gastos com alimentação está abaixo de R\$ 564,93 e não é à toa que os gastos entre 4 e 6 mil estão entre os 5 primeiros da tabela acima. Marllos Sampaio, por exemplo, faz parte dos cerca de 0,3\% que mais gastaram com alimentação.
Por outro lado, pode ser mais interessante investigar o gasto com serviços postais, pois é cerca de 10 vezes maior que os gastos com alimentação. Como podemos destacar os gastos de maior valor aproveitando essa análise dentro de cada categoria? É o que vamos ver a seguir.
Primeiro, destacamos os maiores valores, mas acabamos priorizando categorias de gastos mais caras. Em seguida, destacamos os maiores valores dentro de cada categoria, mas obtivemos valores relativamente baixos. O ideal seria um balanço entre essas duas abordagens. Lembre-se do ensino médio (colegial, pros mais "experientes" :)) que a probabilidade de ocorrer $x$ e $y$ é igual a $P(x) \times P(y)$.
Vamos multiplicar as probabilidades, ordená-las e listar as primeiras:
In [14]:
ceap['prob_total'] = ceap.prob_geral * ceap.prob_grupo
colunas = ['prob_total', 'vlrLiquido', 'txNomeParlamentar', 'sgPartido', 'sgUF', 'txtDescricao']
ceap[colunas].sort_values('prob_total').head(10)
Out[14]:
Note que agora valores maiores e menores se intercalam dependendo do resultado da multiplicação das probabilidades, resolvendo nossos problemas em adotar apenas uma delas.
A tabela final completa, com todas as linhas e colunas, encontra-se em dados/ceap2015resultado.csv.zip
. Com ela, você poderá ver o fornecedor, passageiro da passagem aérea, etc. O site da Câmara da Câmara também disponibiliza algumas notas fiscais dos serviços prestados. Os serviços que não possuem nota estão especificados no item 7 da página de Assessoria de Imprensa. Você também pode baixar este notebook, alterar o código e investigar os dados da sua maneira. Compartilhe o que encontrar de interessante!