如何在Python 3 Jupyter中用指定库实现散点图图例与颜色编码
问题
已成功从CSV文件生成网球选手数据散点图,现需基于代码中的国家列表为散点图添加对应图例,并按国家对数据点进行颜色编码。但网上多数解决方案依赖numpy、pandas库,而仅能使用NamedTuple、List、matplotlib.pyplot和csv这几个库,求可行方案。
解决方案
可通过以下步骤实现,完全不依赖numpy或pandas:
- 给国家分配唯一颜色:利用matplotlib内置颜色映射,根据国家列表长度生成对应数量的唯一颜色,每个国家对应固定颜色。
- 关联国家与平均数据:遍历国家列表,为每个国家计算ACE率平均值和胜率平均值,同时匹配对应颜色。
- 逐国绘制数据点:对每个国家单独绘制散点,记录绘图对象用于生成图例。
- 优化图例显示:将绘图对象与国家名称关联生成图例,调整布局避免遮挡数据。
修改后的完整代码
from cs103 import * from typing import NamedTuple, List import csv import matplotlib.pyplot as plt ################## # 数据定义 TennisPlayer = NamedTuple('TennisPlayer', [('name', str), ('country_id', str), ('ace_percentage', float), # 以百分比表示(如20.5代表20.5%),范围[0, 100] ('matches_won_percentage', float)]) # 以百分比表示(如20.5代表20.5%),范围[0, 100] # 示例选手 HURKACZ = TennisPlayer("Hubert Hurkacz", "POL", 24.94, 59.44) FRITZ = TennisPlayer("Taylor Fritz", "USA", 18.47, 58.26) KORDA = TennisPlayer("Sebastian Korda", "USA", 12.65, 53.61) @typecheck def fn_for_tennis_player(tp: TennisPlayer) -> ...: return ...(tp.name, tp.country_id, tp.ace_percentage, tp.matches_won_percentage) # List[TennisPlayer] LOTP0 = [] LOTP1 = [HURKACZ, FRITZ] LOTP2 = [HURKACZ, FRITZ, KORDA] @typecheck def fn_for_lotp(lotp: List[TennisPlayer]) -> ...: acc = ... for tp in lotp: acc = ... return ... LOC0 = [] LOC1 = ["POL", "USA", "RUS", "KAZ"] LOC2 = ["CAN", "USA", "CHN", "JPN"] @typecheck def fn_for_loc(loc: List[str]) -> ...: acc = ... for c in loc: acc = ... return ... ########### # 函数定义 countries = ['POL', 'USA', 'RUS', 'GER', 'KAZ', 'NED', 'GRE', 'AUS', 'BUL', 'CHN', 'FRA', 'ITA', 'SUI', 'CAN', 'ARG', 'SRB', 'DEN', 'NOR', 'ESP', 'CZE', 'GBR', 'FIN', 'AUT', 'RSA', 'JPN', 'CRO', 'COL', 'BRA', 'CHI', 'PER','HUN', 'BLR'] @typecheck def main(filename: str, countries: List[str]) -> None: return show_scatterplot(read(filename), countries) @typecheck def read(filename: str) -> List[TennisPlayer]: lotp = [] with open(filename) as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: tp = TennisPlayer(row[1], row[3], parse_float(row[4]), parse_float(row[5])) lotp.append(tp) return lotp @typecheck def get_country_avg(lotp: List[TennisPlayer], country: str) -> tuple[float, float]: """返回指定国家的ACE率平均值和胜率平均值""" total_aces = 0.0 total_wins = 0.0 count = 0 for player in lotp: if player.country_id == country: total_aces += player.ace_percentage total_wins += player.matches_won_percentage count += 1 if count == 0: return (0.0, 0.0) return (total_aces / count, total_wins / count) def show_scatterplot(lotp: List[TennisPlayer], loc: List[str]) -> None: plt.xlabel('Ace percentage %') plt.ylabel('Matches won percentage %') plt.title('Relationship between Matches Won % and Ace %') # 生成对应国家数量的唯一颜色 cmap = plt.get_cmap('tab20b') colors = [cmap(i / len(loc)) for i in range(len(loc))] # 存储图例用的对象和标签 legend_handles = [] # 逐个国家绘制数据点 for idx, country in enumerate(loc): avg_aces, avg_wins = get_country_avg(lotp, country) # 跳过无数据的国家 if avg_aces == 0 and avg_wins == 0: continue # 绘制单个国家的散点 scatter = plt.scatter(avg_aces, avg_wins, s=30, color=colors[idx], label=country) legend_handles.append(scatter) # 添加图例并调整位置 plt.legend(handles=legend_handles, bbox_to_anchor=(1.05, 1), loc='upper left') plt.tight_layout() plt.show() return None # 测试部分 start_testing() expect(main("TENNIS_ACE_VS_WIN_empty.csv", countries), None) expect(main("TENNIS_ACE_VS_WIN_test1.csv", countries), None) expect(main("TENNIS_ACE_VS_WIN_test2.csv", countries), None) summary() start_testing() expect(read("TENNIS_ACE_VS_WIN_empty.csv"), []) expect(read("TENNIS_ACE_VS_WIN_test1.csv"), [TennisPlayer("Alexei Popyrin", "AUS", 13.39, 53.95), TennisPlayer("Maxime Cressy", "USA", 11.23, 52.43), TennisPlayer("Adrian Mannarino", "FRA", 10.42, 51.82), TennisPlayer("Marco Cecchinato", "ITA", 5.58, 48.05)]) expect(read("TENNIS_ACE_VS_WIN_test2.csv"), [TennisPlayer("Daniil Medvedev", "RUS", 14.72, 55.01), TennisPlayer("John Isner", "USA", 11.81, 53.18), TennisPlayer("Adrian Mannarino", "FRA", 10.42, 51.82), TennisPlayer("Denis Shapovalov", "CAN", 6.84, 49.11)]) summary() start_testing() expect(get_country_avg(LOTP1, "POL"), (24.94, 59.44)) expect(get_country_avg(LOTP2, "USA"), ((18.47+12.65)/2, (58.26+53.61)/2)) summary() main("TENNIS_ACE_VS_WIN.csv", countries)
关键修改说明
- 新增
get_country_avg函数:替代原有的两个平均值计算函数,一次性返回指定国家的两项平均值,简化逻辑。 - 颜色分配逻辑:用
plt.get_cmap获取颜色映射,生成与国家列表长度匹配的唯一颜色集合。 - 逐国绘制散点:遍历每个国家,计算平均值后单独绘制散点,同时记录绘图对象用于生成图例。
- 图例优化:将图例放在图表右侧外部,避免遮挡数据,自动跳过无数据的国家。
内容的提问来源于stack exchange,提问作者eden
相关产品推荐
相关产品推荐

