You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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)
关键修改说明
  1. 新增get_country_avg函数:替代原有的两个平均值计算函数,一次性返回指定国家的两项平均值,简化逻辑。
  2. 颜色分配逻辑:用plt.get_cmap获取颜色映射,生成与国家列表长度匹配的唯一颜色集合。
  3. 逐国绘制散点:遍历每个国家,计算平均值后单独绘制散点,同时记录绘图对象用于生成图例。
  4. 图例优化:将图例放在图表右侧外部,避免遮挡数据,自动跳过无数据的国家。

内容的提问来源于stack exchange,提问作者eden

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:15:54