NBAの選手・試合情報をAPIから取得する

特定の選手のスタッツを取得する方法

選手のスタッツ情報とはbasketball-referenceで言うとコチラに当たります
www.basketball-reference.com

コチラのチュートリアルを参考にしました
github.com

# Jayson Tatumのキャリアスタッツを見る方法
from nba_api.stats.static import players
player_id = None
for _ in players.get_active_players():
    if _['full_name'] == 'James Harden':
        player_id = _['id']

from nba_api.stats.endpoints import playercareerstats
career = playercareerstats.PlayerCareerStats(player_id)

# 各種シーズンのスタッツが見れる
print(career.get_normalized_dict().keys())
# dict_keys(['SeasonTotalsRegularSeason', 'CareerTotalsRegularSeason', 'SeasonTotalsPostSeason', 'CareerTotalsPostSeason', 'SeasonTotalsAllStarSeason', 'CareerTotalsAllStarSeason', 'SeasonTotalsCollegeSeason', 'CareerTotalsCollegeSeason', 'SeasonRankingsRegularSeason', 'SeasonRankingsPostSeason'])

# SeasonTotalsRegularSeasonのスタッツ
print(career.get_data_frames()[0])
#    PLAYER_ID SEASON_ID LEAGUE_ID     TEAM_ID TEAM_ABBREVIATION  PLAYER_AGE  GP  GS     MIN  FGM   FGA  FG_PCT  FG3M  FG3A  FG3_PCT  FTM  FTA  FT_PCT  OREB  DREB  REB  AST  STL  BLK  TOV   PF   PTS
# 0      201935   2009-10        00  1610612760               OKC        20.0  76   0  1738.0  233   578   0.403    93   248    0.375  194  240   0.808    47   197  244  137   80   20  106  200   753
# 1      201935   2010-11        00  1610612760               OKC        21.0  82   5  2189.0  298   684   0.436   113   324    0.349  289  343   0.843    42   213  255  176   92   24  106  207   998
# 2      201935   2011-12        00  1610612760               OKC        22.0  62   2  1946.0  309   629   0.491   114   292    0.390  312  369   0.846    30   222  252  229   62   15  137  150  1044
# 3      201935   2012-13        00  1610612745               HOU        23.0  78  78  2985.0  585  1337   0.438   179   486    0.368  674  792   0.851    62   317  379  455  142   38  295  178  2023
# 4      201935   2013-14        00  1610612745               HOU        24.0  73  73  2777.0  549  1205   0.456   177   483    0.366  576  665   0.866    61   283  344  446  115   29  265  177  1851
# 5      201935   2014-15        00  1610612745               HOU        25.0  81  81  2981.0  647  1470   0.440   208   555    0.375  715  824   0.868    75   384  459  565  154   60  321  208  2217
# 6      201935   2015-16        00  1610612745               HOU        26.0  82  82  3125.0  710  1617   0.439   236   657    0.359  720  837   0.860    63   438  501  612  139   51  374  229  2376
# 7      201935   2016-17        00  1610612745               HOU        27.0  81  81  2947.0  674  1533   0.440   262   756    0.347  746  881   0.847    95   564  659  907  121   38  464  215  2356
# 8      201935   2017-18        00  1610612745               HOU        28.0  72  72  2551.0  651  1449   0.449   265   722    0.367  624  727   0.858    41   348  389  630  126   50  315  169  2191
# 9      201935   2018-19        00  1610612745               HOU        29.0  78  78  2867.0  843  1909   0.442   378  1028    0.368  754  858   0.879    66   452  518  586  158   58  387  244  2818
# 10     201935   2019-20        00  1610612745               HOU        30.0  68  68  2483.0  672  1514   0.444   299   843    0.355  692  800   0.865    70   376  446  512  125   60  308  227  2335
# 11     201935   2020-21        00  1610612745               HOU        31.0   8   8   290.0   60   135   0.444    25    72    0.347   53   60   0.883     5    36   41   83    7    6   34   14   198
# 12     201935   2020-21        00  1610612751               BKN        31.0  36  35  1319.0  282   599   0.471    96   262    0.366  225  263   0.856    30   277  307  392   46   27  143   85   885
# 13     201935   2020-21        00           0               TOT        31.0  44  43  1609.0  342   734   0.466   121   334    0.362  278  323   0.861    35   313  348  475   53   33  177   99  1083

特定の試合を検索する方法

コチラのチュートリアルをpythonベースで書き直しました
github.com

ipythonの方がSQLのような操作ができるため、チュートリアル通りにやったほうが良いかもしれませんが……

from dataclasses import dataclass
from typing import List, Dict, Tuple, Set, Any, Optional

# 1つの試合を見つけるには、LeagueGameFinderクラスを使います。
# 引数なしで呼び出すことができ、NBA、WNBA、Gリーグ、国際球の中から約30,000試合
# (nba.comがレスポンスで送信する最大行数だと思います)が返されますが、
# チームIDを渡した方が良いでしょう。

# 2017-18 シーズンのCeltics vs Raptorsの最後に対戦した試合
# 1. Fetch all Celtics games
# 2. 2017-18のゲームに絞る
# 3. 対戦相手がラプターズであるゲームに絞る
# 4. 日付順に並べ最後の行を選択する

@dataclass
class Team:
    id: int
    full_name: str
    abbreviation: str
    nickname: str
    city: str
    state: str
    year_founded: int

@dataclass
class Game:
    SEASON_ID: str
    TEAM_ID: str
    TEAM_ABBREVIATION: str
    TEAM_NAME: int
    GAME_ID: str
    GAME_DATE: str
    MATCHUP: str
    WL: str
    MIN: int
    PTS: int
    FGM: int
    FGA: int
    FG_PCT: float
    FG3M: int
    FG3A: int
    FG3_PCT: float
    FTM: int
    FTA: int
    FT_PCT: float
    OREB: int
    DREB: int
    REB: int
    AST: int
    STL: int
    BLK: int
    TOV: int
    PF: int
    PLUS_MINUS: float

# 1. Fetch all Celtics games
# 1.1 Get Celtics team_id
from nba_api.stats.static import teams
nba_teams: List[Team] = []
for i, _ in enumerate(teams.get_teams()):
    team = Team(**_)
    nba_teams.append(team)

bucks_id = [_ for _ in nba_teams if _.abbreviation == 'MIL'][0].id

# 1.2 Fetch all Celtics games
from nba_api.stats.endpoints import leaguegamefinder
gamefinder = leaguegamefinder.LeagueGameFinder(team_id_nullable=bucks_id)
games_ = gamefinder.get_normalized_dict()
games: List[Game] = []
for _ in games_['LeagueGameFinderResults']:
    game = Game(**_)
    games.append(game)

# 2. 2020-21のゲームに絞る
# ※シーズンIDはテーブル見ないと分からないかも?
bucks_games = [_ for _ in games if _.SEASON_ID == '42020']

# 3. NETSとの対戦に絞る
vs_nets_games = [_ for _ in bucks_games if 'BKN' in _.MATCHUP]

# 4. 最新の試合を取得する(最初のインデックス)
print(vs_nets_games[0])
# Game(SEASON_ID='42020', TEAM_ID=1610612749, TEAM_ABBREVIATION='MIL', TEAM_NAME='Milwaukee Bucks', GAME_ID='0042000217', GAME_DATE='2021-06-19', MATCHUP='MIL @ BKN', WL='W', MIN=263, PTS=115, FGM=43, FGA=98, FG_PCT=0.439, FG3M=15, FG3A=36, FG3_PCT=0.417, FTM=14, FTA=21, FT_PCT=0.667, OREB=18, DREB=30, REB=48, AST=20, STL=9, BLK=7, TOV=7, PF=20, PLUS_MINUS=4.0)

試合に関する情報を取得する方法

試合の情報とはbasketball-referenceで言うとコチラのページに該当します
www.basketball-reference.com

コチラのチュートリアルをpythonベースで書き直しました
github.com

試合に関する情報とは主に

  • 試合の基本情報
    • 日程
    • マッチアップ
  • BoxScore
  • Play-by-Play
  • ShotChart
from dataclasses import dataclass
from typing import Dict, List, Tuple, Any, Set

@dataclass
class Team:
    id: int
    full_name: str
    abbreviation: str
    nickname: str
    city: str
    state: str
    year_founded: int

@dataclass
class Game:
    SEASON_ID: str
    TEAM_ID: str
    TEAM_ABBREVIATION: str
    TEAM_NAME: int
    GAME_ID: str
    GAME_DATE: str
    MATCHUP: str
    WL: str
    MIN: int
    PTS: int
    FGM: int
    FGA: int
    FG_PCT: float
    FG3M: int
    FG3A: int
    FG3_PCT: float
    FTM: int
    FTA: int
    FT_PCT: float
    OREB: int
    DREB: int
    REB: int
    AST: int
    STL: int
    BLK: int
    TOV: int
    PF: int
    PLUS_MINUS: float

# チームIDの取得
from nba_api.stats.static import teams
# 30チームを取得
nba_teams = teams.get_teams()
teams_by_abbreviation: Dict[str, Team] = dict()
for nba_team in nba_teams:
    team = Team(**nba_team)
    teams_by_abbreviation[team.abbreviation] = team

# ペイサーズのIDを見つける
pacers_id = teams_by_abbreviation['IND'].id

# 最新のレギュラーシーズンのペイサーズの情報
from nba_api.stats.endpoints import leaguegamefinder
from nba_api.stats.library.parameters import Season
from nba_api.stats.library.parameters import SeasonType

gamefinder = leaguegamefinder.LeagueGameFinder(team_id_nullable=pacers_id, 
                                                season_nullable=Season.default, # 2020-21
                                                season_type_nullable=SeasonType.regular # Regular Season
                                            )

games_dict : Dict[str, List[Dict[Any, Any]]] = gamefinder.get_normalized_dict()
games: List[Game] = []
for game_ in games_dict['LeagueGameFinderResults']:
    game = Game(**game_)
    games.append(game)
    
print(games[0])
# Game(SEASON_ID='22020', TEAM_ID=1610612754, TEAM_ABBREVIATION='IND', TEAM_NAME='Indiana Pacers', GAME_ID='0022001079', GAME_DATE='2021-05-16', MATCHUP='IND @ TOR', WL='W', MIN=239, PTS=125, FGM=48, FGA=95, FG_PCT=0.505, FG3M=15, FG3A=37, FG3_PCT=0.405, FTM=14, FTA=18, FT_PCT=0.778, OREB=10, DREB=37, REB=47, AST=34, STL=8, BLK=3, TOV=13, PF=19, PLUS_MINUS=12.0)

全試合で最もPTSが高かったゲームを求める

gamefinder = leaguegamefinder.LeagueGameFinder(season_type_nullable=SeasonType.regular) # Regular Season

games_dict : Dict[str, List[Dict[Any, Any]]] = gamefinder.get_normalized_dict()

# 2020-21のレギュラーシーズンの全試合
games: List[Game] = []
for game_ in games_dict['LeagueGameFinderResults']:
    game = Game(**game_)
    games.append(game)

games.sort(key=lambda g: g.PTS, reverse=True)
print(games[0])
# Game(SEASON_ID='22014', TEAM_ID=1612709905, TEAM_ABBREVIATION='LAD', TEAM_NAME='Los Angeles D-Fenders', GAME_ID='2021400117', GAME_DATE='2014-12-20', MATCHUP='LAD vs. RNO', WL='W', MIN=241, PTS=175, FGM=69, FGA=112, FG_PCT=0.616, FG3M=11, FG3A=21, FG3_PCT=0.524, FTM=26, FTA=35, FT_PCT=0.743, OREB=18, DREB=45, REB=63, AST=48, STL=8, BLK=5, TOV=24, PF=26, PLUS_MINUS=23.0)

特定の試合のPlay-by-Playデータを見る

@dataclass
class PlayByPlay:
    GAME_ID: int
    EVENTNUM: int
    EVENTMSGTYPE: int
    EVENTMSGACTIONTYPE: int
    PERIOD: int
    WCTIMESTRING: str
    PCTIMESTRING: str
    HOMEDESCRIPTION: Optional[str]
    NEUTRALDESCRIPTION: str
    VISITORDESCRIPTION: Optional[str]
    SCORE: str
    SCOREMARGIN: int

from nba_api.stats.endpoints import playbyplay
game_id = games[0].GAME_ID
play_by_play_dict: Dict[str, List[Dict[str, Any]]] = playbyplay.PlayByPlay(game_id).get_normalized_dict()
play_by_plays: List[PlayByPlay] = []
for _ in play_by_play_dict['PlayByPlay']:
    pbp = PlayByPlay(**_)
    play_by_plays.append(pbp)

# HOMEDESCRIPTIONはHOMEチームのイベント説明
# VISITORDESCRIPTIONはVISITORチームのイベントの説明
# NEUTRALDESCRIPTIONはクォーターの始まりと終わり
for pbp in play_by_plays:
    if pbp.SCORE:
        print(f'{pbp.SCORE} | {pbp.HOMEDESCRIPTION} | {pbp.VISITORDESCRIPTION}')
        print(pbp.SCORE, pbp.HOMEDESCRIPTION, pbp.VISITORDESCRIPTION)
    if pbp.NEUTRALDESCRIPTION:
        if pbp.NEUTRALDESCRIPTION.startswith('Start'):
            print('-'*60)
            print(pbp.NEUTRALDESCRIPTION)
        if pbp.NEUTRALDESCRIPTION.startswith('End'):
            print(pbp.NEUTRALDESCRIPTION)
            print('-'*60)
# ------------------------------------------------------------
# Start of 1st Period (8:11 PM EST)
# 2 - 0 | None | Brissett 12' Driving Floating Jump Shot (2 PTS) (Sabonis 1 AST)
# 2 - 0 None Brissett 12' Driving Floating Jump Shot (2 PTS) (Sabonis 1 AST)
# 4 - 0 | None | Sumner 2' Cutting Layup Shot (2 PTS) (Sabonis 2 AST)
# 4 - 0 None Sumner 2' Cutting Layup Shot (2 PTS) (Sabonis 2 AST)
# 6 - 0 | None | Sumner 1' Driving Layup (4 PTS) (Sabonis 3 AST)
# 6 - 0 None Sumner 1' Driving Layup (4 PTS) (Sabonis 3 AST)
        

# 得点の推移のみを見る
from enum import Enum
class EventMsgType(Enum):
    FIELD_GOAL_MADE = 1
    FIELD_GOAL_MISSED = 2
    FREE_THROWfree_throw_attempt = 3
    REBOUND = 4
    TURNOVER = 5
    FOUL = 6
    VIOLATION = 7
    SUBSTITUTION = 8
    TIMEOUT = 9
    JUMP_BALL = 10
    EJECTION = 11
    PERIOD_BEGIN = 12
    PERIOD_END = 13

for pbp in play_by_plays:
    if pbp.EVENTMSGTYPE == EventMsgType.FIELD_GOAL_MADE.value:
        print(pbp.SCORE)
# 2 - 0
# 4 - 0
# 6 - 0
# 8 - 0
# 10 - 2
# 11 - 4

ある試合のbox_scoreを閲覧する方法

# boxscoreについて
@dataclass
class BoxScore:
    GAME_ID: str
    TEAM_ID: str
    TEAM_ABBREVIATION: str
    TEAM_CITY: str
    PLAYER_ID: str
    PLAYER_NAME: str
    NICKNAME: str
    START_POSITION: str
    COMMENT: str
    MIN: str
    PCT_FGA_2PT: str
    PCT_FGA_3PT: str
    PCT_PTS_2PT: str
    PCT_PTS_2PT_MR: str
    PCT_PTS_3PT: str
    PCT_PTS_FB: str
    PCT_PTS_FT: str
    PCT_PTS_OFF_TOV: str
    PCT_PTS_PAINT: str
    PCT_AST_2PM: str
    PCT_UAST_2PM: str
    PCT_AST_3PM: str
    PCT_UAST_3PM: str
    PCT_AST_FGM: str
    PCT_UAST_FGM: str

from nba_api.stats.endpoints import boxscorescoringv2
box_score_dict: Dict[str, List[Dict[str, Any]]] = boxscorescoringv2.BoxScoreScoringV2(game_id).get_normalized_dict()
box_scores: List[BoxScore] = []
for _ in box_score_dict['sqlPlayersScoring']:
    box_score = BoxScore(**_)
    box_scores.append(box_score)

print(box_scores[0])
# BoxScore(GAME_ID='0022000963', TEAM_ID=1610612754, TEAM_ABBREVIATION='IND', TEAM_CITY='Indiana', PLAYER_ID=203926, PLAYER_NAME='Doug McDermott', NICKNAME='Doug', START_POSITION='F', COMMENT='', MIN='26:59', PCT_FGA_2PT=0.429, PCT_FGA_3PT=0.571, PCT_PTS_2PT=0.323, PCT_PTS_2PT_MR=0.0, PCT_PTS_3PT=0.581, PCT_PTS_FB=0.097, PCT_PTS_FT=0.097, PCT_PTS_OFF_TOV=0.161, PCT_PTS_PAINT=0.323, PCT_AST_2PM=1.0, PCT_UAST_2PM=0.0, PCT_AST_3PM=1.0, PCT_UAST_3PM=0.0, PCT_AST_FGM=1.0, PCT_UAST_FGM=0.0)

ある試合のある選手のショットチャートを閲覧する方法
ショットチャートの検索にはキーとしてteam_id, player_idが必要だったので、roster検索でgame_idからplayer_idを探しています
続いて、APIで得られたショットチャートの結果から特定の試合game_idの結果のみを表示しています

# Shot Chartについて

# Rosterから選手IDを取得
from nba_api.stats.endpoints import commonteamroster
player_id = None
team_id = games[0].TEAM_ID
commonteamroster_dict = commonteamroster.CommonTeamRoster(team_id).get_normalized_dict()
for _ in commonteamroster_dict['CommonTeamRoster']:
    if _['PLAYER'] == 'Domantas Sabonis':
        player_id = _['PLAYER_ID']

# Shot Chart
from nba_api.stats.endpoints import shotchartdetail
shot_chart_detail_dict: Dict[str, List[Dict[str, Any]]] = shotchartdetail.ShotChartDetail(team_id, player_id).get_normalized_dict()
# print(shot_chart_detail_dict.keys())
# dict_keys(['Shot_Chart_Detail', 'LeagueAverages'])
for _ in shot_chart_detail_dict['Shot_Chart_Detail']:
    if _['GAME_ID'] == games[0].GAME_ID:
        print(_['SHOT_TYPE'], _['SHOT_ZONE_BASIC'], _['SHOT_ZONE_AREA'], _['SHOT_ZONE_RANGE'], _['SHOT_DISTANCE'])
# 2PT Field Goal Restricted Area Center(C) Less Than 8 ft. 2
# 2PT Field Goal Restricted Area Center(C) Less Than 8 ft. 1
# 3PT Field Goal Above the Break 3 Left Side Center(LC) 24+ ft. 25
# 2PT Field Goal Restricted Area Center(C) Less Than 8 ft. 1
# 2PT Field Goal Restricted Area Center(C) Less Than 8 ft. 1
# 3PT Field Goal Above the Break 3 Right Side Center(RC) 24+ ft. 25
# 2PT Field Goal Restricted Area Center(C) Less Than 8 ft. 2
# 2PT Field Goal Restricted Area Center(C) Less Than 8 ft. 1
# 2PT Field Goal Restricted Area Center(C) Less Than 8 ft. 1
# 2PT Field Goal Restricted Area Center(C) Less Than 8 ft. 1