[Ubiquant Market Prediction]集計特徴量についてのk-meansによるレジーム推定と可視化

こんにちは、tmrtjと申します。

私は現在株式会社音圧爆上げくんという会社に所属しており、プロKagglerとして活動しています。


私は現在プロKagglerとして、業務の一環で、現在Kaggleで行われているコンペの一つである[Ubiquant Market Prediction](https://www.kaggle.com/c/ubiquant-market-prediction)に参加しています。



また、Kaggle上にCodeとして同じ内容の記事を英語で投稿しています。
リンクは[こちら](https://www.kaggle.com/code/tmrtj9999/regime-classification-with-aggregated-features)です。

# はじめに
以前k-means法によるクラスタリングを用いて、相場のレジームを特定することができるのではないかというアイデアを投稿しました。（リンクは[こちら](https://qiita.com/tmrtj9999/items/6d153ce0956ce0e8ff36)）

<br>

この記事の内容を要約すると、与えられた300の特徴量を用いて、各行をそのままk-means法によりクラスタリングし、その分類結果をレジームとみなすというものです。

<br>

この記事を公開した際にある方にコメントで、各time_idごとに、特徴量を集計し、その集計した特徴量を用いてそれぞれのtime_idのクラスタリングを行うことでもレジーム分類できるのではないかというアイデアをいただきました。

<br>

そこで今回は、time_idごとにそれぞれの特徴量の平均を取り、その集計特徴量を用いてクラスタリングをしていきたいと思います。

# 実装

まずはtrainデータを読み込んでDataFrameにします。

```
import os
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
%matplotlib inline

import warnings
warnings.filterwarnings('ignore')

import lightgbm as lgbm
from lightgbm import *

df = pd.read_parquet('../input/ubiquant-parquet/train_low_mem.parquet')

```

また、今回はレジームの推定結果をグラフにより可視化したいと思います。
その際、各time_idごとのinvestment_idの数を比較対象として表示させます。

具体的には次のようなグラフです。

```
import matplotlib.pyplot as plt

num_investment = df.groupby('time_id').count()

time_id = num_investment.index
num = num_investment['investment_id']

plt.plot(time_id, num)

plt.show()
```

**結果**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2465043/db86ae20-1fbe-5ed8-90a1-5b357becd341.png)

必要な下準備をしています。

メモリを省力化し、不要な列を削除しています。

```


del df['row_id']
del df['investment_id']
del df['target']


def reduce_mem_usage(df):
  
    start_mem = df.memory_usage().sum() / 1024**2
    print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
    
    for col in df.columns:
        col_type = df[col].dtype
        
        if col_type != object:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)
        else:
            df[col] = df[col].astype('category')

    end_mem = df.memory_usage().sum() / 1024**2
    
    print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
    print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
     
    return df

df = reduce_mem_usage(df)
```


time_idごとに平均を取ります。

```

df_agg = df.groupby('time_id').mean()

df = [[]]

```
<br>
<br>

エルボー法を用いて最適なクラスタ数を決めます。



```

from sklearn.cluster import KMeans
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from IPython.display import display

# Elbow Method
wcss = []

for i in range(1, 10):
    kmeans = KMeans(n_clusters = i, init = 'k-means++', max_iter = 300, n_init = 30, random_state = 0)
    kmeans.fit(X_train.head(10000))
    wcss.append(kmeans.inertia_)


plt.plot(range(1, 10), wcss)
plt.title('The elbow method')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS') 
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2465043/a48d291e-c2af-46c4-b9e3-dba7ea3fd485.png)


<br>
エルボー法では傾きがなだらかになっているところを最適なクラスタと考えます。この場合はクラスタ数4が最適です。

クラスタ数4が最適と分かったので、n_clusters=4でモデリングします。
<br>



```
clf = KMeans(n_clusters=4, random_state=0)
clf.fit(df_agg)

y_clustering = clf.predict(df_agg)
df_clustering = pd.DataFrame({'time_id': df_agg.index, 'cluster' : y_clustering})

```

<br>
<br>
クラスタごとにDataFrameを分けます。

```

df_0 = df_clustering[df_clustering['cluster'] == 0]
df_1 = df_clustering[df_clustering['cluster'] == 1]
df_2 = df_clustering[df_clustering['cluster'] == 2]
df_3 = df_clustering[df_clustering['cluster'] == 3]

```

<br>
これで準備が整いました、
これから、クラスタリングの結果を可視化していきます。

各クラスタに分類されるtime_idの背景を色を塗って可視化しています。
また、表示される青線は、time_idごとのinvestment_idの数の推移です。


# クラスタ0

```
from matplotlib import pyplot as plt
import pandas as pd




plt.plot(time_id, num)





for i in df_0['time_id'].values:
    start_datetime0 = i - 0.01
    end_datetime0 = i + 0.01
    plt.axvspan(start_datetime0, end_datetime0, color="red")





plt.show()
```
<br>

**結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2465043/c46a8947-b3ae-3120-2c16-915204be7f5e.png)

**コメント**
クラスタ0は満遍なく出現しているものの、特に、investment_idの数が大きく下がっている部分で濃くなっていることがわかります。このことから、おそらく、暴落に対応するクラスタだと推測できます。


<br>

# クラスタ1

```
from matplotlib import pyplot as plt
import pandas as pd
import matplotlib.pyplot as plt



plt.plot(time_id, num)






for i in df_1['time_id'].values:
    start_datetime1 = i - 0.01
    end_datetime1 = i + 0.01
    plt.axvspan(start_datetime1, end_datetime1, color="green")
    
    
plt.show()
```
<br>

**結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2465043/c94930a3-04bd-0a0d-a057-ac3ea159e543.png)


**コメント**
クラスタ1は前半はあまりなく、後半に集中しているように見えます。investment_idの数の推移を踏まえると、上昇相場に対応するクラスタなのかと思います。

<br>

# クラスタ2

```
from matplotlib import pyplot as plt
import pandas as pd
import matplotlib.pyplot as plt





plt.plot(time_id, num)



for i in df_2['time_id'].values:
    start_datetime2 = i - 0.01
    end_datetime2 = i + 0.01
    plt.axvspan(start_datetime2, end_datetime2, color="yellow")
    
    
plt.show()
```
<br>

**結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2465043/9a626826-4015-b0ac-9f0a-16f319a9026d.png)



**コメント**
クラスタ2はinvestment_idの数が大きく減るところでは現れていないというのが特徴的だと思います。
このことを踏まえると、相場の変動があまりない時に対応するクラスタではないかと推測できます。

<br>


# クラスタ3

```
from matplotlib import pyplot as plt
import pandas as pd
import matplotlib.pyplot as plt






plt.plot(time_id, num)



for i in df_3['time_id'].values:
    start_datetime3 = i - 0.01
    end_datetime3 = i + 0.01
    plt.axvspan(start_datetime3, end_datetime3, color="aqua")
    
    
plt.show()
```
<br>

**結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2465043/4fef35f5-8658-0d2c-eb85-4642f5dd0c44.png)





**コメント**
クラスタ3は満遍なく現れていることがわかります。消去法でボラティリティが高い相場に対応するクラスタではないかと思います。


分析は以上になります。
精度向上の何らかの参考になれば幸いです。





# さいごに
株式会社音圧爆上げくんではプロKagglerを募集しています。
ご興味のある方はぜひ以下のリンクをご覧ください。
[Wantedlyリンク](https://www.wantedly.com/projects/748285)