`pandas.Series`に`None`を設定したときの挙動

# 環境
* Python 3.12.4
* pandas 2.2.3
* numpy 1.26.4

# 概要
`pandas.Series`に`np.nan`か`None`どちらを設定すればよいのか、悩むときがありました。
そこで、`pandas.Series`に`None`を格納しようとしたときに、どのような挙動になるかを確認しました。

# 結果

## `pandas.Series`オブジェクトの生成

### `None`のみを格納

```
In [67]: pd.Series([None])
Out[67]:
0    None
dtype: object
```

### 整数と`None`を格納（dtypeを指定しない）
```
In [69]: pd.Series([None, 1])
Out[69]:
0    NaN
1    1.0
dtype: float64
```

None以外の値からdtypeが`int64`に推測されます。Noneは、`int64`型の欠損値として`np.nan`に変換されます。しかし、`np.nan`は`float`なので、dtypeは`int64`ではなく`float64`になりました。

### 整数と`None`を格納（dtypeを指定する）

```
In [281]: pd.Series([None, 1], dtype="int64")
----------------------------------------------

TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
```

`int(None)`を実行しようとして、`TypeError`が発生しました。

### datetime型の値と`None`を格納

```
In [312]: import datetime

In [313]: pd.Series([None, datetime.datetime.now()])
Out[313]:
0                          NaT
1   2024-11-20 10:12:45.863132
dtype: datetime64[ns]
```

dtypeが`datetime64`の場合、Noneは`NaT`に変換されます。

### `None`と`True`を格納する（dtypeを指定しない）

```
In [334]: pd.Series([None, True])
Out[334]:
0    None
1    True
dtype: object

In [335]: pd.Series([True])
Out[335]:
0    True
dtype: bool
```

dtypeが`bool`の場合欠損値を格納できません。`None`を格納するために、dtypeが`object`になったようです。

### `None`と`True`を格納する（dtypeを指定する）

```
In [350]: pd.Series([None, True], dtype="bool")
Out[350]:
0    False
1     True
dtype: bool
```

dtypeが`bool`の場合欠損値を格納できないので、`None`は`bool(None)`の結果`False`に変換されました。



### `dtype="Int64"`を指定する

```
In [331]: pd.Series([None], dtype="Int64")
Out[331]:
0    <NA>
dtype: Int64
```

# 値の代入


### dtypeが`int64`の`pandas.Series`に`None`を設定
```
In [121]: s = pd.Series([1])

In [122]: s
Out[122]:
0    1
dtype: int64

In [123]: s[0] = None

In [124]: s
Out[124]:
0   NaN
dtype: float64
```

### dtypeが`Int64`の`pandas.Series`に`None`を設定

```
In [324]: s = pd.Series([1], dtype="Int64")

In [325]: s
Out[325]:
0    1
dtype: Int64

In [326]: s[0] = None

In [327]: s
Out[327]:
0    <NA>
dtype: Int64
```

### dtypeが`datetime64`の`pandas.Series`に`None`を設定


```
In [320]: s = pd.Series([datetime.datetime.now()])

In [321]: s
Out[321]:
0   2024-11-20 10:15:34.494504
dtype: datetime64[ns]

In [322]: s[0] = None

In [323]: s
Out[323]:
0   NaT
dtype: datetime64[ns]
```

### dtypeが`object`の`pandas.Series`に`None`を設定
```
In [125]: s2 = pd.Series(["foo"])

In [126]: s2
Out[126]:
0    foo
dtype: object

In [127]: s2[0] = None

In [128]: s2
Out[128]:
0    None
dtype: object
```

当然ではありますが、dtypeが`object`ならば`None`を格納できるので、`None`は変換されません。


### dtypeが`bool`の`pandas.Series`に`None`を設定

```
In [346]: s = pd.Series([True])

In [347]: s
Out[347]:
0    True
dtype: bool

In [348]: s[0] = None
<ipython-input-348-3e3b188ac867>:1: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise an error in a future version of pandas. Value 'nan' has dtype incompatible with bool, please explicitly cast to a compatible dtype first.
  s[0] = None

In [349]: s
Out[349]:
0    NaN
dtype: object
```

`None`ではなく`NaN`に変換されました。想定外の挙動でした。


なお、`None`でなく`pd.NA`を設定したときも同じ挙動でした。

```
In [352]: s = pd.Series([True])

In [353]: s[0] = pd.NA
<ipython-input-353-cfb9da72f810>:1: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise an error in a future version of pandas. Value 'nan' has dtype incompatible with bool, please explicitly cast to a compatible dtype first.
  s[0] = pd.NA

In [354]: s
Out[354]:
0    NaN
dtype: object
```

# まとめ
* `None`そのものを格納するには、dtypeを`object`にする必要がある
* 欠損値を格納できるdtypeの場合（`int64`,`Int64`,`datetime64`など）、`None`はそのdtypeに合った欠損値（`np.nan`, `pd.NaT`, `pd.NA`のいずれか）に変換される
* dtypeが`bool`の場合は、dtypeが`object`になって`None`が格納されるときと、`None`が`np.nan`に変換されるときがある


# 補足

## pandas 1.4.4では、存在しない index に`None`を設定したときの挙動が異なる

pandas 1.4.4 では、存在するindexに`None`を設定するときと、存在しないindexに`None`を設定するときで、挙動が異なりました。


* Python 3.10.14
* pandas 1.4.4
* numpy 1.26.4

```
In [4]: s = pd.Series([1.2])

In [5]: s
Out[5]:
0    1.2
dtype: float64

In [6]: s[0] = None

In [7]: s
Out[7]:
0   NaN
dtype: float64

In [8]: s[1] = None

In [9]: s
Out[9]:
0     NaN
1    None
dtype: object
```

存在しないindex `1` に `None`を設定すると、dtypeが`object`になり、`None`は変換されずに格納されました。

なお、pandas 1.5.0以降では、存在するindexに`None`を設定するときと、存在しないindexに`None`を設定するときで、挙動は同じです。


```
In [380]:  s = pd.Series([1.2])

In [381]: s
Out[381]:
0    1.2
dtype: float64

In [382]: s[0] = None

In [383]: s
Out[383]:
0   NaN
dtype: float64

In [384]: s[1] = None

In [385]: s
Out[385]:
0   NaN
1   NaN
dtype: float64
```


## `numpy.array`にNoneを設定したときの挙動
`numpy.array`に`None`を設定したときの挙動は、`pandas.Series`と微妙に異なります。注意しましょう。

```
In [394]: arr1 = np.array([1.2, None])

In [405]: arr1
Out[405]: array([1.2, None], dtype=object)

In [395]: arr1.dtype
Out[395]: dtype('O')

In [396]: arr2 = np.array([1.2, None], dtype="float64")

In [407]: arr2
Out[407]: array([1.2, nan])

In [397]: arr2.dtype
Out[397]: dtype('float64')
```



```
In [398]: arr3 = np.array([1.2])

In [399]: arr3.dtype
Out[399]: dtype('float64')

In [401]: arr3[0] = None

In [402]: arr3
Out[402]: array([nan])

In [403]: arr3.dtype
Out[403]: dtype('float64')

```


# 参考サイト

https://note.nkmk.me/python-pandas-nan-none-na/#none


https://qiita.com/kenchin110100/items/4ce98eeabdbd9806a44c