如何用Python绘制排序复选制选举的票数变化动画图表?
排序复选制(RCV)选举可视化与代码优化方案
一、Matplotlib动画实现与保存
使用FuncAnimation构建逐轮动画,通过animation.save导出为视频/GIF,需先安装依赖:
pip install matplotlib ffmpeg-python # 导出MP4用ffmpeg,导出GIF替换为pillow
核心实现逻辑:
- 每轮更新柱状图数据,将淘汰候选人的柱子设为红色高亮
- 用箭头注释展示淘汰选票的流向
- 动画帧对应流程:初始票数统计→高亮淘汰对象→展示选票拆分→更新下一轮结果
代码片段
import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from collections import Counter # 假设已实现RCV核心逻辑函数:返回当前轮票数、淘汰者、更新后选票、无效票数 def get_round_results(ballots, remaining_candidates): remaining_set = set(remaining_candidates) # 统计有效首选票 top_candidates = [b[0] for b in ballots if b[0] in remaining_set] votes = Counter(top_candidates) for cand in remaining_candidates: votes.setdefault(cand, 0) # 计算无效选票 exhausted = sum(1 for b in ballots if not set(b) & remaining_set) # 确定淘汰者(示例:票数最低者) eliminated = min(votes, key=votes.get) if len(remaining_candidates) > 1 else None # 模拟选票转移(实际需按RCV规则处理) updated_ballots = [b[1:] if b[0] == eliminated else b for b in ballots] return votes, eliminated, updated_ballots, exhausted # 初始化图表 fig, ax = plt.subplots(figsize=(10,6)) bars = None round_info = ax.text(0.02, 0.98, '', transform=ax.transAxes, va='top') def init(): ax.set_ylabel('票数') ax.set_title('排序复选制选举逐轮结果') return bars, round_info def update(frame): global bars, ballots, remaining_candidates round_num = frame + 1 current_votes, eliminated, ballots, exhausted = get_round_results(ballots, remaining_candidates) # 清除旧柱状图 if bars: for bar in bars: bar.remove() # 准备绘图数据 candidates = list(current_votes.keys()) + ['无效选票'] vote_counts = list(current_votes.values()) + [exhausted] # 设置颜色:淘汰者标红,其余蓝色,无效票灰色 colors = ['red' if c == eliminated else 'steelblue' for c in candidates[:-1]] + ['gray'] # 绘制新柱状图 bars = ax.bar(candidates, vote_counts, color=colors) # 更新轮次信息 round_info.set_text(f"第{round_num}轮 | 淘汰: {eliminated or '无'} | 无效选票: {exhausted}") # 绘制选票流向箭头(示例:按比例模拟) if eliminated and round_num > 1: elim_votes = current_votes[eliminated] for i, cand in enumerate(candidates[:-1]): if cand != eliminated and cand in remaining_candidates: transfer = int(elim_votes * 0.3) # 实际替换为真实转移票数 ax.annotate(f"+{transfer}", xy=(eliminated, elim_votes), xytext=(cand, current_votes[cand]), arrowprops=dict(arrowstyle='->', color='orange')) # 更新剩余候选人列表 if eliminated: remaining_candidates.remove(eliminated) return bars, round_info # 模拟测试数据 ballots = [['A', 'B', 'C'], ['B', 'A'], ['C', 'B'], ['A'], ['B', 'C'], ['C'], ['A', 'C'], ['B']] remaining_candidates = ['A', 'B', 'C'] # 创建动画(轮次数量根据实际选举流程调整) ani = FuncAnimation(fig, update, frames=3, init_func=init, blit=True, interval=2500) # 保存动画 ani.save('rcv_election_animation.mp4', writer='ffmpeg', fps=0.4) # 每帧停留2.5秒 # 若需GIF格式,替换为: # ani.save('rcv_election_animation.gif', writer='pillow', fps=0.4) plt.show()
二、字典遍历优化
替换低效的键遍历+手动计数,直接用collections.Counter和dict.items()简化逻辑:
原低效写法
votes = {} for ballot in ballots: top_cand = ballot[0] if top_cand in votes: votes[top_cand] += 1 else: votes[top_cand] = 1
优化写法
from collections import Counter # 直接统计有效首选票 top_candidates = [b[0] for b in ballots if b[0] in remaining_candidates] votes = Counter(top_candidates) # 补全剩余候选人的0票记录 for cand in remaining_candidates: votes.setdefault(cand, 0)
三、无效选票计数简化
通过集合交集快速判断选票是否无有效候选人,替代多层循环:
原复杂写法
exhausted = 0 for ballot in ballots: has_valid = False for cand in ballot: if cand in remaining_candidates: has_valid = True break if not has_valid: exhausted += 1
简化写法
remaining_set = set(remaining_candidates) exhausted = sum(1 for ballot in ballots if not set(ballot) & remaining_set)
内容的提问来源于stack exchange,提问作者Burmeind
相关产品推荐
相关产品推荐

