Python:Pandasの日付処理はクセが凄いので迷わない為の備忘録

Python:Pandasの日付処理はクセが凄いので迷わない為の備忘録
最近調べる回数が多いPandasの日付処理。
毎回違う事で悩んでいるのですが「こんな悩みを生む仕組みってある意味凄い」と思っています。
と言う事で、Pandasでの日付処理について迷わない為の基本事項をまとめておきます。
まず頭に入れておかないといけないのはコレです。
※datetime64[ns]型のDataFrameで日付をIndexとした場合DatetimeIndexとなり様々な機能を使えるようになりますが今回は触れません。
DataFrameに変換すると文字列になるがSeriesは普通の文字列とは違う
文字列の日付をdatetime型に変換するコード
1 2 3 4 5 6 7 |
import datetime as dt str_dateA = '2021-03-04' # datetime型へ変換 dt_dateA = dt.datetime.strptime(str_dateA, '%Y-%m-%d') # 更にdatetime.dateへ変換 dtdate_dateA = dt.date(dt_dateA.year, dt_dateA.month, dt_dateA.day) |
これをDataFrameに対して実行するとエラーが発生します。
訳:系列を <class ‘int’> に変換することはできません。 ※系列=Series
と言う事でPandasに格納した時点でdatetime関数は利用できません。
同じ文字列ではありますがSeriesというタグ付きの文字列でありピュアな文字列とは違います。
これが地味に痛い…。
Seriesにある日付はto_datetime関数でDatetimeもどきに変換する
to_datetime関数はDataFrameに入っている日付データをDatetimeLikeに扱えるように変換してくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import datetime as dt import pandas as pd # DataFrame # 下はデータ型=DateでSQLに格納された値を df_data = pd.DataFrame(data)でDataFrame化 print (df_date) start end 0 2020-12-20 2021-01-03 1 2021-01-22 2021-03-01 2 2021-02-05 2021-03-04 3 2021-03-01 2021-03-01 # Series をDatetimeLikeな形に変換 ldt_date_start = pd.todatetime(df_date['start']) print(ldt_date_start.dt.day) # 20 22 05 01 print(ldt_date_start.dt.month) # 12 01 02 03 |
ただし、あくまでもDatatimeLikeであって完全一致で同じ処理はできません。
【もどき】である事の理解が重要です。
Printで日付や月を取り出せるのだからdatetime.dateにすることもできるんじゃないかと思いテストしてみました。
1 2 |
# 強引にdatetime.dateに置換えてみる dt_dateA = dt.date(ldt_date_start.dt.year,ldt_date_start.dt.month,ldt_date_start.dt.day) |
訳:datetime.datetime’オブジェクトのディスクリプタ’date’は’Series’オブジェクトには適用されません。
やはりSeriesオブジェクトである点がネックとなりdate型への変換は失敗しました。
今回使いたかった差分の月数を計算する関数【monthmod】はdatetime型である必要があるため撃沈。
この事例のように、Datatimeを使ってゴニュゴニョする関数系は大きな影響があります。
Seriesオブジェクトはto_datetimeなしでも『なぜか』日付の差分計算は行う
この動作が私を誤解させて長考のきっかけになってました。
原則から考えれば文字列なので減算処理なんて出来ないハズ。
でもできるんですよね。なんででしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import datetime as dt import pandas as pd # DataFrame # 下はデータ型=DateでSQLに格納された値を df_data = pd.DataFrame(data)でDataFrame化 print (df_date) start end 0 2020-12-20 2021-01-03 1 2021-01-22 2021-03-01 2 2021-02-05 2021-03-04 3 2021-03-01 2021-03-01 # startとendの差分(期間)を求める diff_date = df_date['end'] - df_date['start'] print(diff_date) # 14 days 00:00:00 38 days 00:00:00 27 days 00:00:00 0 days 00:00:00 # 日付だけ取り出すこともできる diff_date = (df_date['end'] - df_date['start']).dt.days print(diff_date) # 14 38 27 0 |
「計算できるならdtオプションで【 .dt.year 】とか個別取得できるんじゃね?」と思い実験。
1 2 |
# 格納した姿そのままで年だけ取り出してみる pu_year = df_date['start'].dt.year |
訳:datetimelike 値を持つ .dt アクセサのみを使用できます。
やっぱりできない。まぁこれが標準ですよね。
で「計算結果をDataFrameに格納した後にtd.daysで取得すると失敗するだろう」と思いきや、これは通ります。
1 2 3 4 5 |
df_date['diff'] = df_date['end'] - df_date['start'] diff_list = df_date['diff'].dt.days print(diff_list) # 14 38 27 0 |
forループするとto_datetimeなしでdatetimeとして扱うことができる
「折角DataFrameにしたのをForループでリストに戻すの?」というご意見があるのは重々承知してます。
なのでこれも実験と言う事で。
先に振れたように、monthmodというdatetimeを利用した経過月数計算関数があります。
Useは from monthdelta import monthmod
これをForループなしで使ってみます。
1 |
test = monthmod(df['operation__startdate'],df['operation__enddate']) |
訳:開始と終了は日付でなければなりません
こんなエラーが出てきます。これはto_datetimeを利用しても同じ結果です。
では、forループを使って走らせてみます。
1 2 3 4 5 6 7 8 9 |
test = [] for index, item in df.iterrows(): test.append(monthmod(item['start'],item['end'])) print(test) # [(monthdelta(0), datetime.timedelta(days=14)), (monthdelta(1), datetime.timedelta(days=7)), (monthdelta(0), datetime.timedelta(days=27)), (monthdelta(0), datetime.timedelta(days=0))] |
通りましたね。
それも文字列⇒datetime変換をしない状態で通ってます。
う~ん、文字列として格納されるとか言っといて不思議な動きです。
Pandasの日付操作でよく使うコマンド
今回はDataFrame内の日付データの基本的な動きとしてまとめてみました。
最後にコマンドとしてよく使うものをまとめてみます。
- Seriesの日付変| pd.to_datetime(df[‘date’]) ※import pandas as pd
- 「年」だけ取得| pd.to_datetime(df[‘date’]).dt.year ※ dt.month⇒「月」取得 / dt.day⇒「日」取得
- 曜日の取得| pd.to_datetime(df[‘date’]).dt.dayofweek ※ 月曜を0として0~6までの数字で曜日を返す 6=日曜日
- 日数の加減算_1| pd.tseries.offsets.Day(31) ※ 20日前はpd.tseries.offsets.Day(-20)
- 日数の加減算_2| dt + relativedelta(days=3) ※ dt=元の日付 / use :from dateutil.relativedelta import relativedelta
- 週数で加減算| pd.tseries.offsets.Week(weekday=2)
- 日付の置き換え| dt + relativedelta(day=3) ※ days=3と【s】を付けると3日後を計算するので要注意
- 2つの間の期間| (dt_A – dt_B).dt.days ※ dt.daysを付けない時、20 days 00:00:00 のような形が返ってくる
- forループで値を取る| for index, item in df.iterrows(): ※index不要の時は変数1つだけでOK
dtオプションの細かい情報はこちらにまとまってました。
まとめ
「Aだと出来てBだと出来ない」というのがあると慣れて覚えるしかない。
処理を書く数が多いので、まぁ嫌でも覚えていくことでしょう。
最初簡単に日付操作ができたのは『DatetimeIndexにしてたから』だったようです。
-
前の記事
Django:「pandasの集計結果をTemplateでForループしたらIndexが消えた」を解消する方法 2021.03.03
-
次の記事
Django:Templateから独自関数を呼び出して利用する方法 2021.03.05
コメントを残す