pandasのDataFrameから期間を範囲指定して抽出する

# 記事の概要
pandasで時系列データを扱う時に長時間の欠損値や異常値があった場合、補間ではどうにもできないので、既にあるDataFrameから日付を指定して抽出したい事がありました。
本記事はDataFrameのカラムに時間情報がある時に日付(あるいは時刻)で範囲指定して時系列データを抽出する方法を備忘録がてら記述したものになります。
また扱うテーブルの情報によって変換の仕方が違うためそれに関しても言及しています。

# DataFrameから日時データの抽出
データの下準備になります。長いので必要ない場合この節は飛ばしても大丈夫だと思います。

```python
import pandas as pd
import numpy as np
import datetime as dt
```

本記事では上記の3つのライブラリを使用しています。
## 標準形式で記載された日付カラムが存在する場合
### 用いるDataFrame

```python
df1 = pd.DataFrame({
    '来店者数' : [120, 114, np.nan, 105, 128, 98],
    '仕入れ数' : [140, np.nan, 100, 130, 120, np.nan],
    '日付' : ['2019-05-01', '2019-05-02', '2019-05-03', '2019-05-04', '2019-05-05', '2019-05-06']},
)
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>来店者数</th>
      <th>仕入れ数</th>
      <th>日付</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>120.0</td>
      <td>140.0</td>
      <td>2019-05-01</td>
    </tr>
    <tr>
      <th>1</th>
      <td>114.0</td>
      <td>NaN</td>
      <td>2019-05-02</td>
    </tr>
    <tr>
      <th>2</th>
      <td>NaN</td>
      <td>100.0</td>
      <td>2019-05-03</td>
    </tr>
    <tr>
      <th>3</th>
      <td>105.0</td>
      <td>130.0</td>
      <td>2019-05-04</td>
    </tr>
    <tr>
      <th>4</th>
      <td>128.0</td>
      <td>120.0</td>
      <td>2019-05-05</td>
    </tr>
    <tr>
      <th>5</th>
      <td>98.0</td>
      <td>NaN</td>
      <td>2019-05-06</td>
    </tr>
  </tbody>
</table>
</div>
"日付"カラムに日付情報が埋め込まれています。DataFrameは列ごとにSeriesを保持し、各Seriesにデータ型が設定されています。各列のデータ型を確認してみます。
ちなみにNaNが入っている特別な意味はないです。

```python
print(df1.dtypes)
```

    来店者数    float64
    仕入れ数    float64
    日付       object
    dtype: object

"日付"カラムののデータ型はobjectです。
通常csvやtsvファイルをpandasのread_csvで読み込んだ場合はこのようなDataFrameになっていると思います。

### object型からdatetime型への変換

```python
df1['日付'] = pd.to_datetime(df1['日付'])
print(df1.dtypes)
```
    来店者数           float64
    仕入れ数           float64
    日付      datetime64[ns]
    dtype: object
ちゃんと"日付"カラムがdatetime64[ns]型になっていますね。
datetimeの標準な形になっていればpandasのto_datetimeメソッドをそのまま使えばよいです。

## 標準形式でない日付カラムが存在する場合
### 用いるDataFrame

```python
df2 = pd.DataFrame({
    '乗車人数' : [150, 180, 140, 160, 161, 145],
    '日時' : ['2019/5月1日-12:00', '2019/5月1日-12:50', '2019/5月1日-13:30', '2019/5月1日-13:59', '2019/5月1日-14:00', '2019/5月1日-14:30']},
)
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>乗車人数</th>
      <th>日時</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>150</td>
      <td>2019/5月1日-12:00</td>
    </tr>
    <tr>
      <th>1</th>
      <td>180</td>
      <td>2019/5月1日-12:50</td>
    </tr>
    <tr>
      <th>2</th>
      <td>140</td>
      <td>2019/5月1日-13:30</td>
    </tr>
    <tr>
      <th>3</th>
      <td>160</td>
      <td>2019/5月1日-13:59</td>
    </tr>
    <tr>
      <th>4</th>
      <td>161</td>
      <td>2019/5月1日-14:00</td>
    </tr>
    <tr>
      <th>5</th>
      <td>145</td>
      <td>2019/5月1日-14:30</td>
    </tr>
  </tbody>
</table>
</div>

まずこんな形式の日時データはないと思いますが、"日時"カラムの中身の形式がぐちゃぐちゃの場合でも変換可能です。

### object型からdatetime型への変換

```python
df2['日時'] = pd.to_datetime(df2['日時'], format='%Y/%m月%d日-%H:%M')
print(df2.dtypes)
```

    乗車人数     int64
    日時      datetime64[ns]
    dtype: object

フォーマットを指定して変換するにはto_datetimeメソッドのformat引数に対応する文字列を渡してあげればよいです。
主なDirective(指示語)は以下の表のとおりです。[^1]

| Directive       |       意味 |
|:-----------------|------------------:|
| %Y             |    西暦4桁の10進法表記 |
| %y           |      西暦下2桁の10進法表記 |
| %m             |             月の10進法表記 |
| %d               |           日の10進法表記 |
| %H             |    時(24時間表記)の10進法表記 |
| %I         |     時(12時間表記)の10進法表記|
| %M         |     分の10進法表記 |
| %S         |     秒の10進法表記 |
| %p         |  'AM'か'PM'かの文字列|
| %w        | 曜日の10進法表記 [0(日曜), 6] |
| %A        | 曜日名の文字列を返す ['Sunday', 'Saturday'] |
| %%        | '%'を表す |

## 年月日等の情報が別々のカラムに保持されている場合
### 用いるDataFrame

```python
df3 = pd.DataFrame({
    '来店者数' : [120, 114, 123, 105, 128, 98],
    '仕入れ数' : [140, 110, 100, 130, 120, 100],
    '年' : [2019, 2019, 2019, 2019, 2019, 2019],
    '月' : [5, 5 ,5 ,6, 6, 6],
    '日' : [29, 30, 31, 1, 2, 3]}
)
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>来店者数</th>
      <th>仕入れ数</th>
      <th>年</th>
      <th>月</th>
      <th>日</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>120</td>
      <td>140</td>
      <td>2019</td>
      <td>5</td>
      <td>29</td>
    </tr>
    <tr>
      <th>1</th>
      <td>114</td>
      <td>110</td>
      <td>2019</td>
      <td>5</td>
      <td>30</td>
    </tr>
    <tr>
      <th>2</th>
      <td>123</td>
      <td>100</td>
      <td>2019</td>
      <td>5</td>
      <td>31</td>
    </tr>
    <tr>
      <th>3</th>
      <td>105</td>
      <td>130</td>
      <td>2019</td>
      <td>6</td>
      <td>1</td>
    </tr>
    <tr>
      <th>4</th>
      <td>128</td>
      <td>120</td>
      <td>2019</td>
      <td>6</td>
      <td>2</td>
    </tr>
    <tr>
      <th>5</th>
      <td>98</td>
      <td>100</td>
      <td>2019</td>
      <td>6</td>
      <td>3</td>
    </tr>
  </tbody>
</table>
</div>

こんな感じのデータは結構ありそう。
この例の場合"年","月","日"それぞれのカラムの型はint64になっているかと思います。実際

```python
print(df3.dtypes)
```

    来店者数    int64
    仕入れ数    int64
    年       int64
    月       int64
    日       int64
    dtype: object

このような結果となりました。

### object型からdatetime型への変換

データ型がint64なので、一旦string(object)に変換してからまとめてto_datetimeメソッドに引き渡してdatetime64型にします。

```python
df3['日付'] = pd.to_datetime(df3['年'].astype(str)+'-'+ df3['月'].astype(str)+'-'+ df3['日'].astype(str))
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>来店者数</th>
      <th>仕入れ数</th>
      <th>年</th>
      <th>月</th>
      <th>日</th>
      <th>日付</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>120</td>
      <td>140</td>
      <td>2019</td>
      <td>5</td>
      <td>29</td>
      <td>2019-05-29</td>
    </tr>
    <tr>
      <th>1</th>
      <td>114</td>
      <td>110</td>
      <td>2019</td>
      <td>5</td>
      <td>30</td>
      <td>2019-05-30</td>
    </tr>
    <tr>
      <th>2</th>
      <td>123</td>
      <td>100</td>
      <td>2019</td>
      <td>5</td>
      <td>31</td>
      <td>2019-05-31</td>
    </tr>
    <tr>
      <th>3</th>
      <td>105</td>
      <td>130</td>
      <td>2019</td>
      <td>6</td>
      <td>1</td>
      <td>2019-06-01</td>
    </tr>
    <tr>
      <th>4</th>
      <td>128</td>
      <td>120</td>
      <td>2019</td>
      <td>6</td>
      <td>2</td>
      <td>2019-06-02</td>
    </tr>
    <tr>
      <th>5</th>
      <td>98</td>
      <td>100</td>
      <td>2019</td>
      <td>6</td>
      <td>3</td>
      <td>2019-06-03</td>
    </tr>
  </tbody>
</table>
</div>

```python
print(df3.dtypes)
```

    来店者数             int64
    仕入れ数             int64
    年                int64
    月                int64
    日                int64
    日付      datetime64[ns]
    dtype: object

変換出来ました。新たに"日付"カラムが生成されているのがわかります。
データ型もちゃんとdatetime64[ns]型になっています。
データ分析において日時の情報を入力する場合はいいですが、もし使わないのであれば"年","月","日"の各カラムは削除してしまってもいいと思います。


```python
df3 = df3.drop(columns=['年','月','日'])
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>来店者数</th>
      <th>仕入れ数</th>
      <th>日付</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>120</td>
      <td>140</td>
      <td>2019-05-29</td>
    </tr>
    <tr>
      <th>1</th>
      <td>114</td>
      <td>110</td>
      <td>2019-05-30</td>
    </tr>
    <tr>
      <th>2</th>
      <td>123</td>
      <td>100</td>
      <td>2019-05-31</td>
    </tr>
    <tr>
      <th>3</th>
      <td>105</td>
      <td>130</td>
      <td>2019-06-01</td>
    </tr>
    <tr>
      <th>4</th>
      <td>128</td>
      <td>120</td>
      <td>2019-06-02</td>
    </tr>
    <tr>
      <th>5</th>
      <td>98</td>
      <td>100</td>
      <td>2019-06-03</td>
    </tr>
  </tbody>
</table>
</div>

以上の処理でdrop可能です。

# 期間を範囲指定する
ここから本題となります。

df1から2019/5/3より最近のレコードだけ取り出す場合は

```python
df1[df1['日付'] > dt.datetime(2019,5,3)]
```

のように記述します。抽出結果は以下の通りになります。

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>来店者数</th>
      <th>仕入れ数</th>
      <th>日付</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>3</th>
      <td>105.0</td>
      <td>130.0</td>
      <td>2019-05-04</td>
    </tr>
    <tr>
      <th>4</th>
      <td>128.0</td>
      <td>120.0</td>
      <td>2019-05-05</td>
    </tr>
    <tr>
      <th>5</th>
      <td>98.0</td>
      <td>NaN</td>
      <td>2019-05-06</td>
    </tr>
  </tbody>
</table>
</div>

上界と下界を指定する場合は、それぞれの条件を括弧でくくり、'&'で繋げます。
たとえばdf1から2019/5/3以降かつ2019/5/6より前のレコードだけ取り出す場合は

```python
df1[(df1['日付'] >= dt.datetime(2019,5,3)) & (df1['日付'] < dt.datetime(2019,5,6))]
```

のように記述すればよいです。抽出結果は以下の通りになります。

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>来店者数</th>
      <th>仕入れ数</th>
      <th>日付</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2</th>
      <td>NaN</td>
      <td>100.0</td>
      <td>2019-05-03</td>
    </tr>
    <tr>
      <th>3</th>
      <td>105.0</td>
      <td>130.0</td>
      <td>2019-05-04</td>
    </tr>
    <tr>
      <th>4</th>
      <td>128.0</td>
      <td>120.0</td>
      <td>2019-05-05</td>
    </tr>
  </tbody>
</table>
</div>

もちろん時間や分、秒まで指定可能です。
df2から2019/5/1の12:30より後から同日の14:00より前までのレコードだけ取り出す場合は

```python
df2[(df2['日時'] > dt.datetime(2019,5,1,12,30)) & (df2['日時'] < dt.datetime(2019,5,1,14))]
```
のように書きます。抽出結果は以下の通りです。

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>乗車人数</th>
      <th>日時</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1</th>
      <td>180</td>
      <td>2019-05-01 12:50:00</td>
    </tr>
    <tr>
      <th>2</th>
      <td>140</td>
      <td>2019-05-01 13:30:00</td>
    </tr>
    <tr>
      <th>3</th>
      <td>160</td>
      <td>2019-05-01 13:59:00</td>
    </tr>
  </tbody>
</table>
</div>

# 他の方法(index)
これまではカラムに保存された日時のデータをdatetimeの型に変換してカラムの上書きをしてきました。
これらとは別の方法として、DataFrameのindexに変換後の日時を保持してインデックス参照するものがあります。
この時期間の範囲指定の方法が少し違います。
日時の情報を保持しつつカラムから削除したい場合にこの方法は有効かと思われます。

たとえばdf2において

```python
df2.index = pd.to_datetime(df2['日時'], format='%Y/%m月%d日-%H:%M').values
df2 = df2.drop(columns='日時')
```

とすればindexに日時データが保持され、元の"日時"カラムは削除されます。

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>乗車人数</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2019-05-01 12:00:00</th>
      <td>150</td>
    </tr>
    <tr>
      <th>2019-05-01 12:50:00</th>
      <td>180</td>
    </tr>
    <tr>
      <th>2019-05-01 13:30:00</th>
      <td>140</td>
    </tr>
    <tr>
      <th>2019-05-01 13:59:00</th>
      <td>160</td>
    </tr>
    <tr>
      <th>2019-05-01 14:00:00</th>
      <td>161</td>
    </tr>
    <tr>
      <th>2019-05-01 14:30:00</th>
      <td>145</td>
    </tr>
  </tbody>
</table>
</div>

この時indexで機関の範囲指定をして抽出する方法は以下の通りです。

```python
df2['2019-05-01 12:30' : '2019-05-01 14:00']
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>乗車人数</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2019-05-01 12:50:00</th>
      <td>180</td>
    </tr>
    <tr>
      <th>2019-05-01 13:30:00</th>
      <td>140</td>
    </tr>
    <tr>
      <th>2019-05-01 13:59:00</th>
      <td>160</td>
    </tr>
    <tr>
      <th>2019-05-01 14:00:00</th>
      <td>161</td>
    </tr>
  </tbody>
</table>
</div>

上記の方法を用いる場合、条件に**境界値は含まれる**ので注意！

[^1]: https://docs.python.org/3/library/time.html