静かなる名辞

pythonとプログラミングのこと

2019/03/22:TechAcademyがteratailの質問・回答を盗用していた件
2019/03/26:TechAcademy盗用事件 公式発表と深まる疑念


pandasで年月日時刻の列を結合して一列にする(datetime64で)

概要

 ローデータ(生データ)を取り込むと、年月日が独立して入っている感じの嫌なデータになっていることがあります。

年,月,日
1996,8,1
1998,12,2
2012,05,3

 こういうのは嬉しくないので、できるだけ単一のdatetime風の型に変換しておきたいのですが、意外と難しかったりします。

文字列操作として考える

 以下のように読み込みます(io.StringIOを使っていますが実際はCSVファイルだと思ってください)。

import io
import pandas as pd

data = """
年,月,日
1996,8,1
1998,12,2
2012,05,3
"""

df = pd.read_csv(io.StringIO(data), dtype={k:object for k in "年月日"})

 型をobject型にしておくのがミソで、整数型にされると文字列操作で変換できません。読み込んでからastypeで変換してもいいですが、二度手間ですね。

df["DateTime"] = pd.to_datetime(df["年"].str.cat([df["月"], df["日"]], sep=" "))
print(df)
""" =>
      年   月  日   DateTime
0  1996   8  1 1996-08-01
1  1998  12  2 1998-12-02
2  2012  05  3 2012-05-03
"""

 これはこれでできるのですが、文字列を介すると二度手間感が否めません。

時刻もある場合

 とにかくそれっぽいフォーマットに無理矢理仕立てれば、この方法でできます(というかできるはずです)。

import io
import pandas as pd

data = """
年,月,日,時,分
1996,8,1,12,5
1998,12,2,4,12
2012,05,3,23,56
"""

df = pd.read_csv(io.StringIO(data), dtype={k:object for k in "年月日時分"})
df["DateTime"] = pd.to_datetime(df["年"].str.cat([df["月"], df["日"], df["時"]], sep=" ").str.cat(df["分"], sep=":"))
print(df)
""" =>
      年   月  日   時   分            DateTime
0  1996   8  1  12   5 1996-08-01 12:05:00
1  1998  12  2   4  12 1998-12-02 04:12:00
2  2012  05  3  23  56 2012-05-03 23:56:00
"""

 やはりスマートではない。

内包表記でdatetimeっぽい型のリストにすればいいんだよ

 そう、普通はそうしたいところ。

 型で迷うと思いますが、たぶんTimestampでいいと思います。

pandas.Timestamp — pandas 0.25.3 documentation

import io
import pandas as pd

data = """
年,月,日,時,分
1996,8,1,12,5
1998,12,2,4,12
2012,05,3,23,56
"""

df = pd.read_csv(io.StringIO(data))
df["DateTime"] = [
    pd.Timestamp(
        year=row["年"], month=row["月"], day=row["日"],
        hour=row["時"], minute=row["分"])
    for i, row in df.iterrows()]

print(df)
""" =>
      年   月  日   時   分            DateTime
0  1996   8  1  12   5 1996-08-01 12:05:00
1  1998  12  2   4  12 1998-12-02 04:12:00
2  2012   5  3  23  56 2012-05-03 23:56:00
"""

 読み込みで文字列にしないといけない、二度手間、といった微妙さがなくなりました。わーい。

 これはこれで上手くいきます。が、スマートなはずなのにスマートに見えない。キーワード引数の指定が汚すぎるからですね。

 ダブルアスタリスクのアンパックを使えば……とか一瞬は思いましたが、そのためには列名を変えたdfをコピーして作らないといけません。

import io
import pandas as pd

data = """
年,月,日,時,分
1996,8,1,12,5
1998,12,2,4,12
2012,05,3,23,56
"""

df = pd.read_csv(io.StringIO(data))
df_d = df[["年", "月", "日", "時", "分"]].copy()
df_d.columns = ["year", "month", "day", "hour", "minute"]
df["DateTime"] = [pd.Timestamp(**row)
                  for i, row in df_d.iterrows()]

print(df)
""" =>
      年   月  日   時   分            DateTime
0  1996   8  1  12   5 1996-08-01 12:05:00
1  1998  12  2   4  12 1998-12-02 04:12:00
2  2012   5  3  23  56 2012-05-03 23:56:00
"""

 こっちの方が多少スマートかな。上の書き方でも別に困ることはないです。

まとめ

 普通にTimestampのiterableを突っ込めばいいだけだけど、このやり方が調べても出てこなくて、できないのかなとか思って焦りつつやってみたらできたので記事にしました。

 日付時刻の扱いは割と面倒ですが、けっきょくのところ素直に組んでいけば良いはず。

参考

Pandasでの日付・時間周りのちょっとしたチートシート - Qiita
 これと同じようなことをやっています。