【Python完全版】np.where徹底解説:条件分岐もインデックス抽出もこれ一つ!

python

Pythonで数値データを扱う際に欠かせないのがNumPyライブラリ。

中でもnp.where(エヌピー・ウェア)は、条件に応じた値の選択や、条件を満たす要素のインデックス取得に便利な関数です。

この記事では、np.where基本的な使い方から応用パターンまでを初心者にもわかりやすく解説し、実務で役立つ使用例も紹介します。

データ分析や機械学習でよく使われる条件処理を効率的に行えるようになりましょう。

スポンサーリンク

np.whereとは?その役割と重要性

np.whereの基本概念

NumPyのnp.whereは、条件に一致する要素を見つけ出すための多機能な関数です。

この関数は使い方によって以下の2つの主要な役割を果たします:

  1. 条件に応じた値の置き換えnp.where(条件, 真の場合の値, 偽の場合の値)
  2. 条件を満たす要素のインデックス取得np.where(条件)

なぜnp.whereが重要なのか

従来のPythonとの比較

# 通常のPythonでの条件処理(非効率)
normal_list = [10, 20, 30, 40, 50]
result_list = []
for value in normal_list:
    if value > 25:
        result_list.append("大")
    else:
        result_list.append("小")

print(result_list)  # ['小', '小', '大', '大', '大']

NumPyのnp.whereを使った場合(効率的)

import numpy as np

arr = np.array([10, 20, 30, 40, 50])
result = np.where(arr > 25, "大", "小")

print(result)  # ['小' '小' '大' '大' '大']

np.whereの利点

  • ベクトル化処理:ループを書かずに配列全体を一括処理
  • 高速処理:C言語で実装された内部処理により高速
  • 簡潔なコード:複雑な条件分岐も短いコードで表現
  • メモリ効率:大量データでもメモリ使用量を抑制

np.whereは「条件に一致する位置を調べる」または「条件に応じて値を切り替える」ための多用途な関数です。

次に、具体的な使い方を詳しく見ていきましょう。

基本的な使い方①:条件に応じた値の切り替え

基本構文と動作原理

これは、if文をベクトル化したような使い方で、最も頻繁に使用されるパターンです。

基本構文:

np.where(条件, 真の場合の値, 偽の場合の値)

基本的な例

数値の大小判定:

import numpy as np

# 基本的な数値配列での使用
arr = np.array([10, 20, 30, 40, 50])
result = np.where(arr > 25, "大", "小")

print("元の配列:", arr)
print("結果:", result)
# 出力: 元の配列: [10 20 30 40 50]
#      結果: ['小' '小' '大' '大' '大']

# より詳細な条件
scores = np.array([85, 92, 76, 88, 95, 67])
grades = np.where(scores >= 90, "A", 
                 np.where(scores >= 80, "B",
                         np.where(scores >= 70, "C", "D")))

print("点数:", scores)
print("成績:", grades)
# 出力: 点数: [85 92 76 88 95 67]
#      成績: ['B' 'A' 'C' 'B' 'A' 'D']

数値での置き換え

欠損値の処理:

# 負の値を0に置き換える
data = np.array([-5, 10, -3, 15, 0, -1])
cleaned_data = np.where(data < 0, 0, data)

print("元のデータ:", data)
print("処理後:", cleaned_data)
# 出力: 元のデータ: [-5 10 -3 15  0 -1]
#      処理後: [ 0 10  0 15  0  0]

# 範囲外の値を修正
temperatures = np.array([25.5, 102.3, -10.2, 30.1, 150.0])
# 0-100度の範囲外を適切な値に修正
corrected_temps = np.where(temperatures < 0, 0,
                          np.where(temperatures > 100, 100, temperatures))

print("元の温度:", temperatures)
print("補正後:", corrected_temps)
# 出力: 元の温度: [ 25.5 102.3 -10.2  30.1 150. ]
#      処理後: [ 25.5 100.   0.  30.1 100. ]

2次元配列での使用

行列での条件処理:

# 2次元配列での使用例
matrix = np.array([[1, 5, 9],
                   [3, 7, 2],
                   [8, 4, 6]])

# 閾値以上を1、未満を0に変換
binary_matrix = np.where(matrix >= 5, 1, 0)

print("元の行列:")
print(matrix)
print("二値化後:")
print(binary_matrix)
# 出力: 元の行列:
#      [[1 5 9]
#       [3 7 2]
#       [8 4 6]]
#      二値化後:
#      [[0 1 1]
#       [0 1 0]
#       [1 0 1]]

# 統計的な処理
avg = np.mean(matrix)
above_avg = np.where(matrix > avg, "平均以上", "平均以下")

print(f"平均値: {avg:.2f}")
print("平均との比較:")
print(above_avg)

複数条件の組み合わせ

論理演算子の活用:

# 複数条件を組み合わせた例
ages = np.array([15, 25, 35, 45, 65, 75])
income = np.array([0, 30000, 50000, 70000, 40000, 60000])

# 年齢と収入に基づく分類
# (年齢30-60 かつ 収入50000以上) なら "ターゲット", そうでなければ "非ターゲット"
target_group = np.where((ages >= 30) & (ages <= 60) & (income >= 50000), 
                       "ターゲット", "非ターゲット")

print("年齢:", ages)
print("収入:", income)
print("分類:", target_group)
# 出力: 年齢: [15 25 35 45 65 75]
#      収入: [    0 30000 50000 70000 40000 60000]
#      分類: ['非ターゲット' '非ターゲット' 'ターゲット' 'ターゲット' '非ターゲット' '非ターゲット']

np.where(条件, 真, 偽)の形式で、配列全体に一括で条件分岐を適用できます。次に、インデックスの取得方法を詳しく見てみましょう。

基本的な使い方②:条件に一致するインデックスの取得

インデックス取得の基本

1つの引数でnp.whereを使うと、条件を満たす要素の位置(インデックス)を返すことができます。

基本構文:

np.where(条件)  # 条件を満たす要素のインデックスを返す

1次元配列でのインデックス取得

基本的な使用例:

import numpy as np

# 1次元配列での例
arr = np.array([1, 3, 5, 7, 9, 2, 4, 6])
indices = np.where(arr > 4)

print("元の配列:", arr)
print("インデックス:", indices)
print("該当する値:", arr[indices])

# 出力: 元の配列: [1 3 5 7 9 2 4 6]
#      インデックス: (array([2, 3, 4, 7]),)
#      該当する値: [5 7 9 6]

# より実用的な例:特定の値を持つ要素の位置を特定
product_ids = np.array([101, 203, 101, 405, 203, 101, 507])
target_id = 101
positions = np.where(product_ids == target_id)

print("商品ID配列:", product_ids)
print(f"ID {target_id}の位置:", positions[0])
print(f"ID {target_id}の個数:", len(positions[0]))

# 出力: 商品ID配列: [101 203 101 405 203 101 507]
#      ID 101の位置: [0 2 5]
#      ID 101の個数: 3

2次元配列でのインデックス取得

行列での位置特定:

# 2次元配列での使用例
matrix = np.array([[10, 25, 30],
                   [15, 35, 20],
                   [40, 5, 45]])

# 特定の値以上の要素の位置を取得
large_indices = np.where(matrix >= 30)

print("元の行列:")
print(matrix)
print("30以上の要素の行インデックス:", large_indices[0])
print("30以上の要素の列インデックス:", large_indices[1])
print("30以上の値:", matrix[large_indices])

# 出力: 元の行列:
#      [[10 25 30]
#       [15 35 20]
#       [40  5 45]]
#      30以上の要素の行インデックス: [0 1 2 2]
#      30以上の要素の列インデックス: [2 1 0 2]
#      30以上の値: [30 35 40 45]

# 行と列の座標を組み合わせて表示
for i, (row, col) in enumerate(zip(large_indices[0], large_indices[1])):
    value = matrix[row, col]
    print(f"位置 ({row}, {col}): 値 {value}")

# 出力: 位置 (0, 2): 値 30
#      位置 (1, 1): 値 35
#      位置 (2, 0): 値 40
#      位置 (2, 2): 値 45

複雑な条件でのインデックス取得

データ分析での活用例:

# 売上データの分析例
dates = np.array(['2024-01', '2024-02', '2024-03', '2024-04', '2024-05'])
sales = np.array([120, 95, 150, 80, 175])
costs = np.array([80, 70, 100, 90, 110])

# 利益率20%以上の月を特定
profit_rates = (sales - costs) / sales * 100
high_profit_indices = np.where(profit_rates >= 20)

print("月:", dates)
print("売上:", sales)
print("コスト:", costs)
print("利益率:", np.round(profit_rates, 1))
print("高利益率の月:", dates[high_profit_indices])
print("該当月の利益率:", np.round(profit_rates[high_profit_indices], 1))

# 出力: 月: ['2024-01' '2024-02' '2024-03' '2024-04' '2024-05']
#      売上: [120  95 150  80 175]
#      コスト: [ 80  70 100  90 110]
#      利益率: [33.3 26.3 33.3 -12.5 37.1]
#      高利益率の月: ['2024-01' '2024-02' '2024-03' '2024-05']
#      該当月の利益率: [33.3 26.3 33.3 37.1]

複数条件でのインデックス抽出

高度なフィルタリング:

# 学生データの例
students = np.array(['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'])
math_scores = np.array([85, 72, 90, 88, 76])
english_scores = np.array([78, 85, 82, 92, 80])

# 数学80点以上かつ英語80点以上の学生を抽出
excellent_indices = np.where((math_scores >= 80) & (english_scores >= 80))

print("学生:", students)
print("数学点数:", math_scores)
print("英語点数:", english_scores)
print("優秀な学生:", students[excellent_indices])
print("該当学生の数学点数:", math_scores[excellent_indices])
print("該当学生の英語点数:", english_scores[excellent_indices])

# 出力: 学生: ['Alice' 'Bob' 'Charlie' 'Diana' 'Eve']
#      数学点数: [85 72 90 88 76]
#      英語点数: [78 85 82 92 80]
#      優秀な学生: ['Charlie' 'Diana' 'Eve']
#      該当学生の数学点数: [90 88 76]
#      該当学生の英語点数: [82 92 80]

np.where()インデックスのタプル**を返すので、要素を取り出すときはarr[...]のように使います。**条件に一致する場所を知りたいときは、np.where(条件)だけで簡単に抽出できます。次は実務での応用例を詳しく見ていきましょう。

実務で使える応用例とデータ処理

データクリーニングでの活用

欠損値と異常値の処理:

import numpy as np

# 欠損値(NaN)の処理
data_with_nan = np.array([1.5, np.nan, 3.2, np.nan, 4.8, 2.1])
print("元のデータ:", data_with_nan)

# NaNを平均値で置き換え
mean_value = np.nanmean(data_with_nan)  # NaNを無視して平均計算
cleaned_data = np.where(np.isnan(data_with_nan), mean_value, data_with_nan)

print("平均値:", round(mean_value, 2))
print("処理後:", cleaned_data)

# 出力: 元のデータ: [ 1.5  nan  3.2  nan  4.8  2.1]
#      平均値: 2.9
#      処理後: [1.5 2.9 3.2 2.9 4.8 2.1]

# 異常値の処理(外れ値を中央値で置き換え)
sensor_data = np.array([22.1, 23.5, 999.0, 21.8, 24.2, -50.0, 22.9])
median_temp = np.median(sensor_data[(sensor_data > 0) & (sensor_data < 100)])

# 0-50度の範囲外を中央値で置き換え
corrected_data = np.where((sensor_data < 0) | (sensor_data > 50), 
                         median_temp, sensor_data)

print("センサーデータ:", sensor_data)
print("中央値:", median_temp)
print("補正後:", corrected_data)

# 出力: センサーデータ: [ 22.1  23.5 999.  21.8  24.2 -50.   22.9]
#      中央値: 22.9
#      補正後: [22.1 23.5 22.9 21.8 24.2 22.9 22.9]

カテゴリ変換とラベリング

数値コードから文字ラベルへの変換:

# 性別コードの変換
gender_codes = np.array([0, 1, 0, 1, 0, 1, 0])
gender_labels = np.where(gender_codes == 0, "男性", "女性")

print("性別コード:", gender_codes)
print("性別ラベル:", gender_labels)

# 出力: 性別コード: [0 1 0 1 0 1 0]
#      性別ラベル: ['男性' '女性' '男性' '女性' '男性' '女性' '男性']

# 年齢層の分類
ages = np.array([15, 25, 35, 45, 55, 65, 75])
age_groups = np.where(ages < 20, "10代",
                     np.where(ages < 30, "20代",
                             np.where(ages < 40, "30代",
                                     np.where(ages < 50, "40代",
                                             np.where(ages < 60, "50代",
                                                     np.where(ages < 70, "60代", "70代以上"))))))

print("年齢:", ages)
print("年齢層:", age_groups)

# 出力: 年齢: [15 25 35 45 55 65 75]
#      年齢層: ['10代' '20代' '30代' '40代' '50代' '60代' '70代以上']

# より効率的な方法(np.selectを使用)
age_conditions = [
    ages < 20,
    (ages >= 20) & (ages < 30),
    (ages >= 30) & (ages < 40),
    (ages >= 40) & (ages < 50),
    (ages >= 50) & (ages < 60),
    (ages >= 60) & (ages < 70),
    ages >= 70
]
age_choices = ['10代', '20代', '30代', '40代', '50代', '60代', '70代以上']
age_groups_select = np.select(age_conditions, age_choices)

print("np.select使用:", age_groups_select)

財務・ビジネス分析での活用

売上データの分析と分類:

# 月次売上データの分析
months = np.array(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'])
sales = np.array([850000, 720000, 950000, 680000, 1100000, 920000])
targets = np.array([800000, 750000, 900000, 700000, 1000000, 850000])

# 目標達成状況の評価
achievement_rate = (sales / targets) * 100
performance = np.where(achievement_rate >= 110, "優秀",
                      np.where(achievement_rate >= 100, "目標達成",
                              np.where(achievement_rate >= 90, "要改善", "要対策")))

print("月:", months)
print("売上:", sales)
print("目標:", targets)
print("達成率:", np.round(achievement_rate, 1))
print("評価:", performance)

# ボーナス計算
base_bonus = 50000
bonus = np.where(achievement_rate >= 110, base_bonus * 1.5,
                np.where(achievement_rate >= 100, base_bonus,
                        np.where(achievement_rate >= 90, base_bonus * 0.5, 0)))

print("ボーナス:", bonus.astype(int))

# 出力例:
# 月: ['Jan' 'Feb' 'Mar' 'Apr' 'May' 'Jun']
# 売上: [ 850000  720000  950000  680000 1100000  920000]
# 目標: [ 800000  750000  900000  700000 1000000  850000]
# 達成率: [106.2  96.  105.6  97.1 110.  108.2]
# 評価: ['目標達成' '要改善' '目標達成' '要改善' '優秀' '目標達成']
# ボーナス: [50000 25000 50000 25000 75000 50000]

画像処理・科学計算での応用

画像データの処理例:

# 画像のような2次元データの処理例
image_data = np.random.randint(0, 256, size=(5, 5))  # 5x5のサンプル画像
print("元の画像データ:")
print(image_data)

# 閾値処理(二値化)
threshold = 128
binary_image = np.where(image_data >= threshold, 255, 0)

print(f"\n閾値 {threshold} での二値化:")
print(binary_image)

# コントラスト強調
contrast_enhanced = np.where(image_data > 200, 255,
                           np.where(image_data < 50, 0, image_data))

print("\nコントラスト強調:")
print(contrast_enhanced)

# ノイズ除去(中央値での置き換え)
# 非常に大きな値(255に近い)または小さな値(0に近い)をノイズとみなす
median_val = np.median(image_data)
denoised = np.where((image_data > 240) | (image_data < 15), 
                   median_val, image_data)

print(f"\nノイズ除去(中央値 {median_val} で置き換え):")
print(denoised)

時系列データの処理

株価データの分析例:

# 株価データの例
dates = np.array(['2024-01-01', '2024-01-02', '2024-01-03', 
                  '2024-01-04', '2024-01-05'])
prices = np.array([100, 105, 98, 110, 107])
volumes = np.array([1000, 1500, 2000, 1200, 1800])

# 移動平均の計算(簡易版)
moving_avg = np.array([100, 102.5, 101, 104.3, 105])  # 実際は rolling計算

# トレードシグナルの生成
# 価格が移動平均を上回り、かつ出来高が平均以上なら「買い」
avg_volume = np.mean(volumes)
signals = np.where((prices > moving_avg) & (volumes > avg_volume), "買い",
                  np.where(prices < moving_avg * 0.95, "売り", "ホールド"))

print("日付:", dates)
print("価格:", prices)
print("移動平均:", moving_avg)
print("出来高:", volumes)
print("シグナル:", signals)

# 出力例:
# 日付: ['2024-01-01' '2024-01-02' '2024-01-03' '2024-01-04' '2024-01-05']
# 価格: [100 105  98 110 107]
# 移動平均: [100.  102.5 101.  104.3 105. ]
# 出来高: [1000 1500 2000 1200 1800]
# シグナル: ['ホールド' 'ホールド' 'ホールド' '買い' 'ホールド']

データクリーニングやカテゴリラベリングでも、np.whereは非常に役立ちます。次に、使用時の注意点とベストプラクティスを確認しておきましょう。

注意点とベストプラクティス

配列サイズと形状の注意点

配列の形状一致の重要性:

import numpy as np

# 正しい例:同じ形状の配列
arr = np.array([1, 2, 3, 4, 5])
condition = arr > 3
true_vals = np.array([10, 20, 30, 40, 50])  # 同じ形状
false_vals = np.array([0, 0, 0, 0, 0])     # 同じ形状

result = np.where(condition, true_vals, false_vals)
print("正しい例:", result)
# 出力: 正しい例: [ 0  0 30 40 50]

# エラーになる例:形状が合わない
try:
    wrong_true = np.array([10, 20])  # 形状が合わない
    result_wrong = np.where(condition, wrong_true, false_vals)
except ValueError as e:
    print("エラー:", e)

# ブロードキャスティングを活用した正しい方法
scalar_true = 100  # スカラー値は自動的にブロードキャスト
result_broadcast = np.where(condition, scalar_true, false_vals)
print("ブロードキャスト例:", result_broadcast)
# 出力: ブロードキャスト例: [  0   0 100 100 100]

データ型の統一と変換

型の不一致による問題と対策:

# 型の混在による問題
numbers = np.array([1, 2, 3, 4, 5])
condition = numbers > 3

# 数値と文字列を混在させると object型になる
mixed_result = np.where(condition, "大きい", numbers)
print("混在結果:", mixed_result)
print("結果の型:", mixed_result.dtype)
# 出力: 混在結果: ['1' '2' '大きい' '大きい' '大きい']
#      結果の型: <U2

# 型を統一した正しい方法
# 方法1: すべて文字列にする
string_result = np.where(condition, "大きい", numbers.astype(str))
print("文字列統一:", string_result)

# 方法2: すべて数値にする(文字を数値コードに)
numeric_result = np.where(condition, 1, 0)  # 1="大きい", 0="小さい"
print("数値統一:", numeric_result)

# 方法3: 後から適切な型に変換
final_result = np.where(condition, 100, numbers)
print("数値のみ:", final_result)
print("結果の型:", final_result.dtype)

パフォーマンス最適化

大量データでの効率的な処理:

import time

# 大量データでのパフォーマンステスト
large_array = np.random.randint(0, 100, size=1000000)

# np.whereの使用
start_time = time.time()
result_where = np.where(large_array > 50, large_array * 2, large_array // 2)
where_time = time.time() - start_time

print(f"np.where処理時間: {where_time:.4f}秒")

# 通常のPythonループとの比較
start_time = time.time()
result_loop = []
for val in large_array:
    if val > 50:
        result_loop.append(val * 2)
    else:
        result_loop.append(val // 2)
result_loop = np.array(result_loop)
loop_time = time.time() - start_time

print(f"ループ処理時間: {loop_time:.4f}秒")
print(f"速度改善: {loop_time / where_time:.1f}倍高速")

# メモリ効率の確認
print(f"元配列サイズ: {large_array.nbytes / 1024 / 1024:.1f} MB")
print(f"結果配列サイズ: {result_where.nbytes / 1024 / 1024:.1f} MB")

複雑な条件の最適化

np.selectとの使い分け:

# 複雑な条件分岐での比較

# np.whereでネストした条件(読みにくい)
values = np.array([10, 25, 45, 65, 85])
nested_where = np.where(values < 20, "E",
                       np.where(values < 40, "D",
                               np.where(values < 60, "C",
                                       np.where(values < 80, "B", "A"))))

print("ネストしたnp.where:", nested_where)

# np.selectを使った読みやすい方法
conditions = [
    values < 20,
    (values >= 20) & (values < 40),
    (values >= 40) & (values < 60),
    (values >= 60) & (values < 80),
    values >= 80
]
choices = ['E', 'D', 'C', 'B', 'A']
select_result = np.select(conditions, choices)

print("np.select:", select_result)
print("値:", values)

# 出力: ネストしたnp.where: ['E' 'D' 'C' 'B' 'A']
#      np.select: ['E' 'D' 'C' 'B' 'A']
#      値: [10 25 45 65 85]

エラーハンドリングと例外処理

安全なnp.where使用のためのチェック:

def safe_where(condition, true_val, false_val, array_name="array"):
    """安全なnp.where実行のためのラッパー関数"""
    try:
        # 条件がブール配列かチェック
        if not isinstance(condition, np.ndarray) or condition.dtype != bool:
            raise ValueError(f"{array_name}: 条件はブール配列である必要があります")
        
        # 形状の確認
        if hasattr(true_val, 'shape') and hasattr(false_val, 'shape'):
            if true_val.shape != condition.shape or false_val.shape != condition.shape:
                print(f"警告: 配列の形状が一致しません。ブロードキャストを試行します。")
        
        result = np.where(condition, true_val, false_val)
        return result
        
    except Exception as e:
        print(f"エラー in {array_name}: {e}")
        return None

# 使用例
test_array = np.array([1, 2, 3, 4, 5])
test_condition = test_array > 3

# 正常なケース
good_result = safe_where(test_condition, "大", "小", "test_array")
print("正常結果:", good_result)

# エラーケース
bad_condition = np.array([1, 2, 3])  # 形状が合わない
bad_result = safe_where(bad_condition, "大", "小", "bad_array")

メモリ効率とパフォーマンス

メモリ使用量の最適化:

import sys

# メモリ効率の比較
def compare_memory_usage():
    # 大きな配列でのメモリ使用量比較
    size = 100000
    original_array = np.random.randint(0, 100, size=size)
    
    print(f"元配列のメモリ使用量: {original_array.nbytes / 1024:.1f} KB")
    
    # 方法1: 新しい配列を作成
    new_array = np.where(original_array > 50, 1, 0)
    print(f"新配列のメモリ使用量: {new_array.nbytes / 1024:.1f} KB")
    
    # 方法2: インプレース操作(可能な場合)
    # 注意: np.whereはインプレース操作をサポートしていないため、
    # 他の方法を考慮する必要がある
    
    # メモリ効率的な代替案
    mask = original_array > 50
    efficient_result = mask.astype(int)  # ブール配列を整数に変換
    print(f"効率的な方法: {efficient_result.nbytes / 1024:.1f} KB")
    
    return original_array, new_array, efficient_result

original, new, efficient = compare_memory_usage()

# 結果の確認
print("最初の10要素の比較:")
print("元の配列:", original[:10])
print("np.where結果:", new[:10])
print("効率的な方法:", efficient[:10])

実践的な応用パターンと高度な使用法

pandasとの連携

DataFrameでのnp.where活用:

import pandas as pd
import numpy as np

# サンプルデータの作成
df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'],
    'age': [25, 30, 35, 28, 32],
    'salary': [50000, 60000, 70000, 55000, 65000],
    'department': ['IT', 'Sales', 'IT', 'HR', 'Sales']
})

print("元のデータ:")
print(df)

# np.whereをpandasで使用
# 年齢による分類
df['age_group'] = np.where(df['age'] < 30, 'Young', 'Senior')

# 給与による評価
df['salary_level'] = np.where(df['salary'] >= 60000, 'High',
                             np.where(df['salary'] >= 55000, 'Medium', 'Low'))

# 複合条件:IT部門かつ給与60000以上なら昇進候補
df['promotion_candidate'] = np.where((df['department'] == 'IT') & 
                                    (df['salary'] >= 60000), 'Yes', 'No')

print("\n処理後のデータ:")
print(df)

# より複雑な例:部門別の給与調整
salary_adjustment = np.where(df['department'] == 'IT', df['salary'] * 1.1,
                           np.where(df['department'] == 'Sales', df['salary'] * 1.05,
                                   df['salary'] * 1.02))

df['adjusted_salary'] = salary_adjustment.astype(int)

print("\n給与調整後:")
print(df[['name', 'department', 'salary', 'adjusted_salary']])

機械学習での前処理

特徴量エンジニアリングでの活用:

# 機械学習用のデータ前処理例
from sklearn.datasets import make_classification
import matplotlib.pyplot as plt

# サンプルデータの生成
X, y = make_classification(n_samples=1000, n_features=2, n_redundant=0, 
                          n_informative=2, n_clusters_per_class=1, random_state=42)

# 特徴量の変換
# 特徴量1を3つのカテゴリに分類
feature1_categories = np.where(X[:, 0] < -0.5, 0,
                              np.where(X[:, 0] < 0.5, 1, 2))

# 特徴量2を二値化
feature2_binary = np.where(X[:, 1] > 0, 1, 0)

# 特徴量の組み合わせ
interaction_feature = np.where((feature1_categories == 2) & (feature2_binary == 1), 1, 0)

# 外れ値の処理
# 各特徴量で95パーセンタイルを超える値を上限に制限
percentile_95_f1 = np.percentile(X[:, 0], 95)
percentile_5_f1 = np.percentile(X[:, 0], 5)
X_clipped_f1 = np.where(X[:, 0] > percentile_95_f1, percentile_95_f1,
                       np.where(X[:, 0] < percentile_5_f1, percentile_5_f1, X[:, 0]))

print("元の特徴量1の範囲:", f"{X[:, 0].min():.2f} - {X[:, 0].max():.2f}")
print("クリップ後の範囲:", f"{X_clipped_f1.min():.2f} - {X_clipped_f1.max():.2f}")
print("カテゴリ分布:", np.bincount(feature1_categories))
print("二値化分布:", np.bincount(feature2_binary))
print("相互作用特徴量の分布:", np.bincount(interaction_feature))

# 新しい特徴量マトリックスの作成
X_engineered = np.column_stack([
    X_clipped_f1,
    X[:, 1],
    feature1_categories,
    feature2_binary,
    interaction_feature
])

print("元の特徴量形状:", X.shape)
print("処理後の特徴量形状:", X_engineered.shape)

時系列データの高度な処理

株価データの技術的分析:

# 株価データの技術的指標計算
def calculate_technical_indicators(prices):
    """株価データから技術的指標を計算"""
    
    # 移動平均
    window = 5
    moving_avg = np.convolve(prices, np.ones(window)/window, mode='valid')
    # 配列サイズを合わせるため前にNaNを追加
    moving_avg = np.concatenate([np.full(window-1, np.nan), moving_avg])
    
    # RSI(簡易版)
    price_changes = np.diff(prices)
    gains = np.where(price_changes > 0, price_changes, 0)
    losses = np.where(price_changes < 0, -price_changes, 0)
    
    # 平均利得と平均損失(簡易計算)
    avg_gain = np.mean(gains[gains > 0]) if len(gains[gains > 0]) > 0 else 1
    avg_loss = np.mean(losses[losses > 0]) if len(losses[losses > 0]) > 0 else 1
    
    rsi = 100 - (100 / (1 + avg_gain / avg_loss))
    
    # トレードシグナル
    # 価格が移動平均を上回り、RSIが過売り状態(30以下)から回復なら買い
    buy_signals = np.where((prices[4:] > moving_avg[4:]) & (rsi < 50), 1, 0)
    # 価格が移動平均を下回り、RSIが過買い状態(70以上)なら売り
    sell_signals = np.where((prices[4:] < moving_avg[4:]) & (rsi > 70), 1, 0)
    
    return {
        'moving_avg': moving_avg,
        'rsi': rsi,
        'buy_signals': buy_signals,
        'sell_signals': sell_signals
    }

# サンプル株価データ
np.random.seed(42)
days = 20
base_price = 100
price_changes = np.random.normal(0, 2, days)
prices = base_price + np.cumsum(price_changes)

# 技術的指標の計算
indicators = calculate_technical_indicators(prices)

print("株価データ:")
for i, price in enumerate(prices):
    ma = indicators['moving_avg'][i]
    signal = ""
    if i >= 4:  # シグナルは5日目から
        if indicators['buy_signals'][i-4] == 1:
            signal = " [買い]"
        elif indicators['sell_signals'][i-4] == 1:
            signal = " [売り]"
    
    print(f"Day {i+1}: {price:.2f} (MA: {ma:.2f if not np.isnan(ma) else 'N/A'}){signal}")

print(f"\nRSI: {indicators['rsi']:.1f}")
print(f"買いシグナル数: {np.sum(indicators['buy_signals'])}")
print(f"売りシグナル数: {np.sum(indicators['sell_signals'])}")

パフォーマンス比較と最適化

他の手法との性能比較

np.where vs 他の条件処理手法:

import time
import numpy as np

def performance_comparison(size=1000000):
    """様々な条件処理手法のパフォーマンス比較"""
    
    # テストデータの準備
    data = np.random.randint(0, 100, size=size)
    condition = data > 50
    
    results = {}
    
    # 1. np.where
    start = time.time()
    result1 = np.where(condition, data * 2, data // 2)
    results['np.where'] = time.time() - start
    
    # 2. ブール索引
    start = time.time()
    result2 = data.copy()
    result2[condition] = data[condition] * 2
    result2[~condition] = data[~condition] // 2
    results['boolean_indexing'] = time.time() - start
    
    # 3. np.select
    start = time.time()
    result3 = np.select([condition, ~condition], [data * 2, data // 2])
    results['np.select'] = time.time() - start
    
    # 4. 手動ループ(小さなサイズでのみテスト)
    if size <= 10000:
        start = time.time()
        result4 = np.empty_like(data)
        for i in range(len(data)):
            if condition[i]:
                result4[i] = data[i] * 2
            else:
                result4[i] = data[i] // 2
        results['manual_loop'] = time.time() - start
    
    # 結果の一致確認
    print(f"データサイズ: {size:,}")
    print("結果の一致確認:")
    print(f"np.where vs boolean_indexing: {np.array_equal(result1, result2)}")
    print(f"np.where vs np.select: {np.array_equal(result1, result3)}")
    
    # パフォーマンス結果
    print("\nパフォーマンス結果(秒):")
    fastest_time = min(results.values())
    for method, elapsed_time in sorted(results.items(), key=lambda x: x[1]):
        speedup = fastest_time / elapsed_time
        print(f"{method:20}: {elapsed_time:.6f}秒 ({speedup:.1f}x)")
    
    return results

# 異なるサイズでのテスト
print("=== 小サイズ(10,000要素)===")
small_results = performance_comparison(10000)

print("\n=== 中サイズ(100,000要素)===")
medium_results = performance_comparison(100000)

print("\n=== 大サイズ(1,000,000要素)===")
large_results = performance_comparison(1000000)

メモリ使用量の最適化

メモリ効率的なnp.where使用:

def memory_efficient_processing(data, chunk_size=10000):
    """メモリ効率的な大量データ処理"""
    
    total_size = len(data)
    result = np.empty_like(data)
    
    # チャンク単位での処理
    for start_idx in range(0, total_size, chunk_size):
        end_idx = min(start_idx + chunk_size, total_size)
        chunk = data[start_idx:end_idx]
        
        # チャンクごとにnp.whereを適用
        chunk_result = np.where(chunk > np.median(chunk), 
                               chunk * 1.5, 
                               chunk * 0.8)
        
        result[start_idx:end_idx] = chunk_result
        
        # 進捗表示
        progress = (end_idx / total_size) * 100
        print(f"\r処理進捗: {progress:.1f}%", end="", flush=True)
    
    print()  # 改行
    return result

# 大量データでのテスト
large_data = np.random.normal(100, 15, size=100000)
print("メモリ効率的な処理開始...")
processed_data = memory_efficient_processing(large_data, chunk_size=5000)

print(f"元データの統計: 平均={np.mean(large_data):.2f}, 標準偏差={np.std(large_data):.2f}")
print(f"処理後の統計: 平均={np.mean(processed_data):.2f}, 標準偏差={np.std(processed_data):.2f}")

まとめとベストプラクティス

np.whereの使い分け指針

適切な手法の選択基準:

条件推奨手法理由
単純な条件分岐np.where(condition, true_val, false_val)最もシンプルで高速
複数条件(3つ以上)np.select()可読性が高い
インデックス取得np.where(condition)専用機能として最適
大量データチャンク処理 + np.whereメモリ効率が良い
パンダスDataFramedf.where() または np.where()データ構造に適合

効率的なコーディングパターン

推奨されるパターン:

# ✅ 良い例:明確で効率的
def process_sales_data(sales, targets):
    """売上データの効率的な処理"""
    
    # 事前に条件を計算
    achievement_rate = sales / targets
    high_achievers = achievement_rate >= 1.1
    low_performers = achievement_rate < 0.9
    
    # 一度に複数の分類を実行
    performance_category = np.where(high_achievers, "優秀",
                                   np.where(low_performers, "要改善", "普通"))
    
    bonus_multiplier = np.where(high_achievers, 1.5,
                               np.where(low_performers, 0.5, 1.0))
    
    return {
        'category': performance_category,
        'bonus_multiplier': bonus_multiplier,
        'achievement_rate': achievement_rate
    }

# ❌ 避けるべき例:非効率
def inefficient_processing(sales, targets):
    """非効率なデータ処理例"""
    
    # 毎回条件を再計算(無駄)
    category = np.where(sales/targets >= 1.1, "優秀",
                       np.where(sales/targets < 0.9, "要改善", "普通"))
    
    # 個別に処理(まとめて処理できる)
    bonus = []
    for i in range(len(sales)):
        if sales[i]/targets[i] >= 1.1:
            bonus.append(1.5)
        elif sales[i]/targets[i] < 0.9:
            bonus.append(0.5)
        else:
            bonus.append(1.0)
    
    return category, np.array(bonus)

エラー回避のチェックリスト

開発時の確認事項:

def robust_where_operation(condition, true_val, false_val, name="operation"):
    """堅牢なnp.where操作のためのラッパー"""
    
    # 1. 型の確認
    if not isinstance(condition, np.ndarray) or condition.dtype != bool:
        raise TypeError(f"{name}: 条件はブール型のNumPy配列である必要があります")
    
    # 2. 形状の確認
    try:
        # ブロードキャストが可能かテスト
        np.broadcast_arrays(condition, true_val, false_val)
    except ValueError as e:
        raise ValueError(f"{name}: 配列の形状が互換性がありません: {e}")
    
    # 3. NaN値の確認
    if isinstance(true_val, np.ndarray) and np.issubdtype(true_val.dtype, np.floating):
        if np.any(np.isnan(true_val)):
            print(f"警告 {name}: true_valにNaN値が含まれています")
    
    # 4. 実行
    try:
        result = np.where(condition, true_val, false_val)
        return result
    except Exception as e:
        raise RuntimeError(f"{name}: np.where実行中にエラーが発生: {e}")

# 使用例
test_data = np.array([1, 2, 3, 4, 5])
test_condition = test_data > 3

try:
    safe_result = robust_where_operation(test_condition, "大", "小", "test_operation")
    print("安全な実行結果:", safe_result)
except Exception as e:
    print("エラー:", e)

最終的な推奨事項

np.whereを効果的に使用するための要点:

  1. 単純性を重視:複雑なネストよりもnp.selectを検討
  2. 型の一致:異なる型の混在を避け、必要に応じて事前変換
  3. メモリ効率:大量データは分割処理を検討
  4. 可読性:変数名と コメントで意図を明確に
  5. テスト:期待する結果と実際の結果を必ず確認

np.whereはPythonでデータ処理を行う際に非常に重宝される関数です。

条件に応じた値の選択や、インデックスの取得など、多くの場面で活用できます。

基本的な使い方

  • np.where(条件, 真, 偽):条件に応じた値の切り替え
  • np.where(条件):条件を満たす要素のインデックス取得

実用的な応用場面

  • データクリーニング:欠損値処理、異常値の修正
  • カテゴリ変換:数値コードから文字ラベルへの変換
  • ビジネス分析:売上評価、顧客分類
  • 機械学習:特徴量エンジニアリング、前処理

コメント

タイトルとURLをコピーしました