u++の備忘録

日付のフォーマットが混在するDataFrameを扱う

はじめに

今回は日付のフォーマットに関する試行錯誤のメモです。

"Zuerich monthly sunspot numbers 1749-1983" データセット

時系列データで遊びたくて "Zuerich monthly sunspot numbers 1749-1983" というデータセットcsvでダウンロードしてみました。

Zuerich monthly sunspot numbers 1749-1983 — Dataset — DataMarket

データの末尾に余計な要素が入っていたので、手動で削除して、pandasで読み込みます。

import matplotlib.pyplot as plt
%matplotlib inline
import pandas as pd

df = pd.read_csv(
    'zuerich-monthly-sunspot-numbers-.csv',
)

ごく普通のフォーマット。。。かと思ったら、どこかのタイミングから日付のフォーマットが変わっている???

f:id:upura:20181227120702p:plain

pandasはかなり優秀で、フォーマットが混在していても普通にplotで可視化してくれました。

df['Zuerich_monthly_sunspot_numbers_1749-1983'].plot()

f:id:upura:20181227120932p:plain

日付から特徴量を作る

ここからが本題です。

日付は文字列型で格納されており、機械学習の特徴量として扱うため、値をdatetime型に変換して年や月などを取り出したいです。しかし、このDataFrameでは日付のフォーマットが混在しているため、例えば単純に次のようにしてもエラーが発生してしまいます。これは、フォーマットの指定が一部で正しくないためです。

df['datetime'] = pd.to_datetime(df['Month'], format='%Y-%m')
ValueError: time data 'Jan-00' doesn't match format specified

フォーマット指定をなくせば、よしなに処理してくれるかと期待しましたが、あまり一般的なフォーマットでないためか、エラーとなりました。

df['datetime'] = pd.to_datetime(df['Month'])
ValueError: day is out of range for month

結局今回は、try~exceptを使い、フォーマットごとに処理する関数を作成しました。この関数をmapの引数として、DataFrameを処理します。

def convert_to_datetime(date):
    try:
        return pd.to_datetime(date, format='%Y-%m')
    except:
        return pd.to_datetime(date, format='%b-%y')

df['datetime'] = df['Month'].map(convert_to_datetime)

フォーマットの指定に当たっては、公式の「ディレクティブ」を確認しました。
datetime — Basic date and time types — Python 3.7.2 documentation

この 'datetime' を用いることで、年や月などの特徴量を作成できました。

df['year'] = df['datetime'].dt.year
df['month'] = df['datetime'].dt.month

おまけ

世紀の指定がない2桁の年を処理する際に、自動補完される世紀が正しくない値になっていました。

例えば「1934年」を意味する「34」が、「2034年」として扱われていました。

df['year'].plot()

f:id:upura:20181227122928p:plain

今回は暫定的に次の処理で回避しましたが、この辺りの仕様はきちんと確認しておきたいです。

df['year'] = [y if y < 2000 else (y - 100) for y in df['year']]

f:id:upura:20181227122940p:plain