[Python3 / pandas] elementwise comparison failed; が出るとき

# 経緯

このwarningはたまにしか見かけないけれども、発生すると毎回解決に時間がかかってしまっている。
もっと理解を深め（なるべく即座に解決す）るため、事例を記録してみた。


# 概要

タイトルの通り。

`FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison`が、

- どんな時に発生したか
- どうやって解決したか

を記録したもの。

---

※ただし、pandasがメインターゲットです。
　numpyで発生している場合の対策は特に記載しません。

（numpyはそんなに使わないので...。numpyで同じ問題にぶつかったら追記するかもしれません。）

# Environment

|      |version|
|:-----|:-----:|
|Python|3.8.1  |

# 事例

## 1. `'string' in np.array`

ソースコードは[stackoverflow](https://stackoverflow.com/questions/40659212/futurewarning-elementwise-comparison-failed-returning-scalar-but-in-the-futur)より拝借いたしました...

```python
import numpy as np
'x' in np.arange(5)

# 結果
<stdin>:1: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
```

## 2. np.array同士の比較だが、一方が数値型でもう一方が文字列型

ソースコードは[stackoverflow](https://stackoverflow.com/questions/40659212/futurewarning-elementwise-comparison-failed-returning-scalar-but-in-the-futur)より拝借いたしました...

```python
import numpy as np
np.arange(5) == np.arange(5).astype(str)

# 結果
<stdin>:1: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
```

## 3. `series == 'string'`

私が実際に見かけた事例です。

```python
import numpy as np
import pandas as pd
df = pd.DataFrame({
    'number': [1, np.nan, 3, np.nan, np.nan, 11],
    'memo': ['sign', '', np.nan, 'sign2', np.nan, 'sign3']
})
df
   number   memo
0     1.0   sign
1     NaN
2     3.0    NaN
3     NaN  sign2
4     NaN    NaN
5    11.0  sign3

df['number'] == 'sign'

# 結果
/usr/local/pyenv/versions/20200222/lib/python3.8/site-packages/pandas/core/ops/array_ops.py:253: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
```

ただし、いろいろ試してみてわかったが、「3.」の事例をほんのちょっと変更するだけで、warningは発生しなくなった。
変更箇所はどこかというと、問題になっているseriesの中に、比較対象として合致する`'sign'`を入れておくのである。

```python
import pandas as pd
import numpy as np
df = pd.DataFrame({
    'number': [1, 'sign', 3, np.nan, np.nan, 11], # <= 2個目の値を 'sign' にした
    'memo': ['sign', '', np.nan, 'sign2', np.nan, 'sign3']
})
df['number'] == 'sign'

# 結果
0    False
1     True
2    False
3    False
4    False
5    False
```

# 考察

上記1, 2, 3の結果と、stackoverflowの[この記事](https://stackoverflow.com/questions/40659212/futurewarning-elementwise-comparison-failed-returning-scalar-but-in-the-futur)を見る限り、「**numpy配列やseries**」と「**文字列**」とを比較することで発生することは間違いなさそう。

ただ、上記「3.」の二つ目のソースコードを見るとわかるのだが、**検出したい値が実際にseriesの中に入っていれば、このwarningは発生しない**。
この事実には、今回の記事を書いて初めて気が付いた。

対策については、やはりstackoverflowの[この記事](https://stackoverflow.com/questions/40659212/futurewarning-elementwise-comparison-failed-returning-scalar-but-in-the-futur)頼りになってしまうが、以下のようになる。

# 対策

seriesと文字列の比較を行う際に、`==`や`in`ではなく、`.isin(['string'])`メソッドや`.contains('string')`メソッドを用いることで回避できる。

```python
# Bad: warningが発生してしまう、ダメな書き方
df['number'] == 'sign'

# Good: warningが発生しない
df['number'].isin(['sign'])

# Error: contains を使おうとしたら、series（df['number']）内のデータが文字列型ではないのでエラーになった
df['number'].str.contains('sign')
--- error (略) ---
AttributeError: Can only use .str accessor with string values!

# Good: warningが発生しない
df.astype({'number': str})['number'].str.contains('sign')
```

# 考察の続き

『基本的にはこのwarningは普段出ないのだけど、ときどき出る』、ということが私には何度かありました。
調べてみると、その原因は『データの中に、期待している文字列が1つも含まれていないこと』であることがわかりました。

実際に大量のデータを扱う際、期待していた文字列が1つも含まれていないということは通常あり得るので、そんなときにこのwarningが発生してしまうのだと思います。

これで次からもっとスムーズにwarningが消せる気がする。
というより、この書き方さえ避けて`.isin()`を使えば、そもそも発生させないことができそう(^ワ^*)