出力時の整形 - pandas
df.to_csv()
などで出力する時、ちょっとした変換方法
UnixTimeをDateStringに変換
1514777696 → 2018/01/01 12:34:56 にする。int ⇒ numpy.datetime64 ⇒ str と変換する
# 目的: df['unix'] のUnixTimeを変換して df['datetime'] に格納する # df['unix'] をdatetime64型に変換、時差9時間を足し日本時刻とする df['datetime'] = pd.to_datetime(df['unix'], unit='s') + pd.Timedelta(9, unit='h') # フォーマット df['datetime'] = df['datetime'].dt.strftime('%Y/%m/%d %H:%M:%S')
NaNを含むintカラム
np.nan
はint型で表現できないため、それを1つでも含むカラムは自動的にfloat型になる。float型は出力時の記述に「1.0」のように小数点が付いてしまう。
code code 1 1.0 → 1 2 1.0 整数にしたい 1 3 nan 4 3.0 3 ... ...
文字列に変換する
メモリや処理速度は多少効率悪くてもいい場合、文字列に変換するのがよい。
df['col'] = df['col'].dropna().apply(lambda x: str(int(x))) # または df['col'] = df['col'].dropna().apply(int).apply(str)
上記の変換ではnp.nanはnp.nanのまま残っているが、通常は空文字列になる。
もし他の表現にしたい場合、df.to_csv(na_rep='NULL')
などとしてやれば、指定した文字列で表現される。
nanを示す整数値を定義
妥協として、'-1
'など、nanであることを示す値を定義して、その値で代替する。
df['col'] = df['col'].fillna(-1).astype(int)
pandas.Int64Dtype型を使う
未検証、pandas ver.0.24から導入された新しめの手法。
numpyはnanを扱えないので、pandasのレイヤーでnanを扱えるint型を独自定義した感じ。
ただ、floatからのキャストでエラーが出て、イマイチよく使い方がわかっていない。
カラムの選択・並べ替え
# 元のカラムの並び順 print(df.columns.tolist()) # ['a', 'b', 'c', 'd'] cols = ['a', 'c', 'b'] # この順で新しいdfを作る df = df[cols]
整列
これは別に出力時に限った話でも無いけど。
# 'col'カラムでソート df.sort_values('col', inplace=True) # 'primary' => 'secondary' の優先順位でソート df.sort_values(['primary', 'secondary'], inplace=True)
インデックスを振り直す
インデックス(行ID)を、今の並び順で0からの連番にする。
# col1 col2 # 2 1 2 # 18 30 5 # 3 40 8 df.reset_index(drop=True, inplace=True) # col1 col2 # 0 1 2 # 1 30 5 # 2 40 8
カラム毎に小数点以下を揃える
df.round()を使う。{'カラム名': 小数点以下桁数} の辞書を渡すと、カラム毎にその桁数に揃える。
ただし桁数に0を指定してもint型にはならず、「1.0」のようなfloat型のままとなる。int型への変換はastype()を用いる。
# col1 col2 # 0 3.141592653 3.141592653 # 1 2.718281828 2.718281828 # 2 1.414213562 1.414213562 df = df.round({'col1': 2, 'col2': 5}) # col1 col2 # 0 3.14 3.14159 # 1 2.72 2.71828 # 2 1.41 1.41421
また、環境に依るのかも知れないが、メモリ節約で「np.float32」型などにしていると、roundが効かないことがあった。 効かない事態に遭遇したら、「np.float64」型にキャストすると上手くいったので、メモとして残しておく。
# col1 col2 # 0 3.141592653 3.141592653 # 1 2.718281828 2.718281828 # 2 1.414213562 1.414213562 # (float32) (float64) df.round({'col1': 2, 'col2': 2}) # col1 col2 # 0 3.141592653 3.14 # 1 2.718281828 2.72 # 2 1.414213562 1.41 df['col1'] = df['col1'].astype(np.float64) df.round({'col1': 2, 'col2': 2}) # col1 col2 # 0 3.14 3.14 # 1 2.72 2.72 # 2 1.41 1.41
丸め方法についての注意
注意点として、NumPyのroundは偶数丸めであること。(参考: 端数処理)
偶数丸めは、丸めた値が実際の値から上振れするか下振れするかの期待値を、なるべく均等にする目的で使われる。 よって、丸め結果の総和や平均を取る場合などは望ましい。 しかし、丸め結果の差分が重要なデータなどでは、四捨五入の方が誤差が少なくなる場合もある(*)ため、使い分ける必要がある。
だが、PythonにもNumPyにも本来の四捨五入(xx.5は常に切り上げ)に相当する関数が無いんだよなあ。 一応、標準モジュール Decimal.quantize() でDecimal型に変換することで、切り上げ方を選択できるが、変換が必要なことがネック。 1 で割ってあまりが 0.5 に等しいものは一律 0.01 を加算するみたいな方がいいかもしれん。
- (*) 検証方法
- $0.0~1.9$ の0.1刻みの $(a,b)$ の組400通りにつき、小数点1桁目を丸めての $a'-b'$ と、元の $a-b$ との誤差は、
- 偶数丸めで処理した結果: MAE=0.335, RMSE=0.4123
- 四捨五入で処理した結果: MAE=0.33, RMSE=0.4062
- となり、四捨五入の方が僅かに誤差が少なくなる