仕事メモとか

仕事中に調べた情報とか知ったことをメモしています。
unixコマンド, vim, oracle, putty, postgresql, bash, EXCEL, python, SQL全般 など。
最近は tableau, movabletype とかも触ったりしています。
雑な読書感想とかはこちら

カテゴリ: Python


pythonでZipFileを使ってzip圧縮したらディレクトリ構造まで付いてきたので、ファイルだけにしたい

元々カレントの場所に出力するプログラムを作っていたのですが、
zip圧縮するなら元のファイルは消さないとなー、
くらいに思っていたのですが、

TemporaryDirectoryという素晴らしいものがありまして。

これを使うと一時的に使えるディレクトリが作成できます。
使い終わったら消えます。便利。

んで本題。
出力pathを指定したところ、zip圧縮した中身のファイルもディレクトリ構造をそのまま持ってきちゃいまして。

例)
{TemporaryDirectory}/outfile_{today_str}.csv
をzipにそのまま放り込んだら、「{TemporaryDirectory}/」がそのまま付いてきた。

これを解消する方法を調べたら、
arcnameで入れるファイル名の階層を指定できるので、ファイル名だけをそのまま指定すれば、
zipファイルの中に直接ファイルを入れられる、ということが分かりました。


with TemporaryDirectory() as tempdir:

out_file_name = f'outfile_{today_str}.csv'
out_file_name_zip = f'outfile_{today_str}.zip'
to_csv(df, Path(tempdir) / out_file_name) -- dfをファイルに出力する部分

with ZipFile(out_file_name_zip, 'w') as out_zip:
out_zip.write(Path(tempdir) / out_file_name, arcname=out_file_name)



逆にarcnameにディレクトリ構造を指定することもできるので、
込み入ったディレクトリ構造をzipに入れるタイミングで制御することもできます(あんまり要望無さそう)

pythonでテスト実行した際、EUC-JP使ってないのに、INTERNALERROR> UnicodeEncodeError:
'euc_jp'... っていうエラーがでた

はい、pythonでプログラムを書いてるのですが、未だによくわからないことが発生します。
今回のお題はこちら。

テスト実行したら、こんなんが出た。

INTERNALERROR> UnicodeEncodeError: 'euc_jp' codec can't encode
character '\u9ad9' in position xx: illegal multibyte sequence

●環境
プログラム:python 3.7
エディタ:IntelliJ
テスト環境:IntelliJ組み込みのpytest

●文字コード
テスト対象、テスト用プログラム両方ともUTF-8

●テスト内容
特殊文字のテストもしたかったので、髙(通称、はしごだか)を入れた


テスト実行してみたところ、上記のようなエラーが出ました。
\u9ad9は「髙」なので、これをEUC-JPにしようとしてエラーになりました、とのことなのですが、
そもそもプログラム全部UTF-8なのに? と。

結論から書きますが、IntelliJの基本設定をEUC-JPに変更していることが原因でした。

●設定
[IntelliJ]-[Preferences]-[Editor]-[FileEncodings]

ここのProjectEncodingが「EUC-JP」になっていました。
(デフォルトはUTF-8っぽいのですが、読み込むファイルがEUC-JPが多かったので、変更していたことが原因です。


●解消方法
1.ProjectEncodingは「SystemDefault: UTF-8」に戻します。
2.良く開くファイルはやはりEUC-JPとして開きたいので、そのディレクトリだけ直接指定(下の+ボタンで個別追加)

これで解消できました。

pandasで、データフレームに対し、同様のフォーマットだったら全部置換したい、的な手法=applymap

はい、データフレーム使ってますか?
便利ですね、pandas。

ということで、今回の要件はこんな感じです。

要件:
データフレーム上では全てのデータをそのまま扱いたいが、
csvなどで出力するときは日付のフォーマットを全部固定差し替えしたい。

調べてみると、applymapというものが使えそうなんですが、これ指定の仕方がちょっと面白い。

df = df.applymap(function)

直接、function(関数)をぶっこみます。
ここで指定する関数はlambdaでも、defでもよいのですが、こんな感じになります。


def _format_date_time(value):
if isinstance(value, datetime):
return value.strftime('%Y/%m/%d')
else:
return value


formatted = {変換したいdf}.applymap(_format_date_time)

このようにすることで、内部関数「_format_date_time」をapplymapに突っ込んで、
変換したいdfの中身のうち、datetime型のものだけ出力を「YYYY/MM/DD」型に置換します。

慣れるとかなり強力です。


pythonで指定した期間の毎月1日を取得

はい、複数月のデータを取るときに、SQL一発でやっていましたが、
月を指定してループで回す方が可読性あがるよね、ということで、
python側で月をループで取るようにしたサンプルです。


import datetime

def month_range(start_date: date, end_date: date):
m = start_date
while m < end_date:
yield m.replace(day=1)
m = (m.replace(day=1) + timedelta(31)).replace(day=1)


def main():

for m in month_range(datetime.date(2019, 1, 1), datetime.date.today()):
print m


こんな感じにすると、
・貰ったスタートの日付を一旦その月の1日に変換、yieldで返す
・次はそこに31日を足して、その月の1日に変換、yieldで返す
・・・

となります。

timedeltaは月の加算減産は出来ないそうで。
やりたいならサードパーティのライブラリを使う、という手もあるっポイです。
https://python.civic-apps.com/add-month-relativedelta/


参考:
https://www.sejuku.net/blog/23716
https://python.civic-apps.com/timedelta/


pandasの出力で、floatにすると、整数が「x.0」ってなるやつの対応方法

はい、pandas便利ですね。
データフレームにすると大体便利に扱えるので、これからの時代はpandasじゃないかな、
って思うんですが、ちょっとした落とし穴が。

数値データを取り扱うとfloatになるんですが、これのせいでデータが、

10

10.0

となります。小数値が欲しいときは良いんですが、整数として取り扱いたいときに、
csvで出力しようとすると、こうなります。

df.to_csv('outfile.csv', encoding='MS932', index=False,
header=['xxx', 'yyy', 'zzz'])

この時
float_formatでフォーマットを指定すると、色々出来ます。
こうすると、整数のときの「.0」が出ないようになります。

float_format='%g'

例)
df.to_csv('outfile.csv', encoding='MS932', index=False, float_format='%g',
header=['xxx', 'yyy', 'zzz'])


参考
https://note.nkmk.me/python-pandas-option-display/
https://pythondatascience.plavox.info/pandas/%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B

↑このページのトップヘ