Voltar

Primeiro Turno Presidencial - Eleições 2022: Municípios¶

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.

Importando as Bibliotecas Necessárias¶

Começaremos importando as bibliotecas que serão utilizadas no projeto:

  • Pandas: para importação e manipulação dos dados.
  • Numpy: biblioteca para processamento e matemática.
  • Plotly: geração de gráficos.
  • Json: leitura de arquivos desse tipo.
  • Sklearn: para machine learning.

In [1]:
# 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")

Obtenção e Limpeza dos Dados¶

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.

In [2]:
# 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.

In [3]:
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.

In [4]:
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!
In [5]:
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.

In [6]:
# Salvando em um arquivo csv
df_mun_of.reset_index(drop=True, inplace=True)
df_mun_of.to_csv('eleicao_municipios.csv', index = False)
In [7]:
df_mun_of.head()
Out[7]:
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:

  • num_cand: o número do candidato nas urnas.
  • vot_abs: quantidade absoluta de votos obtido pelo candidato no município.
  • vot_perc: o percentual obtido dos votos válidos no município.
  • uf: estado do município.
  • ufr: abreviação do estado do município.
  • Codigo: código do município nos padrões do IBGE.
  • partido: partido do candidato.
  • municipio: nome do município.
  • latitude: latitude do município a partir de dados do IBGE.
  • longitude: longitude do município a partir de dados do IBGE.
  • distancia: dados que medem a distância do município a capital, aqui usamos como proxy de ser zona rural ou não. Fonte: IPEA.
  • populacao: estimativas das populações residentes em nível municipal, calculadas com data de referência em 1º de julho de cada ano civil. Para população residente, considera-se a população de direito, constituída pelos moradores presentes e moradores ausentes, por período não superior a 12 meses, na data do censo. Fonte: IPEA
  • custeio: registro do total das despesas com pessoal, encargos, serviços de terceiros e ou-tros custeios necessários à operação e manutenção dos serviços públicos municipais anterior-mente criados e instalados, inclusive aquelas destinadas a obras de conservação, adaptação e manutenção do seu patrimônio. Fonte: IPEA.
  • pessoal: subconta Pessoal e Encargos Sociais das despesas correntes, que registra as despesas de natureza remuneratória decorrentes de exercício de cargo público, pagamento de aposentadorias, obrigações trabalhistas, contribuição a entidades fechadas de previdência, remuneração dos militares, ressarcimento de pessoal, contratação temporária, em contratos de terceirização, dentre outros. Corresponde a despesa empenhada. Fonte: IPEA.
  • pibpercap: Pib percapita dos municípios para o ano de 2019, serve como medida de atividade econômica dos municípios. Fonte: IBGE.
  • atv_princ: atividade principal do município, que mais contribui para o PIB. Fonte: IBGE.
  • taxa_hom: dados de violência nos municípios obtidos do Atlas da Violência para o ano de 2019. O Atlas busca informações principalmente dos dados do Sistema de Informações sobre Mortalidade (SIM) e do Sistema de Informação de Agravo de Notificação (Sinan) do Ministério da Saúde. Fonte: Atlas da Violência.
  • nota_ideb: Índice de Desenvolvimento da Educação Básica (Ideb) calculado a partir dos dados sobre apro-vação escolar, obtidos no Censo Escolar, e das médias de desempenho no Sistema de Avaliação da Educação Básica (Saeb). Serve como uma medida de qualidade da educação dos municípios. Fonte: INEP.

Análise Exploratória¶

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.

In [8]:
df_mun_of.head()
Out[8]:
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.

In [9]:
df_mun_of.dtypes
Out[9]:
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:

  1. A quantidade maior de valores 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.
  2. Alguns municípios não possuem dados para as despesas.
  3. Como a taxa de homicídio do Atlas da Violência é obtido a partir de dados do SUS, alguns municípios não possuem as informações, podendo ser municípios rurais.
  4. 497 municípios possuem o levantamento do IDEB.

Será necessário tratar esses dados faltantes.

In [10]:
df_mun_of.isna().sum()
Out[10]:
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.

In [11]:
df_mun_of.shape
Out[11]:
(61270, 19)

Por questões constitucionais do orçamento de Brasília, não possuímos os dados de custeio e pessoal.

In [12]:
df_mun_of.loc[df_mun_of.municipio == "Brasília"]
Out[12]:
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.

In [13]:
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.

In [14]:
df_mun_of.isna().sum()
Out[14]:
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
In [15]:
df_mun_of.shape
Out[15]:
(61270, 19)
In [16]:
df_mun_of.head()
Out[16]:
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.

In [17]:
df_filtered = df_mun_of[(df_mun_of['partido'] == 'PT') | (df_mun_of['partido'] == 'PL')]
df_filtered.reset_index(drop=True, inplace=True)
In [18]:
df_filtered.isna().sum()
Out[18]:
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.

In [19]:
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.

In [20]:
df_filtered.shape
Out[20]:
(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.

In [21]:
df_filtered = df_filtered.loc[df_filtered.groupby('Codigo')['vot_perc'].idxmax(),]
df_filtered.reset_index(drop=True, inplace=True)
In [22]:
df_filtered.head()
Out[22]:
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

In [23]:
df_filtered.shape
Out[23]:
(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.

In [24]:
df_filtered.groupby('partido').mean()[['vot_perc', 'vot_abs', 'populacao', 'custeio', 'pessoal', 'pib_percap', 'taxa_hom', 'nota_ideb', 'custeio_perc', 'pessoal_perc']]
Out[24]:
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

Visualização Gráfica¶

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.

In [25]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [26]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [27]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [28]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [29]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [30]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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:

  • A definição de um município ser rural não está atrelada simplesmente a sua distância a capital;
  • Lula venceu em mais de 90% dos municípios da região Nordeste, dessa forma possui um grande número de municípios distantes das capitais;
  • Mesmo com porcentagens menores, Bolsonaro ganhou na Região Norte e Centro-Oeste, apresentando também muitos municípios distantes das capitais.
De fato, a escolha dessa variável para a interpretação do município ser rural foi um erro de seleção de variáveis. Talvez, uma melhor opção seria a própria classificação do IBGE para a atividade econômica de maior agregação de valor para cada município.

In [31]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [32]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [33]:
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
Out[33]:
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.

In [34]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [35]:
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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

Modelo de Machine Learning¶

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.

In [36]:
# 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.

In [37]:
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.

In [38]:
# 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")

CLIQUE AQUI PARA ACESSAR ESSE GRÁFICO DE FORMA INTERATIVA

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.

In [39]:
# 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')

Conclusão¶

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.

Complemento¶

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.

Obrigado pela leitura!¶

Repositório desse Projeto: Clique Aqui¶

Contato:¶

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/

Voltar