時刻の計算と関数のDataFrameへの適用

#はじめに

私のインターン先であるレッドフォックス株式会社では
cyzen([https://www.cyzen.cloud/](https://www.cyzen.cloud/))
という営業支援システムアプリを開発しています。

今回、このアプリの顧客のデータを、Pythonを用いて分析する
にあたって、PandasのDataFrame上で時刻の計算をする必要が
あったため、ここにまとめます。

###作業内容
- 時刻の文字列型からdatetime型への変換
- datetime型時刻の計算
- 上記の計算をDataFrame上で関数を用い実行


###データの内容
顧客となる企業は、2週間程度の試用期間の中でその機能を体験し、
その後実際に導入するかどうかを判断します。

分析に用いたデータは、この試用期間中の顧客の、企業IDとアプリの
各機能の利用履歴です。

なお、上記のデータは、個人が特定できないようマスクされ、
利用日時のみの情報となっています。また、過去データの一部
をサンプルとして抽出したものを利用しています。

##時刻の計算
2つの文字列型の時刻を引き算し、差を出します。

1\.  datetimeライブラリをインポートします。

```python:
#ライブラリのインポート
import datetime 
```
2\. 時刻が文字列型になっている場合、datetime型に修正します。
`datateime.datetime.strptime(文字列型の日時, 形式)`を用います。

今回は、データ上で「YYYY-mm-dd HH:MM:SS」という形式
となっていたため、以下のように変換しました。

```python:
datetime_string_1 = '2017-01-01 00:00:00'
datetime_string_2 = '2018-01-01 00:00:00'

datetime_1 = datetime.datetime.strptime(tstr_1, '%Y-%m-%d %H:%M:%S') 
datetime_2 = datetime.datetime.strptime(tstr_2, '%Y-%m-%d %H:%M:%S')

print(time_1)       # => 2017-01-01 00:00:00
print(time_2)       # => 2018-01-01 00:00:00
print(type(time_1)) # => <class 'datetime.datetime'>
#　一見して変化がないように見えますが型はちゃんと変わっています。
```
3\. datetime_1とdatetime_2との差を計算します。datetime_2から
datetime_1を引きます。

```python:
delta = datetime_2 - datetime_1

#timedelta型で2つの時刻の差を出力します。
print(delta)                             # => 365 days, 0:00:00
print(type(delta))                       # => <class 'datetime.timedelta'>
#deltaを秒に直したものを小数型で出力します。
print(delta.total_seconds())             # => 31536000.0
print(type(delta.total_seconds()))       # => <class 'float'>
print(delta.total_seconds()/(60*60*24))  # => 365.0

```
時刻の型の変換、計算については、[Python datetime 日付の計算、
文字列から変換【決定版】](https://qiita.com/7110/items/4ece0ce9be0ce910ee90)を参照しました。

##DataFrameへの関数の適用
上で説明した時刻の計算をDataFrame上で行ないます。

1\. datetimeとpandasをインポートします。

```python:
#ライブラリのインポート
import datetime
import pandas as pd

#tsvをDataFrameとして読み込み
df = pd.read_csv('/Users/data/trial.tsv', sep='\t')

```
tsvの読み込みに関しては、[pandasでcsv/tsvファイル読み込み
（read_csv, read_table）](https://note.nkmk.me/python-pandas-read-csv-tsv/)を参照しました。

DataFrameの構成は以下のようになっています。

|Index|datetime_1|datetime_2|
|:-----------------|-----------------:|-----:|
|0|2017-01-01 00:00:00|2018-01-01 00:00:00|
|1|2017-01-01 00:00:00|2018-01-01 00:00:00|
||||
|n|2017-01-01 00:00:00|2018-01-01 00:00:00|


2\. 関数を作成します。文字列型の時刻をdatetime型に変換する関数と、
DataFrameの2つの列にある時刻の差を出力する関数です。

```python:
# 与えられた文字列型の時刻をdatetime型へ変換する関数
def str_to_datetime(t):
    return datetime.detetime.strptime(t, '%Y-%m-%d %H:%M:%S')

def delta(t):
    t1, t2 = t
    delta = t2 - t1
    return delta.total_seconds()/(60*60*24) #差を秒に直したものを一日当たりの秒数でわったものを出力します。
```
3\. 作成した関数をDataFrameの``datetime_1``, ``datetime_2``の列に
適用し、新たな列``delta``に表示します。

```python:
#列'detetime_1','datetime_2'を文字列型からdatetime型へ変換
df['datetime_1'] = df['datetime_1'].apply(str_to_datetime) 
df['datetime_2'] = df['datetime_2'].apply(str_to_datetime)

df['delta'] = df[['datetime_1', 'datetime_2']].apply(delta, axis=1)
#引数axis=1とすると各行に対して適用されます。
```
DataFrameには以下のように新しい列が作成されます。

|Index|datetime_1|datetime_2|dalta|
|:-----------------|-----------------:|-----:|--:|
|0|2017-01-01 00:00:00|2018-01-01 00:00:00|365.0|
|1|2017-01-01 00:00:00|2018-01-01 00:00:00|365.0|
|||||
|n|2017-01-01 00:00:00|2018-01-01 00:00:00|365.0|

上記では、DataFrameの特定の2列の各行に対して関数を適用し、
新たな列に出力しましたが、DataFrameへの適用方法には以下
のようなものもあります。

- 関数をDataFrameの全ての要素それぞれに適用する。

```python:
df = df.applymap(関数名)
```

- 関数を各列、あるいは各行に適用する。

```python:
df = df.apply(関数名, axis=0)
#axis=0とすると各列、axis=1とすると各行において適用される。
```