Streamlit で列ごとのグループ分け：ラジオボタンと表形式でスッキリ実装

# **user**:

pandas dataframe 形式のデータがあります。その列ラベルのひとつひとつを、グループAかグループBかグループCか、ユーザがStreamlitアプリ内で選択するコードを書いてください。

# **assistant**:

以下は、**PandasのDataFrameの列ラベルをStreamlit上でユーザーが「グループA / B / C」から選択できる**アプリのコード例です。ユーザーは各列に対してグループを指定し、最終的にグループ分け結果を表示できます。

---

### ✅ Streamlitアプリのコード例

```python
import streamlit as st
import pandas as pd

# サンプルのDataFrame（実際はファイルアップロードや別途読み込みでもOK）
df = pd.DataFrame({
    'age': [25, 30, 22],
    'income': [50000, 60000, 45000],
    'education': ['Bachelors', 'Masters', 'PhD']
})

st.title("列ラベルをグループ分けするアプリ")

st.subheader("元のデータ")
st.dataframe(df)

# グループ候補
group_options = ["グループA", "グループB", "グループC"]

# ユーザー入力による列ごとのグループ選択
st.subheader("各列に対してグループを選んでください")
group_selection = {}

for col in df.columns:
    selected_group = st.selectbox(f"{col} のグループ:", group_options, key=col)
    group_selection[col] = selected_group

# 結果表示
st.subheader("グループ分け結果")
result_df = pd.DataFrame({
    "列名": list(group_selection.keys()),
    "選択されたグループ": list(group_selection.values())
})
st.dataframe(result_df)

# オプション: グループごとの列リストを表示
st.subheader("グループごとの列リスト")
for group in group_options:
    cols_in_group = [col for col, grp in group_selection.items() if grp == group]
    st.write(f"**{group}**: {cols_in_group}")
```

---

このコードを実行すると、各列に対して選択肢が表示され、グループの割り当て結果が下部に表形式で出力されます。

必要であれば、ファイルアップロード機能を加えることもできます。追加をご希望ですか？

# **user**:

ありがとうございます。プルダウンメニューではなくてチェックボックスにできますか？

# **assistant**:

はい、**プルダウンメニューの代わりにチェックボックスでグループを選ぶ形式**にもできます。ただし、**チェックボックスは複数選択可能なので、1つの列につき1グループだけ選ばせたい場合**は、制御が必要です。

以下は「1つの列につき、**チェックボックス形式でグループA/B/Cのうち1つのみ**を選ばせる」Streamlitコード例です。

---

### ✅ チェックボックス形式（1グループのみ選択可能）

```python
import streamlit as st
import pandas as pd

# サンプルのDataFrame
df = pd.DataFrame({
    'age': [25, 30, 22],
    'income': [50000, 60000, 45000],
    'education': ['Bachelors', 'Masters', 'PhD']
})

st.title("列ラベルをチェックボックスでグループ分け")

st.subheader("元のデータ")
st.dataframe(df)

# グループ候補
groups = ["グループA", "グループB", "グループC"]
group_selection = {}

st.subheader("各列に対して1つのグループをチェックしてください")

# 各列に対してチェックボックス（排他的に選択）
for col in df.columns:
    st.markdown(f"### {col}")
    selected = None
    for group in groups:
        checked = st.checkbox(f"{group}", key=f"{col}_{group}")
        if checked:
            if selected:
                st.error(f"{col} に複数のグループが選ばれています。1つだけにしてください。")
            selected = group if not selected else None
    if selected:
        group_selection[col] = selected

# 結果表示
if group_selection:
    st.subheader("グループ分け結果")
    result_df = pd.DataFrame({
        "列名": list(group_selection.keys()),
        "選択されたグループ": list(group_selection.values())
    })
    st.dataframe(result_df)
```

---

### 🔎 ポイント

* 1つの列に対して複数のチェックが入ったら警告を出す仕組みを入れています。
* もっと確実に1つしか選ばせたくするなら、\*\*ラジオボタン（`st.radio`）\*\*の方が自然です。

---

☑ チェックボックス形式を維持したい理由が特にあれば伺えますか？それに応じてより最適なUIをご提案できます。

# **user**:

ありがとうございます。ラジオボタンにしてください。

# **assistant**:

了解しました！チェックボックスの代わりに **ラジオボタン（`st.radio`）** を使って、各列に対して「グループA / グループB / グループC」のうち1つだけを選ばせるようにしたコードはこちらです。

---

### ✅ ラジオボタンで列をグループ分けするStreamlitアプリ

```python
import streamlit as st
import pandas as pd

# サンプルのDataFrame
df = pd.DataFrame({
    'age': [25, 30, 22],
    'income': [50000, 60000, 45000],
    'education': ['Bachelors', 'Masters', 'PhD']
})

st.title("列ラベルをラジオボタンでグループ分け")

st.subheader("元のデータ")
st.dataframe(df)

# グループ候補
groups = ["グループA", "グループB", "グループC"]
group_selection = {}

st.subheader("各列に対してグループを選んでください")

# 各列に対してラジオボタンでグループ選択
for col in df.columns:
    selected_group = st.radio(
        f"{col} のグループを選択:",
        options=groups,
        key=col,
        horizontal=True  # 水平方向に表示（見やすくなります）
    )
    group_selection[col] = selected_group

# 結果表示
st.subheader("グループ分け結果")
result_df = pd.DataFrame({
    "列名": list(group_selection.keys()),
    "選択されたグループ": list(group_selection.values())
})
st.dataframe(result_df)

# オプション: グループごとの列一覧
st.subheader("グループごとの列一覧")
for group in groups:
    cols = [col for col, grp in group_selection.items() if grp == group]
    st.write(f"**{group}**: {cols}")
```

---

このラジオボタン形式なら、各列ごとに**必ず1つだけ**グループを選ばせることができ、操作ミスも防げます。

ファイルアップロードや列名の自動抽出も追加できますが、必要ですか？

# **user**:

ありがとうございます。ラジオボタンのデフォルトとして、何も選択されていないようにしたいのですが、できますか？

# **assistant**:

とても良い質問です。残念ながら、`Streamlit` の `st.radio` では **現時点（2025年5月時点）で「何も選ばれていない状態」をデフォルトにすることはできません**。必ずいずれかの選択肢が選ばれた状態で表示されます。

---

### ✅ 回避策：先頭に「未選択」オプションを追加する

よく使われる回避策は、「未選択」や「選択してください」というダミーの選択肢を先頭に追加する方法です。これにより、ユーザーが明示的に選ばない限り、実質的に「未選択」状態になります。

以下がその実装例です：

---

### ✅ 修正版コード：デフォルトを「未選択」にする

```python
import streamlit as st
import pandas as pd

# サンプルのDataFrame
df = pd.DataFrame({
    'age': [25, 30, 22],
    'income': [50000, 60000, 45000],
    'education': ['Bachelors', 'Masters', 'PhD']
})

st.title("列ラベルをラジオボタンでグループ分け（未選択対応）")

st.subheader("元のデータ")
st.dataframe(df)

# グループ候補 + 未選択
groups = ["（選択してください）", "グループA", "グループB", "グループC"]
group_selection = {}

st.subheader("各列に対してグループを選んでください")

# 各列に対してラジオボタンで選択（デフォルトは未選択）
for col in df.columns:
    selected_group = st.radio(
        f"{col} のグループを選択:",
        options=groups,
        index=0,  # 「（選択してください）」をデフォルトに
        key=col,
        horizontal=True
    )
    if selected_group != "（選択してください）":
        group_selection[col] = selected_group

# 結果表示
if group_selection:
    st.subheader("グループ分け結果")
    result_df = pd.DataFrame({
        "列名": list(group_selection.keys()),
        "選択されたグループ": list(group_selection.values())
    })
    st.dataframe(result_df)
else:
    st.info("少なくとも1つの列にグループを割り当ててください。")
```

---

この方法なら、ユーザーがまだ何も選んでいないことが明確に分かり、誤操作も防げます。

さらにバリデーションなどを加えることもできますが、必要でしょうか？

# **user**:

ありがとうございます。この入力フォームを、表形式にできますか？

# **assistant**:

はい、できます！
**列ラベルごとのグループ選択を、1つの表形式で一覧できるフォームにまとめる**方法をご紹介します。以下のようなUIになります：

* 左側に「列名」
* 右側に「ラジオボタン（グループ選択）」が横並び
* 全体が表形式に見えるので、ユーザーが一度に全体を確認しながら選択できます

---

### ✅ Streamlitで「表形式のグループ選択フォーム」を実装する例

```python
import streamlit as st
import pandas as pd

# サンプルDataFrame
df = pd.DataFrame({
    'age': [25, 30, 22],
    'income': [50000, 60000, 45000],
    'education': ['Bachelors', 'Masters', 'PhD']
})

st.title("列ごとのグループ分け（表形式UI）")

st.subheader("元のデータ")
st.dataframe(df)

# グループ候補（未選択を含む）
group_options = ["（選択してください）", "グループA", "グループB", "グループC"]
group_selection = {}

st.subheader("表形式でグループを選択")

# 表形式のレイアウト：列名 + ラジオボタン
col1, col2 = st.columns([1, 3])
with col1:
    st.markdown("**列名**")
    for col in df.columns:
        st.markdown(f"{col}")
with col2:
    st.markdown("**グループ選択**")
    for col in df.columns:
        selected = st.radio(
            label="",
            options=group_options,
            index=0,
            key=f"group_{col}",
            horizontal=True,
            label_visibility="collapsed"  # ラベル非表示でスッキリ
        )
        if selected != "（選択してください）":
            group_selection[col] = selected

# 結果表示
if group_selection:
    st.subheader("グループ分け結果")
    result_df = pd.DataFrame({
        "列名": list(group_selection.keys()),
        "選択されたグループ": list(group_selection.values())
    })
    st.dataframe(result_df)
else:
    st.info("少なくとも1つの列にグループを割り当ててください。")
```

---

### ✅ UIイメージ（擬似）

| 列名        | グループ選択                     |
| --------- | -------------------------- |
| age       | ○（選択） ○グループA ○グループB ○グループC |
| income    | ○（選択） ○グループA ○グループB ○グループC |
| education | ○（選択） ○グループA ○グループB ○グループC |

---

### 🔧 カスタマイズ案

* **列数が多い場合** → スクロール可能にするなどの工夫が必要です。
* **選択ミスチェック**や**エクスポート**なども追加できます。

ご希望であれば、それらの機能も組み込みましょうか？

# **user**:

ありがとうございます。しかし、上記のコードを実行すると、「グループ選択」で文章の折り返しが発生するため、「グループ選択」の行と「列名」の行がずれてきます。改善策はありませんか？

# **assistant**:

素晴らしい指摘です！
おっしゃる通り、**`st.columns()` による2カラム構成**では、**ラジオボタンの内容が折り返されると縦の位置がずれてしまう**という問題があります。

---

## ✅ 解決策：行ごとに `st.columns()` を使って完全にペアで表示する

この問題の本質は、「列名リスト」と「ラジオボタンリスト」を**別々のループで描画している**ため、**高さが揃わない**ことです。

### ✅ 対策としては：

* 各列（=1行）ごとに `st.columns([1, 4])` を生成し、**1行ごとにセットで描画する**
* これにより、どれだけ長いラジオボタンでも折り返しによるズレが起きません

---

## ✅ 改善済みコード（折り返しずれ防止）

```python
import streamlit as st
import pandas as pd

# サンプルのDataFrame
df = pd.DataFrame({
    'age': [25, 30, 22],
    'income': [50000, 60000, 45000],
    'education': ['Bachelors', 'Masters', 'PhD']
})

st.title("列ごとのグループ分け（ズレない表形式）")

st.subheader("元のデータ")
st.dataframe(df)

# グループ選択肢（未選択を含む）
group_options = ["（選択してください）", "グループA", "グループB", "グループC"]
group_selection = {}

st.subheader("各列のグループを表形式で選択")

# 見出し行
header_cols = st.columns([1, 4])
with header_cols[0]:
    st.markdown("**列名**")
with header_cols[1]:
    st.markdown("**グループ選択**")

# 各行（列名 + ラジオボタン）を表示
for col in df.columns:
    row_cols = st.columns([1, 4])
    with row_cols[0]:
        st.write(col)
    with row_cols[1]:
        selected = st.radio(
            label="",
            options=group_options,
            index=0,
            key=f"group_{col}",
            horizontal=True,
            label_visibility="collapsed"
        )
        if selected != "（選択してください）":
            group_selection[col] = selected

# 結果表示
if group_selection:
    st.subheader("グループ分け結果")
    result_df = pd.DataFrame({
        "列名": list(group_selection.keys()),
        "選択されたグループ": list(group_selection.values())
    })
    st.dataframe(result_df)
else:
    st.info("少なくとも1つの列にグループを割り当ててください。")
```

---

## ✅ この方法のメリット

* 各「列名＋ラジオボタン」を1行で構成 → **高さが同期されズレない**
* 見た目がExcel風の「表形式」に近くなる
* 横に並ぶラジオボタンも折り返されず**行揃え**

---

ご希望があれば、以下のような追加機能も組み込めます：

* グループごとに色分け表示
* 割り当て結果のCSV出力
* グループごとの列をタブで表示

必要に応じてお知らせください！

# **user**:

ありがとうございます。選択すると次のエラーが生じるので解決してください。 

```
Traceback (most recent call last):
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/exec\_code.py", line 121, in exec\_func\_with\_error\_handling
result = func()
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script\_runner.py", line 639, in code\_to\_exec
\_mpa\_v1(self.\_main\_script\_path)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script\_runner.py", line 159, in *mpa\_v1
page.run()
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/navigation/page.py", line 297, in run
exec(code, module.**dict**)
File "/Users/ikemen/streamlit-app/pages/bootstrap\_lingam.py", line 583, in <module>
st.dataframe(result\_df)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/metrics\_util.py", line 410, in wrapped\_func
result = non\_optional\_func(\*args, \*\*kwargs)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/elements/arrow\.py", line 607, in dataframe
proto.data = dataframe\_util.convert\_pandas\_df\_to\_arrow\_bytes(data\_df)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/dataframe\_util.py", line 831, in convert\_pandas\_df\_to\_arrow\_bytes
table = pa.Table.from\_pandas(df)
File "pyarrow/table.pxi", line 4751, in pyarrow\.lib.Table.from\_pandas
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pyarrow/pandas\_compat.py", line 639, in dataframe\_to\_arrays
arrays = \[convert\_column(c, f)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pyarrow/pandas\_compat.py", line 639, in <listcomp>
arrays = \[convert\_column(c, f)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pyarrow/pandas\_compat.py", line 626, in convert\_column
raise e
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pyarrow/pandas\_compat.py", line 620, in convert\_column
result = pa.array(col, type=type*, from\_pandas=True, safe=safe)
File "pyarrow/array.pxi", line 362, in pyarrow\.lib.array
File "pyarrow/array.pxi", line 87, in pyarrow\.lib.\_ndarray\_to\_array
File "pyarrow/error.pxi", line 92, in pyarrow\.lib.check\_status
pyarrow\.lib.ArrowInvalid: ('cannot mix list and non-list, non-null values', 'Conversion failed for column 選択されたグループ with type object')
```

# **assistant**:

このエラーの原因は、`result_df` に格納されている `"選択されたグループ"` 列に **リストと文字列が混在している** ことです。
Streamlit の `st.dataframe()`（内部的に `pyarrow` を使って変換）が、**「同じ列の中にリストと文字列が混在している」ことを許容していない**ため、次のようなエラーが出ます：

```
ArrowInvalid: cannot mix list and non-list, non-null values
```

---

## ✅ 原因の詳細

以下のような誤った格納がされている可能性があります：

```python
group_selection = {
    "age": "グループA",
    "income": ["グループB"],  # ← ここだけリスト
    "education": "グループC"
}
```

これはたとえば、`st.radio()` の戻り値を正しく処理せず、何らかの形でリストとして扱った場合に起こります。

---

## ✅ 解決策

**`group_selection.values()` を `str()` で明示的に文字列として扱う**ようにし、PandasのDataFrameに入れる前に「全てが文字列」になるよう整えます。

---

## ✅ 修正コード（`result_df` 作成部分）

次のように `result_df` の作成部分を修正してください：

```python
# 結果表示
if group_selection:
    st.subheader("グループ分け結果")

    # 各値を str に変換（リスト混入対策）
    result_df = pd.DataFrame({
        "列名": list(group_selection.keys()),
        "選択されたグループ": [str(v) for v in group_selection.values()]
    })

    st.dataframe(result_df)
else:
    st.info("少なくとも1つの列にグループを割り当ててください。")
```

---

## ✅ 補足：さらに安全にするには？

もしどこかで意図せずリストで値を保持してしまっているなら、以下のようなコードで防げます：

```python
group_selection[col] = str(selected)  # ラジオボタンの値を常に文字列として保存
```

---

## ✅ まとめ

* `pyarrow` 経由の `st.dataframe()` は列にリスト混入を許さない
* `.values()` の結果を `str()` 化すれば問題を回避可能
* `st.radio()` の戻り値がリストになることは通常ないが、意図しない操作でそうなる場合は要対策

---

必要なら、ラジオボタンの入力値チェックや、意図しないデータ構造混入を防ぐための**バリデーション関数**も提供可能です。希望があればお知らせください。

# **user**:

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

# **assistant**:

もちろんです！今回の会話の内容は、\*\*Streamlitでの列ごとの分類インターフェース構築（ラジオボタンUI・表形式表示・エラー解決）\*\*が中心でしたので、それを反映したブログタイトル案をいくつかご提案します：

---

### ✨ Streamlitでのグループ分けUI構築に関するタイトル案

#### 🔹 シンプル・実用志向

1. **Streamlitで列ごとのグループ分け：ラジオボタンと表形式でスッキリ実装**
2. **列ごとの分類UIをStreamlitで作る：ラジオボタン×表レイアウトで分かりやすく**
3. **Streamlitでグループ選択フォームを構築する方法：列名ごとにラジオボタンを表示**

#### 🔹 エラー解決も強調

4. **「ArrowInvalid」エラーを解決しつつ、Streamlitでグループ分けUIを作る方法**
5. **Streamlitで表形式の入力フォームを作ってみたらPyArrowエラーが出た件**
6. **ラジオボタンのデフォルト未選択とレイアウト崩れと戦うStreamlit実装録**

#### 🔹 ブログ風・ストーリー仕立て

7. **Streamlitで「列をグループ分けするUI」を作ったら地味にハマった話**
8. **ラジオボタンがずれて見える？Streamlitで表っぽく見せるテクニック**
9. **StreamlitでUI作りに迷った日：ラジオボタンとデータ型エラーと私**

---

ご希望のトーン（技術寄り・体験談風・キャッチーなど）があれば、さらに調整可能です。お気に入りが見つからなければ、絞り込みや再提案もできますので、お気軽にどうぞ！

# **user**:

ありがとうございます。「Streamlitで列ごとのグループ分け：ラジオボタンと表形式でスッキリ実装」にしたいと思います。最後に、今回のまとめを書いてください。

# **assistant**:

了解しました！以下に、ブログ記事の締めにも使える\*\*「今回のまとめ」\*\*をお届けします。

---

## 📝 今回のまとめ

**タイトル：Streamlitで列ごとのグループ分け：ラジオボタンと表形式でスッキリ実装**

今回は、**Pandasの列ごとに「グループA / B / C」などのカテゴリを割り当てるStreamlitアプリ**を作成しながら、以下のようなポイントを実装・改善しました：

---

### ✅ 主な実装内容

* **各列に対してラジオボタンでグループを選択できるUI**
* **選択肢のデフォルトは「未選択」として明示**
* **UIは表形式（列名とグループ選択を1行に表示）で視認性を向上**
* **`st.columns()` を活用して折り返しによるズレを防止**

---

### ✅ トラブルと解決

* `st.dataframe()` に渡したDataFrameで **"ArrowInvalid: cannot mix list and non-list"** というエラーが発生
  　→ 原因は、**列にリストと文字列が混在**していたこと
  　→ 対策として、**`str()` で文字列に統一**して解決

---

### ✅ 得られた知見

* Streamlit のラジオボタンは `index=0` と `label_visibility="collapsed"` を使えば、見た目もスッキリ制御できる
* `st.columns()` を1行ごとに使うことで、**Excel風の行揃えUI**を構築可能
* `st.dataframe()` で表示するデータは、**内部のデータ型の一貫性が非常に重要**

---

このように、**見た目の工夫とエラーハンドリングを組み合わせることで、ユーザーフレンドリーなUIが構築できる**と実感できる開発体験でした。

次回は、グループ選択後の処理（フィルタリング・グラフ化・保存など）も検討してみると、より実用的なアプリにつながりそうです。

---

ご希望があれば、このまとめを**マークダウン形式で整形**することも可能です。ブログの流れや口調に合わせて微調整もできますので、遠慮なくお知らせください！

# **user**:

ありがとうございました。また今後もよろしくお願いします！

# **assistant**:

こちらこそ、ありがとうございました！丁寧に進めていただけてとても助かりました 🙌
またいつでもお気軽にご相談ください。今後の開発やブログ執筆も応援しています！

どうぞよろしくお願いいたします 😊