mecobalamin’s diary

人間万事塞翁が馬

https://help.hatenablog.com/entry/developer-option

メモ:Pythonで時系列グラフの作成

時系列のデータをグラフにするとき
ハマったところをメモ


参考にしたサイト
note.nkmk.me
oku.edu.mie-u.ac.jp

元のデータはこれを使った
陽性者一覧(CSV:175KB)

csvのカラムは以下のようになっている

確定陽性者 性別 年齢 発病日 確定日 居住地 職業 推定感染経路

確定日に日付が入っているのでその数をカウントすると1日あたりの陽性者数を数えられる
それはpythonvalue_counts()を使えばいいのだが
陽性者のいない日付もあるのでそこはNaNで埋めて日付が飛ばないようにしたい

初めは日付だけのデータフレームを作ってマージしたらよいかと思ったがうまくいかない
日付のデータフレームはDataFrame(pandas.date_range('日付1', '日付2'))を使うと
日付1から日付2の範囲の作れる
例えば次のスクリプトを実行する

from pandas import Series, DataFrame
import pandas as pd
import numpy as np

date1 = DataFrame(pd.date_range('2020/2/1', '2020/2/7'))
tmp_df = []
for i in date1.index:
    tmp_df.append('date1')
tmp_df = DataFrame(tmp_df)
date1 = pd.concat([date1, tmp_df], axis = 1)
date1.columns = ['date', 'values']
date1.set_index('date', inplace = True)
date1.drop(date1.index[[4, 5]], inplace = True)
print(date1)

出力では5日と6日が抜けている

           values
date             
2020-02-01  date1
2020-02-02  date1
2020-02-03  date1
2020-02-04  date1
2020-02-07  date1

更にインデックスを書き換えると

new_idx = pd.date_range(date1.index[0], date1.index[-1], freq = 'D')
print(type(new_idx[0]))
print(date1.index)
date1 = date1.asfreq('D')
print(date1)

なかった5日と6日の行にはNaNが挿入されている

           values
date             
2020-02-01  date1
2020-02-02  date1
2020-02-03  date1
2020-02-04  date1
2020-02-05    NaN
2020-02-06    NaN
2020-02-07  date1

更に日付の範囲の異なるデータを作る
date1とdate2をpandas.concat()をつかって列で合成する

date2 = DataFrame(pd.date_range('2020/2/3', '2020/2/8'))
tmp_df = []
for i in date2.index:
    tmp_df.append('date2')
tmp_df = DataFrame(tmp_df)
date2 = pd.concat([date2, tmp_df], axis = 1)
date2.columns = ['date', 'values']
date2.set_index('date', inplace = True)
print(date2)

date = pd.concat([date1, date2], axis = 1)
print(date)

結果は次の通り

           values values
date                    
2020-02-01  date1    NaN
2020-02-02  date1    NaN
2020-02-03  date1  date2
2020-02-04  date1  date2
2020-02-05    NaN  date2
2020-02-06    NaN  date2
2020-02-07  date1  date2
2020-02-08    NaN  date2

date1とdate2それぞれにNaNの補完があって合成されている

同じようにしてvalue_counts()で日付の頻度をカウントして
日付をindexにしたらいいかと思ったが
このindexの日付とdate_range()で作った日付をconcat()で合成できない
できないというか日付の取り扱いが違っているようでうまくできない


ここまで書いてなんだが別の方法を試した

時系列データの場合、asfreq()でリサンプリングすることが可能
時系列の入ったデータフレームdf_dateを以下のようにすると
日付でデータを補完する

df_date = df_date.asfreq('D')

引数によってオフセットが変わるらしい
pandas.DataFrame.asfreq — pandas 1.5.3 documentation
Time series / date functionality — pandas 1.5.3 documentation

これを使って時系列のデータセットを作り
グラフを作るスクリプトが以下の通り

# -*- coding: utf-8 -*-

import os, sys, datetime
from pandas import Series, DataFrame
import pandas as pd

import numpy as np

import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

def Count_Numbers(df_date, col_name):
    df_date = df_date.replace({'確認中': np.nan, '無症状': np.nan, '調査中': np.nan})
    df_date = df_date.dropna()
    for n, i in enumerate(df_date):
        df_date[n] = '2020年' + df_date[n]
    df_date = pd.to_datetime(df_date, format = '%Y年%m月%d日')
    df_date = df_date.value_counts().sort_index()
    df_date = DataFrame(df_date)
    df_date = df_date.asfreq('D')
    df_date.columns = [col_name]

    return df_date

if __name__ == '__main__':

    path = os.getcwd()
    path = os.chdir(os.path.dirname(os.path.abspath(__file__)))
    path = os.getcwd()

    date_today = datetime.date.today()
    day_save_file = str(date_today.year) + str(date_today.month) + str(date_today.day)

    arg = path + '\\' + 'Positives_List\\youseisyaitiran.csv'
    try:
        df = pd.read_csv(arg, index_col = 0, encoding = 'shift_jis')
    except Exception as e:
        print('cannot open file: ', arg)
        print(str(e))
        sys.exit(1)
    else:
        print('we were able to open the file')

    fixed_date = Count_Numbers(df.iloc[:, 3], 'fixed_date')
    onset_date = Count_Numbers(df.iloc[:, 2], 'onset_date')

    tmp_date = pd.concat([fixed_date, onset_date], axis = 1)
    print(tmp_date)

    fig, ax = plt.subplots()
    locator = mdates.AutoDateLocator()
    formatter = mdates.ConciseDateFormatter(locator)
    ax.xaxis.set_major_locator(locator)
    ax.xaxis.set_major_formatter(formatter)
    ax.bar(tmp_date.index, tmp_date['fixed_date'])
    ax.bar(tmp_date.index, tmp_date['onset_date'])
    ax.legend(['fixed date', 'date of onset'])
    #ax.set_xticks()

    plt.savefig(path + '\\' + day_save_file + '_graph_bar.png')
    plt.close()

日付のないセルはNaNに置き換えている

グラフを作る命令はまだよくわかっていないが
このようにするといい感じのグラフができる