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

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

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

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

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

```python
import numpy as np

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


##1.2 具体例

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

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

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

```python
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 具体例

```python
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()`なるものを使う．


```python
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 具体例

```python
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つだけある場合．

```python
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 具体例

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



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

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

```python
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 具体例


```python
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次元となってくるともっと見にくい．

```python
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) 番目と`array([0, 0, 1])`の i 番目と`array([0, 1, 0])`の i 番目をつなげて読んでいる（リストの中の2つ目のタプルを合わせて見れば意味がわかる）．

見にくい！




## 2.5 多次元に拡張（改良版）

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


```python
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.array(np.where(distance == np.min(distance))).T
        #before：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/1 更新：1つ目の if 文の中 
`indices[i] = np.where(distance == np.min(distance))`
だったところを，
`indices[i] = np.array(np.where(distance == np.min(distance))).T`
に変えました．これで1つ目の if 文の出力が2つ目の if 文の出力と統一されました．)

##2.6 具体例

```python
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]])]
'''
```


```python
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 まとめ

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