Streamlit × Plotly で学ぶ：折れ線・密度・散布図を簡単に表示する方法

# **user**:

初学者向けにVSCodeを使ってStreamlitアプリを作るガイドを書いてください。そのアプリは、次のような動作をします。
・ユーザにcsvファイルまたはエクセルファイルの入力を促します。 ・エクセルファイルの場合は、どのシートを見たいかユーザに確認します。
・ユーザは、可視化の方法として、折れ線グラフ、密度プロット、散布図のいずれかから選択できる。
・折れ線グラフを選んだ場合、ユーザは１つ以上の列からデータを選択して折れ線グラフとして可視化できる。選んだ複数の列のスケールが異なる場合は、スケールを合わせることもできる。
・密度プロットを選んだ場合、ユーザは１つ以上の列からデータを選択して密度プロットとして可視化できる。
・散布図を選んだ場合、ユーザは２つの列からデータを選択して密度プロットとして可視化できる。

# **assistant**:

以下は、**初学者向けに VSCode を使って Streamlit アプリを作るガイド**です。このガイドでは、CSV または Excel ファイルの読み込み、インタラクティブなシート選択、列の選択、グラフの種類選択といった基本的な Streamlit の操作方法を学びながらアプリを作成します。

---

## 🔰初学者向け：Streamlit でグラフ可視化アプリを作ろう（VSCode 編）

### 🎯目標
このチュートリアルの最終目標は、以下の機能を持つ Streamlit アプリを VSCode 上で作成することです：

- CSVまたはExcelファイルの読み込み
- Excelファイルならシート選択
- 折れ線グラフ・密度プロット・散布図を選んで可視化
- 複数列対応・スケール補正（折れ線グラフ）

---

## 1. 📦準備：必要なライブラリのインストール

VSCode でターミナルを開き、次のコマンドを実行します：

```bash
pip install streamlit pandas matplotlib seaborn openpyxl
```

---

## 2. 📝Python スクリプトの作成

VSCode で `visualizer_app.py` というファイルを作成し、以下のコードを貼り付けます：

```python
import streamlit as st
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from io import BytesIO

st.set_page_config(page_title="グラフ可視化アプリ", layout="wide")

st.title("📊 データ可視化アプリ")
st.markdown("CSV または Excel ファイルをアップロードし、可視化方法を選んでください。")

# ファイルアップロード
uploaded_file = st.file_uploader("CSVまたはExcelファイルをアップロード", type=["csv", "xlsx"])

if uploaded_file:
    # Excelシート選択
    if uploaded_file.name.endswith(".xlsx"):
        xls = pd.ExcelFile(uploaded_file)
        sheet_name = st.selectbox("シートを選択してください", xls.sheet_names)
        df = pd.read_excel(xls, sheet_name=sheet_name)
    else:
        df = pd.read_csv(uploaded_file)

    st.subheader("データのプレビュー")
    st.dataframe(df.head())

    # グラフの種類を選ぶ
    plot_type = st.selectbox("可視化の種類を選んでください", ["折れ線グラフ", "密度プロット", "散布図"])

    if plot_type == "折れ線グラフ":
        numeric_cols = df.select_dtypes(include='number').columns.tolist()
        selected_cols = st.multiselect("表示する列を選んでください", numeric_cols)

        if selected_cols:
            normalize = st.checkbox("スケールを合わせる（0〜1に正規化）")
            plot_df = df[selected_cols]

            if normalize:
                plot_df = (plot_df - plot_df.min()) / (plot_df.max() - plot_df.min())

            st.subheader("📈 折れ線グラフ")
            st.line_chart(plot_df)

    elif plot_type == "密度プロット":
        numeric_cols = df.select_dtypes(include='number').columns.tolist()
        selected_cols = st.multiselect("表示する列を選んでください", numeric_cols)

        if selected_cols:
            st.subheader("📊 密度プロット")
            fig, ax = plt.subplots()
            for col in selected_cols:
                sns.kdeplot(df[col].dropna(), label=col, ax=ax)
            ax.legend()
            st.pyplot(fig)

    elif plot_type == "散布図":
        numeric_cols = df.select_dtypes(include='number').columns.tolist()
        x_col = st.selectbox("X軸に使う列", numeric_cols)
        y_col = st.selectbox("Y軸に使う列", [col for col in numeric_cols if col != x_col])

        if x_col and y_col:
            st.subheader("📉 散布図")
            fig, ax = plt.subplots()
            sns.scatterplot(data=df, x=x_col, y=y_col, ax=ax)
            st.pyplot(fig)
```

---

## 3. 🚀Streamlit アプリの起動方法

VSCode のターミナルで以下のコマンドを実行します：

```bash
streamlit run visualizer_app.py
```

ブラウザが開いてアプリが表示されます。もし開かれない場合は、表示された URL（例：http://localhost:8501）を手動で開いてください。

---

## ✅補足ポイント

- Excelファイルの読み込みには `openpyxl` が必要です（上でインストール済）。
- グラフ描画には `matplotlib` と `seaborn` を使っています。
- 正規化は `(x - min) / (max - min)` の簡易スケーリングです。
- Streamlit の `line_chart` は軽量な折れ線グラフ表示用です。高機能なグラフを作りたい場合は `matplotlib` や `plotly` に切り替えると良いでしょう。

---

## 🌟次のステップ（応用編に向けて）

- Plotly を使ったインタラクティブなグラフへの拡張
- 時系列やカテゴリ変数の処理への対応
- ファイルのダウンロード・保存機能の追加

---

このガイドを使って、初めての可視化アプリを楽しく作ってみてください！  
他にも Streamlit の機能を知りたい場合はお知らせくださいね。

作成したアプリに他の機能も加えてみたいですか？

# **user**:

ありがとうございます。散布図を選択した場合、２変数の相関係数も表示するようにしてください。また、Plotly を使ったインタラクティブなグラフへの拡張もお願いします。

# **assistant**:

承知しました！以下では、**Plotly によるインタラクティブグラフ**を導入し、**散布図選択時に相関係数（ピアソン相関）を表示する機能**を追加した改良版コードをご提供します。

---

## ✅ 機能の概要（更新後）

- CSV または Excel ファイルの読み込み（Excel はシート選択付き）
- 可視化方法の選択（折れ線グラフ・密度プロット・散布図）
- 折れ線グラフ：複数列選択＋スケール補正オプション
- 密度プロット：複数列選択対応
- 散布図：2変数選択、相関係数の表示、Plotly でインタラクティブ描画
- すべてのグラフに Plotly を使用（インタラクティブ）

---

## 📦 追加インストールが必要なライブラリ

以下をターミナルでインストールしてください：

```bash
pip install plotly
```

---

## 📝 完全な Python コード（`visualizer_app.py`）

```python
import streamlit as st
import pandas as pd
import plotly.express as px
import seaborn as sns

st.set_page_config(page_title="データ可視化アプリ", layout="wide")

st.title("📊 データ可視化アプリ")
st.markdown("CSV または Excel ファイルをアップロードし、可視化方法を選択してください。")

# ファイルアップロード
uploaded_file = st.file_uploader("CSV または Excel ファイルをアップロード", type=["csv", "xlsx"])

df = None

if uploaded_file:
    file_type = uploaded_file.name.split('.')[-1]
    
    if file_type == "csv":
        df = pd.read_csv(uploaded_file)
    elif file_type == "xlsx":
        sheet_names = pd.ExcelFile(uploaded_file).sheet_names
        sheet_name = st.selectbox("📄 シートを選択してください", sheet_names)
        df = pd.read_excel(uploaded_file, sheet_name=sheet_name)

if df is not None:
    st.subheader("🔍 データプレビュー")
    st.dataframe(df.head())

    numeric_cols = df.select_dtypes(include='number').columns.tolist()

    chart_type = st.selectbox("📈 表示するグラフの種類を選んでください", ["折れ線グラフ", "密度プロット", "散布図"])

    if chart_type == "折れ線グラフ":
        selected_cols = st.multiselect("表示する列を選んでください（数値列のみ）", numeric_cols)
        if selected_cols:
            normalize = st.checkbox("スケールを正規化（0〜1）する")
            plot_df = df[selected_cols].copy()
            if normalize:
                plot_df = (plot_df - plot_df.min()) / (plot_df.max() - plot_df.min())
            fig = px.line(plot_df, labels={"index": "Index", "value": "値", "variable": "列"})
            st.plotly_chart(fig, use_container_width=True)

    elif chart_type == "密度プロット":
        selected_cols = st.multiselect("表示する列を選んでください（数値列のみ）", numeric_cols)
        if selected_cols:
            fig = px.histogram(df, x=selected_cols[0], marginal="violin", nbins=50, opacity=0.7)
            for col in selected_cols[1:]:
                fig.add_trace(px.histogram(df, x=col, marginal="violin", nbins=50, opacity=0.7).data[0])
            st.plotly_chart(fig, use_container_width=True)

    elif chart_type == "散布図":
        col_x = st.selectbox("X軸の列を選択してください", numeric_cols)
        col_y = st.selectbox("Y軸の列を選択してください", [c for c in numeric_cols if c != col_x])
        if col_x and col_y:
            st.write(f"📈 **{col_x} と {col_y} の相関係数（Pearson）**: `{df[col_x].corr(df[col_y]):.3f}`")
            fig = px.scatter(df, x=col_x, y=col_y, trendline="ols", labels={col_x: col_x, col_y: col_y})
            st.plotly_chart(fig, use_container_width=True)
```

---

## ▶ 実行方法（VSCodeターミナル）

```bash
streamlit run visualizer_app.py
```

---

## 🧪 補足とアドバイス

- `plotly.express` はインタラクティブ性が高く、グラフをズーム・パン・保存できます。
- Excel ファイルを扱うには `openpyxl` が必要です（既にインストール済みなら OK）。

---

ご希望があれば、このアプリの「保存機能」や「複数シートへのエクスポート」なども追加可能です。さらに拡張してみたい機能はありますか？

# **user**:

ありがとうございます。だいたいOKですが、密度プロットで複数の列を選択した時、１つの列しか可視化できてなくないですか？それとも、ひょっとして、選択した二つの列を足し合わせたりしちゃってます？

# **assistant**:

ご指摘ありがとうございます。おっしゃる通り、**密度プロットの部分**では、`plotly.express.histogram` を使って複数列を表示しようとした際、**最初の列しか正しく表示されておらず、他の列が上書きや無視されている状態**になっています。

また、Plotly の `px.histogram` に複数列を直接渡すと、それらを**結合して1つの系列として扱うような挙動（結合・連結）**になってしまうことがあり、「足し合わせて」はいませんが、**意図と異なる表示**になります。

---

## ✅ 正しい対応：列ごとに個別のヒストグラムを追加

以下のように、**選択された各列に対して個別にヒストグラム（密度プロット）を描画し、1つのグラフにまとめる**のが望ましい方法です。

### 🔧 修正コード（密度プロットの部分だけ抜粋）

```python
    elif chart_type == "密度プロット":
        selected_cols = st.multiselect("表示する列を選んでください（数値列のみ）", numeric_cols)
        if selected_cols:
            import plotly.graph_objs as go
            fig = go.Figure()
            for col in selected_cols:
                fig.add_trace(go.Histogram(
                    x=df[col],
                    histnorm='probability density',
                    name=col,
                    opacity=0.6
                ))
            fig.update_layout(
                barmode='overlay',
                title="密度プロット",
                xaxis_title="値",
                yaxis_title="確率密度",
            )
            st.plotly_chart(fig, use_container_width=True)
```

### 💡ポイント

- `go.Histogram` で列ごとにヒストグラムを個別追加。
- `histnorm='probability density'` で**密度プロット**として解釈。
- `barmode='overlay'` でヒストグラムを**重ねて**表示（透過あり）。
- `opacity=0.6` により重なりが見やすくなります。

---

この修正を入れれば、**複数列の密度プロットを正確に可視化**できるようになります。

必要であれば、**KDE（カーネル密度推定）曲線**を追加することも可能です。ご希望ですか？

# **user**:

はい、KDE（カーネル密度推定）曲線を追加してください。

# **assistant**:

承知しました！密度プロットに **KDE（カーネル密度推定）曲線** を追加する方法をご説明します。Plotly は直接 KDE 曲線を描く機能を持っていないため、**`seaborn` で KDE を計算し、Plotly に渡す**ことで対応します。

---

## ✅ 修正ポイント

- `seaborn.kdeplot` を使って **各列の KDE（x, y）データを抽出**
- Plotly の `go.Scatter` を使ってラインプロットとして追加

---

## 🧪 修正後の密度プロットコード（KDE 曲線付き）

```python
    elif chart_type == "密度プロット":
        selected_cols = st.multiselect("表示する列を選んでください（数値列のみ）", numeric_cols)
        if selected_cols:
            import plotly.graph_objs as go
            import numpy as np
            import seaborn as sns

            fig = go.Figure()

            for col in selected_cols:
                # ヒストグラム（確率密度）
                fig.add_trace(go.Histogram(
                    x=df[col],
                    histnorm='probability density',
                    name=f"{col}（ヒストグラム）",
                    opacity=0.4
                ))

                # KDE曲線の計算（seaborn内部でKDE評価してx, yを取得）
                kde = sns.kdeplot(df[col].dropna(), bw_adjust=1).get_lines()[0]
                x_kde, y_kde = kde.get_data()
                plt.close()  # matplotlib の余分なプロットを閉じる

                # KDE 曲線（Plotly）
                fig.add_trace(go.Scatter(
                    x=x_kde,
                    y=y_kde,
                    mode='lines',
                    name=f"{col}（KDE）"
                ))

            fig.update_layout(
                barmode='overlay',
                title="密度プロット（KDE付き）",
                xaxis_title="値",
                yaxis_title="確率密度"
            )

            st.plotly_chart(fig, use_container_width=True)
```

---

## 📌 注意点

- `seaborn.kdeplot(...).get_lines()[0].get_data()` を使って KDE のデータを取り出します。
- `plt.close()` を呼んでおかないと、Streamlit 実行時に matplotlib の空のグラフが出てしまうことがあります。
- KDE 計算は `NaN` を含むとエラーになるので `.dropna()` を忘れずに。

---

## 🔄 結果

この修正により、**各列のヒストグラムと KDE 曲線が同じグラフ上に重ねて表示される**ようになります。

---

ご希望であれば、KDE の滑らかさ（`bw_adjust`）をユーザが GUI で調整できるようにもできますが、追加しましょうか？

# **user**:

ありがとうございます。正規化の部分は今、Min-Max正規化だけになってますが、平均値を引いて標準偏差で割るような標準化とか、もうひとつ何かありませんでしたっけ？追加してください。密度グラフの横軸に関しても、同様の正規化・標準化ができるようにしてください。

# **assistant**:

とても良い視点です！

### ✅ ご質問の整理
1. **正規化方法を増やしたい**
   - 今は「Min-Max正規化（0～1）」のみ実装済み。
   - 追加すべきもの：
     - **Zスコア標準化**（平均0, 標準偏差1）
     - **もう一つ何か？** → よく使われる選択肢として以下が挙げられます：
       - **最大絶対値スケーリング（MaxAbs）**：全データを最大値で割る（対称スケーリング、外れ値の影響にやや強い）

2. **折れ線グラフにこれらの選択肢を追加**
3. **密度プロット（KDE）にも同様の変換を適用**

---

## ✅ 正規化・標準化の選択肢（UI付き）

Streamlitで以下のような選択肢を提供します：

```python
scale_option = st.selectbox("スケーリング方法を選択してください", ["なし", "Min-Max 正規化", "Zスコア標準化", "最大絶対値スケーリング"])
```

---

## ✅ データ変換関数

```python
def scale_data(df, method):
    if method == "Min-Max 正規化":
        return (df - df.min()) / (df.max() - df.min())
    elif method == "Zスコア標準化":
        return (df - df.mean()) / df.std()
    elif method == "最大絶対値スケーリング":
        return df / df.abs().max()
    else:
        return df
```

---

## ✅ 折れ線グラフ部分（修正版）

```python
elif chart_type == "折れ線グラフ":
    selected_cols = st.multiselect("表示する列を選んでください（数値列のみ）", numeric_cols)
    if selected_cols:
        scale_option = st.selectbox("スケーリング方法を選択してください", ["なし", "Min-Max 正規化", "Zスコア標準化", "最大絶対値スケーリング"])
        plot_df = df[selected_cols].copy()
        plot_df = scale_data(plot_df, scale_option)
        fig = px.line(plot_df, labels={"index": "Index", "value": "値", "variable": "列"})
        st.plotly_chart(fig, use_container_width=True)
```

---

## ✅ 密度プロット部分（KDE付き、スケーリング対応）

```python
elif chart_type == "密度プロット":
    selected_cols = st.multiselect("表示する列を選んでください（数値列のみ）", numeric_cols)
    if selected_cols:
        scale_option = st.selectbox("スケーリング方法を選択してください", ["なし", "Min-Max 正規化", "Zスコア標準化", "最大絶対値スケーリング"])
        scaled_df = scale_data(df[selected_cols], scale_option)

        import plotly.graph_objs as go
        import numpy as np
        import seaborn as sns
        import matplotlib.pyplot as plt

        fig = go.Figure()

        for col in selected_cols:
            # ヒストグラム（確率密度）
            fig.add_trace(go.Histogram(
                x=scaled_df[col],
                histnorm='probability density',
                name=f"{col}（ヒストグラム）",
                opacity=0.4
            ))

            # KDE 曲線（Seabornで評価）
            kde_plot = sns.kdeplot(scaled_df[col].dropna(), bw_adjust=1)
            x_kde, y_kde = kde_plot.get_lines()[0].get_data()
            plt.close()

            fig.add_trace(go.Scatter(
                x=x_kde,
                y=y_kde,
                mode='lines',
                name=f"{col}（KDE）"
            ))

        fig.update_layout(
            barmode='overlay',
            title="密度プロット（KDE付き）",
            xaxis_title="スケーリング済みの値" if scale_option != "なし" else "値",
            yaxis_title="確率密度"
        )

        st.plotly_chart(fig, use_container_width=True)
```

---

## ✅ 今後の拡張案（必要なら）

- KDE の **`bw_adjust`（帯域幅）をUIで選べるようにする**
- 各スケーリング後の値範囲を表として表示
- スケーリング前後の分布比較機能

---

この追加で、**折れ線グラフ・密度プロットともに、柔軟なスケーリングが可能**になります。他に追加したいグラフや前処理はありますか？

# **user**:

ありがとうございます。スケーリングした値を、後続の処理で使えるようにできますか？

# **assistant**:

はい、**スケーリングしたデータを後続の処理に使えるようにすることは可能です**。Streamlitでは、状態（データ）を保持するには以下のような方法がよく使われます：

---

## ✅ 方法：`st.session_state` に保存する

```python
scaled_df = scale_data(df[selected_cols], scale_option)
st.session_state["scaled_df"] = scaled_df
```

これにより、後続の処理（別のチャートや分析機能）で `st.session_state["scaled_df"]` を使うことができます。

---

## ✅ 応用例：他の処理で使用

```python
if "scaled_df" in st.session_state:
    st.write("スケーリング後のデータ（先頭5行）:")
    st.dataframe(st.session_state["scaled_df"].head())
```

---

## ✅ 代替方法：`@st.cache_data`（計算コストが高い場合）

もしスケーリング処理が重たい場合は、Streamlit の `@st.cache_data` で関数をキャッシュするのも有効です：

```python
@st.cache_data
def scale_data_cached(df, method):
    ...
```

---

## ✅ 追加で便利なUI案

スケーリング済みかどうかを明示的に選べるセレクトボックスを、他のチャートUIにも導入できます：

```python
use_scaled = st.checkbox("スケーリング済みのデータを使う", value=True)

plot_data = st.session_state["scaled_df"] if use_scaled and "scaled_df" in st.session_state else df[selected_cols]
```

---

## ✅ まとめ

- スケーリング後のデータは `st.session_state` に保存すれば、どこからでも使えます。
- スケーリング有無を選ばせるチェックボックスと組み合わせると柔軟に制御できます。

---

他にも「スケーリング前後を比較したい」「スケーリング後に因果推論に使いたい」など、具体的な後続処理があれば、それに合わせた構成もご提案できます。何か使い道は決まっていますか？

# **user**:

いえ、今回はこのくらいでOKです。ありがとうございます。この会話をブログに残したいと思いますので、タイトル案をいくつか提案していただけますか？

# **assistant**:

それは素晴らしいですね！今回の内容（VSCode＋Streamlitで、CSV/Excelを読み込み、スケーリング＋可視化）に基づいて、以下のようなブログタイトル案をいくつかご提案します：

---

### 📝 タイトル案

1. **「初心者向け：VSCodeとStreamlitで始めるインタラクティブデータ可視化アプリ」**
2. **「CSVやExcelを読み込んで可視化！Streamlitアプリ開発入門」**
3. **「Streamlitで作る！スケーリング対応・Plotly連携のデータ可視化アプリ」**
4. **「Streamlit×Plotlyで学ぶ：折れ線・密度・散布図を簡単に表示する方法」**
5. **「データの読み込みから可視化まで！初心者でもできるStreamlitアプリ開発」**
6. **「標準化・正規化も自由自在！Pythonで作る実用的データ可視化ツール」**
7. **「VSCodeとStreamlitで作る：エクセルも扱える！簡単データビジュアライザ」**

---

どのタイトルも、検索性と初心者の目を引くことを意識しています。  
「実用性」重視や「教育」重視など、もう少し絞り込んだ方向性にしたい場合も、調整できます。

方向性に合いそうなものはありましたか？

# **user**:

「Streamlit×Plotlyで学ぶ：折れ線・密度・散布図を簡単に表示する方法」でいきたいと思います。最後に、簡単なまとめを書いていただけますか？

# **assistant**:

了解しました！以下に、ブログ記事の締めくくりとして使える**簡潔なまとめ**をご提案します。

---

## 🧾 まとめ

本記事では、**VSCodeとStreamlitを使ってCSVやExcelファイルを読み込み、インタラクティブなグラフを表示するアプリの作成方法**を紹介しました。  
折れ線グラフ・密度プロット・散布図の3種類の可視化を、Plotly を活用して美しくインタラクティブに描画できるようにし、さらに**正規化（Min-Max）や標準化（Zスコア、最大絶対値スケーリング）などの前処理も選択可能**にしました。

また、密度プロットでは **カーネル密度推定（KDE）曲線**の追加により、より直感的な分布の理解が可能となっています。  
このようにStreamlitは、初心者でも扱いやすく、短時間で実用的な可視化ツールを作成できる非常に強力なフレームワークです。

今後は、このアプリをベースにして**時系列解析、因果推論、機械学習モデルとの連携**などへ発展させることも可能です。ぜひ挑戦してみてください！

---

必要であれば、このまま全文構成やコード付きテンプレートに仕立てることもできます。投稿予定のブログ媒体（Qiita、Zenn、Noteなど）は決まっていますか？

# **user**:

ありがとうございました。これで大丈夫です。また次回もよろしくお願いします！

# **assistant**:

どういたしまして！こちらこそ、楽しいやりとりをありがとうございました。  
ブログ執筆、応援しています 📚✨

また何かあれば、いつでも気軽にご相談くださいね。次回もどうぞよろしくお願いします！