Consider a small country with 5 states, two of which are much larger than the others. We need to apportion 70 representatives. We will apportion using both Webster’s method and the Huntington-Hill method.
# Math in Society 4.5 -- One population table, every apportionment method
# Hamilton and Lowndes share out leftovers; Jefferson, Adams, Webster and
# Huntington-Hill hunt for a modified divisor. Here the divisor methods are run in
# their equivalent "hand out one seat at a time to the highest priority" form.
from math import sqrt
pops = {"A": 300500, "B": 200000, "C": 50000, "D": 38000, "E": 21500}
SEATS = 70 # EDIT: the house size, or edit the populations above
def leftover_method(p, s, lowndes):
divisor = sum(p.values()) / s
quota = {k: v / divisor for k, v in p.items()}
got = {k: int(q) for k, q in quota.items()}
rank = (lambda k: (-(quota[k] - got[k]) / max(got[k], 1), k)) if lowndes \
else (lambda k: (-(quota[k] - got[k]), k))
for k in sorted(p, key=rank)[:s - sum(got.values())]:
got[k] += 1
return got
def divisor_method(p, s, priority):
got = {k: 0 for k in p}
for _ in range(s):
got[max(p, key=lambda k: (priority(p[k], got[k]), p[k]))] += 1
return got
BIG = float("inf")
methods = {
"Hamilton": lambda p, s: leftover_method(p, s, False),
"Lowndes": lambda p, s: leftover_method(p, s, True),
"Jefferson": lambda p, s: divisor_method(p, s, lambda w, n: w / (n + 1)),
"Adams": lambda p, s: divisor_method(p, s, lambda w, n: BIG if n == 0 else w / n),
"Webster": lambda p, s: divisor_method(p, s, lambda w, n: w / (n + 0.5)),
"Hunt-Hill": lambda p, s: divisor_method(p, s,
lambda w, n: BIG if n == 0 else w / sqrt(n * (n + 1))),
}
std = sum(pops.values()) / SEATS
names = list(pops)
print(f"{SEATS} seats, standard divisor {sum(pops.values()):,} / {SEATS} = {std:,.3f}\n")
print(f"{'':<11}" + "".join(f"{k:>9}" for k in names) + f"{'total':>9}")
print(f"{'population':<11}" + "".join(f"{pops[k]:>9,}" for k in names))
print(f"{'quota':<11}" + "".join(f"{pops[k]/std:>9.3f}" for k in names))
print("-" * (11 + 9 * (len(names) + 1)))
results = {}
for m, fn in methods.items():
results[m] = fn(pops, SEATS)
flags = [k for k in names if not (pops[k]/std - 1 < results[m][k] < pops[k]/std + 1)]
note = " quota rule VIOLATED: " + ", ".join(flags) if flags else ""
print(f"{m:<11}" + "".join(f"{results[m][k]:>9}" for k in names)
+ f"{sum(results[m].values()):>9}{note}")
print("\nSame people, same seats, different answers:")
for k in names:
answers = {results[m][k] for m in methods}
if len(answers) > 1:
print(f" State {k}: quota {pops[k]/std:>7.3f}"
f" -> anywhere from {min(answers)} to {max(answers)} seats")
print("\nAdams rounds everything up and so leans small; Jefferson cuts everything off")
print("and so leans large; Webster and Huntington-Hill sit in between, and here they")
print("disagree by one seat -- Webster gives it to the largest state, Huntington-Hill")
print("to the smallest.")
print("Try it: set SEATS = 71 and re-run, then 69. The disagreements move around.")
Show solution
1. The total population is 610,000. Dividing this by the 70 representatives gives the divisor: 8714.286.
2. Dividing each state’s population by the divisor gives the quotas.
Webster’s Method
3. Using Webster’s method, we round each quota to the nearest whole number
4. Adding these up, they only total 69 representatives, so we adjust the divisor down. Adjusting the divisor down to 8700 gives an updated allocation totaling 70 representatives
Huntington-Hill Method
3. Using the Huntington-Hill method, we round down to find the lower quota, then calculate the geometric mean based on each lower quota. If the quota is less than the geometric mean, we round down; if the quota is more than the geometric mean, we round up.
These allocations add up to 70, so we’re done.
Notice that this allocation is different than that produced by Webster’s method. In this case, state E got the extra seat instead of state A.