Hoje, iremos criar isto:

Como dito antes, a intenção do projeto era chegar até o mapa visto na parte 5. Mas essa parada de mapas é meio viciante, então decidi criar um mapa pra cada candidato, pra saber onde cada candidato foi mais votado. Então, podemos pensar que basta pegar a coluna de quantidade de votos e plotar um mapa tendo como variável principal a quantidade de votos pra cada candidato.
Entretanto, pegando somente quantidade de votos absolutos, o mapa não ficaria legal por conta dos maiores colégios eleitorais tais como São Paulo e Rio de Janeiro. Em quantidade absoluta de votos, esses maiores colégios eleitorais dificultariam a visualização correta dos dados. Portanto, preciso normalizar os dados de alguma forma.
Decidi normalizar pegando a quantidade de votos de cada candidato e dividindo pela quantidade de eleitores por município. Dessa forma, irei ter um valor que representa melhor e facilita a visualização de onde cada candidato teve uma quantidade mais expressiva de votos em comparação com o eleitorado do local.
Para isso, vou precisar da quantidade de eleitores por município. No site do TSE tem um conjunto de dados que nos mostra esse dado, que fica aqui:
http://agencia.tse.jus.br/estatistica/sead/odsele/perfil_eleitorado/perfil_eleitorado_2018.zipVou continuar usando meu jupyter notebook do projeto. Então, preciso carregar esses dados:
df_eleitorado = pd.read_csv('perfil_eleitorado_2018.csv', encoding='latin1', sep=';')
Pedindo um head dos meus dados com um transpose:

Vemos que há colunas que não me interessam. Vou filtrar só pelas colunas de interesse:
df_eleitorado_f = df_eleitorado[['SG_UF', 'CD_MUNICIPIO', 'NM_MUNICIPIO',
'QT_ELEITORES_PERFIL']]
Onde QT_ELEITORES_PERFIL é o total de eleitores por zona eleitoral.
O dataframe está agrupado por zonas eleitorais. Eu quero a quantidade de eleitores por municípios e não por zonas. Então, tal qual nas partes 3 e 4 do projeto, irei agrupar este dataframe por município usando groupby, e somarei a quantidade de eleitores por municípios, resetando o índice tal como foi feito antes (dúvidas nesta parte, basta ver o tópico da parte 4, onde foi agrupado o dataframe):
df_eleitorado_f = df_eleitorado_f.groupby(['SG_UF', 'CD_MUNICIPIO', 'NM_MUNICIPIO']).sum().reset_index()
Agora nosso dataframe está da seguinte forma:

Consegui somar a quantidade de eleitores por município dessa forma. Já posso criar um mapa medindo a quantidade de votos por candidato dividida pela quantidade de eleitores por município. Para um primeiro teste, vou pegar o candidato que teve mais votos no primeiro turno e criar um dataframe somente com os dados correspondentes a este candidato:
df_cand_K = df_1o_turno[df_1o_turno['NM_CANDIDATO'] == 'CANDIDATO K']
Obs: df_1o_turno foi um dataframe criado nas partes anteriores do projeto, com todos os dados dos votos dos candidatos no primeiro turno. Quem não leu ainda, recomendo ler o projeto desde o início antes de chegar aqui.
Agora vou juntar os dados do dataframe de votação do candidato K com os dados do dataframe de eleitores por município:
df_cand_K = pd.merge(df_cand_K, df_eleitorado_f, on='CD_MUNICIPIO')
Novamente, usei o comando merge, que já foi visto anteriormente. Juntei os dois dataframes e disse que a referência para a junção dos dois é a coluna comum a eles que é a CD_MUNICIPIO, que é um código do TSE pra cada município. Destrinchei mais o porquê de ter feito dessa forma na parte 4 deste tutorial.
Vamos ver como estão nossos dados:

Vemos que colunas repetidas tiveram seus nomes alterados, como SG_UF virou SG_UF_x e SG_UF_y, assim como NM_MUNICIPIO. Vamos apagar essas colunas repetidas e renomear as originais:
df_cand_K.rename(columns={'SG_UF_x': 'SG_UF',
'NM_MUNICIPIO_x': 'NM_MUNICIPIO',
'QT_ELEITORES_PERFIL': 'QT_ELEITORES',
'CD_MUNICIPIO': 'COD_TSE'}, inplace=True)
df_cand_K.drop(columns=['SG_UF_y', 'NM_MUNICIPIO_y'], inplace=True)
Agora nossos dados estão assim:

Para criar a relação de votos / eleitores, irei dividir a coluna QT_VOTOS_NOMINAIS pela _QT_ELEITORES. Posso realizar isso com um simples passo, criando uma coluna ao lado com a relação VOTOS/ELEITORES:
df_cand_K['VOTOS/ELEITORES'] = df_cand_K['QT_VOTOS_NOMINAIS']/df_cand_K['QT_ELEITORES']

Agora preciso juntar a esses dados os dados dos mapas (nosso geodataframe) para gerar o plot do mapa. Aqui, acontece o mesmo problema já visto antes, de não poder juntar os dataframes do jeito que estão, pois o IBGE usa um código pra cada município e o TSE usa outro código. Aqui, novamente irei usar o dataframe de equivalência código IBGE x código TSE, obtido no GitHub do Estadão. Vamos relembrar este dataframe de equivalência e já apagar as colunas desnecessárias:

Como visto antes, vou usar a função merge para juntar os dois dataframes, o nosso dataframe do candidato K com o dataframe de equivalência, usando a coluna COD_TSE como referência:

Novamente, temos colunas repetidas ou desnecessárias. Vou apagar a coluna NOME e a coluna UF:
df_cand_K.drop(columns=['NOME', 'UF'], inplace=True)
Agora, basta juntar a este dataframe o nosso geodataframe, que possui os desenhos dos mapas:
df_cand_K = pd.merge(df_cand_K, mapa_br, on='GEOCOD_IBGE')

Quase lá. Vemos mais uma coluna repetida. Irei apagá-la:
df_cand_K.drop(columns='NM_MUNICIP', inplace=True)
Como visto anteriormente, nosso dataframe precisa ser um geodataframe para ocorrer o correto plot dos mapas. Ao realizar os merges acima, meus dados estão num dataframe e não num geodataframe. Basta transformar novamente em geodataframe usando o GeoPandas:
df_cand_K = gpd.GeoDataFrame(df_cand_K)
Nossos dados estão assim:

Tudo pronto para o plot dos mapas. Irei usar a coluna VOTOS/ELEITORES como referência para o mapa.
Aqui, novamente, tudo é comando já visto em tópicos anteriores, principalmente na parte 5 deste projeto, então não irei detalhar muito:
# Criando a figura e os eixos
fig, ax = plt.subplots(figsize=(15, 15))
# Plotando o mapa usando os eixos como referência, usando dados da coluna VOTOS/ELEITORES
df_cand_K.plot(column='VOTOS/ELEITORES', cmap='BuGn', ax=ax)
# Adicionando um título
plt.title('Relação votos/eleitores - Candidato K', fontdict={'fontsize': '24', 'fontweight': '3'})
# Apagando as coordenadas
ax.axis('off')
# Mostrando o mapa
plt.show()
Como resultado, temos:

Eu posso adicionar ao lado uma imagem mostrando os valores de maior relação e menor relação voto/eleitor. Vamos fazer isso:
fig, ax = plt.subplots(figsize=(15, 15))
df_cand_K.plot(column='VOTOS/ELEITORES', cmap='BuGn', ax=ax)
plt.title('Relação votos/eleitores - Candidato K', fontdict={'fontsize': '24', 'fontweight': '3'})
ax.axis('off')
# Pegando o valor mínimo e o valor máximo de relação votos/eleitores
vmin_rel_vot_elei, vmax_rel_vot_elei = df_cand_K['VOTOS/ELEITORES'].min(), df_cand_K['VOTOS/ELEITORES'].max()
# Criando a barra vertical que irá mostrar os valores
sm = plt.cm.ScalarMappable(cmap='BuGn', norm=plt.Normalize(vmin=vmin_rel_vot_elei, vmax=vmax_rel_vot_elei))
# Adicionando a barra à figura, diminuindo a barra pra um tamanho 0.5 para não ficar muito grande
cbar = fig.colorbar(sm, shrink=0.5)
# Salvando a figura
fig.savefig('votação K', dpi=300)
plt.show()
A figura agora fica assim:

Gostei de fazer isso pra esse candidato. Agora quero fazer para todos os outros candidatos. Pra não repetir todos os comandos, irei criar uma função que faz tudo que fiz até agora. Não é lá uma boa prática criar uma função que faz várias coisas ao mesmo tempo. O ideal é criar pequenas funções que fazem poucas coisas e usar os retornos das funções como argumentos das funções seguintes. Como não abordei funções ainda, vou fazer tudo numa função só pra agilizar o processo aqui. Não é intenção aqui explicar definições de funções:

A função acima só refaz tudo que fiz até agora. Agora, irei criar o mapa acima para todos os candidatos e plotar no meu jupyter. Lembrando que temos uma variável criada em outro tópico que engloba nossos candidatos, que se chamava lista_candidatos_nova:
In [1]: lista_candidatos_nova
Out[1]: ['CANDIDATO A',
'CANDIDATO B',
'CANDIDATO C',
'CANDIDATO D',
'CANDIDATO E',
'CANDIDATO F',
'CANDIDATO G',
'CANDIDATO H',
'CANDIDATO I',
'CANDIDATO J',
'CANDIDATO K',
'CANDIDATO L',
'CANDIDATO M']
Então, irei iterar sobre essa lista acima e aplicar a função criada pra cada candidato:
for candidato in lista_candidatos_nova:
cria_mapa_vot_eleitor_por_candidato(candidato[-1])
Na função criada, só preciso passar como argumento a letra que representa o candidato. Por isso o candidato[-1], pois pego somente a letra do nome de cada candidato e uso para criar meus mapas.
O resultado fica dessa forma, com o matploblib plotando os mapas, um por um:

Como na minha função eu salvo cada imagem, posso criar um gif animado com todas as imagens:
from PIL import Image
import glob
frames = []
images = glob.glob('*.png')
for image in images:
new_frame = Image.open(image)
frames.append(new_frame)
frames[0].save('votação.gif', format='GIF', append_images=frames[1:],
save_all=True, duration=1500, loop=0)
Acima, eu importo Image de PIL e glob. Crio uma lista vazia chamada frames, pego na variável images todas as imagens do meu diretório raiz que têm a extensão png, itero sobre essas imagens, abro cada uma delas e adiciono à lista de frames. Por fim, salvo o primeiro frame no formato gif e por ser gif, eu adiciono da segunda imagem em diante, com duração de 1,5 segundo entre cada imagem e com um loop infinito, ou seja, ao chegar ao final o gif volta pro começo. O resultado, já vimos no início deste post:

Interessante ver como dependendo do candidato, aparece bonitinho um mapa de um estado, como no candidato E que aparece o mapa do Ceará bem definido, no candidato I, o mapa do Paraná, no candidato M, o de Alagoas, etc.
Ainda quero destrinchar mais esses dados, talvez saia uma parte 7 do projeto. Vamos aguardar haha