【Python】ブールマスクの使い方解説 : numpy と list の使い分けと要素番号の取得方法

# はじめに  

Pythonでデータ処理をしていると「条件を満たす要素の抽出」や「要素番号（インデックス）の取得」が必要になることがあります。このような操作では **ブールマスク** が非常に便利です。

本記事では、`numpy` と標準 `list` における **ブールマスクの用法**、および **要素番号の取得方法** について解説します。

:::note alert
**よくある間違いやすい点** `np.where` を使う場合は、条件を満たす配列の要素番号が取得できるが、`pixel==3` のようにブールマスクを使う場合は、`True/False` の numpy.array が返ってくるので、要素数は**前者はカット後の要素数**となり、後者は**要素数は変わらない**ことに注意が必要。ただし、`pixel[(pixel==3)]` のようにブールカットをかけたら**カット後の要素数**になる。
:::

## 1. ブールマスクとは？

ブールマスクは、条件に合う要素を **True**、それ以外を **False** とする **ブール型配列** です。データ処理のフィルタリングや要素抽出に使います。

## 2. `numpy` におけるブールマスク

### 2.1 **ブールマスクで要素を抽出する**
`numpy` 配列では、ブールマスクを使って条件に合う要素を直接抽出できます。

```python
import numpy as np

# サンプルデータ
data = np.array([0, 1, 2, 1, 3, 1, 4])

# 条件: 値が1に等しい要素を抽出
mask = (data == 1)  # ブールマスクの生成
filtered_data = data[mask]  # マスクを使って抽出

print("Boolean Mask:", mask)
print("Filtered Data:", filtered_data)
```

**出力:**
```
Boolean Mask: [False  True False  True False  True False]
Filtered Data: [1 1 1]
```

### 2.2 **True の要素番号（インデックス）を取得する**

`np.nonzero()` または `np.where()` を使います。

```python
# True の要素番号を取得
indices = np.nonzero(mask)[0]
# または np.where を使う
indices_alt = np.where(mask)[0]

print("True Indices (nonzero):", indices)
print("True Indices (where):", indices_alt)
```

**出力:**
```
True Indices (nonzero): [1 3 5]
True Indices (where): [1 3 5]
```

### 2.3 **多次元配列でのブールマスク**

`numpy` では多次元配列にもブールマスクを適用できます。

```python
data = np.array([[0, 1], [1, 0]])

# 条件: 値が1の要素
mask = (data == 1)

# True の位置を取得
indices = np.nonzero(mask)

print("True Indices (Row):", indices[0])  # 行インデックス
print("True Indices (Col):", indices[1])  # 列インデックス
```

**出力:**
```
True Indices (Row): [0 1]
True Indices (Col): [1 0]
```

## 3. Pythonリストにおけるブールマスクの用法

### 3.1 **ブールマスクを使った要素抽出**
Python標準 `list` では、`numpy` のようにブールマスクを直接適用することは **できません**。

```python
data = [0, 1, 2, 1, 3, 1, 4]
mask = [False, True, False, True, False, True, False]

# これを実行するとエラーになる
# filtered_data = data[mask]  # TypeError
```

### 3.2 **リスト内包表記を使った抽出**
リストとブールマスクの要素を対応させるには、`zip` やリスト内包表記を使います。

```python
# zip を使って条件に合う要素を抽出
filtered_data = [value for value, flag in zip(data, mask) if flag]

print("Filtered Data:", filtered_data)
```

**出力:**
```
Filtered Data: [1, 1, 1]
```

### 3.3 **True の要素番号（インデックス）を取得する**

リストでは `enumerate()` を使ってインデックスを取得します。

```python
# True の要素番号を取得
indices = [i for i, flag in enumerate(mask) if flag]

print("True Indices:", indices)
```

**出力:**
```
True Indices: [1, 3, 5]
```

## 4. `itertools.compress` を使った簡潔なリスト処理

Python標準ライブラリの `itertools.compress` を使うと、ブールマスクをリストに適用できます。

```python
from itertools import compress

data = [0, 1, 2, 1, 3, 1, 4]
mask = [False, True, False, True, False, True, False]

# compress を使って抽出
filtered_data = list(compress(data, mask))

print("Filtered Data:", filtered_data)
```

**出力:**
```
Filtered Data: [1, 1, 1]
```

## 5. 要素数の取得

`pixel == 1` でブールマスクを作成した場合、`np.sum(pixel == 1)` で要素数が求められます。その理由は、**ブール値 (`True` / `False`) が数値として扱われる** ためです。

---

### **ブール値と数値の関係**

Pythonでは、`True` と `False` は **整数** として扱われることがあります：

- `True` は **1** として扱われる  
- `False` は **0** として扱われる  

したがって、`numpy` 配列のブールマスクに対して **`np.sum()`** を適用すると、`True` の数を合計する動作になります。

#### **具体例**

以下の例で確認してみましょう：

```python
import numpy as np

# サンプルデータ
pixel = np.array([0, 1, 2, 1, 3, 1, 4])

# ブールマスクの生成 (True: pixel == 1, False: otherwise)
mask = (pixel == 1)

print("Boolean Mask:", mask)

# True の要素数を計算
count = np.sum(mask)

print("Number of True Elements:", count)
```

**出力:**
```
Boolean Mask: [False  True False  True False  True False]
Number of True Elements: 3
```

#### **動作の仕組み**
1. **`pixel == 1`**  
   各要素に対して条件を評価し、`True` または `False` のブール型配列 (`mask`) を生成します。

   ```
   [False, True, False, True, False, True, False]
   ```

2. **`np.sum(mask)`**  
   `True` が **1**、`False` が **0** として数値扱いされるため、`np.sum()` は `True` の個数を合計します。

   ```
   0 + 1 + 0 + 1 + 0 + 1 + 0 = 3
   ```
   
この性質を利用すれば、条件を満たす要素数を簡単に求めることができます。

## 6. ブールマスクの `and` と `or` を使った条件結合

複数の条件を組み合わせて要素を抽出する際、`and` や `or` に相当する操作をブールマスクで行う方法について解説します。

### 6.1 **`numpy` の場合**

`numpy` 配列では、`&`（AND）と `|`（OR）を使って条件を結合できます。ただし、これらの演算子を使う場合は **各条件を括弧で囲む** 必要があります。

#### **具体例：複数条件のAND**
```python
import numpy as np

# サンプルデータ
data = np.array([0, 1, 2, 3, 4, 5])

# 条件: 1以上かつ4以下
mask = (data >= 1) & (data <= 4)
filtered_data = data[mask]

print("Boolean Mask (AND):", mask)
print("Filtered Data (AND):", filtered_data)
```

**出力:**
```
Boolean Mask (AND): [False  True  True  True  True False]
Filtered Data (AND): [1 2 3 4]
```

#### **具体例：複数条件のOR**
```python
# 条件: 1以下または4以上
mask = (data <= 1) | (data >= 4)
filtered_data = data[mask]

print("Boolean Mask (OR):", mask)
print("Filtered Data (OR):", filtered_data)
```

**出力:**
```
Boolean Mask (OR): [ True  True False False  True  True]
Filtered Data (OR): [0 1 4 5]
```

### 6.2 **注意点**
`and` や `or` を直接使うとエラーになるため、**`&`** や **`|`** を必ず使いましょう。

```python
# NG: and / or を使うとエラー
mask = (data >= 1) and (data <= 4)  # ValueError
```

### 6.3 **`~`（NOT）を使った否定条件**

条件を反転させたい場合は、`~` 演算子を使用します。

#### **具体例：NOT**
```python
# 条件: 1以上ではない
mask = ~(data >= 1)
filtered_data = data[mask]

print("Boolean Mask (NOT):", mask)
print("Filtered Data (NOT):", filtered_data)
```

**出力:**
```
Boolean Mask (NOT): [ True False False False False False]
Filtered Data (NOT): [0]
```

### 6.4 **複数条件を組み合わせた複雑なフィルタリング**
複数の条件をAND・OR・NOTで組み合わせることで、複雑なフィルタリングが可能です。

#### **具体例：複合条件**
```python
# 条件: (1以上かつ4以下) または (5)
mask = ((data >= 1) & (data <= 4)) | (data == 5)
filtered_data = data[mask]

print("Boolean Mask (Complex):", mask)
print("Filtered Data (Complex):", filtered_data)
```

**出力:**
```
Boolean Mask (Complex): [False  True  True  True  True  True]
Filtered Data (Complex): [1 2 3 4 5]
```

### 6.5 **Pythonリストでのブールマスクと条件結合**
`numpy` のように直接的に `&` や `|` を使うことはできませんが、リスト内包表記を使うことで同様の操作が可能です。

#### **具体例：複数条件のANDとOR**
```python
data = [0, 1, 2, 3, 4, 5]

# 条件: 1以上かつ4以下
filtered_data_and = [x for x in data if x >= 1 and x <= 4]

# 条件: 1以下または4以上
filtered_data_or = [x for x in data if x <= 1 or x >= 4]

print("Filtered Data (AND):", filtered_data_and)
print("Filtered Data (OR):", filtered_data_or)
```

**出力:**
```
Filtered Data (AND): [1, 2, 3, 4]
Filtered Data (OR): [0, 1, 4, 5]
```
---

このように、ブールマスクと条件結合を活用することで、柔軟かつ効率的なデータフィルタリングが実現できます。

## 7. `numpy` と `list` の比較まとめ

| 機能                      | `numpy`                          | Python `list`                       |
|---------------------------|-------------------|------------------------------------|
| **ブールマスク適用**        | `data[mask]`            | `zip` or `compress` or リスト内包表記      |
| **True の要素番号取得**      | `np.nonzero(mask)` / `np.where(mask)` | `enumerate()` を使う                |
| **多次元配列の対応**        | 可能                           | 非対応           |
| **処理速度**               | ベクトル化で高速                  | ループベースのため遅い(?) |



# まとめ

- `numpy` ではブールマスクを直接使って要素の **抽出** や **インデックス取得** が簡単にできます。
- Pythonの標準 `list` ではブールマスクが直接適用できないため、`zip` や `enumerate` や、リスト内包表記を活用する必要があります。
- **多次元配列** の場合は `numpy` 一択です。

データ処理の規模や用途に応じて、`numpy` と `list` を使い分けましょう！