ホテルの評価値の次元削減をする

# はじめに
本記事では、Pythonで特異値分解を行い、ホテルの評価値データの次元削減を行います。使用するデータは以前紹介した記事[Pythonでじゃらんの口コミをスクレイピングする][Scraping]で取得したものになります。

[Scraping]:https://qiita.com/yuya2220/items/870739cf07a61de18299

# 環境
python 3.10.4
Windows 11 Home

# 次元削減とは
教師なし学習の1つで、多次元のデータをなるべく情報を失わないように低次元のデータに変換することです。データを要約して全体像を分かりやすくことができます。今回使用する特異値分解はこの次元削減の手法の1つになります。

# データの構造
今回使うデータは以下のような構造になっています。
| index | 部屋 | 風呂 | 料理（朝食） | 料理（夕食） | 接客・サービス | 清潔感 | 見出し | 本文 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 0 | 4 | 5 | 5 | 4 | 4 | 4 | 見出し1 | 本文1 |
| 1 | 3 | 4 | - | 5 | 4 | 5 | 見出し2 | 本文2 |
| 2 | 4 | 3 | 5 | 4 | 4 | 3 | 見出し3 | 本文3 |
| 以降データが続く |  |  |  |  |  |  |  |  |

5段階評価の「部屋」、「風呂」、「料理（朝食）」、「料理（夕食）」、「接客・サービス」、「清潔感」の6項目を次元削減し、2次元に変換します。

# 使用するライブラリ
使用するライブラリは以下の4つです。
```python
pip install pandas
pip install matplotlib
pip install scikit-learn
pip install seaborn
```

# 作成コード
作成したコードは以下になります。
```python
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import TruncatedSVD
import seaborn as sns

def main():
    df = make_df()
    #２次元に変換する
    model_svd = TruncatedSVD(n_components=2)
    vecs_list = model_svd.fit_transform(df)
    #寄与率を算出する
    print(model_svd.explained_variance_ratio_)

    make_item_info(model_svd, df)
    make_scatter(vecs_list)

#csvを読み込んでdataframeを作成する
def make_df():
    df = pd.read_csv("箱根湯本温泉_ホテル南風荘.csv", index_col=0)
    #不要な列の削除
    df = df.drop(["見出し","本文"], axis=1)
    #未回答を中間値の3に置換する
    df = df.replace("-","3")
    return df

#評価尺度の散布図を作成する
def make_item_info(model_svd, df):
    X_comp,Y_comp = model_svd.components_

    plt.figure(figsize=(10,10))
    sns.set(font="MS Gothic")
    sns.scatterplot(x=X_comp,y=Y_comp)

    for i,(annot_x,annot_y) in enumerate(zip(X_comp,Y_comp)):
        plt.annotate(df.columns[i],{annot_x,annot_y})

    plt.show()

#2次元に変換したデータで散布図を作成する
def make_scatter(vecs_list):
    X = vecs_list[:,0]
    Y = vecs_list[:,1]

    plt.figure(figsize=(10,10))
    sns.set()
    sns.scatterplot(x=X,y=Y)
    plt.show()    

if __name__ == "__main__":
    main()
```

# 解説

## 特異値分解を行う
```python
model_svd = TruncatedSVD(n_components=2)
vecs_list = model_svd.fit_transform(df)
```
- `TruncatedSVD(n_components=n)`
n次元のモデルを作成
- `model_svd.fit_transform(df)`
指定したデータに特異値分解を行い、結果を取得

何次元のデータに変換するかを`TruncatedSVD(n_components=n)`で指定し、`model_svd.fit_transform(df)`で指定した次元に変換したデータを取得します。

## 寄与率を算出する
```python
model_svd.explained_variance_ratio_
```
寄与率とは、次元削減を実行後、情報がどれくらい残っているかを示す割合です。明確な基準はありませんが、算出した数値の合計（累積寄与率）が60％より低い場合は、分析対象を再検討する方が良いそうです。

## 評価尺度のグラフを作成する
```python
def make_item_info(model_svd, df):
    X_comp,Y_comp = model_svd.components_

    plt.figure(figsize=(10,10))
    sns.set(font="MS Gothic")
    sns.scatterplot(x=X_comp,y=Y_comp)

    for i,(annot_x,annot_y) in enumerate(zip(X_comp,Y_comp)):
        plt.annotate(df.columns[i],{annot_x,annot_y})

    plt.show()
```
- `seaborn.scatterplot(x,y)`
散布図を作成
- `for i, (a, b, ...) in enumerate(zip( ... ))`
複数のリストの要素とインデックスを同時に取得するループ
- `pyplot.annotate(表示する文字,{x座標,y座標})`
指定した座標に注釈を表示

どの項目が高いと座標が上にくるのか、どの項目が高いと座標が下にくるのかが分かるグラフを作成します。

# 実行結果

## 寄与率
```
[0.48859845 0.18190892]
```
第1成分が0.48859845、第2成分が0.18190892、累積寄与率が0.67050737となりました。情報が約67％残っており、60％以上となっているため、使用データに問題はなさそうです。

## 評価尺度のグラフ
<img width="800" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/ac0257f3-ac45-c8fe-935c-79df9551b99b.png>
散布図から「料理（朝食）」、「料理（夕食）」の数値が高いと座標が上の方に配置され、「部屋」、「清潔感」、「風呂」の数値が高いと座標が下の方に配置されることが分かります。つまり、食事の満足度の方が高い人は上に、施設の満足度の方が高い人は下に配置されるということになります。

## データを2次元に変換した際の散布図
<img width="800" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/8d24fed0-0098-c52e-48c3-25b5c2a638b9.png>
データを2次元に変換し、散布図で表示することができました。上下の偏りがあまりなく、満遍なく座標のある図となりました。なお、横軸は評価値合計の高さ（合計値が高いほど右に位置する）を表します。

# 終わりに
今回は、ホテルの評価値データを特異値分解し、次元削減を行いました。
次元削減は特異値分解だけではなく、様々な手法があるため、別の手法も試して比較してみるのも面白そうだなと思いました。
次回は、同じデータを使ってクラスタリングを試してみる予定です。

# 参考
この記事は以下の動画を参考にして執筆しました。
[Pythonで次元削減（次元圧縮）してみよう【Python機械学習#1】](https://www.youtube.com/watch?v=UrER2rADxH4)