João Victor Vieira Passon - @victorpasson
Outubro de 2022
As eleições presidenciais de 2022 está marcada pela polarização entre os candidatos Bolsonaro e Lula, polarização tamanha que afetou as eleições para deputados e senadores. Um exemplo disso é a disputa pelo governo de São Paulo, onde os candidatos de ambos os presidenciáveis passaram para o segundo turno sendo considerado um feito histórico, pois o partido que governou o estado por quase 30 anos, PSDB, não conseguiu passar para a segunda fase do pleito.
Do ponto de vista nacional, no primeiro turno, foi possível verificar a diferença de votação entre os candidatos nas diferentes regiões do país. O candidato do PT, Luiz Inácio Lula da Silva, obteve uma diferença expressiva na região nordeste, região onde seu partido possui um histórico de vitórias. Do lado do atual presidente, Jair Messias Bolsonaro, uma diferença significativa foi verificada na região Centro-Oeste que é marcada pela presença do Agronegócio.
Quando tratamos de regiões do ponto de vista macro estamos, de certa forma, retirando da análise as próprias diferenças regionais e de dentro dos próprios estados. Um exemplo disso é o estado do Espírito Santo, onde na região Sul, mais próxima de São Paulo e Rio de Janeiro, o candidato do PL obteve vitória sobre o ex-presidente. Já a região Norte do estado, mais próxima do sul da Bahia, o candidato do PT teve vitória na grande maioria dos municípios. Mesmo padrão é observado no estado de Minas Gerais.
Dessa forma, nesse projeto, buscamos entender se existem diferenças econômicas e sociais claras entre os municípios onde o candidato Luiz Inácio Lula da Silva obteve grande maioria dos votos e entre os municípios que o candidato Jair Messias Bolsonaro levou a melhor.
Posto isso, é importante ressaltar que os percentuais de votos dos outros candidatos foram desconsiderados pois, como posto, a eleição desse ano foi marcada pela polarização entre os dois candidatos de forma nacional, não tendo aparecido outras opções com forças suficiente fazer frente a ambos os candidatos e estando esses nas duas primeiras posições em todos os municípios.
Começaremos importando as bibliotecas que serão utilizadas no projeto:
# Importando as bibliotecas necessárias
import pandas as pd
import numpy as np
import time
import plotly.graph_objects as go
import plotly.express as px
from urllib.request import urlopen
import json
import warnings
import kaleido
import statsmodels.api as sm
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler, LabelEncoder
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
warnings.filterwarnings("ignore")
Os dados utilizados nesse projeto foram retirados sites do TSE, IBGE, IPEA, Atlas da Violência e INEP. Como os dados foram obtidos de fontes diversas, será necessário um grande processo de obtenção e manipulação dos dados para posteriormente a junção deles e utilização.
A linha que se segue é para pegar do Tribunal Superior Eleitoral (TSE) os dados das eleições. Esse bloco de código leva um grande tempo para ser processado, pois, primeiro se pega os dados dos municípios a partir do site do TSE para, em seguida, gerar os links para a retirada dos dados. Essa obtenção é feita município por município, por isso leva tempo, pois, são retiradas 1 por 1 os dados de 5570 municípios.
Caso tenha interesse em utilizar os dados desse projeto, não é necessária a execução do código abaixo. Ao final desse bloco salvamos os dados como 'eleicao_municipios.csv', esse CSV pode ser obtido no meu GitHub.
# Lendo os municípios de acordo com o TSE
df_mun = pd.read_json("https://resultados.tse.jus.br/oficial/ele2022/544/config/mun-e000544-cm.json")
# Uma lista para a coluna "abr" com os dados dos municípios
dic1 = df_mun.abr.to_dict()
# Criando um novo dataframe para obter os dados passados ao dicionário anterior e criando duas colunas vazias.
df_mun = pd.DataFrame()
df_mun['ufr'] = 0.0
df_mun['uf'] = 0.0
# Loop For para a obtenção dos dados do dicionários e passar eles ao dataframe
for i in dic1:
df_mun = pd.concat([df_mun, df_mun.from_dict(dic1[i]['mu'])], axis=0)
df_mun.ufr.fillna(dic1[i]['cd'], inplace=True)
df_mun.uf.fillna(dic1[i]['ds'], inplace=True)
df_mun.reset_index(inplace=True)
df_mun.drop('index', axis=1, inplace=True)
df_mun.drop(['c', 'z'], axis=1, inplace=True)
print("1 - Dados de Informações dos Municípios obtidos com sucesso!")
# Gerando os links do TSE para obtenção dos dados
links = "https://resultados.tse.jus.br/oficial/ele2022/544/dados/" + df_mun['ufr'].str.lower() + "/" + df_mun['ufr'].str.lower() + df_mun['cd'] + "-c0001-e000544-v.json"
print("2 - Links Obtidos com Sucessos!")
print()
print()
print("3 - Pegando os dados do site do TSE...")
# Importando os dados dos municípios no TSE
# Obs: Essas linhas levam tempo para serem executadas, pois são dados de mais de 5 mil municípios obtidos um a um.
# Criando um dataframe com os nomes das colunas e um contador para o número do municípo que estamos lendo
df_mun_of = pd.DataFrame(columns=['dt', 'ht', 'tf', 'and', 'tpabr', 'cdabr', 's', 'snt', 'si',
'sni', 'sa', 'sna', 'pst', 'psnt', 'psi', 'psni', 'psa', 'psna',
'ea', 'ena', 'esi', 'esni', 'c', 'a', 'pea', 'pena', 'pesi', 'pesni',
'pa', 'pc', 'vscv', 'vnom', 'tv', 'vvc', 'vb', 'tvn', 'vn', 'vnt', 'vp',
'vv', 'van', 'vansj', 'pvnom', 'pvvc', 'pvb', 'ptvn', 'pvn', 'pvnt',
'pvp', 'pvv', 'pvan', 'pvansj', 'seq', 'n', 'vap', 'pvap', 'e', 'st'])
linha = 0
for i in links:
print(linha, " ", end="")
linha += 1
# Puxando os dados do TSE
new = pd.read_json(i)
# Tratando os dados
sem = pd.DataFrame(new['abr'].to_dict()[0]).drop(['cand', 'st', 'e'], axis=1)
com = pd.DataFrame(pd.DataFrame(new['abr'].to_dict()[0])['cand'].to_dict()).transpose()
join = pd.concat([sem, com], axis=1)
join.reset_index(inplace=True)
join.drop('index', axis=1, inplace=True)
# Colocando em um novo dataframe
df_mun_of = pd.concat([df_mun_of, join], axis=0)
df_mun_of.reset_index(inplace=True)
df_mun_of.drop('index', axis=1, inplace=True)
# Esperando para não sobrecarregar o servidor do TSE
time.sleep(0.25)
print("\n\nDados Obtidos com sucesso!")
1 - Dados de Informações dos Municípios obtidos com sucesso! 2 - Links Obtidos com Sucessos! 3 - Pegando os dados do site do TSE... Linha 0 1 2 3 4 5 6 7 8 ... 5744 5745 5746 5747 5748 5749 5750 Dados Obtidos com sucesso!
Após a aquisição dos dados fazemos uma limpeza inicial. Primeiro, juntando para ter em um DataFrame unificado os códigos dos municípios a partir do IBGE. Segundo, mudando os dados e os nomes das colunas. E, por fim, pegando os nomes dos partidos de cada candidato.
df_mun_of = df_mun_of[['cdabr', 'n', 'vap', 'pvap', 'st']]
df_mun_of = df_mun_of.merge(df_mun[['cd', 'uf', 'ufr', 'cdi']], left_on='cdabr', right_on='cd', how='left')
pvap = df_mun_of.pvap
df_mun_of.drop(['cdabr', 'cd', 'pvap'], axis=1, inplace=True)
porcentagem = []
for i in pvap:
j = i.replace(',', '.')
porcentagem.append(j)
df_mun_of['pvap'] = pd.to_numeric(porcentagem)
df_mun_of.rename(columns={
'cd': 'uf',
'ds': 'estado',
'n':"num_cand",
'vap':"vot_abs",
'st':'eleito',
'cdi': 'Codigo',
'pvap': 'vot_perc'
}, inplace=True)
df_mun_of = df_mun_of.merge(pd.read_csv('dados_partidos.CSV', delimiter=';', names=['partido', 'num_cand']), left_on='num_cand',
right_on='num_cand', how='left')
df_mun_of = df_mun_of.merge(pd.read_csv('lat_long_mun.csv',
usecols=[i for i in [0, 1, 2, 3]],
names=['Codigo', 'municipio', 'latitude', 'longitude'],
skiprows=1,
dtype={
'Codigo': np.str_
}), left_on="Codigo", right_on="Codigo", how="left")
df_mun_of['vot_abs'] = pd.to_numeric(df_mun_of['vot_abs'])
df_mun_of['Codigo'] = pd.to_numeric(df_mun_of['Codigo'])
del j, porcentagem, pvap, df_mun
print("Limpeza concluida!")
Limpeza concluida!
Agora estamos prontos para pegar os dados referente as questões econômicas e sociais dos municípios. Alguns municípios não possuem determinados tipos de dados, por isso, posteriormente, faremos um trabalho de modificação dos dados desses municípios por meio da média. Outros municípios serão retirados, pois não têm grandes quantidades dos dados, o que prejudicaria a análise. Ao final desse bloco temos um dicionário sobre as variáveis obtidas.
dados_econ = {"distancia":"http://www.ipeadata.gov.br/ExibeSerieR.aspx?oper=exportCSVUS&serid=1574534192&DateCarto=1998&MINDATA=1998&MAXDATA=1998&TNIVID=5&TPAID=1",
"populacao": "http://www.ipeadata.gov.br/ExibeSerieR.aspx?oper=exportCSVUS&serid=1776285356&DateCarto=2021&MINDATA=2017&MAXDATA=2021&TNIVID=5&TPAID=1",
"custeio": "http://www.ipeadata.gov.br/ExibeSerieR.aspx?oper=exportCSVUS&serid=40121&tipoCarto=R&DateCarto=2020&MINDATA=2019&MAXDATA=2019&TNIVID=5&TPAID=1",
"pessoal": "http://www.ipeadata.gov.br/ExibeSerieR.aspx?oper=exportCSVUS&serid=40119&tipoCarto=R&DateCarto=2020&MINDATA=2019&MAXDATA=2019&TNIVID=5&TPAID=1",
"pib_perc": "pib_municipios.xls",
"taxa_hom": "taxa-homicidios.csv",
"nota_ideb": "ideb.xlsx"
}
for key in dados_econ:
if key == "distancia":
df_econ_mun = pd.read_csv(dados_econ[key],
skiprows=2,
usecols=[i for i in [1, 3]],
names=["Codigo", key])
print("Coluna", key,"Criada!")
else:
if key == "pib_perc":
df2 = pd.read_excel(dados_econ[key])
df2 = df2[df2["Ano"] == 2019][["Código do Município", "Produto Interno Bruto, \na preços correntes\n(R$ 1.000)", "Atividade com maior valor adicionado bruto"]]
df2.rename(columns={
"Código do Município": "Codigo",
"Produto Interno Bruto, \na preços correntes\n(R$ 1.000)": "pib_percap",
"Atividade com maior valor adicionado bruto": "atv_princ"
}, inplace=True)
df_econ_mun = df_econ_mun.merge(df2, left_on="Codigo", right_on="Codigo", how="left")
print("Coluna", key,"Criada!")
else:
if key == "taxa_hom":
df2 = pd.read_csv(dados_econ[key],
delimiter=";",
usecols=[i for i in [0, 2, 3]],
names=["Codigo", "ano", key], skiprows=1)
df2 = df2[df2["ano"] == 2019]
df2.drop("ano", axis=1, inplace=True)
df_econ_mun = df_econ_mun.merge(df2, left_on="Codigo", right_on="Codigo", how="left")
print("Coluna", key,"Criada!")
else:
if key == "nota_ideb":
df2 = pd.read_excel(dados_econ[key], usecols=[i for i in [1, 4]], names=["Codigo", key])
new_l = []
for i in df2.nota_ideb.to_list():
j = str(i).replace("-", "")
new_l.append(j)
df2['nota_ideb'] = pd.to_numeric(new_l)
df_econ_mun = df_econ_mun.merge(df2, left_on="Codigo", right_on="Codigo", how='left')
print("Coluna", key,"Criada!")
else:
df2 = pd.read_csv(dados_econ[key],
skiprows=2,
usecols=[i for i in [1, 3]],
names=["Codigo", key])
df_econ_mun = df_econ_mun.merge(df2, left_on="Codigo", right_on="Codigo", how="left")
print("Coluna", key,"Criada!")
df_econ_mun['Codigo'] = pd.to_numeric(df_econ_mun['Codigo'], downcast='float')
del df2, dados_econ
print("Dataframe 'df_econ_mun' criado!")
Coluna distancia Criada! Coluna populacao Criada! Coluna custeio Criada! Coluna pessoal Criada! Coluna pib_perc Criada! Coluna taxa_hom Criada! Coluna nota_ideb Criada! Dataframe 'df_econ_mun' criado!
df_mun_of = df_mun_of.merge(df_econ_mun, left_on='Codigo', right_on='Codigo')
del df_econ_mun
Abaixo salvamos o DataFrame para não termos que executar as linhas acimas caso queiramos utilizar os dados novamente.
# Salvando em um arquivo csv
df_mun_of.reset_index(drop=True, inplace=True)
df_mun_of.to_csv('eleicao_municipios.csv', index = False)
df_mun_of.head()
num_cand | vot_abs | eleito | uf | ufr | Codigo | vot_perc | partido | municipio | latitude | longitude | distancia | populacao | custeio | pessoal | pib_percap | atv_princ | taxa_hom | nota_ideb | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 44 | 37 | Não eleito | ACRE | AC | 1200013.0 | 0.52 | UNIAO | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
1 | 13 | 1516 | 2º turno | ACRE | AC | 1200013.0 | 21.30 | PT | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
2 | 15 | 192 | Não eleito | ACRE | AC | 1200013.0 | 2.70 | MDB | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
3 | 16 | 1 | Não eleito | ACRE | AC | 1200013.0 | 0.01 | PSTU | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
4 | 22 | 5238 | 2º turno | ACRE | AC | 1200013.0 | 73.59 | PL | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
Dicionário dos dados:
Por meio a análise exploratória utilizamos técnicas para examinar e estudar as características do conjunto de dados que obtivemos, antes que utilizemos eles ao propósito pretendido. Isso permite que caso identifiquemos novas necessidades de modificações nos dados possamos fazer antes da utilização para sua finalidade. Além disso, por meio dos resumos das suas características principais já podemos formar hipóteses.
No conjunto que estamos utilizando, podemos perceber que os gastos com custeio e pessoal estão em seu valor absoluto. Isso pode nos causar problemas, pois, de forma intuitiva concluímos que municípios com populações maiores tendem a gastar mais com custeio e pessoal. Por isso, faremos a divisão desses valores pela população dos municípios, para que a análise seja feita a partir de uma base comum: em gasto per capita.
df_mun_of.head()
num_cand | vot_abs | eleito | uf | ufr | Codigo | vot_perc | partido | municipio | latitude | longitude | distancia | populacao | custeio | pessoal | pib_percap | atv_princ | taxa_hom | nota_ideb | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 44 | 37 | Não eleito | ACRE | AC | 1200013.0 | 0.52 | UNIAO | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
1 | 13 | 1516 | 2º turno | ACRE | AC | 1200013.0 | 21.30 | PT | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
2 | 15 | 192 | Não eleito | ACRE | AC | 1200013.0 | 2.70 | MDB | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
3 | 16 | 1 | Não eleito | ACRE | AC | 1200013.0 | 0.01 | PSTU | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
4 | 22 | 5238 | 2º turno | ACRE | AC | 1200013.0 | 73.59 | PL | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
Os dados estão com os tipos corretos para serem utilizados na análise.
df_mun_of.dtypes
num_cand object vot_abs int64 eleito object uf object ufr object Codigo float64 vot_perc float64 partido object municipio object latitude float64 longitude float64 distancia float64 populacao float64 custeio float64 pessoal float64 pib_percap float64 atv_princ object taxa_hom float64 nota_ideb float64 dtype: object
Por meio do código abaixo temos como saída a quantidade da dos faltantes para cada variável. Aqui é importante algumas pontuações:
Nan
na variável distancia
pode ser por causa da base de dados utilizada ser de 1998, dessa forma, o levantamento da distância de alguns municípios para a capital estadual não foi feito.Será necessário tratar esses dados faltantes.
df_mun_of.isna().sum()
num_cand 0 vot_abs 0 eleito 0 uf 0 ufr 0 Codigo 0 vot_perc 0 partido 0 municipio 0 latitude 0 longitude 0 distancia 693 populacao 0 custeio 165 pessoal 165 pib_percap 0 atv_princ 0 taxa_hom 99 nota_ideb 5467 dtype: int64
A base possui 61.270 linhas e 19 colunas, como temos 11 candidatos para a eleição presidencial, estamos falando de dados para 5.570 municípios. Pelas informações do IBGE, o Brasil possui 5.568 municípios, mais Distrito Federal e Distrito Estadual de Fernando de Noronha, assim temos o equilíbrio de dados para os 5.570 municípios brasileiros.
df_mun_of.shape
(61270, 19)
Por questões constitucionais do orçamento de Brasília, não possuímos os dados de custeio
e pessoal
.
df_mun_of.loc[df_mun_of.municipio == "Brasília"]
num_cand | vot_abs | eleito | uf | ufr | Codigo | vot_perc | partido | municipio | latitude | longitude | distancia | populacao | custeio | pessoal | pib_percap | atv_princ | taxa_hom | nota_ideb | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
8833 | 14 | 1554 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 0.09 | PTB | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8834 | 16 | 454 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 0.03 | PSTU | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8835 | 21 | 894 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 0.05 | PCB | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8836 | 27 | 518 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 0.03 | DC | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8837 | 12 | 74308 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 4.22 | PDT | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8838 | 30 | 10342 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 0.59 | NOVO | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8839 | 15 | 105377 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 5.98 | MDB | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8840 | 44 | 7935 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 0.45 | UNIAO | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8841 | 80 | 1262 | Não eleito | DISTRITO FEDERAL | DF | 5300108.0 | 0.07 | UP | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8842 | 13 | 649534 | 2º turno | DISTRITO FEDERAL | DF | 5300108.0 | 36.85 | PT | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
8843 | 22 | 910397 | 2º turno | DISTRITO FEDERAL | DF | 5300108.0 | 51.65 | PL | Brasília | -15.7795 | -47.9297 | 0.0 | 3039444.0 | NaN | NaN | 2.736137e+08 | Demais serviços | 15.92 | 5.9 |
Como utilizaremos os dados para a geração de um mapa com o preenchimento do candidato vencedor para o município é importante ter todos os dados de todos os municípios para não termos espaços em branco no mapa. Dessa maneira, iremos tratar esses dados por meio da imputação.
Nas variáveis "distancia", "custeio" e "pessoal" será colocado o valor da média das colunas respectivas. Já para a taxa de homicídio, "taxa_hom", e notas do IDEB, "nota_ideb", será utilizada a mediana dos valores das colunas.
df_mun_of.distancia.fillna(value=df_mun_of.distancia.mean(), inplace=True)
df_mun_of.custeio.fillna(value=df_mun_of.custeio.mean(), inplace=True)
df_mun_of.pessoal.fillna(value=df_mun_of.pessoal.mean(), inplace=True)
df_mun_of.taxa_hom.fillna(value=df_mun_of.taxa_hom.median(), inplace=True)
df_mun_of.nota_ideb.fillna(value=df_mun_of.nota_ideb.median(), inplace=True)
Agora não temos mais valores faltantes no DataFrame, mantendo a mesma quantidade de linhas, 61.270, e a mesma quantidade de colunas, 19.
df_mun_of.isna().sum()
num_cand 0 vot_abs 0 eleito 0 uf 0 ufr 0 Codigo 0 vot_perc 0 partido 0 municipio 0 latitude 0 longitude 0 distancia 0 populacao 0 custeio 0 pessoal 0 pib_percap 0 atv_princ 0 taxa_hom 0 nota_ideb 0 dtype: int64
df_mun_of.shape
(61270, 19)
df_mun_of.head()
num_cand | vot_abs | eleito | uf | ufr | Codigo | vot_perc | partido | municipio | latitude | longitude | distancia | populacao | custeio | pessoal | pib_percap | atv_princ | taxa_hom | nota_ideb | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 44 | 37 | Não eleito | ACRE | AC | 1200013.0 | 0.52 | UNIAO | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
1 | 13 | 1516 | 2º turno | ACRE | AC | 1200013.0 | 21.30 | PT | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
2 | 15 | 192 | Não eleito | ACRE | AC | 1200013.0 | 2.70 | MDB | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
3 | 16 | 1 | Não eleito | ACRE | AC | 1200013.0 | 0.01 | PSTU | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
4 | 22 | 5238 | 2º turno | ACRE | AC | 1200013.0 | 73.59 | PL | Acrelândia | -9.82581 | -66.8972 | 102.925123 | 14366.0 | 21468597.9 | 17851184.46 | 253609.712 | Administração, defesa, educação e saúde públic... | 32.77 | 5.2 |
Como dito na primeira parte desse projeto, só iremos utilizar os dados para os dois primeiros colocados nas eleições. Por isso filtramos os dados para o partido de ambos os candidatos. Em seguida, verifica-se se continuamos não tendo valores ausente, pois durante o processo de manipulação de dados podemos criar valores "NaN" por conta própria.
df_filtered = df_mun_of[(df_mun_of['partido'] == 'PT') | (df_mun_of['partido'] == 'PL')]
df_filtered.reset_index(drop=True, inplace=True)
df_filtered.isna().sum()
num_cand 0 vot_abs 0 eleito 0 uf 0 ufr 0 Codigo 0 vot_perc 0 partido 0 municipio 0 latitude 0 longitude 0 distancia 0 populacao 0 custeio 0 pessoal 0 pib_percap 0 atv_princ 0 taxa_hom 0 nota_ideb 0 dtype: int64
Foi mencionado anteriormente que os gastos com custeio e pessoal estão em seu valor absoluto o que pode causar problemas, pois, municípios com populações maiores tendem a gastar mais com custeio e pessoal. Logo, iremos dividir o valor dessas colunas pela população do município e criar uma nova variável de cada em forma per capita.
df_filtered['custeio_perc'] = df_filtered['custeio'] / df_filtered['populacao']
df_filtered['pessoal_perc'] = df_filtered['pessoal'] / df_filtered['populacao']
Só estamos trabalhando com dois candidatos agora, assim o total de 11.140 linhas. Uma para cada candidato em cada município.
df_filtered.shape
(11140, 21)
Para fins de análise só utilizaremos as linhas referentes ao candidato com maior quantidade de votos para cada município, que é o que obtemos com a linha logo em seguida. Por conseguinte, temos o total de 5.570 observações, uma para cada município do país.
df_filtered = df_filtered.loc[df_filtered.groupby('Codigo')['vot_perc'].idxmax(),]
df_filtered.reset_index(drop=True, inplace=True)
df_filtered.head()
num_cand | vot_abs | eleito | uf | ufr | Codigo | vot_perc | partido | municipio | latitude | ... | distancia | populacao | custeio | pessoal | pib_percap | atv_princ | taxa_hom | nota_ideb | custeio_perc | pessoal_perc | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 22 | 9034 | 2º turno | RONDÔNIA | RO | 1100015.0 | 65.86 | PL | Alta Floresta D'Oeste | -11.92830 | ... | 413.957085 | 25437.0 | 4.315441e+07 | 3.711541e+07 | 495628.767 | Administração, defesa, educação e saúde públic... | 34.87 | 4.8 | 1696.521245 | 1459.111232 |
1 | 22 | 36941 | 2º turno | RONDÔNIA | RO | 1100023.0 | 73.52 | PL | Ariquemes | -9.90571 | ... | 158.768710 | 107345.0 | 1.609147e+08 | 1.487715e+08 | 2578829.725 | Demais serviços | 35.23 | 4.7 | 1499.042183 | 1385.919438 |
2 | 22 | 2206 | 2º turno | RONDÔNIA | RO | 1100031.0 | 62.76 | PL | Cabixi | -13.49450 | ... | 639.017040 | 6224.0 | 1.648121e+07 | 1.272046e+07 | 139959.390 | Administração, defesa, educação e saúde públic... | 0.00 | 5.1 | 2648.008678 | 2043.775101 |
3 | 22 | 34681 | 2º turno | RONDÔNIA | RO | 1100049.0 | 69.55 | PL | Cacoal | -11.43430 | ... | 400.204776 | 88507.0 | 1.239290e+08 | 1.110954e+08 | 2260643.565 | Demais serviços | 24.60 | 5.7 | 1400.217159 | 1255.215643 |
4 | 22 | 7323 | 2º turno | RONDÔNIA | RO | 1100056.0 | 74.73 | PL | Cerejeiras | -13.18700 | ... | 594.969020 | 17934.0 | 2.992989e+07 | 2.357524e+07 | 506854.457 | Comércio e reparação de veículos automotores e... | 18.38 | 5.7 | 1668.891156 | 1314.555401 |
5 rows × 21 columns
df_filtered.shape
(5570, 21)
Por fim, vamos obter o resumo estatístico de algumas variáveis para cada partido. O resumo é tido para a média dos dados categorizado pelos partidos em cada município.
Conclusões iniciais permitem-nos inferir que há diferença média nos perfis dos municípios em relação ao PIB per capita, taxa de homicídio e nota do IDEB dos eleitores municipais de cada candidato. Perceba que o PIB per capita do candidato do PL está elevado a sexta potência, enquanto o do candidato do PT está elevado a quinta potência somente. Uma das razões disso pode ser por causa de o candidato do PL ter maior eleitores na região Sudeste do país.
Em relação aos dados de custeio e pessoal é necessária uma maior análise para saber se essa diferença é significativa estatisticamente.
df_filtered.groupby('partido').mean()[['vot_perc', 'vot_abs', 'populacao', 'custeio', 'pessoal', 'pib_percap', 'taxa_hom', 'nota_ideb', 'custeio_perc', 'pessoal_perc']]
vot_perc | vot_abs | populacao | custeio | pessoal | pib_percap | taxa_hom | nota_ideb | custeio_perc | pessoal_perc | |
---|---|---|---|---|---|---|---|---|---|---|
partido | ||||||||||
PL | 57.104448 | 14749.374544 | 47464.093978 | 9.656520e+07 | 8.613646e+07 | 2.045526e+06 | 14.128134 | 5.979745 | 2638.315347 | 2162.442408 |
PT | 66.444381 | 10581.103316 | 30674.847543 | 5.526645e+07 | 4.853381e+07 | 8.600765e+05 | 21.377211 | 5.312966 | 2225.823389 | 1820.824094 |
ATENÇÃO: todos os gráficos são interativos mas, por questões de tamanho do arquivo HTML gerado pelo Jupyter Notebook plotamos todos os gráficos no Notebook em JPG. Abaixo de cada imagem há um link de direcionamento para o gráfico interativo.
O gráfico que se segue mostra a relação entre o PIB per capita e o percentual da vitória de cada candidato no município. A conclusão que se pode ter observando o gráfico é que os municípios onde o candidato Luiz Inácio Lula da Silva venceu estão concentrados até 5.000 e nesses municípios o candidato do PT obteve maior vantagem sobre o outro candidato, obtendo, principalmente no Nordeste, percentual acima de 70% dos votos validos, chegando a ter até 92% no município de Guaribas no Piauí.
Quando vamos em direção à municípios com PIB per capita maiores do que 5 mil, há a presença de maiores pontos azuis, ferentes ao candidato Jair Messias Bolsonaro. Só que em relação a esse candidato há uma peculiaridade, quando vamos aumentando o PIB per capita diminuímos a diferença desse candidato em relação ao concorrente. Ou seja, mesmo na faixa dos municípios onde o candidato do PL obteve maior êxito, seu percentual de votos válidos é menor do que a faixa em que o outro candidato obteve vantagem.
fig = px.scatter(df_filtered, x="pib_percap", y="vot_perc", color="partido", log_x=True,
hover_name="municipio", hover_data=["uf", "partido", "vot_perc", "vot_abs"],
color_discrete_map={
"PT": "#EF553B",
"PL": "#636EFA"
}, template="simple_white",
labels={'partido':'Vencedor', "pib_percap": "PIB per capita", "uf": "Estado",
"vot_perc": "% Votação", "vap": "vot_abs"})
fig.update_layout(legend=dict(
title="Partido"
))
fig.update_xaxes(title="PIB per capita (log)")
fig.update_yaxes(title="Porcentagem")
fig.update_layout(title={
'text': "Relação: Pib Per Capita x % Votação",
'y':0.95,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'})
fig.show(renderer="jpg")
A segunda visualização é para os dados da nota do IDEB dos municípios. Aqui é importante lembrar que essa é a variável onde tivemos a maior quantidade de valores ausentes e a essas foram imputadas a mediana das notas que é o valor de 5,6. Esse valor foi acrescentado para 497 municípios do universo de 5.570. Mesmo assim podemos ter informações validas.
A média e a mediana do candidato Jair Messias Bolsonaro para essa variável estão acima do valor de 5.6. Além disso, os municípios desse candidato estão mais concentrados entorno dessa média, obtendo um desvio padrão menor do que o do outro candidato. A probabilidade de pegarmos uma observação desse candidato e o valor estiver acima de 5,98 é de 48%.
Já em relação ao candidato Luiz Inácio Lula da Silva, a média e a mediana estão abaixo do valor médio de 5.6 para o país. E por meio do histograma podemos ver que os valores estão mais dispersos em relação à média do que o do outro candidato, mostrando uma heterogeneidade maior nos municípios onde ele venceu. O percentual de municípios com notas maiores do que 5,8 do seu universo de municípios é próximo de 23%, 77% das observações estão abaixo da nota de 5,8.
fig = px.histogram(df_filtered, x="nota_ideb", facet_col="partido", histnorm='probability',
nbins=32, template="simple_white", color="partido", color_discrete_map={"PT": "#EF553B",
"PL": "#636EFA"})
fig.update_layout(showlegend=False)
fig.update_xaxes(title="IDEB")
fig.update_yaxes(title="Probabilidade")
fig.update_layout(title={
'text': "Histograma: Nota IDEB x Partido",
'y':0.95,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'})
fig.add_vline(x=df_filtered[df_filtered["partido"] == "PL"]["nota_ideb"].mean(), line_width=3, line_dash="dash", line_color="black", col=1, row=1,
annotation_text= "Média IDEB: {:.2f}".format(df_filtered[df_filtered["partido"] == "PL"]["nota_ideb"].mean()))
fig.add_vline(x=df_filtered[df_filtered["partido"] == "PT"]["nota_ideb"].mean(), line_width=3, line_dash="dash", line_color="black", col=2, row=1,
annotation_text= "Média IDEB: {:.2f}".format(df_filtered[df_filtered["partido"] == "PT"]["nota_ideb"].mean()))
fig.show(renderer="jpg")
O que foi dito anteriormente se confirma com a sobreposição dos histogramas, o candidato em vermelho, do PT, se concentra na calda esquerda, onde obtém barras maiores e, nessa faixa de municípios, sua diferença se mostra mais do que o dobro dos obtidos pelo outro candidato.
Já na faixa de notas maiores há percentuais maiores para o candidato do PL, no entanto, nessa calda direita, sua proporção em relação ao outro candidato é menor do que a vitória obtida pelo mesmo na outra calda do histograma.
fig = go.Figure()
fig.add_trace(go.Histogram(x = df_filtered[df_filtered["partido"] == "PL"]["nota_ideb"], name="PL",
xbins=dict(
start=0,
end=7.65,
size=0.4), marker_color="#636EFA"))
fig.add_trace(go.Histogram(x = df_filtered[df_filtered["partido"] == "PT"]["nota_ideb"], name="PT",
xbins=dict(
start=0,
end=7.65,
size=0.4), marker_color="#EF553B"))
# Overlay para ambos histogramas
fig.update_layout(barmode="overlay",
template="simple_white",
legend=dict(
title="Partido"),
title={
"text":"Sobreposição Histogramas Nota IDEB",
"y":0.95,
"x": 0.5,
"xanchor": "center",
"yanchor": "top"
})
fig.update_traces(opacity=0.75)
fig.show(renderer="jpg")
Seguindo nas análises das variáveis econômicas obtidas, passamos para os gastos necessários para o custeio e financiamento dos municípios. Como posto anteriormente, estamos considerando as variáveis per capita porque é intuitivo perceber que municípios com maior atividade econômica e maior população tendem a ter um custeio maior. Ao fazermos a divisão pela população estamos considerando aqueles municípios pequenos que são dependentes das transferências estaduais e federais para a manutenção de serviços essenciais. Um exemplo disso é Fernando de Noronha que depende de grande ajuda de outros entes da federação, principalmente pra a manutenção ecológica para seu custeio.
Por meio do Box Plot para cada candidato verifica-se a presença de outliers para ambos os candidatos, do lado do PT, um desses pontos é o próprio município de Fernando de Noronha que, como já posto, depende de transferência dos outros entes federativos para sua manutenção. Do ponto de vista gráfico, percebe-se uma concentração maior de municípios com maior gasto per capita da máquina ao lado do candidato do PL, tanto que sua mediana é maior do que a mediana do outro candidato. No entanto, como essa mediana é pequena não podemos afirmar visualmente que é algo significativo.
fig = px.box(df_filtered, y="partido", x="custeio_perc", points="all", hover_data=["uf", "partido", "vot_perc", "vot_abs"],
hover_name="municipio", color="partido", template="simple_white",
color_discrete_map={
"PT": "#EF553B",
"PL": "#636EFA"
})
fig.update_xaxes(title="Partido")
fig.update_yaxes(title="Custeio da Máquina (log)")
fig.update_layout(showlegend=False,
title={
"text":"Boxplot: Custeio da Máquina x Voto no Partido",
"y":0.95,
"x":0.5,
"xanchor":"center",
"yanchor":"top"
})
fig.update_traces()
fig.show(renderer="jpg")
Seguindo na mesma linha da proposta da avaliação do gráfico acima, exibimos um gráfico que contrata o gasto per capita com pessoal da máquina pública e o PIB per capita dos municípios. Como era de se esperar eles apresentam uma correlação positiva, ou seja, quanto maior é o PIB dos municípios, maior tende a ser o gasto com pessoal.
A proposta com esse gráfico era avaliar se há um padrão claro entre os gastos dos municípios e os votos em determinados candidatos. Pelo visto, há uma maior concentração de pontos (municípios) para o candidato do PT à medida que diminuímos o gasto e o PIB per capita. Na medida que vão aumentando, vai se aumentando também a quantidade de pontos para o candidato do PL. Isso é apenas a sua reflexão pela maior vitória desse candidato em municípios da região sudeste, como será verificado em mapa posterior.
fig = px.scatter(df_filtered, x="pib_percap", y="pessoal", color="partido", log_x=True, log_y=True,
hover_name="municipio", hover_data=["uf", "partido", "vot_perc", "vot_abs"],
color_discrete_map={
"PT": "#EF553B",
"PL": "#636EFA"
}, template="simple_white",
labels={'partido':'Vencedor', "pib_percap": "PIB per capita", "uf": "Estado",
"vot_perc": "% Votação", "vap": "vot_abs"})
fig.update_layout(legend=dict(
title="Partido"
))
fig.update_xaxes(title="PIB per capita (log)")
fig.update_yaxes(title="Gasto com Pessoal (log)")
fig.update_layout(title={
'text': "Relação: Gasto Pessoal x Pib per capita",
'y':0.95,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'})
fig.show(renderer="jpg")
Quando passamos para o Box Plot da taxa de homicídios, pode-se ver uma diferença mais clara entre os municípios de cada candidatos. Para efeito de entendimento dessa diferença basta pegar a mediana dessa variável para o candidato do PL, 9,19. Do outro lado, a mediana para o candidato do PT é de 17,19, quase o dobro do observado para o outro candidato. Além disso, verifica-se uma concentração muito grande de pontos em vermelho para municípios com taxa de homicídios acima de 32,00.
Por meio dessas observações podemos concluir que há uma maior concentração de municípios violentos no candidato do Partido dos Trabalhadores em relação ao candidato do Partido Liberal. O entendimento do porquê disso, seja pelos eleitores desses municípios entenderem que o candidato Lula pode oferecer uma política de segurança melhor ou que isso é reflexo apenas das diferenças econômicas do perfil dos municípios de cada candidato foge do escopo dessa análise.
fig = px.box(df_filtered, x="taxa_hom", y="partido", points="all", hover_data=["uf", "partido", "vot_perc", "vot_abs"],
hover_name="municipio", color="partido", template="simple_white",
color_discrete_map={
"PT": "#EF553B",
"PL": "#636EFA"
})
fig.update_xaxes(title="Taxa de Homicídio")
fig.update_yaxes(title="Partido")
fig.update_layout(showlegend=False,
title={
"text":"Boxplot: Taxa de Homicídio x Voto no Partido",
"y":0.95,
"x":0.5,
"xanchor":"center",
"yanchor":"top"
})
fig.show(renderer="jpg")
A variável "distancia" mede a distância em KM do município em questão à sua capital estadual. A utilização dessa variável foi uma tentativa de entendimento se votação maior em determinado candidato está ligado ao fato de o município ser rural (uma maior distância da sua capital) ou não. No entanto, não foi verificada nenhuma diferença significativa nesse quesito. Como razões temos como hipótese que:
vpop = df_filtered[(df_filtered.vot_abs / df_filtered.populacao) < 1]
fig = px.scatter(vpop, x="distancia", y=(vpop.vot_abs / vpop.populacao), color="partido", hover_name="municipio",
template="simple_white", hover_data=["uf", "partido", "vot_perc", "vot_abs"],
color_discrete_map={
"PT": "#EF553B",
"PL": "#636EFA"
}, log_y=True)
fig.update_layout(legend=dict(
title="Partido"
))
fig.update_yaxes(title={
'text': None
}, showticklabels=False, ticks="", visible=False)
fig.update_xaxes(title={
'text': 'Km'
}, tickangle=45)
fig.update_layout(title={
'text': "Distância da Capital x Votação",
'y':0.95,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'}, xaxis_range=[0,1500])
fig.show(renderer="jpg")
Nossa próxima visualização será sobre a quantidade de município que cada candidato venceu. Aqui se verifica uma vantagem de mais de 1.100 municípios para o candidato do Partido dos Trabalhadores. O candidato Jair Messias Bolsonaro ganhou em 2.192 municípios, já o candidato Luiz Inácio Lula da Silva ganhou em 3.378 municípios.
fig = px.bar(df_filtered.groupby('partido').agg(count = ('Codigo', 'count')).reset_index(),
x='partido', y='count', color='partido', template='simple_white', labels={'partido':'Partido',
"count": "N Municípios"},
color_discrete_map={'PT':'#EF553B',
'PL':'#636EFA'})
fig.update_yaxes(title={
'text': None
}, showticklabels=False, ticks="", visible=False)
fig.update_xaxes(title={
'text': None
})
fig.update_layout(title={
'text': "Quantidade de Municípios por Candidato",
'y':0.95,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'}, showlegend=False, bargap=0.01)
fig.show(renderer="jpg")
Pra gerarmos a exibição das atividades que mais acrescentam valor adicionado nos municípios e sua quantidade será necessário o agrupamento por partido, dos que ganharam o PT ou PL, e por atividade, das classificadas pelo IBGE, após isso é feita a contagem.
Para uma melhor interpretação iremos dividir a quantidade de municípios em cada atividade para cada partido, pela quantidade de municípios vitoriosos em cada partido, ou seja, internamente para as quantidades do PT por 3.378 e internamente para a quantidade do PL por 2.192. A ideia aqui é: se não existem diferenças no perfil econômico dos municípios de cada candidato, a proporção entre cada atividade deve ser próxima entre eles. Dessa forma, essa variável não seria um diferencial no perfil de votação de cada um.
modified = df_filtered.groupby(['partido', 'atv_princ']).agg(count = ('Codigo', 'count')).reset_index().sort_values('count', ascending=False)
modified['n_mun'] = np.where(np.array(df_filtered.groupby(['partido',
'atv_princ']).agg(count = ('Codigo',
'count')).reset_index().sort_values('count',
ascending=False)['partido'].to_list()) == "PT", 3378, 2192)
modified['percent'] = (modified['count'] / modified['n_mun'])*100
modified.sort_values(['partido', 'percent'], ascending=False, inplace=True)
modified.reset_index(drop=True, inplace=True)
modified
partido | atv_princ | count | n_mun | percent | |
---|---|---|---|---|---|
0 | PT | Administração, defesa, educação e saúde públic... | 2306 | 3378 | 68.265246 |
1 | PT | Demais serviços | 528 | 3378 | 15.630551 |
2 | PT | Agricultura, inclusive apoio à agricultura e a... | 283 | 3378 | 8.377738 |
3 | PT | Eletricidade e gás, água, esgoto, atividades d... | 79 | 3378 | 2.338662 |
4 | PT | Indústrias de transformação | 68 | 3378 | 2.013025 |
5 | PT | Pecuária, inclusive apoio à pecuária | 37 | 3378 | 1.095323 |
6 | PT | Indústrias extrativas | 36 | 3378 | 1.065719 |
7 | PT | Produção florestal, pesca e aquicultura | 22 | 3378 | 0.651273 |
8 | PT | Comércio e reparação de veículos automotores e... | 16 | 3378 | 0.473653 |
9 | PT | Construção | 3 | 3378 | 0.088810 |
10 | PL | Demais serviços | 1136 | 2192 | 51.824818 |
11 | PL | Administração, defesa, educação e saúde públic... | 420 | 2192 | 19.160584 |
12 | PL | Agricultura, inclusive apoio à agricultura e a... | 301 | 2192 | 13.731752 |
13 | PL | Indústrias de transformação | 173 | 2192 | 7.892336 |
14 | PL | Pecuária, inclusive apoio à pecuária | 68 | 2192 | 3.102190 |
15 | PL | Eletricidade e gás, água, esgoto, atividades d... | 33 | 2192 | 1.505474 |
16 | PL | Indústrias extrativas | 28 | 2192 | 1.277372 |
17 | PL | Comércio e reparação de veículos automotores e... | 23 | 2192 | 1.049270 |
18 | PL | Produção florestal, pesca e aquicultura | 9 | 2192 | 0.410584 |
19 | PL | Construção | 1 | 2192 | 0.045620 |
Para uma melhor visualização foi utilizado o log no eixo x, dessa forma conseguiremos ver quais atividades ficaram na frente para cada candidato, no entanto, as proporções dos gráficos não ficam adequadas. Por exemplo, se pegarmos "Demais Atividades" um candidato obtém 52% dos seus municípios nessa definição o outro contém somente 15%, só que a distância das barras está muito mais próxima do que deveriam.
Posto essa observação, podemos ver que em relação a participação interna do outro candidato Bolsonaro possui participação das atividades: "Demais Serviços" 3x a mais, "Indústria de Transformação" 4x a mais, "Pecuária" 3x a mais e "Comércio" 2x a mais.
Já em relação à Lula, internamente seus municípios possuem as rubricas "Administração" 3,5 a mais, "Eletricidade e gás" 2x a mais e "Construção" 2x a mais do que na participação do outro candidato.
As outras atividades apresentam valores bem próximos, exceto para "Agricultura" em que Bolsonaro possui uma ligeira vantagem.
fig = px.bar(modified, x="atv_princ", y="percent", color='partido', barmode='group', template='simple_white',
color_discrete_map={'PT':'#EF553B',
'PL':'#636EFA'}, text='atv_princ', labels={'partido':'Partido',
'atv_princ': 'Atv Principal',
'percent': 'Percentual'}, log_y=True)
fig.update_xaxes(title={
'text': None
}, showticklabels=False, ticks="", visible=True)
fig.update_yaxes(title={
'text': None
}, showticklabels=False, ticks="", visible=False)
fig.update_layout(title={
'text': "Atividade dos Municípios por Candidato",
'y':0.95,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'},
legend={'title':'Partido', 'y':0.5},
bargap=0.15)
fig.show(renderer="jpg")
Por fim iremos gerar um mapa para visualização dos municípios e o candidato vencedor, atribuindo a cor Azul para o candidato do PL e a cor Vermelha para o candidato do PT. Esse gráfico é um pouco pesado e em alguns computadores a exibição pode travar ou apresentar lentidão. Podemos aproximar para melhor visualização dos municípios e ao passarmos o ponteiro do mouse por cima de determinado município aparece seu nome, o partido vencedor, seu código nos padrões do IBGE, seu estado, o percentual de votação do candidato vencedor e a quantidade de votos para esse candidato.
Por meio do mapa podemos entender visualmente as diferenças de escolhas regionais dos candidatos, com uma maior preferência para o candidato do PT na região Nordeste. Já a região Sudeste, Sul e Centro-Oeste o candidato do PL obteve maior preferência, no entanto, em colégios importantes do Sudeste o candidato do PT obteve vitória. Na região Norte, municípios importantes votaram em sua grande maioria no candidato Jair Messias Bolsonaro. Luiz Inácio Lula da Silva ganha em muitos municípios interioranos da Amazonia, em municípios muito pequenos, com menos de 10 mil eleitores. Só para fins de comparação, somente em Manaus, Jair Bolsonaro obteve 622.846 votos.
with urlopen('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-100-mun.json') as response:
geojson = json.load(response)
df_filtered['id'] = (df_filtered.Codigo.astype(int)).astype(str)
fig = px.choropleth(
df_filtered, geojson=geojson, color="partido", hover_name="municipio", hover_data=["uf", "partido", "vot_perc", "vot_abs"],
locations="id", featureidkey="properties.id",
projection="mercator",
labels={'partido':'Vencedor', "id": "ID", "uf": "Estado", "vot_perc": "% Votação", "vot_abs": "Votos"},
color_discrete_map={
"PT": "#EF553B",
"PL": "#636EFA"})
fig.update_traces(marker_line_width=0, marker_line_color='black')
fig.update_geos(fitbounds="locations", visible=False, resolution=50)
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0}, legend=dict(
title="Partido"
), title={
'text': "Primeiro Turno: Municípios",
'y':0.98,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'})
fig.show(renderer="jpg")
Ao que foi proposto, análise do perfil dos municípios de cada candidato, poderíamos encerrar nossa análise por aqui. No entanto, criaremos um modelo de Regressão Logística para os dados para ver se nosso modelo é capaz de prever claramente a partir dos dados econômicos e sociais o vencedor das eleições para cada município.
A Regressão Logística prevê a probabilidade a partir de um conjunto de variáveis de ser o vencedor no município tal candidato. Por exemplo, se o retorno do modelo para tal conjunto é 0,75, têm-se 75% de chance de o vencedor daquele município ser o candidato do PT e 25% de chances de ser o outro candidato.
# Encoder coluna atv principal
le = LabelEncoder()
coder = le.fit_transform(df_filtered.atv_princ.to_list())
df_filtered['atv_princ_encoder'] = coder
# Separaçao das variáveis
X = df_filtered[['distancia', 'populacao', 'custeio_perc', 'pessoal_perc', 'pib_percap', 'atv_princ_encoder', 'taxa_hom',
'nota_ideb']]
y = df_filtered['partido']
# Normalização dos dados
X_min_max = MinMaxScaler().fit_transform(X)
# Divisão em dados de treino e de teste
X_train, X_test, y_train, y_test = train_test_split(X_min_max, y, stratify=y, shuffle=True)
# Treinamento e previsão do Modelo
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
Após a criação e previsão do modelo podemos avaliar suas métricas. Nosso modelo obteve uma acurácia de 75%, ou seja, classificou 75% das observações de maneira correta. No entanto é importante fazer uma observação importante, ele classificou corretamente 81% das observações quando para o município o vencedor era o candidato do PT, mas somente 69% quando era candidato do PL. Isso quer dizer que nosso modelo está aprendendo mais de um candidato do que do outro e uma das causas disso é a diferença de dados entre ambos candidatos, temos mais de 1.000 observações a mais para o candidato do PT.
Uma alternativa para contornar esse problema seria o balanceamento dos dados, o que não será abordado nesse projeto, mas é bastante comum em Ciência de Dados.
print("Relatório de Classificação:\n")
print(classification_report(y_true=y_test, y_pred=y_pred))
print("\n\n")
print("Array da Matriz de Confusão:")
print(confusion_matrix(y_test, y_pred, labels=model.classes_))
print("\n")
print("Visualização da Matriz de Confusão:")
disp = ConfusionMatrixDisplay(confusion_matrix=confusion_matrix(y_test, y_pred, labels=model.classes_, normalize='true'),
display_labels=model.classes_).plot()
Relatório de Classificação: precision recall f1-score support PL 0.71 0.66 0.69 548 PT 0.79 0.82 0.81 845 accuracy 0.76 1393 macro avg 0.75 0.74 0.75 1393 weighted avg 0.76 0.76 0.76 1393 Array da Matriz de Confusão: [[364 184] [150 695]] Visualização da Matriz de Confusão:
Para uma melhor visualização do modelo, iremos gerar o mapa de votação dos municípios a nível Brasil novamente, mas agora para as previsões do modelo. Caso a realidade fosse 100% os parâmetros dos nossos modelos, essa seria a configuração da eleição. Vemos que nosso modelo errou muito.
Um outro entendimento para esse mapa poderia ser as cidades potências de crescimento para cada candidato. Pois aquelas que ele previu errado apresentam características comuns a outros municípios em que o mesmo candidato previsto venceu, mas para adotarmos essa possibilidade temos que fazer diversas melhorias com algoritmo.
De fato, nosso algoritmo pode ainda ser muito melhorado. Podemos balancear os dados, aplicar uma seleção de variáveis mais criteriosa, fazer feature engineering, selecionar outro modelo de machine learning e definir melhor os seus parâmetros de treinamento, o que não foi aplicado aqui.
# Gerando gerando as previsões para todos
y_pred_all = model.predict(X_min_max)
# Criando dataframe
df_prev = X.copy()
df_prev['partido'] = y_pred_all.tolist()
df_prev['id'] = df_filtered.id
df_prev['municipio'] = df_filtered.municipio
df_prev['uf'] = df_filtered.uf
# Gerando a visualização
fig = px.choropleth(
df_prev, geojson=geojson, color="partido", hover_name="municipio", hover_data=["uf", "partido"],
locations="id", featureidkey="properties.id",
projection="mercator",
labels={'partido':'Vencedor', "id": "ID", "uf": "Estado"},
color_discrete_map={
"PT": "#EF553B",
"PL": "#636EFA"})
fig.update_traces(marker_line_width=0, marker_line_color='black')
fig.update_geos(fitbounds="locations", visible=False, resolution=50)
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0}, legend=dict(
title="Partido"
), title={
'text': "Primeiro Turno: Municípios",
'y':0.98,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'})
fig.show(renderer="jpg")
Abaixo nós rodamos um novo modelo de Regressão Logística em que aparecem diversas informações estatísticas interessante sobre as variáveis. Podemos ver que as variáveis 'custeio_perc' e 'pessoal_perc' não possuem significância estatística, talvez seria mais interessante retirar elas do modelo.
# PT = 1, PL = 0
y_encoder = le.fit_transform(y_train)
logit_model = sm.Logit(y_encoder, X_train)
result=logit_model.fit()
print(result.summary())
print(X.columns)
Optimization terminated successfully. Current function value: 0.584745 Iterations 6 Logit Regression Results ============================================================================== Dep. Variable: y No. Observations: 4177 Model: Logit Df Residuals: 4169 Method: MLE Df Model: 7 Date: Thu, 20 Oct 2022 Pseudo R-squ.: 0.1277 Time: 23:11:08 Log-Likelihood: -2442.5 converged: True LL-Null: -2799.9 Covariance Type: nonrobust LLR p-value: 4.172e-150 ============================================================================== coef std err z P>|z| [0.025 0.975] ------------------------------------------------------------------------------ x1 2.6836 0.322 8.326 0.000 2.052 3.315 x2 12.5824 7.820 1.609 0.108 -2.744 27.909 x3 -3.7871 4.502 -0.841 0.400 -12.611 5.037 x4 5.3214 4.826 1.103 0.270 -4.137 14.780 x5 -12.8349 8.540 -1.503 0.133 -29.573 3.903 x6 -2.9154 0.152 -19.229 0.000 -3.213 -2.618 x7 5.6058 0.380 14.770 0.000 4.862 6.350 x8 -0.3194 0.222 -1.437 0.151 -0.755 0.116 ============================================================================== Index(['distancia', 'populacao', 'custeio_perc', 'pessoal_perc', 'pib_percap', 'atv_princ_encoder', 'taxa_hom', 'nota_ideb'], dtype='object')
Na primeira sessão desse trabalho, foi definido como objetivo desse projeto um entendimento dos perfis dos municípios para cada candidatos que passaram ao segundo turno. Por meio de variáveis como PIB per capita, nota do IDEB, taxa de homicídio e atividade principal de agregação de valor do produto pudemos entender que existem sim algumas diferenças onde cada candidato saiu vitorioso.
Um dos grandes problemas desse trabalho foi a definição dos dados econômicos e sociais que seriam utilizados, por isso é importante deixar registrado que não se esgota aqui. Essas variáveis foram escolhidas de forma arbitrárias e seguindo algumas lógicas, mas nem sempre se cruzam com o 100% correto. Por isso, para melhoramento é interessante adentrar em estudos sobre dados que melhores representam crescimento, desenvolvimento, segurança, saúde, educação e etc. dos municípios. Para isso devemos adentrar nas literaturas acadêmicas para suportar nossas escolhas, o que se tornaria um trabalho exaustivos para fins modestos dessa análise.
Para encerramento deixo aqui uma reflexão sobre como podemos enxergar o potencial de análises desse tipo, principalmente eleitoralmente. Entender os perfis de municípios permite que candidatos entendam padrões presentes em seus eleitores e busque maior enfoque em regiões que realmente possuem capacidade de obtenção de voto. Para elucidação desse ponto tenhamos como exemplo o candidato Jair Messias Bolsonaro, ele perdeu em quase toda a totalidade dos municípios da Região Nordeste, seria interessante para ele tentar reverter o voto de toda uma região? Claro que não, é muito mais almejável a identificação de municípios dentro dos padrões de seu eleitorado já obtido para buscar uma vitória especifica nesses municípios e aumentar sua margem frente o outro candidato.
É nesse sentido que algoritmos de Machine Learning podem ajudar a identificar padrões e atributos importantes para identificar municípios potenciais para candidatos. Isso seria semelhante a quando entramos em sites de compra, como a Amazon, e aparecem aquela oferta de produtos indicados para a gente. Atrás de tudo isso está um algoritmo de Machine Learning que identificou padrões em consumidores e, no momento que entramos no site, encontra consumidores com perfis parecidos com o nosso e o que eles levaram, para assim ofertar produtos mais certeiro. Por meio dessa técnica as vendas de e-commerce puderam crescer muito mais.
Portanto, identificar padrões, reconhecer potenciais de crescimento e focar em um direcionamento correto de esforços podem reduzir custos de campanhas, tornar o marketing mais efetivo e levar a resultados muito mais consistentes.
Como complemento desse Notebook foi gerado um Dashboard por meio da biblioteca Dash. Nessa aplicação é possível visualizar as diferenças para cada candidato em nível municipal só que segmentado para cada Estado. O upload do aplicativo foi feito na plataforma Heroku e pode ser acessada por meio do browser clicando aqui.
Saliento que o aplicativo demora um pouco para ser carregado e quando mudamos o estado leva tempo para a atualização, isso se dá principalmente porque a visualização de mapas gera tempo e consome bastante processamento. Fica aberto para mim a necessidade de estudo de como podemos contornar esse problema, tornando o aplicativo mais efetivo. Abaixo segue uma imagem dele, que também pode ser acessado clicando na imagem.
E-mail: victor.passon@yahoo.com.br
Linkedin: https://www.linkedin.com/in/victorpasson/
Github: https://github.com/victorpasson
Site Portfolio: http://victorpasson.github.io/