LoginSignup
8
6

More than 3 years have passed since last update.

☆安西先生…!!データ分析がしたいです……その1 データ準備☆ PythonでNBAの選手スタッツ(成績)を分析してみる。バスケ

Posted at

全国のバスケ好きのみなさん、こんにちは。相田彦一と申します。
普段はとある高校のバスケットボールチームでマネージャー兼データサイエンティストとして様々なデータの分析を仕事としています。

今回は本場アメリカのプロバスケットリーグであるNBAの選手スタッツ(成績)を分析したいと思います。
分析といっても簡単なものですが、お付き合いください。

第1回目はデータ準備ということでスクレイピング&前処理についてです。
第2回以降がいつになるかはわかりません、ご容赦ください。永遠にないかもしれません。

環境

Google Colaboratoryを使いました。
今回紹介する処理はプリインストールされているライブラリだけで動かすことができます。大変便利です。

データ収集

スクレイピング

データ収集の部分をどうしようかと検索していると、下記のブログ記事をみつけました。

ほとんどこの記事と内容そのままですが、選手の身長、体重などもスクレイピングするかもしれないと思い、選手個人ページのURLもスクレイピング対象に含めました。
一定間隔でカラム名を表すレコードが挿入されているので、そのレコードはスキップするようにしました。

data = pd.DataFrame()
years = [i for i in range(2000, 2002)]
for year in years:
    url = "https://www.basketball-reference.com/leagues/NBA_{}_per_game.html".format(year)
    # this is the HTML from the given URL
    html = urlopen(url)
    soup = BeautifulSoup(html)

    soup.findAll('tr', limit=2)
    # use getText()to extract the text we need into a list
    headers = [th.getText() for th in soup.findAll('tr', limit=2)[0].findAll('th')]
    # exclude the first column as we will not need the ranking order from Basketball Reference for the analysis
    headers = ['URL'] + headers[1:] + ['Year']

    rows = soup.findAll('tr')[1:]
    player_stats = [[rows[i].a.get('href')] + [td.getText() for td in rows[i].findAll('td')] for i in range(len(rows)) if (rows[i].findAll('td')) and (rows[i].a)]
    stats = pd.DataFrame(player_stats)
    stats['Year'] = str(year)
    stats.columns = headers
    data = pd.concat([data, stats])
data = data.dropna()

スクレイピングする対象のページの一例はこちらです。

そのシーズンに出場した選手のスタッツがすベて1ページのサイトに収まっているので、ドラッグしてエクセルなどの表計算ソフトにコピペしても十分にデータ収集できると思います。

今回は20年間(2000年~2019年)のデータを対象としました。スクレイピング結果はこのようになりました。

URL Player Pos Age Tm G GS MP FG FGA FG% 3P 3PA 3P% 2P 2PA 2P% eFG% FT FTA FT% ORB DRB TRB AST STL BLK TOV PF PTS Year
0 /players/a/abdulta01.html Tariq Abdul-Wahad SG 25 TOT 61 56 25.9 4.5 10.6 .424 0.0 0.4 .130 4.4 10.2 .435 .426 2.4 3.2 .756 1.7 3.1 4.8 1.6 1.0 0.5 1.7 2.4 11.4 2000
1 /players/a/abdulta01.html Tariq Abdul-Wahad SG 25 ORL 46 46 26.2 4.8 11.2 .433 0.0 0.5 .095 4.8 10.7 .447 .435 2.5 3.3 .762 1.7 3.5 5.2 1.6 1.2 0.3 1.9 2.5 12.2 2000
2 /players/a/abdulta01.html Tariq Abdul-Wahad SG 25 DEN 15 10 24.9 3.4 8.7 .389 0.1 0.1 .500 3.3 8.6 .388 .393 2.1 2.8 .738 1.6 1.9 3.5 1.7 0.4 0.8 1.3 2.1 8.9 2000
3 /players/a/abdursh01.html Shareef Abdur-Rahim SF 23 VAN 82 82 39.3 7.2 15.6 .465 0.4 1.2 .302 6.9 14.4 .478 .477 5.4 6.7 .809 2.7 7.4 10.1 3.3 1.1 1.1 3.0 3.0 20.3 2000
4 /players/a/alexaco01.html Cory Alexander PG 26 DEN 29 2 11.3 1.0 3.4 .286 0.3 1.2 .257 0.7 2.2 .302 .332 0.6 0.8 .773 0.3 1.2 1.4 2.0 0.8 0.1 1.0 1.3 2.8 2000
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
703 /players/z/zellety01.html Tyler Zeller C 29 MEM 4 1 20.5 4.0 7.0 .571 0.0 0.0 4.0 7.0 .571 .571 3.5 4.5 .778 2.3 2.3 4.5 0.8 0.3 0.8 1.0 4.0 11.5 2019
704 /players/z/zizican01.html Ante Žižić C 22 CLE 59 25 18.3 3.1 5.6 .553 0.0 0.0 3.1 5.6 .553 .553 1.6 2.2 .705 1.8 3.6 5.4 0.9 0.2 0.4 1.0 1.9 7.8 2019
705 /players/z/zubaciv01.html Ivica Zubac C 21 TOT 59 37 17.6 3.6 6.4 .559 0.0 0.0 3.6 6.4 .559 .559 1.7 2.1 .802 1.9 4.2 6.1 1.1 0.2 0.9 1.2 2.3 8.9 2019
706 /players/z/zubaciv01.html Ivica Zubac C 21 LAL 33 12 15.6 3.4 5.8 .580 0.0 0.0 3.4 5.8 .580 .580 1.7 2.0 .864 1.6 3.3 4.9 0.8 0.1 0.8 1.0 2.2 8.5 2019
707 /players/z/zubaciv01.html Ivica Zubac C 21 LAC 26 25 20.2 3.8 7.2 .538 0.0 0.0 3.8 7.2 .538 .538 1.7 2.3 .733 2.3 5.3 7.7 1.5 0.4 0.9 1.4 2.5 9.4 2019

前処理

欠損値

FG%(フィールドゴール成功率=シュート成功率と思ってください)などの確率を表すスタッツは試投数が0であれば空文字になっているようです。NaNに置換します。

data = data.replace(r'^\s*$', np.NaN, regex=True)

データ型変更

データ型が文字列になっています。数字として扱いたいデータをfloatに変換します。整数のみのデータもありますが、面倒なので全部floatにします。
変更前に、パーセントで表される成績が.XXXという表記になっており、そのままでは数字に変換できないので先頭に0をつけます。

add_zero_cols = [col for col in data.columns if '%' in col]
num_cols = ['Age'] + list(data.columns[5:-1])

for col in add_zero_cols:
    data[col] = '0' + data[col]
for col in num_cols:
    data[col] = data[col].astype(float)

確認してみましょう。平均得点トップ10を表示します。

data.sort_values('PTS', ascending=False)[['Player', 'PTS', 'Year']].head(10)
Player PTS Year
11135 James Harden 36.1 2019
3266 Kobe Bryant* 35.4 2006
3428 Allen Iverson* 33.0 2006
1813 Tracy McGrady* 32.1 2003
7954 Kevin Durant 32.0 2014
3818 Kobe Bryant* 31.6 2007
10167 Russell Westbrook 31.6 2017
1249 Allen Iverson* 31.4 2002
3442 LeBron James 31.4 2006
715 Allen Iverson* 31.1 2001

問題なさそうです。ジェームズ・ハーデン、アレン・アイバーソン、コービー・ブライアントなど日本でも知名度があるスーパースターが勢揃いしています。

名前の後ろの*マークが何を表すかデータソースのページを軽く確認したのですが、よくわかりませんでした:sweat_smile:
殿堂入りしている選手を表しているかもしれません。

追加データ 身長・体重

追加でスクレイピング項目に含めていた個人ページのURLから身長、体重もデータを収集しました。
(冒頭のコードを少し変えるだけで収集できるので追加データに対するコードは省略します)

最終的にこのようなデータが準備できました。(右端にWeightとHeightのカラムが追加されました)

Player Pos Age Tm G GS MP FG FGA FG% 3P 3PA 3P% 2P 2PA 2P% eFG% FT FTA FT% ORB DRB TRB AST STL BLK TOV PF PTS Year Weight Height
0 Tariq Abdul-Wahad SG 25.0 TOT 61.0 56.0 25.9 4.5 10.6 0.424 0.0 0.4 0.130 4.4 10.2 0.435 0.426 2.4 3.2 0.756 1.7 3.1 4.8 1.6 1.0 0.5 1.7 2.4 11.4 2000 101.24 1.98
3 Shareef Abdur-Rahim SF 23.0 VAN 82.0 82.0 39.3 7.2 15.6 0.465 0.4 1.2 0.302 6.9 14.4 0.478 0.477 5.4 6.7 0.809 2.7 7.4 10.1 3.3 1.1 1.1 3.0 3.0 20.3 2000 102.15 2.06
5 Ray Allen* SG 24.0 MIL 82.0 82.0 37.4 7.8 17.2 0.455 2.1 5.0 0.423 5.7 12.2 0.468 0.516 4.3 4.9 0.887 1.0 3.4 4.4 3.8 1.3 0.2 2.2 2.3 22.1 2000 93.07 1.96
7 John Amaechi C 29.0 ORL 80.0 53.0 21.1 3.8 8.8 0.437 0.0 0.1 0.167 3.8 8.7 0.439 0.438 2.8 3.6 0.766 0.8 2.6 3.3 1.2 0.4 0.5 1.7 2.0 10.5 2000 122.58 2.08
8 Derek Anderson SG 25.0 LAC 64.0 58.0 34.4 5.9 13.4 0.438 0.9 2.8 0.309 5.0 10.7 0.472 0.470 4.2 4.8 0.877 1.3 2.8 4.0 3.4 1.4 0.2 2.6 2.3 16.9 2000 88.08 1.96
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
11561 Delon Wright PG 26.0 TOT 75.0 13.0 22.7 3.2 7.4 0.434 0.7 2.2 0.298 2.6 5.2 0.492 0.478 1.6 2.0 0.793 0.9 2.6 3.5 3.3 1.2 0.4 1.0 1.4 8.7 2019 83.08 1.96
11566 Thaddeus Young PF 30.0 IND 81.0 81.0 30.7 5.5 10.4 0.527 0.6 1.8 0.349 4.8 8.6 0.564 0.557 1.1 1.7 0.644 2.4 4.1 6.5 2.5 1.5 0.4 1.5 2.4 12.6 2019 99.88 2.03
11567 Trae Young PG 20.0 ATL 81.0 81.0 30.9 6.5 15.5 0.418 1.9 6.0 0.324 4.6 9.6 0.477 0.480 4.2 5.1 0.829 0.8 2.9 3.7 8.1 0.9 0.2 3.8 1.7 19.1 2019 81.72 1.86
11572 Ante Žižić C 22.0 CLE 59.0 25.0 18.3 3.1 5.6 0.553 0.0 0.0 NaN 3.1 5.6 0.553 0.553 1.6 2.2 0.705 1.8 3.6 5.4 0.9 0.2 0.4 1.0 1.9 7.8 2019 115.32 2.08
11573 Ivica Zubac C 21.0 TOT 59.0 37.0 17.6 3.6 6.4 0.559 0.0 0.0 NaN 3.6 6.4 0.559 0.559 1.7 2.1 0.802 1.9 4.2 6.1 1.1 0.2 0.9 1.2 2.3 8.9 2019 108.96 2.13

ちなみに過去20年での身長トップ10は

df.groupby(['Player']).max().reset_index().sort_values('Height', ascending=False)[['Player', 'Year', 'Height', 'Weight']].head(10)
Player Year Height Weight
672 Gheorghe Mureșan 2000 2.31 137.56
1641 Shawn Bradley 2005 2.29 106.69
1890 Yao Ming* 2011 2.29 140.74
1653 Sim Bhullar 2015 2.26 163.44
1442 Pavel Podkolzin 2006 2.26 118.04
1656 Slavko Vraneš 2004 2.26 124.85
1519 Rik Smits 2000 2.24 113.50
172 Boban Marjanović 2019 2.24 131.66
1449 Peter John Ramos 2005 2.21 124.85
583 Edy Tavares 2017 2.21 118.04

単位はm(メートル)です。3位は万里の長城とも呼ばれたあのヤオ・ミンです。身長デカすぎぃ。

おわり

無事にデータが準備できたので、次回は可視化してみたいと思います。

おまけ

平均アシスト トップ10

data.sort_values('AST', ascending=False)[['Player', 'AST', 'Year']].head(10)
Player AST Year
6617 Deron Williams 12.8 2011
7061 Rajon Rondo 11.7 2012
9486 Rajon Rondo 11.7 2016
4085 Steve Nash* 11.6 2007
4686 Chris Paul 11.6 2008
2977 Steve Nash* 11.5 2005
6440 Steve Nash* 11.4 2011
6509 Rajon Rondo 11.2 2011
9819 James Harden 11.2 2017
7641 Rajon Rondo 11.1 2013

平均リバウンド トップ10

data.sort_values('TRB', ascending=False)[['Player', 'TRB', 'Year']].head(10)
Player TRB Year
7236 Earl Barron 18.0 2013
651 Danny Fortson 16.3 2001
10372 Andre Drummond 16.0 2018
11056 Andre Drummond 15.6 2019
1974 Ben Wallace 15.4 2003
6391 Kevin Love 15.2 2011
10537 DeAndre Jordan 15.2 2018
8695 DeAndre Jordan 15.0 2015
9163 Andre Drummond 14.8 2016
6894 Dwight Howard 14.5 2012

規定試合数に達していない選手は除外しないとダメそう。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6