たそらぼ

日頃思ったこととかメモとか。

timeitモジュールで呼び出し可能オブジェクトの時間を計測する。

tensorflowのドキュメントを見てたら、timeitというモジュールを使っていたので、これなんやねんと思って調べた。

docs.python.org

Pythonからtimeitをimportして使う場合は、式を文字列として渡すか、呼び出し可能な式あれば時間を計測することができる。

例1

import timeit

timeit.timeit(lambda: print(1), number=10)
1
1
1
1
1
1
1
1
1
1
0.0006752069998583465

例2

import timeit

def test(i):
  for i in range(i):
      print("aaa")

timeit.timeit(lambda: test(1), number=10)
aaa
aaa
aaa
aaa
aaa
aaa
aaa
aaa
aaa
aaa
0.0003541580000501199

例3

import timeit

timeit.timeit("print(1)", number= 10)
1
1
1
1
1
1
1
1
1
1
0.00039979700000003504

なお、文字列を渡す例3のような場合は、以下のように文字列の外側で定義した関数だとダメだった。難しい。

例4(NG)

import timeit

def test(i):
  for i in range(i):
      print("aaa")

timeit.timeit("test(10)", number= 10)
NameError                                 Traceback (most recent call last)
<ipython-input-8-e602a5cee3d7> in <module>()
----> 1 timeit.timeit("test(10)", number= 10)

/usr/lib/python3.7/timeit.py in timeit(stmt, setup, timer, number, globals)
    231            number=default_number, globals=None):
    232     """Convenience function to create Timer object and call timeit method."""
--> 233     return Timer(stmt, setup, timer, globals).timeit(number)
    234 
    235 def repeat(stmt="pass", setup="pass", timer=default_timer,

/usr/lib/python3.7/timeit.py in timeit(self, number)
    175         gc.disable()
    176         try:
--> 177             timing = self.inner(it, self.timer)
    178         finally:
    179             if gcold:

/usr/lib/python3.7/timeit.py in inner(_it, _timer)

NameError: name 'test' is not defined

ちなみに文字列で無理やり食わせると動いた。

例4(OK)

import timeit

formula_str = """
def test(i):
  for i in range(i):
      print('aaa')
test(10)"""

timeit.timeit(formula_str, number= 1)
aaa
aaa
aaa
aaa
aaa
aaa
aaa
aaa
aaa
aaa
0.0010455369999817776

SQLのクエリ書いたり、PyStanのコード書いたりするときは文字列でよく渡すけど、標準ライブラリで求められることがなかったので、新鮮な体験だった。

Seabornのプロットでマーカーの色を変える

最近、Seabornでプロットをすることが増えたのですが、デフォルトだと色味がパッとしなかったり、サブプロットしたら全部同じ色になったりして困ったので、色を変える方法を調べてみました。

サマリ
  • seaborn.scatterplotのpaletteにパレット名を指定することで、色の系列を変えることができる。
  • 単色の場合は、seaborn.scatterplotのcolorにmatplotlibのnamed colorsの色名を渡すことで色を変えることができる。
前提

seabornのバージョン:'0.10.0'
seaborn.scatterplotは以下のドキュメントを参照(ただし、リンクは最新のバージョン)。
seaborn.pydata.org

散布図の色を変えてみる。

データのロード

今回は、ペンギンデータセットを使います。
github.com

penguins = pd.read_csv("https://github.com/allisonhorst/palmerpenguins/raw/5b5891f01b52ae26ad8cb9755ec93672f49328a8/data/penguins_size.csv")
penguins.head()

f:id:tasotasoso:20201018155951p:plain

単純にscatterplotで散布図を書くとこのようになります。

f:id:tasotasoso:20201018160357p:plain
hueなしの散布図

hueを使う場合

ご存知の通り、hueを使うことで、あるカテゴリで層別し、色を変えてプロットすることができます。
ペンギンの場合は、種類を表すspecies_shortの値が{'Adelie', 'Chinstrap', 'Gentoo'}のどれかなので、これをhueに指定しましょう。

sns.scatterplot(data=penguins, x="culmen_length_mm", y="culmen_depth_mm", hue="species_short")
f:id:tasotasoso:20201018160444p:plain
speacies_shortをhueに指定した散布図

paletteにpaletteの名前を指定することで、色味を変えることができます。パステルカラーにしてみましょう。

sns.scatterplot(data=penguins, x="culmen_length_mm", y="culmen_depth_mm", hue="species_short", palette="Pastel1")
f:id:tasotasoso:20201018160610p:plain
上の散布図をパステルカラーに。

パレットの文字列の一覧は、こちらの記事を参考にできます。少々乱暴ですが、エラーを起こして指定できる文字列を吐き出させています。
medium.com

サブプロットなどで層別ごとに個別にグラフを書く場合

さて、先ほどはhueを指定することで一つのグラフに3種のデータを描画しました。しかし、色が違うものの、点の分布が混ざっていて少々見にくいです。そう言う場合は、subplotを使って、species_shortごとに異なるデータとして散布図を描くことになるかもしれません。

fig, axs = plt.subplots(1, 3, sharey=True)

#'Adelie', 'Chinstrap', 'Gentoo'
g_Adelie = sns.scatterplot(data=penguins[penguins["species_short"] == "Adelie"], x="culmen_length_mm", y="culmen_depth_mm", ax=axs[0])
g_Adelie.set_title("Adelie")
g_Adelie.set(xlim=(30, 60))
g_Adelie.set(ylim=(13, 22))

g_Chinstrap = sns.scatterplot(data=penguins[penguins["species_short"] == "Chinstrap"], x="culmen_length_mm", y="culmen_depth_mm", ax=axs[1])
g_Chinstrap.set_title("Chinstrap")
g_Chinstrap.set(xlim=(30, 60))
g_Chinstrap.set(ylim=(13, 22))

g_Gentoo = sns.scatterplot(data=penguins[penguins["species_short"] == "Gentoo"], x="culmen_length_mm", y="culmen_depth_mm", ax=axs[2])
g_Gentoo.set_title("Gentoo")
g_Gentoo.set(xlim=(30, 60))
g_Gentoo.set(ylim=(13, 22))
f:id:tasotasoso:20201018160955p:plain
色指定なしの散布図

さて、無事に描くことができましたが、見ての通り、色が同じなので視覚的にパッとしません。
色を変えたい場合は、colorにmatplotlibのnamed colorsの色名を渡すことで色を変えることができます。
matplotlib.org

fig, axs = plt.subplots(1, 3, sharey=True)

#'Adelie', 'Chinstrap', 'Gentoo'
g_Adelie = sns.scatterplot(data=penguins[penguins["species_short"] == "Adelie"], x="culmen_length_mm", y="culmen_depth_mm", color="aqua", ax=axs[0])
g_Adelie.set_title("Adelie")
g_Adelie.set(xlim=(30, 60))
g_Adelie.set(ylim=(13, 22))

g_Chinstrap = sns.scatterplot(data=penguins[penguins["species_short"] == "Chinstrap"], x="culmen_length_mm", y="culmen_depth_mm", color="fuchsia", ax=axs[1])
g_Chinstrap.set_title("Chinstrap")
g_Chinstrap.set(xlim=(30, 60))
g_Chinstrap.set(ylim=(13, 22))

g_Gentoo = sns.scatterplot(data=penguins[penguins["species_short"] == "Gentoo"], x="culmen_length_mm", y="culmen_depth_mm", color="lightgreen", ax=axs[2])
g_Gentoo.set_title("Gentoo")
g_Gentoo.set(xlim=(30, 60))
g_Gentoo.set(ylim=(13, 22))
f:id:tasotasoso:20201018161211p:plain
色指定ありの散布図

コードにTODOのコメントを埋め込むvimプラグインを作った。

コードの未実装の箇所に、「こんな感じで実装していこうと思います、リファレンスはこれです。」とTODOのコメントを入れるvimプラグインを作りました。
github.com

インストール

{packpath}/pack/plugins/start 配下にgit cloneしてください。

dein.vimを使っている方は、dein.tomlなどに追記してインストールしてください。

[[plugins]]
repo = 'tasotasoso/vim-TodoComment'

dein.vimの使い方は本家レポジトリやさくらのナレッジの記事を参照のこと。

使い方

1. vimのnormalモードで :TodoComment を入力する。
2. コメントの入力を促されるので、任意のコメントを入力する。
3. コメントの根拠になるURLの入力を促されるので、ドキュメントなどのURLを入力する。

f:id:tasotasoso:20200829154353p:plain
実行結果

コメントアウトの種類について

コメントアウトに使う文字は、TodoCommentコマンドを実行するファイルの拡張子を読み込んで、自動的に変更される。
対応言語は以下になる。

extension comment out
py, rb, js, sh #
java, go, cpp //
others "

GithubのReadmeにgifを入れために、gifの作成からReadmeの編集までやってみた。

Vimプラグインを作った際に、Readmeに挙動が分かるようなGif動画を入れたかったので、
gifの作り方から調べてReadmeに入れるところまでやってみた。


出来栄えは、こんな感じ。
github.com

一番最後の画像。
f:id:tasotasoso:20200829154353p:plain

環境

macOS Catalina v10.15で行った。
macでの操作のみ記載する。

手順

1. デモ画面の動画を撮影する。

MacだとQuickTime Playerがあるのでこれを使う。
Mac用QuickTime Playerユーザガイド - Apple サポート

[ファイル] > [新規画面収録] からデモ画面の撮影ができる。このとき、画面の一部だけ指定して撮影できるので、その機能を使う。

f:id:tasotasoso:20200829152036p:plain:w400
デモ動画の撮影

これで、デモ画面を撮影した.movファイルができる。

2. Gifに変換する。

以下の記事などを参考に、1で作成した.movをgifに変換する。
.mov を gif に変換【Mac】 - Qiita


まずインストールする。

brew install ffmpeg

上手くインストールできていそうだったら、1で作成した.movを指定してgifに変換する。

ffmpeg -i sample.mov -r 10 sample.gif

3. Readmeを作成するレポジトリに、gifを格納するbranchを作成して、gifを上げる。

gifの保存先はいろいろ候補があるようだったが、githubに上げておくのが一番スマートそうだったのでそうする。
一方で、masterにgifを入れておくとダウンロードする際にも容量が大きくなり、使ってくれる人に迷惑をかけてしまいかねないので、
gifを入れておくようのmediaというbranchを作成してそこに入れておく。

具体的な操作がわからない場合は、以下が参照になる。
GitHubでREADMEにgif画像を表示する簡単な方法 - Qiita

4. Readmeにリンクを挿入する。

Readmeに先ほどアップロードしたgif動画のリンクを挿入する。リンクはブラウザでgithub上のgif動画を開いて、そのURLをコピペすればいい。
挿入方法は、以下の一行をreadmeに差し込むだけ。

![TodoComment](【皆さんのgif動画のURL】)

Sparkで"使い方が誤っています"でJavaが読み込めない。

Sparkを勉強するのに、spark-3.0.0-bin-hadoop2.7.tgzをダウンロードして、pysparkでshellを起動しようとしたが、
以下のような感じのエラーで起動できなかったのでメモ。
pipでpysparkを入れてpythonからimportしても同様。

\Java\jdk-12.0.1\bin\java の使い方が誤っています。

環境

  • windows10
  • jdk-12.0.1
  • spark-3.0.0-bin-hadoop2.7.tgz

解決策

JDKのパスにスペースが入っていたので、場所を変え、PathとJAVAHOMEを直したところ読み込めた。

ダメ
f:id:tasotasoso:20200801133129p:plain

良い
f:id:tasotasoso:20200801133157p:plain

PythonでAESの暗号化を試した

PythonでAESの暗号化を学んだのでメモ。

やりたいこと

C#で暗号化されたものをPythonで再現する。
c#の暗号化クラスを使ってみた(AES,RSA) - Qiita

PythonでAESの暗号化を行う

CBCモードで行うAESにより暗号化するサンプルコードは、以下が参考になる。
pycryptodome.readthedocs.io

PyCryptodomeを選択した理由は、Developers.IOから、PyCryptodomeがよさそうだったため。
AES対応のPython暗号化ライブラリを比較検証してみた | Developers.IO

実験

サンプルコードを参考に、鍵とinitialization vectorを指定して、暗号が再現できるか確認する。
※initialization vectorは本当は固定しないが、今回は実験のため固定する。

from base64 import b64encode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad
import json

data = "Hello, World!".encode('utf-8')
key = "9Fix4L4HB4PKeKWY".encode('utf-8')
iv = "pf69DL6GrWFyZcMK".encode('utf-8') #get_random_bytes(16)

cipher = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
ct_bytes = cipher.encrypt(pad(data, AES.block_size))
iv = b64encode(iv).decode('utf-8')
ct = b64encode(ct_bytes).decode('utf-8')
result = json.dumps({'iv':iv, 'ciphertext':ct})
print(result)
#{"iv": "cGY2OURMNkdyV0Z5WmNNSw==", "ciphertext": "p8ITppfvm6QnVtL/Ji9/ZQ=="}

確かに暗号化した文が一致している。

参考

C#のAESについて

System.Security.CryptographyにAESのクラス群が定義されている。抽象基底クラスは以下。
docs.microsoft.com
Wikiの記載によると、AESに採用されたのはRijndaelで、これもクラスがある。
ただし、AESとの違いが以下に説明されている。
docs.microsoft.com

AESのパラメータの参考

IPsec 暗号ペイロードにAESを使う際の規定がIPAで発行されている。
特に、今回はinitialization vectorを固定としたが、必ず暗号化ごとに変えることが記載されている。
www.ipa.go.jp

また、initialization vectorの必要性は徳丸先生のnoteが参考になる。
note.com

CloudTrailの証跡機能にだいぶ詳しくなったのでまとめる

CloudTrailの証跡機能にだいぶ詳しくなったので、個人的なまとめです。

CloudTrailの概要

CloudTrailはアカウントの操作ログを記録し、ログを Amazon S3 バケットに送信することができます。

f:id:tasotasoso:20200530234221p:plain:w200
90日分の管理イベントを無料で保存、検索することが可能です。

証跡の作成により、管理イベントを、S3などにエクスポートすることができます。
また、オプションでデータイベントとCloudTrail Insightsも保存することができます。

各々のログの概要と料金は下記のようになります。

種類 概要 料金
管理イベント AWSアカウントの操作履歴 イベント 10 万件あたり 2.00USD(証跡一つ目は無料)
データイベント Amazon S3 オブジェクトレベルの API 呼び出し、Lambda 関数の実行履歴 イベント 10 万件あたり 0.10USD
CloudTrail Insights AWS アカウントの異常な操作履歴 100,000 の書き込み管理イベントごとに 0.35USD

管理イベントは一つ目が無料なため、とりあえず、管理イベントのみ保存して、データイベントとCloudTrail Insightsは要件によって保存する場合が多いかと思います。


ログは、デフォルトで以下のプレフィクスに保存されます。

S3バケット名/AWSLogs/アカウントID/

オプションでAWSLogsの前にプレフィクスを追加することも可能です。

この場所には以下の2種のデータが作成されます。

  • CloudTrailログ

 CloudTrailの階層が掘られ、そのさらに下に、リージョン/YY/mm/dd/にjson.gzで保存されます。
 https://docs.aws.amazon.com/ja_jp/awscloudtrail/latest/userguide/cloudtrail-log-file-examples.html

  • ダイジェストファイル

 CloudTrail-Digest配下に作成される。ログファイルの名前、これらのログファイルのハッシュ値、前のダイジェストファイルのデジタル署名が含まれます。
 https://docs.aws.amazon.com/ja_jp/awscloudtrail/latest/userguide/cloudtrail-log-file-validation-digest-file-structure.html

CloudTrailログの例

レコードの項目は全て公式ドキュメントに記載されています。
CloudTrail レコードの内容 - AWS CloudTrail

具体的には以下のようなログが出力されます。

#From https://docs.aws.amazon.com/ja_jp/awscloudtrail/latest/userguide/cloudtrail-log-file-examples.html

{"Records": [{
    "eventVersion": "1.0",
    "userIdentity": {
        "type": "IAMUser",
        "principalId": "EX_PRINCIPAL_ID",
        "arn": "arn:aws:iam::123456789012:user/Alice",
        "accountId": "123456789012",
        "accessKeyId": "EXAMPLE_KEY_ID",
        "userName": "Alice"
    },
    "eventTime": "2014-03-24T21:11:59Z",
    "eventSource": "iam.amazonaws.com",
    "eventName": "CreateUser",
    "awsRegion": "us-east-2",
    "sourceIPAddress": "127.0.0.1",
    "userAgent": "aws-cli/1.3.2 Python/2.7.5 Windows/7",
    "requestParameters": {"userName": "Bob"},
    "responseElements": {"user": {
        "createDate": "Mar 24, 2014 9:11:59 PM",
        "userName": "Bob",
        "arn": "arn:aws:iam::123456789012:user/Bob",
        "path": "/",
        "userId": "EXAMPLEUSERID"
    }}
}]}

CloudTrailログの検索

Athenaを使ってクエリをかけることも可能です。
テーブルは以下のクエリで作成できます。

#From https://docs.aws.amazon.com/ja_jp/athena/latest/ug/cloudtrail-logs.html

CREATE EXTERNAL TABLE cloudtrail_logs (
eventversion STRING,
useridentity STRUCT<
               type:STRING,
               principalid:STRING,
               arn:STRING,
               accountid:STRING,
               invokedby:STRING,
               accesskeyid:STRING,
               userName:STRING,
sessioncontext:STRUCT<
attributes:STRUCT<
               mfaauthenticated:STRING,
               creationdate:STRING>,
sessionissuer:STRUCT<  
               type:STRING,
               principalId:STRING,
               arn:STRING, 
               accountId:STRING,
               userName:STRING>>>,
eventtime STRING,
eventsource STRING,
eventname STRING,
awsregion STRING,
sourceipaddress STRING,
useragent STRING,
errorcode STRING,
errormessage STRING,
requestparameters STRING,
responseelements STRING,
additionaleventdata STRING,
requestid STRING,
eventid STRING,
resources ARRAY<STRUCT<
               ARN:STRING,
               accountId:STRING,
               type:STRING>>,
eventtype STRING,
apiversion STRING,
readonly STRING,
recipientaccountid STRING,
serviceeventdetails STRING,
sharedeventid STRING,
vpcendpointid STRING
)
PARTITIONED BY (region string, year string, month string, day string)
ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://CloudTrail_bucket_name/AWSLogs/Account_ID/CloudTrail/';

パーティションはリージョン・年・月・日で設定することを推奨されています。
リージョンは、CloudTrailの証跡設定作成時に、全リージョンで作成することを指定しなければ、証跡を設定したリージョンのみになります。
後から設定するのは面倒臭いので、とりあえずリージョンもパーティションに含めておくと良さそうです。


注意点としては、CloudTrail証跡は現状、以下のプレフィクスで出力されるため、
MSCK REPAIR TABLE でパーティションデータを自動的に追加できません。

S3バケット/AWSLogs/アカウントID/CloudTrail/リージョン/YY/mm/dd/

そのため、バッチ処理などでADD PARTITIONでパーティションを追加する必要があります。
Athenaのクエリだと以下のようになります。

ALTER TABLE [テーブル名] ADD PARTITION (region='[リージョン]',year='[設定値]',month='[設定値]',day='[設定値]') location 's3://[バケット]/[プレフィックス]/';

バッチ処理での実行方法は、Lambda・Fargate・Glueなどいろいろな方法があります。lambdaでの実行方法は、以下のサイトが参考になります。
dev.classmethod.jp

ログの記録をOFFにした時

ログの記録をOFFにすると、当然ですがログの保存は中止されます。
ONにしても、OFFにしていた間のログは勝手には配信されません。
ログの記録のON/OFFは、[CloudTrai]l >[ 証跡情報] > (各証跡の)設定 から行えます。

f:id:tasotasoso:20200531110714p:plain:w300
ログ記録のON/OFF

配信エラーになった時

こちらにまとめがあるので参考にしてください。
tasotasoso.hatenablog.com