エンジニアだけでなく経営者・コンサル・営業・企画・人事・HRの方にも読んでほしい「"まず可視化”をやめてみた」変数スクリーニング→式の構造化→可視化の順で見えてきた意思決定支援。タイタニック号乗船者リストの場合

# 1. はじめに：なぜ“まず可視化”をやめてみたのか

データ分析は意思決定に不可欠な手段であり、その一環として「まずグラフにしてみる」アプローチは一般的です。しかし、私はそこにある種の限界と違和感を覚えていました。

たとえば、扱う変数が数個であれば問題になりませんが、数十、数百、あるいは数千に及ぶ場合、それらを片っ端からグラフ化して目視確認するのは現実的ではありません。  
それだけでなく、認知バイアスによって重要な変数を見落としたり、無意味な変数を“意味がありそう”と誤解してしまう危険性もあると考えました。

そこで今回は、従来とは逆の手順を試みました。まず統計的手法で目的変数に強く影響する説明変数を抽出し、その関係性を数式レベルで読み解いたうえで、最後に可視化する――という順番です。

“グラフから考える”のではなく、“構造を掴んでから見せる”。  
この方法が、意思決定においてより本質的な材料提供になり得るのではないかと考えたのです。

# 2.この方式はどこで役立つか？活用のユースケース

本記事では、第5章で決定木や可視化手法の例としてよく使われる「タイタニック号乗船者リスト」を用いてデモンストレーションを行います。  
ただし、この手法は実務においてもさまざまな場面で応用可能であると考えています。

具体的には以下のようなユースケースが挙げられます：

- 契約の有無を目的変数とした **顧客リスト分析（クロージング分析）**
- 退職の有無を目的変数とした **従業員リスト分析（離職要因の特定）**
- 採用・昇進などの判定ロジックを明示化する **人事領域の意思決定支援**
- 問い合わせ対応履歴から **クレームや離反につながる要因の抽出**

これらの場面では、単に可視化を並べるだけでは見えにくかった“構造的な違い”を、  
本アプローチにより **見落としなく把握し、かつ他者に伝えることができる**可能性があります。

# 3. 注意事項と免責について

:::note alert
本記事で紹介しているロジックやアイデアをもとに実装・運用される場合は、必ずご自身の判断・責任にてお願いいたします。  
記事内容を参考にされた結果として、経済的損失やシステム不具合、その他いかなる損害が生じた場合も、筆者は一切の責任を負いかねます。あらかじめご了承ください。
:::

4. ロジックの説明
```python:DialogFile.py（ファイル選択ダイアログ）

from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QCheckBox, QFileDialog

class DialogFile(QWidget):

    def __init__(self):
        super().__init__()

        self.file = ""
        self.showPp = False

        self.setWindowTitle("select data file")
        
        layout = QVBoxLayout()
        self.setLayout(layout)

        btn_file = QPushButton("select data file", self)
        layout.addWidget(btn_file)
        btn_file.clicked.connect(self.onClickFile)

        self.lbl = QLabel("", self)
        layout.addWidget(self.lbl)

        chk_pairPlot = QCheckBox("show PairPlot", self)
        layout.addWidget(chk_pairPlot)
        chk_pairPlot.setChecked(self.showPp)
        chk_pairPlot.stateChanged.connect(self.onStateChanged)

        btn_ok = QPushButton("OK", self)
        layout.addWidget(btn_ok)
        btn_ok.clicked.connect(self.onOkClick)

    def onOkClick(self):

        self.close()

    def onClickFile(self):

        file,check = QFileDialog.getOpenFileName(None,"select file","","All Files (*);;csv Files (*.csv);;Text Files (*.txt)")
        if check:
            self.lbl.setText(file)
            self.file = file

    def onStateChanged(self, state):
        if state == 2:
            self.showPp = True
        else:
            self.showPp = False

```

```python:DialogParam.py（属性選択ダイアログ）

from PyQt6.QtWidgets import QScrollArea, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QButtonGroup, QRadioButton, QLineEdit, QPushButton

class DialogParam(QScrollArea):

    def __init__(self, col_list : list):
        super().__init__()
        self.setWindowTitle("parameter")
        self.setWidgetResizable(True)
        inner = QWidget()
        base_layout = QVBoxLayout()

        self.desc_var = []
        self.trg_var = ""
        self.bg_list = []

        for col in col_list:
            now_layout = QHBoxLayout()
            base_layout.addLayout(now_layout)

            now_layout.addWidget(QLabel(col['name']))
            now_layout.addWidget(QLabel(str(col['min'])))
            now_layout.addWidget(QLabel(str(col['max'])))

            bg = QButtonGroup(now_layout)

            _not = QRadioButton("not use")
            now_layout.addWidget(_not)
            bg.addButton(_not)

            _desc = QRadioButton("descript")
            now_layout.addWidget(_desc)
            bg.addButton(_desc)

            _trg = QRadioButton("target")
            now_layout.addWidget(_trg)
            bg.addButton(_trg)

            self.bg_list.append({'name' : col['name'], 'radio' : [_not, _desc, _trg]})

        btn = QPushButton("OK", self)
        base_layout.addWidget(btn)
        btn.clicked.connect(self.onOkClick)

        inner.setLayout(base_layout)
        self.setWidget(inner)

    def onOkClick(self):

        for col in self.bg_list:
            if col['radio'][1].isChecked():
                self.desc_var.append(col['name'])
            elif col['radio'][2].isChecked():
                self.trg_var = col['name']

        self.close()

```

```python:Main.py（メインルーチン）

1.PyPlotの日本語化対応
2.ファイル選択ダイアログの表示
3.ファイルリード
4.ファイル取得データをダミー変数化
5.ファイル取得データから欠損値と外れ値の除外
6.ペアプロットの表示
7.属性選択ダイアログの表示
8.ファイル取得データからbool値を0,1に変換
9.olsでで選択した属性ごとのP値を取得
10.RandomForestRegressorで選択した属性ごとの重要度を取得
11.9と10のリストを結合し、重要度の降順でソート
12.11のリストをP値と重要度でフィルタしたリストを取得
13.12の結果で式を示す文字列を作成
14.PyPlotとstreamlitでグラフを生成

```

# 5. タイタニック号乗船者リストによるデモンストレーション

本章では、説明変数のスクリーニング → 式の構造化 → 意味のある色分けと可視化、という一連の流れを、よく知られた「タイタニック号の乗船者データ」を用いて実演します。

このデータセットは乗客の年齢・性別・階級・乗船地などが記録されており、生存者（契約有りに相当）と非生存者（契約なしに相当）という明確な目的変数を含むため、今回の可視化フレームの動作確認に適しています。

#### (1)ファイル選択ダイアログ

![スクリーンショット 2025-06-22 161221.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/40ecdb8e-6e2f-4a81-8a8c-70d5df38c6b9.png)

データ件数が多い、属性が多い場合にshowPairPlotをチェックするとグラフの表示に時間が掛ります。

#### (2)属性選択ダイアログ
![スクリーンショット 2025-06-22 161650.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/73e5c5b5-7794-44a2-8bea-bf5816057dbd.png)

目的は生存するかの条件を把握することなので、servivedにtargetを選択し、年齢、性別、客室などはdescriptを選択します。
確実に無関係な属性は未選択かnot useを選択することと、影響しているかしてないか判断し難い場合、descriptを選択します。

#### (3)統計計算結果のダンプ

```
            keisuu  sum_sq   df        F  PR(>F)  impotance
Sex_male    -0.232  34.870  1.0  239.246   0.000   0.307968
Age         -0.093   4.655  1.0   31.936   0.000   0.247016
Fare         0.010   0.046  1.0    0.318   0.573   0.239703
Pclass      -0.157   9.789  1.0   67.164   0.000   0.103970
SibSp       -0.056   1.236  1.0    8.478   0.004   0.047562
Parch       -0.009   0.047  1.0    0.325   0.569   0.025015
Embarked_S  -0.030   0.428  1.0    2.939   0.087   0.021537
Embarked_Q  -0.028   0.216  1.0    1.479   0.224   0.007230
Intercept    0.385     NaN  NaN      NaN     NaN        NaN

```

Intercept行のkeisuuはバイアスです。Sex_male～Embarked_Qまでが説明の変数でkeisuu列が重み、PR(>F)列をP値、importance列を重要度して扱ってます。

#### (4)生還した条件を整理した式

![スクリーンショット 2025-06-22 162909.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/ebf5b687-7fda-4951-af66-5bd2e264b956.png)

女性で若くて1等客室に近いほど生存率が高いという特徴となります。
レディファーストな風潮と避難ボートのある甲板に近い客室ほど移動しやすく生存しやすい状況と推測すると、大外れでない推測と思えます。

#### (5)ブラウザへのグラフの表示

![スクリーンショット 2025-06-22 163328.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/4b1b06c5-665d-404c-87d3-ca58e567aa3f.png)

女性の生存率が高いことを示すグラフです。

![スクリーンショット 2025-06-22 163432.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/753d63aa-7a9d-4132-95b0-4ba45597386b.png)

若い人の生存率が高いことを示すグラフです。

![スクリーンショット 2025-06-22 163512.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3989768/34c40924-5ee1-4bfa-8045-2ebaeebab296.png)

一等客室に近い人の生存率が高いことを示すグラフです。

# 6. 最後に

ここまでお読み頂きありがとうございました。
この小さな試みが、どこかの意思決定を後押ししたり、誰かの迷いを減らす助けになるなら、本当に書いてよかったと思います。

以上