Python環境を起動中... 初回のみ数秒かかります
Lesson 12 / 20

内包表記

このレッスンで学ぶこと

  • リスト内包表記でforループを1行に書ける
  • 条件付き内包表記でフィルタリングと変換を同時にできる
  • 辞書・集合内包表記を使える
  • ネストした内包表記で2次元データを扱える

1. リスト内包表記の基本

内包表記(comprehension)とは、リストを作るための for ループを1行に凝縮した書き方です。 [式 for 変数 in イテラブル] という形で書きます。

通常の for ループと比べて主に3つのメリットがあります。 ①短く書ける(数行 → 1行)、 ②意図が読みやすい(「このリストを作る処理だ」と一目でわかる)、 ③実行速度が速い(内部的に最適化されている)。 データサイエンスや機械学習のコードでは for ループより内包表記の方を好む慣習があります。

ただし複雑な処理を無理やり1行に詰め込むと逆に読みにくくなります。 シンプルな変換・フィルタリングには内包表記、複雑なロジックには素直に for ループ という使い分けが基本です。

sample_1.py
Ctrl+Enter
出力

2. 条件付き内包表記

[式 for 変数 in イテラブル if 条件] で、条件を満たす要素だけを取り出しながら変換できます。 データのクリーニングや異常値除去でよく使います。

sample_2.py
Ctrl+Enter
出力

💡 三項演算子との組み合わせ: [a if 条件 else b for x in lst][a for x in lst if 条件] は意味が違います。 前者は全要素に対して a か b を選ぶ(要素数は変わらない)。 後者は条件を満たす要素だけを取り出す(要素数が変わる)。

3. 辞書・集合内包表記

{key: value for ...} で辞書内包表記、{式 for ...} で集合内包表記が書けます。 辞書の逆引きや重複除去に便利です。

sample_3.py
Ctrl+Enter
出力

💡 集合内包表記がなぜ重複を削除するのか:
{l for l in all_labels}{} はコロンなし(値のみ)なので set(集合)を作る構文です(レッスン7「タプルと集合」で学んだ通り、set は重複を持ちません)。
ループしながら要素を追加していきますが、set の性質上、すでに存在する値は自動的に無視されます。

all_labels = [0, 1, 2, 0, 1, 3, 2, 1, 0, 4]

{l for l in all_labels}  # → {0, 1, 2, 3, 4}(集合内包表記 → 重複が自動で消える)
set(all_labels)          # 同じ結果(単純に重複を消すだけならこちらが簡潔)

# 「加工しながら重複も除去したい」場合が集合内包表記の出番
{l * 10 for l in all_labels}  # → {0, 10, 20, 30, 40}(10倍しつつ重複も除去)

4. ネスト内包表記

内包表記の中に for をもう1つ書くと、2重ループを1行で表現できます。 2次元リスト(行列)の生成・展開(flatten)・フィルタリングに使います。

構造を理解する最短の方法は「まず for ループで書いてから内包表記に変換する」です。 ネスト内包表記の for ... for ... の順番は、 外側のループ → 内側のループの順(通常の for ループのネストと同じ順序)に対応しています。

sample_4.py
Ctrl+Enter
出力

💡 ネスト内包表記 = for ループを内側から外側に読む:
for ... for ... が続く場合、左から右の順(外側→内側)がそのままネストした for ループと対応します。

# ③ の等価な for ループ(こちらを先に読むと構造が分かる)
low_readings = []
for i, readings in enumerate(batch_data):  # 外側
    for spo2 in readings:                   # 内側
        if spo2 < 95:                       # 条件
            low_readings.append((f"患者{i+1}", spo2))

# ↓ 内包表記版(上を1行にまとめた形)
low_readings = [
    (f"患者{i+1}", spo2)
    for i, readings in enumerate(batch_data)
    for spo2 in readings
    if spo2 < 95
]

難しく感じたら「for ループで書いてから内包表記に変換する」という手順で練習すると理解が深まります。
2段階以上のネストは読みにくくなるため、複雑な処理は素直に for ループを使う方が保守しやすいコードになります。

5. 練習問題

問題 1

検査値リストを内包表記で正規化する

リスト内包表記を使って、下の血糖値を Min-Max 正規化した新しいリストを1行で作ってください。

exercise_1.py
出力
ヒントを見る(答え+解説)
glucose = [110, 98, 125, 88, 142, 103]

normalized = [(v - min(glucose)) / (max(glucose) - min(glucose)) for v in glucose]
print([f"{v:.3f}" for v in normalized])
# → ['0.407', '0.185', '0.685', '0.000', '1.000', '0.278']

内包表記 [式 for 変数 in リスト] を使うと、ループと計算を1行で書けます。正規化の分子・分母で min()/max() を直接使うのが簡潔で読みやすいコツです。

問題 2

辞書内包表記でラベル→クラス名マップを作る

ラベルとクラス名の2つのリストから、辞書内包表記を使って {ラベル番号: クラス名} の辞書を1行で作ってください。

exercise_2.py
出力
ヒントを見る(答え+解説)
labels     = [0, 1, 2, 3]
class_names = ["正常", "結節あり", "腫瘤あり", "その他"]

label_map = {k: v for k, v in zip(labels, class_names)}
print(label_map)
print("ラベル2:", label_map[2])
# → {0: '正常', 1: '結節あり', 2: '腫瘤あり', 3: 'その他'}
# → ラベル2: 腫瘤あり

zip() で2つのリストをペアにしてから辞書内包表記に渡すと、「ラベル番号→クラス名」のマップを1行で作れます。機械学習のクラス名変換で定番のパターンです。

問題 3

陰性患者のスコアだけを内包表記で抽出する

下のリストから label == 0(陰性)の患者の score だけを抽出したリストを内包表記で1行で作ってください。

exercise_3.py
出力
ヒントを見る(答え+解説)
results = [
    {"id": "P001", "label": 1, "score": 0.92},
    {"id": "P002", "label": 0, "score": 0.18},
    {"id": "P003", "label": 1, "score": 0.85},
    {"id": "P004", "label": 0, "score": 0.31},
    {"id": "P005", "label": 0, "score": 0.22},
]

neg_scores = [r["score"] for r in results if r["label"] == 0]
print("陰性スコア:", neg_scores)
# → 陰性スコア: [0.18, 0.31, 0.22]

内包表記の末尾に if 条件 を加えると、フィルタリングと値抽出を同時に1行で行えます。[r["score"] for r in results if r["label"] == 0] は「陰性の患者のスコアだけ取り出す」という意味で、AIの評価分析でよく使うパターンです。

まとめ

このレッスンのポイント

  • [式 for x in lst] がリスト内包表記の基本形
  • [式 for x in lst if 条件] でフィルタリングを同時に行える
  • [a if 条件 else b for x in lst] で全要素に対して条件分岐変換できる
  • {k: v for ...} が辞書内包表記。逆引き辞書・マッピング作成に便利
  • 複雑なネストは可読性が下がるため、forループで書いた方がよい場合もある

自由に試してみましょう:

free_practice.py
Ctrl+Enter
出力

完了するとトップページに進捗が表示されます