numpy.bincount()

2025-05-26

numpy.bincount()は、NumPyライブラリにH含まれる非常に便利な関数で、主に非負の整数の配列において、各値が何回出現するか(頻度)を効率的に数えるために使われます。

簡単に言うと、与えられた配列の要素を「ビン」(区間)に分類し、それぞれのビンに含まれる要素の数を数え上げる関数です。イメージとしては、数値ごとのヒストグラムを作成するようなものです。

主な特徴と使い方

  1. 入力配列は非負の整数: bincount()は、0以上の整数値を含む1次元配列を引数に取ります。負の数や浮動小数点数が含まれているとエラーになります。

  2. 出力配列: 出力は1次元のNumPy配列になります。この配列のインデックスが元の配列の値を表し、そのインデックスに対応する値がその値の出現回数を示します。

    • 例えば、np.bincount([1, 2, 1, 3, 2]) の場合、出力は [0, 2, 2, 1] となります。
      • インデックス0: 0の出現回数 (0回)
      • インデックス1: 1の出現回数 (2回)
      • インデックス2: 2の出現回数 (2回)
      • インデックス3: 3の出現回数 (1回)
    • 出力配列の長さは、入力配列の最大値 + 1 となります。
  3. weights 引数:

    • weights引数を使用すると、単なる出現回数だけでなく、重みを考慮した合計値を計算できます。
    • weightsは入力配列と同じ形状の配列である必要があります。
    • この場合、出力配列の各インデックスに対応する値は、そのインデックスの元の配列の要素に対応するweightsの要素の合計になります。
    • 例: np.bincount([0, 1, 1, 2, 2, 2], weights=[0.3, 0.5, 0.2, 0.7, 1., -0.6]) の場合、
      • インデックス0: 0.3 (0に対応するweightsの値)
      • インデックス1: 0.5 + 0.2 = 0.7 (1に対応するweightsの値の合計)
      • インデックス2: 0.7 + 1.0 + (-0.6) = 1.1 (2に対応するweightsの値の合計)
      • 出力は [0.3, 0.7, 1.1] となります。
  4. minlength 引数:

    • minlength引数を使用すると、出力配列の最小の長さを指定できます。
    • 入力配列の最大値 + 1 がminlengthよりも小さい場合、出力配列はminlengthの長さになります。
    • 例: np.bincount([1, 2], minlength=5) の場合、出力は [0, 1, 1, 0, 0] となります。

ユースケース

  • データサイエンス・機械学習: 特徴量の離散化や、ラベルの分布の確認などにも利用されます。
  • 画像処理: ピクセル値の頻度を計算したり、特定のピクセル値の領域を特定したりするのに使われることがあります。
  • ヒストグラムの作成: 整数値データの分布を素早く把握するのに役立ちます。
  • カテゴリデータの集計: 特定のカテゴリに属するアイテムの数を数える場合に非常に便利です。
import numpy as np

# 基本的な使い方
arr1 = np.array([0, 1, 1, 2, 0, 3, 2, 1])
counts1 = np.bincount(arr1)
print(f"基本例: {counts1}") # 出力: [3 3 2 1] (0が3回, 1が3回, 2が2回, 3が1回)

# weights引数の使用例
arr2 = np.array([0, 1, 1, 2, 2, 2])
weights = np.array([1.0, 0.5, 1.5, 2.0, 0.3, 0.7])
weighted_sums = np.bincount(arr2, weights=weights)
print(f"weights使用例: {weighted_sums}") # 出力: [1.  2.  3. ] (0: 1.0, 1: 0.5+1.5=2.0, 2: 2.0+0.3+0.7=3.0)

# minlength引数の使用例
arr3 = np.array([1, 3])
counts3 = np.bincount(arr3, minlength=5)
print(f"minlength使用例: {counts3}") # 出力: [0 1 0 1 0] (0, 2, 4番目のビンは0で埋められる)

# 負の数を含むとエラー
try:
    np.bincount([-1, 0, 1])
except ValueError as e:
    print(f"エラー例 (負の数): {e}") # 出力: ValueError: bincount: array must be non-negative


ValueError: bincount: array must be non-negative (配列に負の数がある場合)

  • トラブルシューティング:
    • 入力データを確認する: まず、bincount()に渡す配列に負の数が含まれていないかを確認します。
      import numpy as np
      arr_with_neg = np.array([-1, 0, 1, 2])
      # np.bincount(arr_with_neg) # これを実行するとエラー
      
    • 負の数を処理する:
      • 負の数をフィルタリングする: 負の数を計算から除外する必要がある場合は、フィルタリングを行います。

        arr_filtered = arr_with_neg[arr_with_neg >= 0]
        print(np.bincount(arr_filtered))
        
      • 負の数をマッピングする: 負の数を特定の非負の値にマッピングする必要がある場合は、変換を行います。例えば、-10として扱いたい場合など。

        arr_mapped = np.where(arr_with_neg < 0, 0, arr_with_neg)
        print(np.bincount(arr_mapped))
        
      • 別の方法を検討する: 負の数も頻度計算に含めたい場合は、bincount()は適していません。collections.Counter(Python標準ライブラリ)や、Pandasのvalue_counts()、あるいはNumPyのnp.unique()return_counts=Trueを組み合わせるなどの方法を検討してください。

        from collections import Counter
        print(Counter(arr_with_neg))
        
        import pandas as pd
        s = pd.Series(arr_with_neg)
        print(s.value_counts().sort_index())
        
        unique_elements, counts = np.unique(arr_with_neg, return_counts=True)
        print(dict(zip(unique_elements, counts)))
        
  • エラーの原因: bincount()の入力配列には、**非負の整数(0以上の整数)**しか含めることができません。負の数が一つでも含まれていると、このエラーが発生します。

TypeError: bincount: array must be integral type (配列が整数型ではない場合)

  • トラブルシューティング:
    • データ型を確認する: arr.dtypeで配列のデータ型を確認します。
      arr_float = np.array([0.5, 1.0, 1.5, 2.0])
      # np.bincount(arr_float) # これを実行するとエラー
      
    • 整数型に変換する: 浮動小数点数を整数として扱いたい場合は、astype(int)などで明示的に整数型に変換します。ただし、この変換は小数点以下を切り捨てるため、意図しない結果になる可能性があります。
      arr_converted = arr_float.astype(int)
      print(f"変換後の配列: {arr_converted}")
      print(np.bincount(arr_converted))
      
    • 別の方法を検討する: 浮動小数点数の正確な頻度を数えたい場合は、bincount()は不適切です。np.histogramcollections.Counter(ただし、浮動小数点数の場合は完全に一致しないと別物として扱われる点に注意)、またはPandasのvalue_counts()を検討してください。

ValueError: weights and array must have the same length (weightsの長さが異なる場合)

  • トラブルシューティング:
    • 両方の配列の長さを確認する: len(arr)len(weights)で長さを比較します。
      arr = np.array([0, 1, 2])
      weights_mismatch = np.array([10, 20])
      # np.bincount(arr, weights=weights_mismatch) # これを実行するとエラー
      
    • 長さを一致させる: weights配列が入力配列と同じ長さになるように調整します。データが足りない場合は埋めるか、過剰な場合は切り詰めるか、どちらかの配列の生成ロジックを見直す必要があります。
  • エラーの原因: weights引数を使用する場合、weights配列は、主入力配列と同じ数の要素を持っている必要があります。長さが一致しないとこのエラーが発生します。

出力配列の長さが意図と異なる (最大値が考慮されていない、または短すぎる)

  • トラブルシューティング:
    • minlength引数を使用する: 特定の長さの出力配列が必要な場合や、後続の処理で固定の長さの配列が求められる場合は、minlength引数を使用して出力配列の最小長を指定します。
      arr = np.array([0, 1, 2])
      print(np.bincount(arr))           # 出力: [1 1 1] (長さ3)
      print(np.bincount(arr, minlength=5)) # 出力: [1 1 1 0 0] (長さ5)
      
    • 最大値に注意する: 入力配列に非常に大きな値が含まれている場合、出力配列も非常に長くなる可能性があります。これはメモリ消費や処理時間に影響を与える可能性があります。もし最大値が意図的に小さい範囲に収まるようにしたい場合は、データを事前に加工することを検討してください。
  • 問題の原因: bincount()の出力配列の長さは、デフォルトで入力配列の最大値+1になります。minlengthを指定しないと、この振る舞いを変更することはできません。
  • トラブルシューティング:
    • 引数の渡し方を確認する: minlengthはキーワード引数として渡す必要があります。
      arr = np.array([0, 1, 2])
      # np.bincount(arr, None, 5) # これを実行するとエラー
      print(np.bincount(arr, minlength=5)) # 正しい渡し方
      
  • エラーの原因: bincount()は、基本的にarrweightsの2つの位置引数(positional arguments)しか取りません(minlengthはキーワード引数)。もし、誤って3つ以上の引数を位置引数として渡すとこのエラーが発生します。

numpy.bincount()を使う際は、以下の点を常に意識することが重要です。

  • 出力配列の長さは、入力配列の最大値+1が基本であり、固定長にしたい場合はminlengthを使用すること
  • weightsを使用する場合は、入力配列と同じ長さであること
  • 入力配列は整数型であること
  • 入力配列は非負の整数であること


基本的な使用法:要素の出現回数を数える

最も基本的な使い方で、配列内の各整数値が何回出現するかを数えます。

import numpy as np

# 例1: シンプルな整数配列
arr1 = np.array([0, 1, 1, 2, 0, 3, 2, 1, 4])
counts1 = np.bincount(arr1)
print(f"配列1: {arr1}")
print(f"結果1: {counts1}")
# 出力例:
# 配列1: [0 1 1 2 0 3 2 1 4]
# 結果1: [2 3 2 1 1]
# 解説:
# インデックス0: 0が2回出現
# インデックス1: 1が3回出現
# インデックス2: 2が2回出現
# インデックス3: 3が1回出現
# インデックス4: 4が1回出現

# 例2: 0を含まない配列
arr2 = np.array([5, 2, 8, 2, 5])
counts2 = np.bincount(arr2)
print(f"\n配列2: {arr2}")
print(f"結果2: {counts2}")
# 出力例:
# 配列2: [5 2 8 2 5]
# 結果2: [0 0 2 0 0 2 0 0 1]
# 解説:
# 出力配列の長さは入力配列の最大値 (8) + 1 = 9 となります。
# インデックス0, 1, 3, 4, 6, 7 には元の配列に該当する値がないため、0が入ります。
# インデックス2: 2が2回出現
# インデックス5: 5が2回出現
# インデックス8: 8が1回出現

weights 引数の使用:重み付けされた合計を計算する

weights引数を使用すると、単なる出現回数ではなく、各要素に対応する重みの合計を計算できます。これは、たとえば、あるカテゴリに属するアイテムの総量を計算する際などに便利です。

import numpy as np

# 例3: 重み付けされたbincount
item_categories = np.array([0, 1, 1, 2, 0, 2]) # カテゴリID
item_values = np.array([10.0, 5.0, 15.0, 20.0, 8.0, 12.0]) # 各アイテムの価値

# 各カテゴリIDに対応するitem_valuesの合計を計算
total_values_by_category = np.bincount(item_categories, weights=item_values)
print(f"カテゴリID: {item_categories}")
print(f"アイテムの価値: {item_values}")
print(f"カテゴリごとの合計価値: {total_values_by_category}")
# 出力例:
# カテゴリID: [0 1 1 2 0 2]
# アイテムの価値: [10.  5. 15. 20.  8. 12.]
# カテゴリごとの合計価値: [18.  20.  32.]
# 解説:
# インデックス0 (カテゴリ0): 10.0 (最初の0) + 8.0 (2番目の0) = 18.0
# インデックス1 (カテゴリ1): 5.0 (最初の1) + 15.0 (2番目の1) = 20.0
# インデックス2 (カテゴリ2): 20.0 (最初の2) + 12.0 (2番目の2) = 32.0

minlength 引数の使用:出力配列の最小長を指定する

minlength引数は、出力配列の最小の長さを保証したい場合に役立ちます。これは、例えば、データに現れないカテゴリに対しても、0で埋められたビンを確実に含めたい場合に便利です。

import numpy as np

# 例4: minlengthを指定
sparse_data = np.array([1, 4]) # 0, 2, 3などの値は含まれていない

# minlengthを指定しない場合
counts_sparse_default = np.bincount(sparse_data)
print(f"\n疎なデータ: {sparse_data}")
print(f"minlengthなし: {counts_sparse_default}")
# 出力例:
# 疎なデータ: [1 4]
# minlengthなし: [0 1 0 0 1] (長さ5, 最大値4+1)

# minlengthを指定する場合
counts_sparse_minlength = np.bincount(sparse_data, minlength=7)
print(f"minlength=7を指定: {counts_sparse_minlength}")
# 出力例:
# minlength=7を指定: [0 1 0 0 1 0 0] (長さ7, 指定された最小長)
# 解説:
# 元のデータにはないインデックス (0, 2, 3, 5, 6) には0が挿入されます。

複数の配列を組み合わせる応用例:条件付きの集計

bincountは、他のNumPy操作と組み合わせることで、より複雑な集計を行うことができます。

import numpy as np

# 例5: 条件付きの集計 (商品の購入頻度と購入金額)
product_ids = np.array([101, 102, 101, 103, 102, 101, 104]) # 商品ID
purchase_amounts = np.array([1500, 2000, 1000, 500, 2500, 1200, 3000]) # 購入金額

# 商品IDを0から始まる連続した整数にマッピング (bincountの要件のため)
# ここでは簡単のため、ユニークなIDの最小値を引いてオフセットを調整
unique_ids = np.unique(product_ids)
# ID: 101 -> 0, 102 -> 1, 103 -> 2, 104 -> 3 に変換
mapped_ids = product_ids - np.min(product_ids)
print(f"元の商品ID: {product_ids}")
print(f"マッピングされたID: {mapped_ids}")

# 各商品IDの購入回数をカウント
purchase_counts = np.bincount(mapped_ids)
print(f"\n商品ごとの購入回数: {purchase_counts}")
# (インデックス0=ID101, インデックス1=ID102, ...)
# 出力例: [3 2 1 1] (101が3回, 102が2回, 103が1回, 104が1回)

# 各商品IDの購入金額の合計
total_purchase_amounts = np.bincount(mapped_ids, weights=purchase_amounts)
print(f"商品ごとの購入金額合計: {total_purchase_amounts}")
# 出力例: [3700. 4500.  500. 3000.]
# (101: 1500+1000+1200=3700, 102: 2000+2500=4500, ...)

# bincountの結果に元のIDを対応させるためのヘルパー関数 (オプション)
def map_bincount_result(bincount_result, original_ids):
    min_id = np.min(original_ids)
    max_id = np.max(original_ids)
    result_dict = {}
    for i in range(len(bincount_result)):
        original_id = i + min_id
        if original_id in original_ids: # 元のIDが存在するか確認
            result_dict[original_id] = bincount_result[i]
    return result_dict

print("\n(参考) 商品IDと購入回数の辞書形式:")
print(map_bincount_result(purchase_counts, product_ids))

print("\n(参考) 商品IDと購入金額合計の辞書形式:")
print(map_bincount_result(total_purchase_amounts, product_ids))
  • 負の整数や浮動小数点数の場合: bincountは非負の整数に特化しています。負の数や浮動小数点数を扱う場合は、別の方法を検討します。
    • collections.Counter: あらゆるハッシュ可能なオブジェクト(文字列、タプル、負の整数、浮動小数点数など)の出現回数を数えられます。
      from collections import Counter
      data = [-1, 0, 1.5, 0, -1, 2.0]
      print(f"Counterの結果: {Counter(data)}")
      # 出力: Counter({-1: 2, 0: 2, 1.5: 1, 2.0: 1})
      
    • pandas.Series.value_counts(): Pandasを使用している場合、非常に便利で、欠損値の扱いなども柔軟です。
      import pandas as pd
      s = pd.Series([-1, 0, 1.5, 0, -1, 2.0])
      print(f"Pandas value_countsの結果:\n{s.value_counts().sort_index()}")
      # 出力:
      # Pandas value_countsの結果:
      # -1.0    2
      #  0.0    2
      #  1.5    1
      #  2.0    1
      # dtype: int64
      
    • numpy.histogram: 連続値のデータをビンに区切って頻度を数える場合に最適です。
      data_float = np.array([0.1, 0.5, 1.2, 1.8, 2.3, 2.9])
      # ビンを定義 (例: 0-1, 1-2, 2-3)
      hist, bin_edges = np.histogram(data_float, bins=[0, 1, 2, 3])
      print(f"histogramの結果 (頻度): {hist}")      # 各ビンの要素数
      print(f"histogramの結果 (ビン境界): {bin_edges}") # ビンの境界
      # 出力:
      # histogramの結果 (頻度): [2 2 2]
      # histogramの結果 (ビン境界): [0 1 2 3]
      


collections.Counter (Python標準ライブラリ)

  • コード例:

    from collections import Counter
    import numpy as np
    
    # 負の数を含む整数
    data_int = np.array([-2, 0, 1, 0, -2, 3, 1])
    counts_int = Counter(data_int.tolist()) # CounterはPythonリストを直接渡せる
    print(f"collections.Counter (整数): {counts_int}")
    # 出力例: Counter({-2: 2, 0: 2, 1: 2, 3: 1})
    
    # 浮動小数点数
    data_float = np.array([0.1, 0.5, 0.1, 1.2, 0.5, 2.3])
    counts_float = Counter(data_float.tolist())
    print(f"collections.Counter (浮動小数点数): {counts_float}")
    # 出力例: Counter({0.1: 2, 0.5: 2, 1.2: 1, 2.3: 1})
    
    # 文字列
    data_str = ["apple", "banana", "apple", "orange", "banana"]
    counts_str = Counter(data_str)
    print(f"collections.Counter (文字列): {counts_str}")
    # 出力例: Counter({'apple': 2, 'banana': 2, 'orange': 1})
    
  • 欠点:

    • NumPy配列ではないため、NumPyの他の操作と組み合わせる際に変換が必要な場合がある。
    • 大規模な数値データセットの場合、bincountに比べてパフォーマンスが劣る可能性がある(特に数値が密に分布している場合)。
  • 利点:

    • シンプルで直感的なAPI。
    • bincountの制約を受けない(負の数、浮動小数点数、文字列などに対応)。
    • 結果が辞書形式で返されるため、視認性が良い。

pandas.Series.value_counts() (Pandasライブラリ)

  • コード例:

    import numpy as np
    import pandas as pd
    
    # 負の数を含む整数
    data_int = np.array([-2, 0, 1, 0, -2, 3, 1])
    s_int = pd.Series(data_int)
    counts_int = s_int.value_counts().sort_index() # sort_index()で値の小さい順にソート
    print(f"Pandas value_counts (整数):\n{counts_int}")
    # 出力例:
    # Pandas value_counts (整数):
    # -2    2
    #  0    2
    #  1    2
    #  3    1
    # dtype: int64
    
    # 浮動小数点数とNaN
    data_float = np.array([0.1, 0.5, np.nan, 0.1, 1.2, 0.5, np.nan, 2.3])
    s_float = pd.Series(data_float)
    counts_float = s_float.value_counts(dropna=False).sort_index() # dropna=FalseでNaNもカウント
    print(f"\nPandas value_counts (浮動小数点数とNaN):\n{counts_float}")
    # 出力例:
    # Pandas value_counts (浮動小数点数とNaN):
    # 0.1    2
    # 0.5    2
    # 1.2    1
    # 2.3    1
    # NaN    2
    # dtype: int64
    
    # 文字列
    data_str = ["apple", "banana", "apple", "orange", "banana"]
    s_str = pd.Series(data_str)
    counts_str = s_str.value_counts()
    print(f"\nPandas value_counts (文字列):\n{counts_str}")
    # 出力例:
    # Pandas value_counts (文字列):
    # apple     2
    # banana    2
    # orange    1
    # Name: count, dtype: int64
    
  • 欠点:

    • Pandasライブラリのインストールとインポートが必要。
    • NumPy単体よりもオーバーヘッドがある。
  • 利点:

    • bincountの制約を受けない(負の数、浮動小数点数、文字列、NaNなどに対応)。
    • 結果がPandas Seriesとして返されるため、その後のPandas操作(ソート、フィルタリングなど)が容易。
    • 欠損値のカウントを含めるか除外するか選択できる。
    • 頻度をパーセンテージで表示するオプションもある (normalize=True)。

numpy.unique() と return_counts=True

  • コード例:

    import numpy as np
    
    # 負の数を含む整数
    data_int = np.array([-2, 0, 1, 0, -2, 3, 1])
    unique_vals_int, counts_int = np.unique(data_int, return_counts=True)
    print(f"np.unique (整数):")
    print(f"  ユニークな値: {unique_vals_int}")
    print(f"  カウント: {counts_int}")
    # 出力例:
    # np.unique (整数):
    #   ユニークな値: [-2  0  1  3]
    #   カウント: [2 2 2 1]
    
    # 浮動小数点数
    data_float = np.array([0.1, 0.5, 0.1, 1.2, 0.5, 2.3])
    unique_vals_float, counts_float = np.unique(data_float, return_counts=True)
    print(f"\nnp.unique (浮動小数点数):")
    print(f"  ユニークな値: {unique_vals_float}")
    print(f"  カウント: {counts_float}")
    # 出力例:
    # np.unique (浮動小数点数):
    #   ユニークな値: [0.1 0.5 1.2 2.3]
    #   カウント: [2 2 1 1]
    
  • 欠点:

    • bincountほど高速ではない可能性がある(特に値の範囲が広く、ユニークな値が多い場合)。
    • 結果は2つの配列(ユニークな値の配列と、それに対応するカウントの配列)として返されるため、扱いが若干手間になる場合がある。
  • 利点:

    • NumPy単体で完結する。
    • bincountが使えない負の整数や浮動小数点数も扱える(ただし、浮動小数点数の正確な比較には注意が必要)。
  • コード例:

    import numpy as np
    
    data_continuous = np.array([0.1, 0.7, 1.2, 1.9, 2.5, 0.3, 1.5])
    
    # 0-1, 1-2, 2-3 の3つのビンに分割
    counts, bin_edges = np.histogram(data_continuous, bins=[0, 1, 2, 3])
    print(f"np.histogram (連続値):\n  カウント: {counts}\n  ビン境界: {bin_edges}")
    # 出力例:
    # np.histogram (連続値):
    #   カウント: [2 3 1]
    #   ビン境界: [0 1 2 3]
    # 解説:
    # [0, 1) の範囲に 0.1, 0.7 の2つ
    # [1, 2) の範囲に 1.2, 1.9, 1.5 の3つ
    # [2, 3) の範囲に 2.5 の1つ
    
  • 欠点:

    • 離散的な整数値の個々の頻度を数えるのには適さない。
    • 結果はビンごとのカウントとビンの境界値の2つの配列として返される。
  • 利点:

    • 連続値データの頻度分布を把握するのに最適。
    • ビンの境界を柔軟に指定できる。

どの代替手法を選ぶかは、データの性質と目的に依存します。

  • 連続値データの分布を把握したい場合は numpy.histogram() を使用します。
  • NumPy配列のユニークな値とカウントをNumPy内で処理したい場合は numpy.unique(..., return_counts=True) が適しています。
  • あらゆる種類のデータ(特に非数値データや負の数)の単純な頻度カウントには collections.Counter または pandas.Series.value_counts() が最も手軽で強力です。