TableauとTabPyことはじめ~機械学習をしてみる~

## 目的
DataSaberになるための奮闘記です。
この記事は、3つの記事からなるシリーズ2つ目の記事となります。
1. TabPy とTableauを接続してみる
    * https://qiita.com/s_rokuemon/items/b837aef0682048644a9f
1. TabPyとTableauで機械学習してみる ← これ！
1. TabPyをDockernizeしてみる
    * https://qiita.com/s_rokuemon/items/f2b2df3ecdd09fffe9d1

### 動作環境
**Tableau Desktopバージョン**
2021.3.14 (20213.22.0707.1757) 64 ビット

**データソース**
タイタニック
https://www.kaggle.com/c/titanic/data

``` sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:        20.04
Codename:       focal

$ python -V
Python 3.8.10

$ pip3 show tabpy
Name: tabpy
Version: 2.12.0

$ pip3 show tabpy-client
Name: tabpy-client
Version: 0.2
```

## TabPyサーバの立上げと機械学習モデルのデプロイ
今回は、Tabpyサーバに事前に関数を定義し、TabpyServerにデプロイする方法を試してみます。

### 1. TabPyサーバの起動
前回同様、ローカルのポート9004でTabPyサーバを起動させます。
``` sh
$ tabpy
...
2024-09-28,11:00:42 [INFO] (app.py:app:167): Web service listening on port 9004
```

### 2. カスタム関数のデプロイ
TabPyサーバが起動中に、例として次のようなファイルを実行するとTabPyサーバに関数がデプロイされます。
ロジスティック回帰分析で生存者を予測するモデルを作成するプログラムになります。

``` py
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
import tabpy_client

# tabpy client
client = tabpy_client.Client('http://localhost:9004/') 

def survived_prediction_model(_arg1,_arg2,_arg3,_arg4,_arg5):
    # 引数をDataFrameに格納
    df_titanic = pd.DataFrame(
        {
            "survived":_arg1
            , "sex": _arg2
            , "age":_arg3
            , "fare":_arg4
            , "table_nm":_arg5         
        }
    )

    # 学習用データとテストデータに分割
    train = df_titanic[df_titanic["table_nm"]=="train.csv"]
    test = df_titanic[df_titanic["table_nm"]=="test.csv"]

    # 欠損値補完
    inpute_age = train["age"].median()
    inpute_fare = train["fare"].median()
    train["age"].fillna(inpute_age, inplace=True)
    train["fare"].fillna(inpute_fare, inplace=True)
    test["age"].fillna(inpute_age, inplace=True)
    test["fare"].fillna(inpute_fare, inplace=True)

    # ダミー変換(sex)
    train = pd.get_dummies(train, drop_first=True, columns=["sex"])
    test = pd.get_dummies(test, drop_first=True, columns=["sex"])

    # ターゲット変数
    train_X = train.drop(["survived","table_nm"], axis=1)
    train_y = train["survived"]
    test_X = test.drop(["survived","table_nm"], axis=1)
    # Tableua側でモデル出力結果の正誤判定を行うため予測時には学習用特徴量を利用する
    X = pd.concat([train_X], axis=0)

    # 学習
    LR = LogisticRegression()
    LR.fit(train_X, train_y)
    rs = LR.predict(X)
    return rs.tolist()

# 関数のデプロイ
client.deploy(
    'survived_prediction_model'
    , survived_prediction_model
    , 'LogisticRegression'
    , override=True
)
```

コンソールログから関数が無事デプロイできていることがわかります。
``` sh
2024-09-30,17:33:50 [INFO] (python_service.py:python_service:89): Loading object:, URI=survived_prediction_model, URL=/home/XXXXXX/tabpy/tabpy_server/query_objects/survived_prediction_model/1, version=1, is_updated=False
2024-09-30,17:33:50 [INFO] (query_object.py:query_object:80): Loaded query object "CustomQueryObject" successfully
```

### 3. Tableauからデプロイ済みの関数を利用する
``` python
SCRIPT_STR("
return tabpy.query('survived_prediction_model', _arg1, _arg2,_arg3,_arg4,_arg5)['message']
"
, MAX([Survived]),MAX([Sex]),SUM([Age]),SUM([Fare]),MAX([table_name])
)
```

### 4. モデルの出力結果の正誤判定を行う
計算フィールドを作成し、モデルの出力結果の正誤判定を行ってみます。
``` 
if STR(max([Survived]))!= [Prediction] then '失敗'
else '成功'
end
```

## 結果
オレンジ色であれば予測結果が正しく、青色であれば予測結果が誤りだったケースだとわかります。
誤判定が多いですね... 
モデルがどのような誤り方をしているのか混同行列で確認したほうがよさそう。
![{9BEBED4A-E8F6-4854-B657-2FF292512BF5}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833399/38e13719-8052-6798-a424-0a64593446f7.png)


## スペシャルサンクス
本記事を執筆するにあたり次のサイトを参考にさせていただきました！ありがとうございました。
https://blog.flinters-base.co.jp/entry/2023/09/25/120000
https://lovedata.main.jp/2018/12/09/tabpy-client-basic/
https://www.uncovertruth.co.jp/dx-accelerator/blog/articles/tableau/064/
https://qiita.com/yolo_kiyoshi/items/fbf9d10e1edcef0c0e21