NumPyの集合関数のまとめとTips


## NumPyの集合関数とは


NumPyには基本的な集合関数が備わっています。np.unique（配列を重複除外する）などはよく使うと思うのですが、他のものを説明します。

### 試してみる

まず説明用に配列を２つ用意します。なんでPSYCHO-PASSの登場人物かは気にしないでください。好きだからです。
今回は、一期目と二期目の登場人物を分けてNumPy配列を作りました。それぞれの関数で結果を見ていきます。


```python

psy_1 = np.array(['Kougami', 'Tunemori', 'Ginoza', 'Masaoka', 'Kagari', 'Kunizuka', 'Makisima'])
psy_2 = np.array(['Simotuki', 'Tunemori', 'Ginoza', 'Kunizuka', 'Karanomori', 'Tougane', 'Hinakawa','Saiga','Kamui'])
```

#### in1d(x , y)
配列xの各要素に、配列yの要素が含まれているか判定して、真偽値の配列を返してくれる。

```python
np.in1d(psy_1,psy_2)
>> array([False,  True,  True, False, False,  True, False])

#もちろん逆にすると結果も変わります。
np.in1d(psy_2,psy_1)
>> array([False,  True,  True,  True, False, False, False, False, False])
```

#### intersect1d(x , y)
配列x , yに共通する要素を取り出して返してくれる。結果はソートされる。

```python
np.intersect1d(psy_1,psy_2)
>> array(['Ginoza', 'Kunizuka', 'Tunemori'], dtype='<U10')
```

#### union1d(x , y)
配列x , yのうち、どちらか一方に存在する要素を取り出して返してくれる。結果はソートされる。

```python
np.union1d(psy_1,psy_2)
>> array(['Ginoza', 'Hinakawa', 'Kagari', 'Kamui', 'Karanomori', 'Kougami',
       'Kunizuka', 'Makisima', 'Masaoka', 'Saiga', 'Simotuki', 'Tougane',
       'Tunemori'], dtype='<U10')
```

#### setdiff1d(x , y)
配列xから、配列yに含まれる要素を除外して返してくれる。結果はソートされる。

```python
np.setdiff1d(psy_1,psy_2)
>> array(['Kagari', 'Kougami', 'Makisima', 'Masaoka'], dtype='<U8')
```

#### setxor1d(x , y)
配列xと配列yのうち、どちらか一方にしか存在しない要素を取り出して返してくれる。結果はソートされる。

```python
np.setxor1d(psy_1,psy_2)
>> array(['Hinakawa', 'Kagari', 'Kamui', 'Karanomori', 'Kougami', 'Makisima',
       'Masaoka', 'Saiga', 'Simotuki', 'Tougane'], dtype='<U10')
```

## in1dを使ったTips

仕事でNumPy配列を使って、複数の配列の間の重複を調べる必要があったので、その時に使用した方法を備忘録として残していきます。

今回はin1dを使って、PSYCHO-PASSの登場人物全員が一期と二期どちらで登場しているのか？または重複しているのか？を調べて、データフレームで一覧で見れるようにしたいと思います。

### in1dの実行速度はオブジェクトや文字列だと遅い？

上記の目的だけならすぐできそうなのですが、配列の中がオブジェクト型 or 文字列で、なおかつ配列の大きさが200万を超えてくると、in1dでそのまま処理するととても時間がかかってしまいます。
in1dはとても便利な関数なのですが、オブジェクト型や文字列型同士の判定は数値型よりも時間がかかるようです。（あくまで僕の実行環境です。）

配列のオブジェクトや文字列にそれぞれidを振って、数値型にしてからin1dを実行すると早くなったので、これをやっていきます。

#### 辞書を作る

まずは、一期と二期の登場人物をマージして、登場人物全員のユニークな配列を作っていきます。


```python
characters = np.concatenate([psy_1,psy_2])
>> array(['Kougami', 'Tunemori', 'Ginoza', 'Masaoka', 'Kagari', 'Kunizuka',
       'Makisima', 'Simotuki', 'Tunemori', 'Ginoza', 'Kunizuka',
       'Karanomori', 'Tougane', 'Hinakawa', 'Saiga', 'Kamui'],
      dtype='<U10')
```

ちゃんと全員入っていますが当然重複があるので、配列をユニークにします。

```python
characters = np.unique(characters)
>> array(['Ginoza', 'Hinakawa', 'Kagari', 'Kamui', 'Karanomori', 'Kougami',
       'Kunizuka', 'Makisima', 'Masaoka', 'Saiga', 'Simotuki', 'Tougane',
       'Tunemori'], dtype='<U10')
```

ユニークになりました。

登場人物全員（ユニーク）の配列は準備できたので、これを辞書にしてそれぞれにid（ただの数値）を振っていきます。
名前からidの辞書、idから名前の辞書どちらも作っているのは、あとで色々便利だからです。
今回は名前からidの辞書しかつかいません。

```python
#空の辞書を作る
name_to_id, id_to_name = {}, {}

for name in characters:
    if name not in name_to_id:
        new_id = len(name_to_id)
        name_to_id[name] = new_id
        id_to_name[new_id] = name

print(name_to_id)

>> {'Ginoza': 0, 'Hinakawa': 1, 'Kagari': 2, 'Kamui': 3, 'Karanomori': 4, 'Kougami': 5, 'Kunizuka': 6, 'Makisima': 7, 'Masaoka': 8, 'Saiga': 9, 'Simotuki': 10, 'Tougane': 11, 'Tunemori': 12}
```

今度は作成したPSYCHO-PASSの登場人物全員の辞書から、一期目の配列に含まれている登場人物と照らし合わせて、同じidを振っていきます。最後はNumPy配列にします。

```python
#psy_1

psy_1_id = []
for i in psy_1:
    if i in name_to_id.keys():
        psy_1_id.append(name_to_id[i])
    else:
        continue
#NumPy配列に変換
psy_1_id = np.array(psy_1_id)
print(psy_1_id)

>> [ 5 12  0  8  2  6  7]
```

二期目も同様です。

```python
#psy_2

psy_2_id = []
for i in psy_2:
    if i in name_to_id.keys():
        psy_2_id.append(name_to_id[i])
    else:
        continue

#NumPy配列に変換
psy_2_id = np.array(psy_2_id)
print(psy_2_id)
>> [10 12  0  6  4 11  1  9  3]
```

最後に、PSYCHO-PASSの登場人物全員のidを配列にします。

```python
id_arr = [v for v in name_to_id.values()]
id_arr = np.array(id_arr)
print(id_arr)
>> [ 0  1  2  3  4  5  6  7  8  9 10 11 12]
```

あとは、in1dを実行するだけですね。PSYCHO-PASSの登場人物全員に、一期目と二期目の登場人物が含まれているか判定して、真偽値の配列を返してくれます。
真偽値で返ってくるので、数値に変換します。（すみません僕が0,1の方が見やすいというだけです。。。）

```python
#in1dで比較
psy_1_r = np.in1d(id_arr,psy_1_id)
psy_1_r = psy_1_r.astype(np.int)

psy_2_r = np.in1d(id_arr,psy_2_id)
psy_2_r = psy_2_r.astype(np.int)

print(psy_1_r)
print(psy_2_r)

>> [1 0 1 0 0 1 1 1 1 0 0 0 1]
>> [1 1 0 1 1 0 1 0 0 1 1 1 1]
```

あとは結果の配列を結合します。

```python
#結合
s1 = np.stack([psy_1_r ,psy_2_r], axis=1)
print(s1)
>>
[[1 1]
 [0 1]
 [1 0]
 [0 1]
 [0 1]
 [1 0]
 [1 1]
 [1 0]
 [1 0]
 [0 1]
 [0 1]
 [0 1]
 [1 1]]
```

データフレーム化する前に、登場人物がidのままだと見にくいので、　辞書からidを名前に変換した配列を作ります。

```python
name_arr = [v for v in name_to_id.keys()]
name_arr = np.array(name_key)
print(name_key)

>> ['Ginoza' 'Hinakawa' 'Kagari' 'Kamui' 'Karanomori' 'Kougami' 'Kunizuka'
 'Makisima' 'Masaoka' 'Saiga' 'Simotuki' 'Tougane' 'Tunemori']
```

最後はデータフレーム化します。

```python
columns = ['1期目','2期目'] #カラム名の指定
df_psycho = pd.DataFrame(s1,columns=columns,index=name_arr)
```

結果以下のようになりました。↓

![スクリーンショット 2019-01-31 23.58.23.png](https://qiita-image-store.s3.amazonaws.com/0/210555/37a773b9-c8dd-61e8-7c7f-9e222184a919.png)

一期目と二期目で両方1が付いているところが重複しているところですね〜

もっと良い方法があれば教えてください！