NumPy配列ndarrayのユニークな要素の値・個数・位置を取得
NumPy配列ndarray
のユニーク(一意)な要素の値を抽出したり、個数・頻度(出現回数)をカウントしたり、位置(インデックス、座標)を取得したりするには、np.unique()
を使う。
ここでは以下の内容について説明する。
np.unique()
の基本的な使い方- ユニークな要素の個数・頻度をカウント: 引数
return_counts
- ユニークな要素の位置(インデックス)を取得: 引数
return_index
,return_inverse
- 個数と位置の同時取得
- 二次元配列(多次元配列)の場合: 引数
axis
- 多次元配列におけるユニークな要素の位置(座標)
pandasにおけるユニークな要素についての処理は以下の記事を参照。二次元のテーブルデータを列ごとに処理する場合などはpandasのほうが楽。
np.unique()の基本的な使い方
np.unique()
の第一引数に配列ndarray
を指定すると、そのndarray
のユニークな値を要素とする新たなndarray
が生成される。元のndarray
の順番ではなくソートされる。
import numpy as np
a = np.array([0, 0, 30, 10, 10, 20])
print(a)
# [ 0 0 30 10 10 20]
print(np.unique(a))
# [ 0 10 20 30]
print(type(np.unique(a)))
# <class 'numpy.ndarray'>
ndarray
だけでなくリストなどのいわゆるarray_like
オブジェクトを引数に指定可能。元のオブジェクトの型によらず返り値はndarray
。
l = [0, 0, 30, 10, 10, 20]
print(l)
# [0, 0, 30, 10, 10, 20]
print(np.unique(l))
# [ 0 10 20 30]
print(type(np.unique(l)))
# <class 'numpy.ndarray'>
ユニークな要素の個数・頻度をカウント: 引数return_counts
ユニークな要素の種類の数をカウントするにはnp.unique()
の返り値の要素数をカウントすればよい。size
属性またはlen()
を使う。
print(np.unique(a).size)
# 4
print(len(np.unique(a)))
# 4
ユニークな要素ごとの頻度(出現回数)を取得するにはnp.unique()
の引数return_counts
をTrue
とする。
ユニークな値のndarray
と、それぞれの値の頻度(出現回数)を示すndarray
が返される。
u, counts = np.unique(a, return_counts=True)
print(u)
# [ 0 10 20 30]
print(counts)
# [2 2 1 1]
各ndarray
の要素が対応しており、この例の場合は0
と10
が2個、20
と30
が1個という意味。
重複していない値(出現回数が1回)や重複している値(出現回数が1回でない)は以下のように抽出可能。
print(u[counts == 1])
# [20 30]
print(u[counts != 1])
# [ 0 10]
なお、return_counts=True
とした場合のnp.unique()
の返り値はndarray
を要素とするタプル。
print(np.unique(a, return_counts=True))
# (array([ 0, 10, 20, 30]), array([2, 2, 1, 1]))
print(type(np.unique(a, return_counts=True)))
# <class 'tuple'>
上の例ではアンパックでそれぞれの変数に代入している。次に説明する 引数return_index
, return_inverse
の例でも同様。
ユニークな要素の位置(インデックス)を取得: 引数return_index, return_inverse
引数return_index
をTrue
とすると、ユニークな要素が最初に現れる位置(インデックス)を示すndarray
が同時に返される。
u, indices = np.unique(a, return_index=True)
print(u)
# [ 0 10 20 30]
print(indices)
# [0 3 5 2]
この例におけるindices
の値は、元のndarray
で最初に現れる位置(インデックス)を表している。0
は元の配列の0番目、10
は3番目、20
は5番目、30
は2番目に最初に現れることを示している。
元のndarray
と組み合わせると、ユニークな要素の位置(インデックス)が示されていることが確認できる。
print(a)
# [ 0 0 30 10 10 20]
print(a[indices])
# [ 0 10 20 30]
引数return_inverse
をTrue
とすると、反対に、元のndarray
のどの位置にユニークな要素があるかを示すndarray
が同時に返される。
u, inverse = np.unique(a, return_inverse=True)
print(u)
# [ 0 10 20 30]
print(inverse)
# [0 0 3 1 1 2]
この例におけるinverse
の値は、対応するユニークな要素のndarray
(例ではu
)での位置(インデックス)を表している。
print(a)
# [ 0 0 30 10 10 20]
print(u[inverse])
# [ 0 0 30 10 10 20]
個数と位置の同時取得
引数return_index
, return_inverse
, return_counts
は同時にTrue
とすることが可能。
u, indices, inverse, counts = np.unique(a, return_index=True, return_inverse=True, return_counts=True)
print(u)
# [ 0 10 20 30]
print(indices)
# [0 3 5 2]
print(inverse)
# [0 0 3 1 1 2]
print(counts)
# [2 2 1 1]
キーワード引数での指定の順番は関係なく、常にreturn_index
, return_inverse
, return_counts
の順番となる。
print(np.unique(a, return_counts=True, return_index=True, return_inverse=True))
# (array([ 0, 10, 20, 30]), array([0, 3, 5, 2]), array([0, 0, 3, 1, 1, 2]), array([2, 2, 1, 1]))
二次元配列(多次元配列)の場合: 引数axis
多次元配列の場合、デフォルトでは平坦化(一次元化)された上で全要素に対して処理される。
a_2d = np.array([[20, 20, 10, 10], [0, 0, 10, 30], [20, 20, 10, 10]])
print(a_2d)
# [[20 20 10 10]
# [ 0 0 10 30]
# [20 20 10 10]]
print(np.unique(a_2d))
# [ 0 10 20 30]
引数axis
を指定すると、各軸に沿って処理される。二次元配列の場合、axis=0
だとユニークな行の抽出、axis=1
だとユニークな列の抽出になる。行単位または列単位でユニークかどうかが判定される。
上述の一次元配列と同様に、結果はソートされる。行や列の場合、先頭の要素から順に比較される。
print(np.unique(a_2d, axis=0))
# [[ 0 0 10 30]
# [20 20 10 10]]
print(np.unique(a_2d, axis=1))
# [[10 10 20]
# [10 30 0]
# [10 10 20]]
行ごと、列ごとにユニークな値を抽出するにはそれぞれの行・列を選択してnp.unique()
を適用する。
print(a_2d[0])
# [20 20 10 10]
print(np.unique(a_2d[0]))
# [10 20]
print(a_2d[:, 2])
# [10 10 10]
print(np.unique(a_2d[:, 2]))
# [10]
リスト内包表記を利用すると各行に対して処理可能。ndarray
はtolist()
でリストに変換できる。size
属性などで各行のユニークな要素の種類の数を取得することも可能。
- 関連記事: Pythonリスト内包表記の使い方
- 関連記事: NumPy配列ndarrayとPython標準のリストを相互に変換
print([np.unique(row) for row in a_2d])
# [array([10, 20]), array([ 0, 10, 30]), array([10, 20])]
print([np.unique(row).tolist() for row in a_2d])
# [[10, 20], [0, 10, 30], [10, 20]]
print([np.unique(row).size for row in a_2d])
# [2, 3, 2]
各列に対して処理したい場合は転置する方法がある。
print(a_2d.T)
# [[20 0 20]
# [20 0 20]
# [10 10 10]
# [10 30 10]]
print([np.unique(row) for row in a_2d.T])
# [array([ 0, 20]), array([ 0, 20]), array([10]), array([10, 30])]
列を順に選択してもよい。
print(a_2d.shape)
# (3, 4)
print([np.unique(a_2d[:, i]) for i in range(a_2d.shape[1])])
# [array([ 0, 20]), array([ 0, 20]), array([10]), array([10, 30])]
上述のように、このように列ごとに処理するような場合はpandasのほうが楽。
return_index
やreturn_inverse
, return_counts
などの引数をTrue
とする場合も、デフォルトでは平坦化(一次元化)したndarray
に対して処理される。
u, indices, inverse, counts = np.unique(a_2d, return_index=True, return_inverse=True, return_counts=True)
print(u)
# [ 0 10 20 30]
print(indices)
# [4 2 0 7]
print(a_2d.flatten())
# [20 20 10 10 0 0 10 30 20 20 10 10]
print(a_2d.flatten()[indices])
# [ 0 10 20 30]
print(inverse)
# [2 2 1 1 0 0 1 3 2 2 1 1]
print(u[inverse])
# [20 20 10 10 0 0 10 30 20 20 10 10]
print(u[inverse].reshape(a_2d.shape))
# [[20 20 10 10]
# [ 0 0 10 30]
# [20 20 10 10]]
print(counts)
# [2 5 4 1]
引数axis
を指定すると行や列に対して処理される。
u, indices, inverse, counts = np.unique(a_2d, axis=0, return_index=True, return_inverse=True, return_counts=True)
print(u)
# [[ 0 0 10 30]
# [20 20 10 10]]
print(indices)
# [1 0]
print(a_2d[indices])
# [[ 0 0 10 30]
# [20 20 10 10]]
print(inverse)
# [1 0 1]
print(u[inverse])
# [[20 20 10 10]
# [ 0 0 10 30]
# [20 20 10 10]]
print(counts)
# [1 2]
多次元配列におけるユニークな要素の位置(座標)
上述のように、多次元配列においてreturn_index=True
とした場合、デフォルトでは平坦化したndarray
において最初に出現する位置(インデックス)が返される。
print(a_2d)
# [[20 20 10 10]
# [ 0 0 10 30]
# [20 20 10 10]]
u, indices = np.unique(a_2d, return_index=True)
print(u)
# [ 0 10 20 30]
print(a_2d.flatten())
# [20 20 10 10 0 0 10 30 20 20 10 10]
print(indices)
# [4 2 0 7]
元のndarray
の形状における位置(インデックス、座標)を取得したい場合、np.where()
を利用する。例えば、値が0
である要素の位置のリストは以下のように取得できる。
print(list(zip(*np.where(a_2d == 0))))
# [(1, 0), (1, 1)]
これをnp.unique()
とあわせて利用すると、キーkey
がユニークな値、要素value
がその位置のリストである辞書を生成したりできる。辞書内包表記を使っている。
d = {u: list(zip(*np.where(a_2d == u))) for u in np.unique(a_2d)}
print(d)
# {0: [(1, 0), (1, 1)], 10: [(0, 2), (0, 3), (1, 2), (2, 2), (2, 3)], 20: [(0, 0), (0, 1), (2, 0), (2, 1)], 30: [(1, 3)]}
print(d[0])
# [(1, 0), (1, 1)]
print(d[10])
# [(0, 2), (0, 3), (1, 2), (2, 2), (2, 3)]
print(d[20])
# [(0, 0), (0, 1), (2, 0), (2, 1)]
print(d[30])
# [(1, 3)]
出現回数に応じて条件判定することも可能。出現回数が1回やn回以下といった条件で絞り込んで値とその位置を取得できる。
d = {u: list(zip(*np.where(a_2d == u)))
for u, c in zip(*np.unique(a_2d, return_counts=True)) if c == 1}
print(d)
# {30: [(1, 3)]}
d = {u: list(zip(*np.where(a_2d == u)))
for u, c in zip(*np.unique(a_2d, return_counts=True)) if c <= 2}
print(d)
# {0: [(1, 0), (1, 1)], 30: [(1, 3)]}