Krypf’s Diary

暇なときに書く

【Python】 配列内で一番近い値を取る index を返す関数まとめ

意外と詰まったし良いまとめがないので書く.

1 一番近い値の重複を考えない場合

1.1 最もシンプルなバージョン

探したい値が一つで,返したい index も一つの場合.これが最も簡単.探索されるデータは1次元配列を想定.

import numpy as np

def idx_of_the_nearest(data, value):
    idx = np.argmin(np.abs(np.array(data) - value))
    return idx

1.2 具体例

data = [1, 1 ,1 ,0.5 ,2 ,3 ,-1]
value =  0.8
n = idx_of_the_nearest(data, value)
n
#0

1.3 探したい値が複数ある場合

探したい値をリストで指定できるようにしたい.そのための関数が以下.

import numpy as np

def idx_of_the_nearest(data, value):
    print('value:', type(value))
    if type(value) == float:
        idx = np.argmin(np.abs(np.array(data) - value))
        #print(np.abs(np.array(data) - value))
        return idx
    if type(value) == list:
        idx = [None]*len(value)
        for i in range(len(value)):
            idx[i] = np.argmin(np.abs(np.array(data) - value[i]))
            #idx[i] = [value[i], np.argmin(np.abs(np.array(data) - value[i]))] #としてもよい
            #print(np.abs(np.array(data) - value[i]))
        return idx

1.4 具体例

data = [1, 1 ,1 ,0.5 ,2 ,3 ,-1]
value =  [0.8,0.7]
n = idx_of_the_nearest(data, value)
n
'''Result
value: <class 'list'>
[0, 3]
'''

1.5 多次元に拡張

探索される対象のデータが多次元配列の場合に,多次元配列の index を返したい人はこちら.出力はタプルのリスト.np.unravel_index()なるものを使う.

import numpy as np

def idx_of_the_nearest(data, value):
    print('value:', type(value))
    if type(value) == float:
        idx = np.argmin(np.abs(np.array(data) - value))
        #print(np.abs(np.array(data) - value))
        return idx
    if type(value) == list:
        idx = [None]*len(value)
        for i in range(len(value)):
            idx[i] = np.unravel_index(np.argmin(np.abs(np.array(data) - value[i])) , np.array(data).shape)
            #print(np.abs(np.array(data) - value[i]))
        return idx

1.6 具体例

data = [[1, 1 ,1 ,0.5] ,[2 ,3 ,-1,0]]
value = [0.8, 0.7, 2]
idx_of_the_nearest(data, value)
'''
value: <class 'list'>
[(0, 0), (0, 3), (1, 0)]
'''

2 最も近い値が複数ある場合

最もなのに「複数」とはどういうことだ,とは言わないでいただきたい.そういうことも数学ではままあるのである(今までもそういう具体例をわざと作っておいた.).

2.1 シンプルなバージョン

最もシンプルに添字を複数返したい方はこちら.1次元配列で探したい値が1つだけある場合.

import numpy as np

def indices_of_the_nearest(data, value):
    distance = np.abs(np.array(data) - value)
    indices = np.where(distance == np.min(distance))[0]
    return indices

この np.where() なるものが曲者で,意外と使いにくい.

ただ,シンプルなバージョンでは,具体例で見るようにこれが最適解である.[0]をつけることで arrayが返ってくる.

2.2 具体例

data = [1, 1 ,1 ,0.5 ,2 ,3 ,-1]
value = 0.8
indices_of_the_nearest(data, value)
'''
array([0, 1, 2])
'''

2.3 探したい値が複数ある場合(多次元でも使えるが改良の余地あり)

探す値も複数にしたいという「欲張り」な人はこちら.ただし改良の余地がある.

import numpy as np

def indices_of_the_nearest(data, value):
    print('value:', type(value))
    if type(value) == float:
        distance = np.abs(np.array(data) - value)
        indices = np.where(distance == np.min(distance))
        #print(np.abs(np.array(data) - value))
        return indices
    if type(value) == list:
        indices = [None]*len(value)
        for i in range(len(value)):
            distance = np.abs(np.array(data) - value[i])
            indices[i] = np.where(distance == np.min(distance))
            #print(np.abs(np.array(data) - value[i]))
        return indices

1.3 節を応用しただけ.

2.4 具体例

data = [[1, 1 ,1 ,0.5] ,[2 ,3 ,-1,0]]
value = [0.8,0.7]
indices_of_the_nearest(data, value)
'''
value: <class 'list'>
[(array([0, 0, 0]), array([0, 1, 2])), (array([0]), array([3]))]
'''

行番号と列番号が分離して出力されてわかりにくい(1つ目の要素の意味は[0][0]と[0][1]と[0][2]に一番近い値がありますよ,という意味).

どうしてもという人はこれを使えばよいが,今回の目的からしてあまり使う意味もない.data が1次元の場合は問題なかったのだが,これが更に3次元となってくるともっと見にくい.

data = [[[1, 1] ,[1 ,0.5]] ,[[2 ,3] ,[-1,0]]]
value = [0.8,0.7]
indices_of_the_nearest(data, value)
'''
value: <class 'list'>
[(array([0, 0, 0]), array([0, 0, 1]), array([0, 1, 0])),
 (array([0]), array([1]), array([1]))]
'''

リストの中の1つ目のタプルは[0][0][0], [0][0][1], [0][1][0] に一番近い値がありますよという意味.

これは array の中をそのまま読んでいるのでなくて,array([0, 0, 0])の i (i = 0, 1, 2, 3) 番目とarray([0, 0, 1])の i 番目とarray([0, 1, 0])の i 番目をつなげて読んでいる(リストの中の2つ目のタプルを合わせて見れば意味がわかる).

見にくい!

2.5 多次元に拡張(改良版)

というわけで改良しよう.

import numpy as np

def indices_of_the_nearest(data, value):
    print('value:', type(value))
    if type(value) == float:
        distance = np.abs(np.array(data) - value)
        indices = np.where(distance == np.min(distance))
        #print(np.abs(np.array(data) - value))
        return indices
    if type(value) == list:
        indices = [None]*len(value)
        for i in range(len(value)):
            distance = np.abs(np.array(data) - value[i])
            indices[i] = np.array((np.where(distance == np.min(distance)))).T #Transpose
            #print(np.abs(np.array(data) - value[i]))
        return indices

何をやっているかというと, indices[i] = np.array((np.where(distance == np.min(distance)))).T と書き換えただけ.

2.6 具体例

data = [[1, 1 ,1 ,0.5] ,[2 ,3 ,-1,0]]
value = [0.8,0.7]
indices_of_the_nearest(data, value)
'''
value: <class 'list'>
[array([[0, 0],
        [0, 1],
        [0, 2]]),
 array([[0, 3]])]
'''
data = [[[1, 1] ,[1 ,0.5]] ,[[2 ,3] ,[-1,0]]]
value = [0.8,0.7]
indices_of_the_nearest(data, value)
'''
value: <class 'list'>
[array([[0, 0, 0],
        [0, 0, 1],
        [0, 1, 0]]),
 array([[0, 1, 1]])]
'''

見やすい!(出力から要素を取り出したい場合は 内包表記で for 文を回すなどすればできると思う)

3 まとめ

添字取り出し,意外と大変だったけど,いつかどこかの誰かの役に立てれば.