O método de Jefferson
\(\text{\Large O}\) Método de Jefferson foi criado em 1784 por Thomas Jefferson (um dos pais fundadores dos EUA e seu terceiro presidente) e foi utilizado para distribuir assentos na Câmara dos Representantes dos EUA de 1792 a 1842, proporcionalmente à população de cada estado. Mais tarde, tornou-se a base para o Método de Hondt, muito popular na Europa. Em vez de procurar um divisor ajustado (como Jefferson fazia), Hondt simplesmente apresentou uma tabela de divisões sucessivas \({(1,\ 2,\ 3,\ 4,\ \cdots)}\). Esta apresentação mais intuitiva tornou o método mais acessível para uso eleitoral.
O método de Jefferson veio antes do método de Hamilton. No entanto, este foi adoptado mais tarde (1794) e chegou a ser usado nos EUA até 1842, altura em foi substituído por outros, mas não mais pelo de Jefferson! (O método de Hamilton gerava paradoxos matemáticos estando, por isso, sujeito a distorções.)
Thomas Jefferson quis manter o espírito proporcional do problema e entendia que se deveria ajustar o divisor padrão para que a soma das quotas fosse igual ao número de cadeiras a serem alocadas. Veja abaixo uma exemplificação do método.
Madeira 2023
Copie os dados abaixo (canto superior direito da caixa) e cole-os na caixa de entrada da aplicação (Partidos e votos). Modifique o divisor padrão e recalcule até que a distribuição dos lugares esteja concluída.
PSD,49104
PS,28981
JPP,22959
CH,12562
CDS-PP,5374
IL,3481
PAN,2531
PCP,2217
BE,1912
PTP,1222
Livre,905
ADN,772
MPT,577
RIR,527#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 680
from shiny import App, reactive, render, ui
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.colors as mcolors
def calcular_alocacoes(votos, total_cadeiras, divisor):
quotas_padrao = votos / divisor
quotas_inferiores = np.floor(quotas_padrao)
return quotas_padrao, quotas_inferiores
app_ui = ui.page_fluid(
ui.row(
ui.column(2,
ui.card(
ui.card_header("Dados"),
ui.input_numeric("total_cadeiras", "Número de lugares:", value=75, min=1),
ui.input_text_area("partidos_votos", "Partidos e votos (formato: Partido,Votos):",
rows=5,
placeholder="Ex:\nPartido A,4404\nPartido B,1672\nPartido C,2087"),
ui.input_text("divisor_manual", "Divisor modificado:", value=""),
ui.input_action_button("calcular", "Calcular Alocação", class_="btn-primary"),
),
),
ui.column(6,
ui.card(
ui.card_header("Resultados"),
ui.output_table("resultados"),
ui.output_text("total_alocado"),
ui.output_text("divisor_padrao"),
),
),
ui.column(4,
ui.card(
ui.card_header("Lugares Alocados"),
ui.output_plot("grafico_cadeiras"),
),
),
),
title="Alocação de Cadeiras usando o Método Jefferson"
)
def server(input, output, session):
dados_base_rv = reactive.value(None)
resultados_rv = reactive.value(None)
divisor_padrao_rv = reactive.value(None)
cores_partidos_rv = reactive.value(None)
divisor_manual_rv = reactive.value(None)
primeira_execucao_rv = reactive.value(True)
@reactive.effect
@reactive.event(input.calcular)
def _():
# Process the initial data
linhas = input.partidos_votos().strip().split("\n")
dados = [linha.split(",") for linha in linhas]
if len(dados) < 2 or any(len(linha) != 2 for linha in dados):
ui.notification_show("Por favor, insira pelo menos dois partidos no formato correto: Partido,Votos", type="error")
return None
try:
partidos = [linha[0].strip() for linha in dados]
votos = [float(linha[1].strip()) for linha in dados]
except ValueError:
ui.notification_show("Todos os votos devem ser números válidos.", type="error")
return None
if any(np.isnan(voto) for voto in votos):
ui.notification_show("Todos os votos devem ser números válidos.", type="error")
return None
# Calculate and set the initial divisor
total_votos = sum(votos)
divisor_inicial = total_votos / input.total_cadeiras()
divisor_padrao_rv.set(divisor_inicial)
# Na primeira execução, use o divisor padrão
if primeira_execucao_rv.get():
ui.update_text("divisor_manual", value=str(round(divisor_inicial, 3)))
divisor_manual_rv.set(divisor_inicial)
primeira_execucao_rv.set(False)
else:
try:
divisor_manual = float(input.divisor_manual().replace(",", "."))
if divisor_manual <= 0:
raise ValueError("Divisor deve ser positivo")
divisor_manual_rv.set(divisor_manual)
except ValueError:
ui.notification_show("O divisor manual deve ser um número válido e positivo.", type="error")
return None
dados_base_rv.set((partidos, votos))
# Set up colors
cores = plt.cm.get_cmap('tab20')(np.linspace(0, 1, len(partidos)))
cores_hex = [mcolors.rgb2hex(cor[:3]) for cor in cores]
cores_partidos_rv.set(dict(zip(partidos, cores_hex)))
@reactive.effect
def check_total_seats():
df = calcular_alocacao()
if df is not None:
total_alocado = int(df["QIM"].iloc[:-1].sum())
if total_alocado == 47:
ui.notification_show(
f"Divisor encontrado! Total de cadeiras alocadas: {total_alocado}",
duration=5,
type="message"
)
else:
ui.notification_show(
f"O número de lugares ainda não é 47 (atual: {total_alocado})",
duration=5,
type="error"
)
@reactive.calc
def calcular_alocacao():
dados_base = dados_base_rv.get()
if dados_base is None:
return None
partidos, votos = dados_base
votos_array = np.array(votos)
divisor_padrao = divisor_padrao_rv.get()
divisor_manual = divisor_manual_rv.get()
if divisor_padrao is None or divisor_manual is None:
return None
alocacoes_originais = calcular_alocacoes(votos_array, input.total_cadeiras(), divisor_padrao)
alocacoes_modificadas = calcular_alocacoes(votos_array, input.total_cadeiras(), divisor_manual)
df = pd.DataFrame({
"Partidos": partidos + ["Total"],
"Votos": votos + [sum(votos)],
"QP": np.round(np.append(alocacoes_originais[0], sum(alocacoes_originais[0])), 2),
"QI": np.append(alocacoes_originais[1], sum(alocacoes_originais[1])),
"QPM": np.round(np.append(alocacoes_modificadas[0], sum(alocacoes_modificadas[0])), 2),
"QIM": np.append(alocacoes_modificadas[1], sum(alocacoes_modificadas[1]))
})
return df
@output
@render.table
def resultados():
df = calcular_alocacao()
cores_partidos = cores_partidos_rv.get()
if df is None or cores_partidos is None:
return None
def estilo_linha(row):
estilo = ['text-align: left'] * len(row)
if row.name == df.index[-1]:
estilo = ['font-weight: bold; text-align: left'] * len(row)
partido = row['Partidos']
cor = cores_partidos.get(partido, '')
return [f'background-color: {cor}; color: black; {s}' for s in estilo]
return (df.style
.apply(estilo_linha, axis=1)
.format(precision=2)
.set_properties(**{'font-size': '14px'})
.hide(axis="index"))
@output
@render.text
def total_alocado():
df = calcular_alocacao()
if df is None:
return ""
total_alocado = int(df["QIM"].iloc[:-1].sum())
return f"Cadeiras alocadas: {total_alocado}"
@output
@render.text
def divisor_padrao():
div_padrao = divisor_padrao_rv.get()
if div_padrao is None:
return ""
return f"Divisor padrão original: {round(div_padrao, 3)}"
@output
@render.plot
def grafico_cadeiras():
df = calcular_alocacao()
cores_partidos = cores_partidos_rv.get()
if df is None or cores_partidos is None:
return None
cadeiras_alocadas = df["QIM"].iloc[:-1].astype(int)
partidos = df["Partidos"].iloc[:-1]
total_cadeiras = input.total_cadeiras()
num_rows = int(np.ceil(np.sqrt(total_cadeiras)))
num_cols = int(np.ceil(total_cadeiras / num_rows))
fig, ax = plt.subplots(figsize=(8, 4))
ax.set_xlim(0, num_cols)
ax.set_ylim(0, num_rows)
cadeira_atual = 0
for partido, cadeiras in zip(partidos, cadeiras_alocadas):
cor = cores_partidos[partido]
for _ in range(cadeiras):
row = cadeira_atual // num_cols
col = cadeira_atual % num_cols
rect = patches.Rectangle((col, num_rows - 1 - row), 0.9, 0.9,
facecolor=cor, edgecolor='white')
ax.add_patch(rect)
ax.text(col + 0.45, num_rows - 1 - row + 0.45, partido,
ha='center', va='center', fontsize=8, wrap=True)
cadeira_atual += 1
for _ in range(cadeira_atual, total_cadeiras):
row = cadeira_atual // num_cols
col = cadeira_atual % num_cols
rect = patches.Rectangle((col, num_rows - 1 - row), 0.9, 0.9,
facecolor='white', edgecolor='black')
ax.add_patch(rect)
cadeira_atual += 1
ax.set_xticks([])
ax.set_yticks([])
handles = [patches.Patch(color=cores_partidos[partido], label=partido)
for partido in partidos]
ax.legend(handles=handles, title="Partidos", loc='upper center',
bbox_to_anchor=(0.5, -0.05), ncol=7, fontsize='x-small')
plt.tight_layout()
return fig
app = App(app_ui, server)
