PythonのPandasライブラリを使ってNBAの試合データを操作し、最後に可視化するチュートリアル

# はじめに
Understanding Basketball Analytics にあるPandasチュートリアルを試してみました。

https://squared2020.files.wordpress.com/2018/10/pythonpandasexample.pdf

実行してみたjupyterノートブックとデータはこちら
https://github.com/nanako-ut/pandas_with_NBA_data

# 対象データ
2016年6月10日　GSW（ウォリアーズ）vs CLE（キャバリアーズ）戦

ショット、アシスト、選手交代等のアクションが記録されたCSVデータ

**データの内容**

| ラベル | 入力例 | 内容 |
|:-----------|:------------:|:------------:|
| a1～a5 | Stephen Curry |  ホームチームの選手名 |
| h1～h5 | J.R. Smith | アウェイチームの選手 名|
| period | 1 | ピリオド |
| away_score | 108 | アウェイチームの得点 |
| home_score | 97 | ホームチームの得点 |
| remaining_time |  0:12:00 | 残り時間 |
| play_length | 0:00:16 | プレイ時間 |
| team | GSW or CLE | アクションが発生したチーム|
| event_type | start of period | イベントの種類（jump ball/ miss/ rebound / shot …）
| assist | Stephen Curry | アシストした選手名 |
| away |  Andrew Bogut |  アウェイチームのジャンプボールの選手名 | 
| home |  Tristan Thompson |  ホームチームのジャンプボールの選手名 |
| possession | Stephen Curry | ジャンプボールを確保した選手名 |
| block |  Draymond Green | ブロックをした選手名 |
| entered | Andre Iguodala | 交代（IN）選手名 |
| left | Andrew Bogut | 交代（OUT）選手名 | 
| num | 1 | フリースローの試投数 | 
| opponent | Draymond Green| ファウルを与えた相手の選手名| 
| outof | 2 | フリースローに与えられた本数 |
| player | Draymond Green | アクションを起こした選手名 |
| points | 2 | 得点 |
| result | made or missed | プレーの結果（成功 or 失敗）|
| steal | Kyrie Irving |  スティールした選手名|
| type | Jump Shot | イベントの詳細（Hook Shot/Jump Shot/Jump Shot…）
| shot_distance | 20 | シューターとリングとの距離 |
| original_x | 1| 選手の位置（X座標）|
| original_y | 41 | 選手の位置（Y座標）|
| converted_x | 25.1 | コートチェンジ分変換後の選手の位置（X座標）|
| converted_y | 84.9 | コートチェンジ分変換後の選手の位置（Y座標）|

<br>
***

# Introduction to Pandas Using Play-By-Play

**チュートリアルの説明文は、超意訳です**

**python 3.6.5 です。チュートリアルは 2.7.10  なので 、チュートリアルを一部 python 3.x系で動くよう変えています**

## Abstract
Pandasの基本的なコマンドを実行し、NBAのサンプルデータを分析します。 サンプルデータはPandasで読み込めるカンマ区切りのCSVファイルです。

## 1. Python: Data Manipulation
このドキュメントでは、Python環境でPandasを使ってデータを分析する方法を検討します。
Pandasのデータフレーム（DataFrame）を使ってCSVファイルを扱い、他のPythonパッケージでさらに処理できるよう、簡単な計算や加工をします。
ここでは、2016年3月12日　オクラホマシティ・サンダー vs サントニオ・スパーズの試合データを使います。
※.OKC vs SASのCSVファイルが入手できなかったので、GSW vs CLE 戦のデータを使います

```python
file = '(2016-06-10)-0041500404-GSW@CLE.csv'
```
## 2.Pandas

Pandasをインポートします。
もしPandaがインストールされていない場合は、「pip install pandas」を実行しましょう。

### 2.1 Reading In Files and Accessing Data

CSVファイルを読み込み、データの形式（行数・列数）をチェックします。

```python
game = pd.read_csv(file)
game.shape
---------
(467, 44)
```

ラベル名を表示します。

```python
game.columns.values
---------
array(['game_id', 'data_set', 'date', 'a1', 'a2', 'a3', 'a4', 'a5', 'h1',
       'h2', 'h3', 'h4', 'h5', 'period', 'away_score', 'home_score',
       'remaining_time', 'elapsed', 'play_length', 'play_id', 'team',
       'event_type', 'assist', 'away', 'home', 'block', 'entered', 'left',
       'num', 'opponent', 'outof', 'player', 'points', 'possession',
       'reason', 'result', 'steal', 'type', 'shot_distance', 'original_x',
       'original_y', 'converted_x', 'converted_y', 'explanation'],
      dtype=object)
```

先頭５行、最後の５行を表示します。

```python
game.head(5)
game.tail(5)
```

特定の列のデータを参照したい場合は、ラベルを指定します。

```python
game['away_score']
```

通常の配列を扱うのと同じように、簡単に参照できます。

```python
awayScore = game ['away_score']
awayScore[10]
---------
7
```

行データにアクセスするにはlocコマンドを使います。
もしこのゲームでの11番目のアクションが何か知りたい場合は、10を指定します。
（インデックスは0から始まる）

```python
game.loc[10]
```

さらにラベルを指定することで行の要素を参照することができます。
11番目のアクションが起こった時の、アウェイチームの first playerを見てみましょう。

```python
game.loc[10]['a1']
---------
'Harrison Barnes'
```

### 2.2 Querying Data in a Dataframe

行と列からデータを抽出する方法を知ったので、次にデータへの問い合わせをします。

例えば、先ほど参照した「Harrison Barnes」のアクション数を数えます。
DataFrameを操作し、複数の行・列に対して「Harrison Barnes」の出現回数を数えるだけです。

```python
count = 0
labels = list(game.columns.values)
for i in range(game.shape[0]): # gameの行数分ループ
    for label in labels:       # gameの列数分ループ
        if 'Harrison Barnes' in str(game.loc[i][label]):
            count = count + 1
            break
            
count
---------
354
```

「Harrison Barnes」がプレーしていないのに、アクションが発生しているデータがあります。
（「a1～h5」に名前がないのに、「left」に名前がある）

これは、途中交代で下がったケースですので、除きましょう。

```python
count = 0
labels = list(game.columns.values)
for i in range(game.shape[0]):
    for label in labels:
        if 'Harrison Barnes' in str(game.loc[i]['a1':'h5']):
            count = count + 1
            #print(label)
            break
            
count
---------
349
```

a1～h5には、コート上の10人のプレーヤー名が記載されています。
「Counter」という機能を使って、コート上のプレーヤーのアクション数を数えます。

先発の10人を見てみましょう。
試合開始1つのアクション（0行目のa1～h5）から、選手名を抽出します。

```python
from collections import Counter
totals = Counter(list(game.loc[0, 'a1':'h5']))
totals
---------

Counter({'Harrison Barnes': 1,
         'Draymond Green': 1,
         'Andrew Bogut': 1,
         'Klay Thompson': 1,
         'Stephen Curry': 1,
         'Richard Jefferson': 1,
         'LeBron James': 1,
         'Tristan Thompson': 1,
         'J.R. Smith': 1,
         'Kyrie Irving': 1})
```

全プレーヤーのアクション数を数えます。

```python
for i in range(1, game.shape[0]):
    totals = totals + Counter(list(game.loc[i, 'a1':'h5']))
totals 
---------

Counter({'Harrison Barnes': 349,
         'Draymond Green': 406,
         'Andrew Bogut': 75,
         'Klay Thompson': 383,
         'Stephen Curry': 399,
         # ～以下略
```

### 2.3 Grouping
Pandasのgroupbyを使って、より洗練されたグルーピングの方法を試しましょう。
groupbyは辞書データ（キーと値を持つ配列）を生成します。

基本的な例として、5対5のマッチアップの組み合わせを見てみましょう。

```python
onCourt = game.groupby(['a1','a2','a3','a4','a5','h1','h2','h3','h4','h5'])
onCourt.groups

---------
{('Draymond Green',
  'Klay Thompson',
  'Andre Iguodala',
  'Shaun Livingston',
  'Stephen Curry',
  'LeBron James',
  'Iman Shumpert',
  'Matthew Dellavedova',
  'Channing Frye',
  'Richard Jefferson'): Int64Index([135], dtype='int64'),
 ('Draymond Green',
  'Klay Thompson',
  'Andre Iguodala',
  'Stephen Curry',
  'Festus Ezeli',
  'LeBron James',
  'Richard Jefferson',
  'Kyrie Irving',
  'J.R. Smith',
  'Tristan Thompson'): Int64Index([162, 163, 164, 165, 166, 167, 168, 169, 170, 171], dtype='int64'),
# 以下略

```

最初の組み合わせは、

```python
'Draymond Green',
  'Klay Thompson',
  'Andre Iguodala',
  'Shaun Livingston',
  'Stephen Curry',
  'LeBron James',
  'Iman Shumpert',
  'Matthew Dellavedova',
  'Channing Frye',
  'Richard Jefferson')
```
であり、この組み合わせは135番目のアクションのみで発生しています。

Pythonの辞書データはキー項目をソートしません。
そのため、並び順が違う同じ組み合わせを数えてしまっているかも。

分かりやすくするためa1～h5を a1～a5に変更し、a1～a5の組み合わせの出現回数を数えてみよう。
（任意のホームプレーヤーを指定することで、件数を数えられる。ここでは「h4」とした）

```python
onCourt = game.groupby(['a1','a2','a3','a4','a5'])
onCourt['h4'].count()

---------
a1               a2              a3                 a4                    a5                  
Draymond Green   Klay Thompson   Andre Iguodala     Shaun Livingston      Stephen Curry            1
                                                    Stephen Curry         Festus Ezeli            10
                                                                          James Michael McAdoo    26
                                 Stephen Curry      Andre Iguodala        Anderson Varejao         1
                 Stephen Curry   Andre Iguodala     Anderson Varejao      Shaun Livingston        42
                                                    Shaun Livingston      James Michael McAdoo     7
Harrison Barnes  Draymond Green  Andrew Bogut       Klay Thompson         Stephen Curry           75
                                 Klay Thompson      Andre Iguodala        Shaun Livingston        31
                                                    Stephen Curry         Andre Iguodala          39
                                 Stephen Curry      Andre Iguodala        Shaun Livingston        15
                 Klay Thompson   Andre Iguodala     Draymond Green        Shaun Livingston         3
                                                                          Stephen Curry           62
                                                    Shaun Livingston      Draymond Green          14
                                                                          James Michael McAdoo    20
Klay Thompson    Andre Iguodala  Draymond Green     Shaun Livingston      Stephen Curry           26
                                 Stephen Curry      Festus Ezeli          Harrison Barnes          1
                 Stephen Curry   Draymond Green     Harrison Barnes       Andre Iguodala          41
                                 Festus Ezeli       Draymond Green        Harrison Barnes          1
                                                    Harrison Barnes       Shaun Livingston        14
                                                    Shaun Livingston      Draymond Green           3
Stephen Curry    Andre Iguodala  Anderson Varejao   Marreese Speights     Klay Thompson            1
                                                    Shaun Livingston      Marreese Speights        1
                                 Harrison Barnes    Draymond Green        Klay Thompson            8
                                 Marreese Speights  Klay Thompson         Harrison Barnes          6
                                 Shaun Livingston   Harrison Barnes       Draymond Green           1
                                                    James Michael McAdoo  Harrison Barnes         18
Name: h4, dtype: int64


```

並び順が違うため、別々に数えられてしまっている 組み合わせがありました。

```python
Draymond Green   Klay Thompson   Andre Iguodala     Shaun Livingston      Stephen Curry            1
Klay Thompson    Andre Iguodala  Draymond Green     Shaun Livingston      Stephen Curry           26
```

組み合わせの数を算出するには、次のやり方を試してみましょう。

1. a1～a5列の値の配列を抽出
1. 配列をソート
1. 新しいDataFrameを構築
1. オリジナルDataFrameのa1～a5をソート版DataFrameのa1～a5で置き換える

```python
a = game.loc[0:, 'a1':'a5'].values
a.sort(axis=1) # 行毎にそれぞれの行の並びをソート

awaysort = pd.DataFrame(a, game.index, list(game.columns.values)[3:8])
game.loc[0:, 'a1':'a5'] = awaysort.loc[0:, 'a1':'a5']
onCourt = game.groupby(['a1','a2','a3','a4','a5'])
onCourt['h4'].count()

---------
a1                a2               a3                    a4                 a5              
Anderson Varejao  Andre Iguodala   Draymond Green        Klay Thompson      Stephen Curry         1
                                                         Shaun Livingston   Stephen Curry        42
                                   Klay Thompson         Marreese Speights  Stephen Curry         1
                                   Marreese Speights     Shaun Livingston   Stephen Curry         1
Andre Iguodala    Draymond Green   Festus Ezeli          Klay Thompson      Stephen Curry        10
                                   Harrison Barnes       Klay Thompson      Shaun Livingston     48
                                                                            Stephen Curry       150
                                                         Shaun Livingston   Stephen Curry        16
                                   James Michael McAdoo  Klay Thompson      Stephen Curry        26
                                                         Shaun Livingston   Stephen Curry         7
                                   Klay Thompson         Shaun Livingston   Stephen Curry        27
                  Festus Ezeli     Harrison Barnes       Klay Thompson      Stephen Curry         1
                  Harrison Barnes  James Michael McAdoo  Klay Thompson      Shaun Livingston     20
                                                         Shaun Livingston   Stephen Curry        18
                                   Klay Thompson         Marreese Speights  Stephen Curry         6
Andrew Bogut      Draymond Green   Harrison Barnes       Klay Thompson      Stephen Curry        75
Draymond Green    Festus Ezeli     Harrison Barnes       Klay Thompson      Stephen Curry         1
                                   Klay Thompson         Shaun Livingston   Stephen Curry         3
Festus Ezeli      Harrison Barnes  Klay Thompson         Shaun Livingston   Stephen Curry        14
Name: h4, dtype: int64
```

アウェイチームの組み合わせに対して、
ホームチームがどのようなローテーションを組んでいたか同じように見てみます。

```python
onCourt = game.groupby(['a1','a2','a3','a4','a5','h1','h2','h3','h4','h5'])
onCourt['h4'].count()
len(onCourt['h4'].count())
---------
44
```
合計44パターンのローテーションで対戦していたことが分かりました。

### 2.4 Counting Statistics

groupbyとDataFrameを駆使して、スタッツを集計してみましょう。

例えば、アシストの数

```python
assists = game.groupby('assist')
assists['assist'].count()
```

平均ショット距離
（ショット距離…シュートを打った際の、プレーヤーとリングとの距離）

```python
assists['shot_distance'].mean()
```

フィールドゴールの成功数

```python
events = game.groupby(['event_type'])
shots = game.iloc[events.groups['shot']].groupby('player')
shots['player'].count()
```

フィールドゴール失敗数

```python
misses = game.iloc[events.groups['miss']].groupby('player')
misses['player'].count()
```

フィールドゴールパーセンテージ（FGP）＝シュートの成功数／シュート本数

```python
S = shots['player'].count()
M = misses['player'].count()
FGP = S/(S+M)
FGP

---------
player
Anderson Varejao             NaN
Andre Iguodala          0.333333
Channing Frye                NaN
# 途中略
Stephen Curry           0.440000
Tristan Thompson        0.714286
```

FGPを算出できない選手は「NaN」と表示されてしまいました。
これを修正するために「fillna」関数を利用します。

データがない「Nan」を0で埋めてから、FGPを算出します。

```python
B['shot'] = B['shot'].fillna(0)
B['miss'] = B['miss'].fillna(0)
B['total'] = B['shot']+B['miss']
B['FGP'] = B['shot']/B['total']
B = B.drop('miss', 1)
B

---------
	shot	total	FGP
Anderson Varejao	0.0	1.0	0.000000
Andre Iguodala	4.0	12.0	0.333333
Channing Frye	0.0	1.0	0.000000
Draymond Green	2.0	9.0	0.222222
Harrison Barnes	5.0	11.0	0.454545
Iman Shumpert	1.0	5.0	0.200000
J.R. Smith	3.0	10.0	0.300000
James Michael McAdoo	1.0	1.0	1.000000
Kevin Love	3.0	6.0	0.500000
Klay Thompson	7.0	14.0	0.500000
Kyrie Irving	14.0	28.0	0.500000
LeBron James	11.0	21.0	0.523810
Matthew Dellavedova	0.0	1.0	0.000000
Richard Jefferson	1.0	2.0	0.500000
Shaun Livingston	3.0	8.0	0.375000
Stephen Curry	11.0	25.0	0.440000
Tristan Thompson	5.0	7.0	0.714286
```

最後に、選手のプレイ時間を算出します。

```python
a = game.loc[0:, 'a1':'a5'].values
a.sort(axis=1)
awaysort = pd.DataFrame(a, game.index, list(game.columns.values)[3:8])
game.loc[0:, 'a1':'a5'] = awaysort.loc[0:, 'a1':'a5']
onCourt = game.groupby(['a1','a2','a3','a4','a5'])

for key in onCourt.groups.keys():
    print('{} {}'.format(key, pd.to_numeric(onCourt.get_group(key)['play_length'].str[-2:]).sum()))

---------
('Anderson Varejao', 'Andre Iguodala', 'Draymond Green', 'Klay Thompson', 'Stephen Curry') 0
('Anderson Varejao', 'Andre Iguodala', 'Draymond Green', 'Shaun Livingston', 'Stephen Curry') 242
('Anderson Varejao', 'Andre Iguodala', 'Klay Thompson', 'Marreese Speights', 'Stephen Curry') 0
('Anderson Varejao', 'Andre Iguodala', 'Marreese Speights', 'Shaun Livingston', 'Stephen Curry') 0
('Andre Iguodala', 'Draymond Green', 'Festus Ezeli', 'Klay Thompson', 'Stephen Curry') 28
# 以下略

```

プレイ時間合計

```python
tots = 0
for key in onCourt.groups.keys():
    tots = tots + pd.to_numeric(onCourt.get_group(key)['play_length'].str[-2:]).sum()

tots/60

---------
48.0
```

選手別プレイ時間

```python
playerTimes = {'player':'time'}

for key in onCourt.groups.keys():
    for playerName in key:
        if playerName in playerTimes:
            playerTimes[playerName] += pd.to_numeric(onCourt.get_group(key)['play_length'].str[-2:]).sum()
        else:
            playerTimes[playerName] = pd.to_numeric(onCourt.get_group(key)['play_length'].str[-2:]).sum()
        
playerTimes

---------
{'player': 'time',
 'Anderson Varejao': 242,
 'Andre Iguodala': 2228,
 'Draymond Green': 2534,
 'Klay Thompson': 2362,
 'Stephen Curry': 2379,
 'Shaun Livingston': 1115,
 'Marreese Speights': 5,
 'Festus Ezeli': 79,
 'Harrison Barnes': 2410,
 'James Michael McAdoo': 445,
 'Andrew Bogut': 601}
```

ヘッダ行を削除して、時間を秒⇒HH:MM:SSの形式へフォーマット

```python
import math 

del playerTimes['player']
for key, value in playerTimes.items():
    playerTimes[key] = '{:02}'.format(math.floor(value/60)) + ':'+'{:02}'.format(value%60)

playerTimes
---------
{'Anderson Varejao': '04:02',
 'Andre Iguodala': '37:08',
 'Draymond Green': '42:14',
 'Klay Thompson': '39:22',
 'Stephen Curry': '39:39',
 'Shaun Livingston': '18:35',
 'Marreese Speights': '00:05',
 'Festus Ezeli': '01:19',
 'Harrison Barnes': '40:10',
 'James Michael McAdoo': '07:25',
 'Andrew Bogut': '10:01'}
```

5人のプレイ時間がめちゃめちゃ多いですね

```
 'Andre Iguodala': '37:08',
 'Draymond Green': '42:14',
 'Klay Thompson': '39:22',
 'Stephen Curry': '39:39',
 'Harrison Barnes': '40:10',
```

## 3 Visualization

matplotlibを使ってデータを可視化します。

```python
import matplotlib.pyplot as plt
```

最初にスコアフローを作成します。

```python
time = ((11 - pd.to_numeric(game['remaining_time'].str[-5:-3]))*60) + (60 - pd.to_numeric(game['remaining_time'].str[-2:])) + (pd.to_numeric(game['period']) - 1)*720
                                                                           
away = pd.to_numeric(game['away_score'])
home = pd.to_numeric(game['home_score'])
plt.plot(time,home,'silver')
plt.plot(time,away,'cyan')
plt.show()
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/a56d8e0b-7194-676f-81d6-06dec5804c06.png)


次にショットチャートを作成します。

チームとイベントタイプ別にグルーピングしたデータを用意し、
成功：緑、失敗：赤　でベーシックなショットチャートを作成します。

```python
team = game.groupby(['team','event_type'])
team.get_group(('GSW', 'shot'))
team.get_group(('CLE', 'shot')).loc[0:,'converted_x':'converted_y']

plt.plot(team.get_group(('GSW', 'shot')).loc[0:,'converted_x'],team.get_group(('GSW', 'shot')).loc[0:,'converted_y'],'go')
plt.plot(team.get_group(('GSW', 'miss')).loc[0:,'converted_x'],team.get_group(('GSW', 'miss')).loc[0:,'converted_y'],'ro')
plt.show()
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/4bccc8ca-1820-2009-60ed-02264f5d1c2b.png)

X軸の25あたりがリングですね。ミドルレンジのシュートがあまり決まっていなかったようです。