20210911のPythonに関する記事は30件です。

PythonからGCPへ接続するときの認証設定

TL;DR PythonからGCPへ接続する際の認証情報の設定方法が、サービスアカウントのJSONファイルのパスを指定する方法しか見つからなかった。 Gitには認証JSONを乗せたくないし、本番運用時にサーバーにアップロードするのも面倒なので楽に設定できる方法がないか調査した結果をまとめました。 1. GCPからJSONファイルをダウンロード 詳しくは割愛。中身はこんな感じ。 credential.json { "type": "service_account", "project_id": "***************", "private_key_id": "***************", "private_key": "-----BEGIN PRIVATE KEY-----************-----END PRIVATE KEY-----\n", "client_email": "***************@***************.iam.gserviceaccount.com", "client_id": "***************", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "***************" } 2. ライブラリをインストール pip install google-cloud-storage python-dotenv google-cloud-storage 今回は、GCSへ接続するためのライブラリで認証情報を設定していきます。 python-dotenv 環境変数を.envから読み込むために使用します。 3. .envに環境変数を設定 先ほどダウンロードしたJSONファイルの中身をコピペします。 GCP_PROJECT_ID=******** GCS_PRIVATE_KEY_ID=******** GCS_PRIVATE_KEY=******** GCS_CLIENT_MAIL=******** GCS_CLIENT_ID=******** GCS_CLIENT_X509_CERT_URL=******** 4. 環境変数を読み込み settings.py from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv()) 5. Clientクラスを作成 GCS_PRIVATE_KEYには改行コード\nが含まれているのですが、環境変数経由で読み込むとエスケープされてしまうので、replaceで元に戻してあげます。 client.py import os from google.oauth2.service_account import Credentials from google.cloud import storage class GoogleCloudStorageClient: cred = Credentials.from_service_account_info({ "type": "service_account", "project_id": os.environ["GCP_PROJECT_ID"], "private_key_id": os.environ["GCS_PRIVATE_KEY_ID"], "private_key": os.environ["GCS_PRIVATE_KEY"].replace("\\n", "\n"), "client_email": os.environ["GCS_CLIENT_MAIL"], "client_id": os.environ["GCS_CLIENT_ID"], "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": os.environ["GCS_CLIENT_X509_CERT_URL"] }) client = storage.Client(credentials=cred) @classmethod def _create_bucket_instance(cls, bucket_name: str, destination_blob_name: str): bucket = cls.client.bucket(bucket_name) return bucket.blob(destination_blob_name) @classmethod def upload_file(cls, bucket_name: str, destination_blob_name: str, local_file_path: str) -> None: blob = cls._create_bucket_instance(bucket_name, destination_blob_name) blob.upload_from_filename(local_file_path) storage.Client() credentialsに何も設定していない場合は環境変数に認証JSONファイルパスを設定しないといけません。 今回はJSONファイルが無くても認証が通るように、ライブラリで用意されている認証情報オブジェクトを生成して渡しています。 6. 呼び出し main.py bucket_name = "XXX" # 保存先のバケット名 destination_blob_name = "YYY/ZZZ.csv" # 保存先となるバケット内でのファイルパス local_file_path = "LOCAL/FILE.csv" # 保存したいファイルパス GoogleCloudStorageClient.upload_file( bucket_name, destination_blob_name, local_file_path )
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TensorFlowでコード進行認識

はじめに 私は和音を認識するAIを作っています。 今回は、精度を上げるために工夫したことを紹介したいと思います。 現在、和音を自動認識するアプリは、YAMAHAの「ChordTracker」や「Chord ai」などがあります。 どちらのアプリも、簡単なコード進行であれば、それなりの精度で認識できます。 しかし、コード進行が少し複雑だったり、コードが頻繁に変わるような場合は、うまく認識できないことがあります。 そのため、自分で作ってみることにしました。 完成したAIはGithubに置いてあります。 スペクトログラムに変換 オーディオデータをモデルの入力として使用するためには、スペクトログラムに変換する必要があります。 これは、生のオーディオデータのサイズが巨大なためです。 データを変換する方法はいくつかありますが、ここでは、コード認識でよく使われているCQT(Constant-Q Transform)を使用します。 CQTが使われているのは、入力データのサイズを小さくできることと、周波数方向の分解能がほぼ同じだからです。 コード認識では、低域から中域の部分に重点が置かれます。そのため、周波数方向の分解能がほぼ同じである方が良いのです。 実際に変換する際には、オーディオの読み込みからCQTの使用までができる「librosa」という便利なライブラリがあります。 しかし、私はC++でアプリケーションを作ろうと思っていたので、CQTをC++で実装し、それをpythonに変換して使いました。 その際に低音と高音の解像度が同じになるようにQfactorを調整しました。 以下の画像は実際にCQTを使って変換したスペクトログラムです。 よく見ると、librosaの場合は低音が潰れていて、高音の解像度が高くなっているのがわかります。 私が実装した右側では、低音と高音がほぼ同じ大きさになっていることがわかります。 オクターブあたりのビン数 これは、1オクターブあたりどれだけの解像度で解析するかを意味します。librosaではbins_per_octaveです。 librosaのデフォルト値は12だと思います。1オクターブが12音なので、12は1オクターブあたり1の解像度ということになります。 これの最適な値は12*3、つまり1オクターブあたり3の解像度です。これ以上大きくしても精度にあまり変化は見られませんでした。 解像度を上げると入力のサイズも増加するので、12*3がちょうどいい値だと思います。 データを保存するときの工夫 CQTや音声読み込みなどの処理は、リアルタイムで行えるほど軽くはありません。そのため、変換したデータをファイルに保存しておいて、学習時に読み込む必要があります。 データを保存する際には、必ずコードチェンジの部分で切り取るようにします。 その理由は、適当な場所でデータを切ってしまうと、コードの開始位置ではなく、途中が開始位置になってしまうからです。 ほとんどの場合、コードチェンジの直後に構成音が演奏されます。ベースの場合も同様です。 コードの途中では、ベースの音が動いていたり、構成音が鳴っていなかったりします。 データ拡張 データ拡張のために周波数マスキング、時間マスキングを追加しています。 ピッチシフトをして、学習データを12倍に増やしています。 モデルが出力するもの コード構成音ベクトル コードを予測する際には、いきなりコードを出力するのではなく、コード構成音ベクトルを予測し、それに基づいてコードを予測しています。 これは、コードのデータはかなり不均衡なデータであり、ベクトルで見たほうが音の関係性がわかりやすいからです。 何百ものコードを予測するよりも、どの音が鳴っているのか、そのベクトルがどんなコードを表しているのかを予測する方が簡単です。 実際に予測するベクトルは、コード構成音とベース音を組み合わせたサイズ25のベクトルです。 以下は実際に予測したコード構成音ベクトルです。 平滑化したコード構成音ベクトル コード構成音ベクトルは出力が粗く、不自然に途切れてしまうことがあります。 そこで、出力を滑らかにするために、まったく同じベクトルを再度予測します。出力を滑らかにするだけなので、コード構成音ベクトルを予測するときよりも小さなモデルを使用します。 コード遷移確率 人の場合、コードチェンジの瞬間をほとんど見分けることができます。耳コピができる人は、ほぼ100%コードチェンジの瞬間を見分けることができるでしょう。それと同じことをAIに学習させようというわけです。 コードチェンジの瞬間を予測するのですが、コードチェンジが発生する瞬間は、曲全体のデータと比べて数えるほどしかありません。そのため、不均衡になり、学習が難しくなります。 調 (キー) 曲を耳コピするとき、コード進行をヒントにすることがあります。「王道進行」や「2-5-1進行」などよく使われるものがあります。 キーがわかっていれば、コード進行のルールや音の関係性のようなものを把握できるかもしれません。そうなることを期待して、キーを予測します。 不均衡データ対策 コードや遷移確率などのデータはかなり不均衡になっています。 そのまま学習してもサンプル数が少ないものはうまく検出できません。 不均衡データに対応するために、focallossというloss関数を使用します。 簡単に検出ができるものの重みを下げ、難しいサンプルを重視するというものです。 これを導入することによって不均衡データでもうまく学習できるようにします。 コード遷移確率や調の損失関数にはfocallossを、コード構成音ベクトルにはfocallossとtverskylossを組み合わせたfocaltverskyを使用しました。 損失関数のパラメータは、混同行列を見て調整しました。これはデータセットに依存するので、微調整する必要があります。 実際のモデル CNNとLSTMを組み合わせたモデルです。 LSTM部分はOpen-Unmixという音源分離モデルを参考にして作成しました。 実際のコードの予測は、CRFを使って結果が滑らかになるように出力しています。 混合行列 これが実際の混合行列です。対角線の成分が高いほど正確に予測できています。 一部を除いてほとんど正確に予測できていると思います。 参考 【Python】不均衡な2クラスセグメンテーション問題に適用するロス関数のメモ Tversky loss function for image segmentation using 3D fully convolutional deep networks Focal Lossを提案した革新的物体検出モデルRetinaNetを解説! [論文紹介] Focal Loss for Dense Object Detection
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コード進行認識AIを作るうえで工夫したこと

はじめに 私は和音を認識するAIを作っています。 今回は、精度を上げるために工夫したことを紹介したいと思います。 現在、和音を自動認識するアプリは、YAMAHAの「ChordTracker」や「Chord ai」などがあります。 どちらのアプリも、簡単なコード進行であれば、それなりの精度で認識できます。 しかし、コード進行が少し複雑だったり、コードが頻繁に変わるような場合は、うまく認識できないことがあります。 そのため、自分で作ってみることにしました。 完成したAIはGithubに置いてあります。 スペクトログラムに変換 オーディオデータをモデルの入力として使用するためには、スペクトログラムに変換する必要があります。 これは、生のオーディオデータのサイズが巨大なためです。 データを変換する方法はいくつかありますが、ここでは、コード認識でよく使われているCQT(Constant-Q Transform)を使用します。 CQTが使われているのは、入力データのサイズを小さくできることと、周波数方向の分解能がほぼ同じだからです。 コード認識では、低域から中域の部分に重点が置かれます。そのため、周波数方向の分解能がほぼ同じである方が良いのです。 実際に変換する際には、オーディオの読み込みからCQTの使用までができる「librosa」という便利なライブラリがあります。 しかし、私はC++でアプリケーションを作ろうと思っていたので、CQTをC++で実装し、それをpythonに変換して使いました。 その際に低音と高音の解像度が同じになるようにQfactorを調整しました。 以下の画像は実際にCQTを使って変換したスペクトログラムです。 よく見ると、librosaの場合は低音が潰れていて、高音の解像度が高くなっているのがわかります。 私が実装した右側では、低音と高音がほぼ同じ大きさになっていることがわかります。 オクターブあたりのビン数 これは、1オクターブあたりどれだけの解像度で解析するかを意味します。librosaではbins_per_octaveです。 librosaのデフォルト値は12だと思います。1オクターブが12音なので、12は1オクターブあたり1の解像度ということになります。 これの最適な値は12*3、つまり1オクターブあたり3の解像度です。これ以上大きくしても精度にあまり変化は見られませんでした。 解像度を上げると入力のサイズも増加するので、12*3がちょうどいい値だと思います。 データを保存するときの工夫 CQTや音声読み込みなどの処理は、リアルタイムで行えるほど軽くはありません。そのため、変換したデータをファイルに保存しておいて、学習時に読み込む必要があります。 データを保存する際には、必ずコードチェンジの部分で切り取るようにします。 その理由は、適当な場所でデータを切ってしまうと、コードの開始位置ではなく、途中が開始位置になってしまうからです。 ほとんどの場合、コードチェンジの直後に構成音が演奏されます。ベースの場合も同様です。 コードの途中では、ベースの音が動いていたり、構成音が鳴っていなかったりします。 データ拡張 データ拡張のために周波数マスキング、時間マスキングを追加しています。 ピッチシフトをして、学習データを12倍に増やしています。 モデルが出力するもの コード構成音ベクトル コードを予測する際には、いきなりコードを出力するのではなく、コード構成音ベクトルを予測し、それに基づいてコードを予測しています。 これは、コードのデータはかなり不均衡なデータであり、ベクトルで見たほうが音の関係性がわかりやすいからです。 何百ものコードを予測するよりも、どの音が鳴っているのか、そのベクトルがどんなコードを表しているのかを予測する方が簡単です。 実際に予測するベクトルは、コード構成音とベース音を組み合わせたサイズ25のベクトルです。 以下は実際に予測したコード構成音ベクトルです。 平滑化したコード構成音ベクトル コード構成音ベクトルは出力が粗く、不自然に途切れてしまうことがあります。 そこで、出力を滑らかにするために、まったく同じベクトルを再度予測します。出力を滑らかにするだけなので、コード構成音ベクトルを予測するときよりも小さなモデルを使用します。 コード遷移確率 人の場合、コードチェンジの瞬間をほとんど見分けることができます。耳コピができる人は、ほぼ100%コードチェンジの瞬間を見分けることができるでしょう。それと同じことをAIに学習させようというわけです。 コードチェンジの瞬間を予測するのですが、コードチェンジが発生する瞬間は、曲全体のデータと比べて数えるほどしかありません。そのため、不均衡になり、学習が難しくなります。 調 (キー) 曲を耳コピするとき、コード進行をヒントにすることがあります。「王道進行」や「2-5-1進行」などよく使われるものがあります。 キーがわかっていれば、コード進行のルールや音の関係性のようなものを把握できるかもしれません。そうなることを期待して、キーを予測します。 不均衡データ対策 コードや遷移確率などのデータはかなり不均衡になっています。 そのまま学習してもサンプル数が少ないものはうまく検出できません。 不均衡データに対応するために、focallossというloss関数を使用します。 簡単に検出ができるものの重みを下げ、難しいサンプルを重視するというものです。 これを導入することによって不均衡データでもうまく学習できるようにします。 コード遷移確率や調の損失関数にはfocallossを、コード構成音ベクトルにはfocallossとtverskylossを組み合わせたfocaltverskyを使用しました。 損失関数のパラメータは、混同行列を見て調整しました。これはデータセットに依存するので、微調整する必要があります。 実際のモデル CNNとLSTMを組み合わせたモデルです。 LSTM部分はOpen-Unmixという音源分離モデルを参考にして作成しました。 実際のコードの予測は、CRFを使って結果が滑らかになるように出力しています。 混合行列 これが実際の混合行列です。対角線の成分が高いほど正確に予測できています。 一部を除いてほとんど正確に予測できていると思います。 参考 【Python】不均衡な2クラスセグメンテーション問題に適用するロス関数のメモ Tversky loss function for image segmentation using 3D fully convolutional deep networks Focal Lossを提案した革新的物体検出モデルRetinaNetを解説! [論文紹介] Focal Loss for Dense Object Detection
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

言語処理100本ノック解説 2章

目次 10.行数のカウント 11. タブをスペースに置換 12. 1列目をcol1.txtに,2列目をcol2.txtに保存 13. col1.txtとcol2.txtをマージ 14. 先頭からN行を出力 15. 末尾のN行を出力 16. ファイルをN分割する 17. 1列目の文字列の異なり 18. 各行を3コラム目の数値の降順にソート 19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる 10.行数のカウント pythonでのファイル読み込みは, f = open("ファイル名") で行えますが,これだけだと使い終わった時に f.close() と記述する必要があります. これをしなくてもいいように基本的には以下のように記述しましょう. with open("popular-names.txt") as f: withを用いてで開いたファイルはスコープを外れると閉じてしまう(閉じてくれる)ので,fに関しての処理はwithのスコープ内で記述します. これを合わせて書くと以下のようになります. nolp_nock10.py with open("popular-names.txt") as f: list = f.readlines() print(len(list)) UNIXコマンドについては, wac popular_names.txt を実行すると以下のように行数,列数,全要素数,ファイル名が表示されます. 2780 11120 55026 popular-names.txt 11.タブをスペースに置換 10と同じようにファイルを読み込んでリストに格納します. その後は各行に対してsplit()を適用することで1行にある要素全てをリスト化します. そのあとにjoin()をスペース区切りで用いることでタブからスペースに変換されます. これをコードで表すと以下になります. nlp_nock11.py with open("popular-names.txt") as f: list = f.readlines() for sentense in list: tmp = sentense.split() print(" ".join(tmp)) また,これを内包表記で表すと with open("popular-names.txt") as f: list = f.readlines() print("\n".join([" ".join(sentence.split()) for sentence in list])) ここまで短縮できます.かっこいいですよね?? UNIXコマンドは, sed -e 's/\t/ /g' popular-names.txt のように記述することで同じ出力が得られます. これは,「popular_names.txtに対して全ての(g)タブを(\t)スペース(/ /)に変換する」という意味です. 12.1列目をcol1.txtに,2列目をcol2.txtに保存 さてここからはpandasというデータ解析ライブラリを使っていきたいと思います. まずは pip install pandas でpandasをインストールしてください. ここではこの問題についてどのようにしてpandasを使うかのみを解説するので,pandasに関する詳しい解説は他サイトを見てください. 次にデータの読み込みを行います. pandasでは data = pd.read_table("ファイル名") とすることでカンマ区切りやタブ区切りなどが行われているテキストファイルを表として読み込むことができます. しかし今回のテキストファイルはデータのみで列の名前がついておらず, Anna F 2604 1880 0 Emma F 2003 1880 1 Elizabeth F 1939 1880 2 Minnie F 1746 1880 3 Margaret F 1578 1880 4 Ida F 1472 1880 のように1行目のデータがカラムになってしまいます. そこでファイル読み込みを以下のようにすることでカラムを変更できます. df = pd.read_table("popular-names.txt", names=["col1","col2","col3","col4"]) names引数にリストで指定することでカラムを変更できます. popular-names.txtは4列で構成されているため,カラムも4つ作ります. これにより以下のように読み込めます. col1 col2 col3 col4 0 Mary F 7065 1880 1 Anna F 2604 1880 2 Emma F 2003 1880 3 Elizabeth F 1939 1880 4 Minnie F 1746 1880 続いてカラムを指定してその列全てを取得するには df["カラム名"] と記述します. そしてこれをリスト化してcol.txtに出力します. 出力は以下のように読み込みと同じ記述をし,(openに引数wをしていすることで書き込みモードになります.これと同じで 読み込む時もrを引数に取ると明示的に記述できます.)f.write(文字列)で書き込むことができます. with open("col1.txt", "w") as f: for i in col1: f.write(i+"\n") 以上を組み合わせると以下のようになります. nlp_nock12.py import pandas as pd df = pd.read_table("popular-names.txt", names=["col1","col2","col3","col4"]) col1 = list(df["col1"]) col2 = list(df["col2"]) with open("col1.txt", "w") as f: for i in col1: f.write(i+"\n") with open("col2.txt", "w") as f: for i in col2: f.write(i+"\n") # 13.col1.txtとcol2.txtをマージ 以下のjoinメソッドを用いるだけです. データフレーム名1.join(データフレーム名2) 解答例としては以下のようになります. nlp_nock13.py import pandas as pd df1 = pd.read_table("col1.txt", names=["col1"]) df2 = pd.read_table("col2.txt", names=["col2"]) print(df1.join(df2)) 14. 先頭からN行を出力 複数行を取り出す場合は以下のように記述します. df.iloc[開始位置:終了位置] これをinput()とスライスを組み合わせて以下のように記述できます. nlp_nock14.py import pandas as pd df = pd.read_table("popular-names.txt", names=["col1","col2","col3","col4"]) N = input() print(df.iloc[:int(N)]) 15.末尾のN行を出力 こちらはスライスで指定するだけですね. nlp_nock15.py import pandas as pd df = pd.read_table("popular-names.txt", names=["col1","col2","col3","col4"]) N = input() print(df.iloc[:-int(N)]) 16.ファイルをN分割する for文を用いてN分割していきます. 行数はlen(df)で取得できます. nlp_nock16.py import pandas as pd df = pd.read_table("popular-names.txt", names=["col1","col2","col3","col4"]) N = input() i = 0 while i <= len(df): print(df.loc[i:i+int(N)]) i += int(N) 17.1列目の文字列の異なり これは集合を扱うset型に単語の列を変換し,その後にset型の大きさを取得すればいいです. nlp_nock17.py import pandas as pd df = pd.read_table("popular-names.txt", names=["col1","col2","col3","col4"]) print(len(set(df["col1"]))) 18.各行を3コラム目の数値の降順にソート 逆順にするにはリスト型のreverse()メソッドを用います. しかしreverse()メソッドが返すのはNoneなので以下のようにする必要があります. nlp_nock18.py import pandas as pd df = pd.read_table("popular-names.txt", names=["col1","col2","col3","col4"]) df_list = list(df["col3"]) df_list.reverse() print(df_list) 19.各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる 辞書型を用いて文字列とその頻度を記録していき,sorted(map.items(), key=lambda x:x[1], reverse=True)で辞書型をソートします. これらをまとめると以下のようになります. nlp_nock19.py import pandas as pd df = pd.read_table("popular-names.txt", names=["col1","col2","col3","col4"]) df_list = list(df["col1"]) map = {} for word in df_list: if word in map.keys(): map[word] += 1 else: map[word] = 1 map = sorted(map.items(), key=lambda x:x[1], reverse=True) for word, num in map: print("{0} : {1}".format(word, num)) さいごに お疲れ様でした. 次回は3章の解説を掲載します.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pyautoguiで全角入力されて困った経験

概要 pyautoguiで入力自動化をしようとしていたところ、半角情報が全角入力されて困った import pyautogui import time pyautogui.click(228, 50) pyautogui.write('https:/') #URL指定 全角入力になる に解決方法があった。 無変換キーにIMEオフの設定をつける 2. pg.press("nonconvert") を実行し、プログラム動作時はIMEオフにする 無変換キーにIMEオフの設定をつける方法 キーボード入力の"A"または"あ"に右クリック ”プロパティ"を選択 詳細設定を選択 変更を選択 5. 無変換をIME-オフ に変更 "OK"で完了です 実際に、日本語入力後、無変換を押して半角になったら成功です! テストプログラム Chromeのweb検索を自動入力するものです import pyautogui import time pyautogui.click(228, 50) print(pyautogui.position()) pyautogui.press("nonconvert") pyautogui.write('https://') #URL指定 pyautogui.press('enter')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでしか描けない美しいグラフを描こう!(その2)

はじめに Pythonでしか描けない美しいグラフを描こう!(その2)です。(その1は以下) その2は予定外なのですが、seaborn-analyzerというライブラリがあることを知り、これがすばらしいので、紹介と備忘を兼ね記事にすることにしました。 何がすばらしいかというと・・・ - ヒストグラムに正規分布曲線や、平均値、標準偏差の値を表示してくれたり、 - 散布図に回帰式や相関係数の値を表示してくれたり、 - ペアプロットは散布図行列と相関係数行列を同時に表示してくれたり・・・等 かゆいところまで手を伸ばしていただいた感じで、これはいい! こんなのがあると、Excelで描けないグラフだけをPythonで!というスタンスも再考しないといけないかもしれないなと思う今日この頃です。 動作条件等 ・Google colabで動作確認 ・データはExcel(かCSV)を読込み、グラフ作成という流れを基本にしています ・この記事では、ボストン住宅価格のデータセットで確認しています。 ・ここで紹介するデータを格納したデータフレームは、すべて[df]としています。 Google colaboratoryの準備 Google colaboratoryから準備しないといけない場合は、以下を確認。 Googleのアカウントを持ってさえいれば誰でも使用することができ、開発環境を整える必要もなくPythonによる機械学習実装が可能です。    Google colaboratory起動 「Google Drive」を起動すると、画面左上の[+新規]ボタンをクリックすると以下のように複数のプログラム起動メニューが表示されます。このメニューのGoogle colaboratoryをクリックすればOKです。    ボストン住宅価格のデータセットについて 以下サイト(Kaggle)の「Boston.csv」を使わせていただいた。 データ数:506, 項目数:14のデータセットで、住宅価格を示す「MEDV」という項目と、住宅価格に関連するであろう項目が「CRIM:犯罪率」「RM:部屋数」「B:町の黒人割合」「RAD:高速のアクセス性」・・・等、13項目で構成されたデータとなっています。 ボストン住宅価格データの項目と内容 項目 内容 CRIM 町ごとの一人当たり犯罪率 ZN 25,000平方フィート以上の住宅地の割合 INDUS 町ごとの非小売業の面積の割合 CHAS チャールズ川のダミー変数(川に接している場合は1、そうでない場合は0) NOX 窒素酸化物濃度(1,000万分の1) RM 1住戸あたりの平均部屋数 AGE 1940年以前に建てられた持ち家の割合 DIS ボストンの5つの雇用中心地までの距離の加重平均 RAD 高速道路(放射状)へのアクセス性を示す指標 TAX 10,000ドルあたりの固定資産税の税率 PTRATIO 町ごとの生徒数と教師数の比率 B 町ごとの黒人の割合 LSTAT 人口の下層階級の比率 MEDV 住宅価格の中央値(1000㌦単位)        データを可視化(seaborn-analyzer)してみよう! ライブラリのインストールおよびインポート ライブラリのインストールおよびインポート pip install seaborn-analyzer ※詳しくはわかりませんが、最初はうまくいきませんでした。  ランライムを再起動してからOKになったように思います。 データの読み込み # データセットの読込み df = pd.read_csv("Boston.csv",index_col=0) df.head() ※最初の列は不要な内容でしたので、index_col=0 で最初の列を除き「df」に格納しています。 ※手元データを読込む場合は、読み込むファイル名を変更してください。 ※Excelファイルを読込む場合は、pd.read_excel('ファイル名.xlsx')となります。 ヒストグラム hist.plot_normality(df, x='medv', norm_hist=False, rounddigit=5) ※x='medv'は「ボストン住宅価格データ」の住宅価格を設定しました。 散布図 from seaborn_analyzer import regplot import seaborn as sns regplot.linear_plot(x='lstat', y='medv', data=df) ※x='lstat'は「ボストン住宅価格データ」の下級階級人口比率を設定しました。 ※y='medv'は「ボストン住宅価格データ」の住宅価格を設定しました。 ペアプロット from seaborn_analyzer import CustomPairPlot import seaborn as sns cp = CustomPairPlot() cp.pairanalyzer(df) 最後に seaborn-analyzer、いいですねぇ。 見てるだけで惚れ惚れしてしまいます。 私が実行したのは、ヒストグラムと散布図とペアプロットだけですが、ほかにもできるようですので、ご興味ある方は以下のサイトを確認ください。 作者様にこの場を借りて感謝申し上げます。ありがとうございます。   参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pytorchの自動微分を使ってハミルトン形式の力学計算

環境 OS: Windows 10 Home CPU: Inten Core i9-9900KF RAM: 16.0 GB GPU: Geforce RTX 2070 SUPER VRAM: 8.0 GB Python 3.8.2 Pytorch 1.9.0+cu111 CUDA 11.1 目的  以前の記事に誤りを見つけてそのまま直すのもしんどかったので、拡張性を持つように工夫してコードを書き直しました。さらにニュートン方程式で力のベクトルの向きを考えるのもミスの要因になるので、今回はベクトルを考えなくて済むハミルトン形式に挑戦しました。 問題設計  実際に解く系は以前の記事と同じとして解析力学で表現するお膳立てをします。 ニュートン方程式  みなさんご存知、ニュートンの運動方程式は$m\overrightarrow{a} = \overrightarrow{F}$ですね。ベクトルで書いてしまうとなんてことありませんが、こいつを数値的に解こうとすると$\overrightarrow{F}$を$x, y$や$r, \theta$などの方向に分解する必要があります。具体的には例えばバネ定数$k$、自然長$l$のバネで結ばれた質点の位置を$\overrightarrow{r_1} = (x_1, y_1)^T, \overrightarrow{r_2} = (x_2, y_2)^T$としたニュートン方程式は次の通りです。 \left\{ \begin{align} m\frac{d^2 \overrightarrow{r_1}}{d t^2} &= -k(|\overrightarrow{r_1} - \overrightarrow{r_2} | - l)\frac{\overrightarrow{r_1} - \overrightarrow{r_2}}{|\overrightarrow{r_1} - \overrightarrow{r_2}|}\\ m\frac{d^2 \overrightarrow{r_2}}{d t^2} &= -k(|\overrightarrow{r_1} - \overrightarrow{r_2} | - l)\frac{\overrightarrow{r_2} - \overrightarrow{r_1}}{|\overrightarrow{r_1} - \overrightarrow{r_2}|}\end{align} \right. この右辺をPytorchで書くと次のようになります。 newton.py import torch def F(r1, r2, k=1.0, l=1.0): R = torch.sqrt(((r1-r2)**2).sum()) f1 = -k*(R - l)*(r1 - r2)/R f2 = -k*(R - l)*(r2 - r1)/R return f1, f2  さてここで負号を忘れたりr1, r2の順番を逆にしたりすると間違った結果が出力されてしまいます。なぜそんなことが起こるかといえばベクトルには向きがあるからです。じゃあどうすればそんなミスを防げるのか。例えばスカラーには向きがないことから、スカラーで表現すれば良いと想像できます。動機は朋花くそんな表現体系になっているのが解析力学です。解析力学ではデカルト座標や曲座標でも同じ形の式が使えます。また解析力学の式はニュートン方程式と同値なことが証明されています(物理法則がいくつもあるはずがない)。私は合成化学出身の材料技術者なので正確な理論の話は他に任せる123として、以下では天下り的に解析力学を使っていきます。 正準方程式  頭のいい方々によると物体の座標$q$とそれに対応した運動量$p$の時間微分はハミルトニアン$H(t, q, p)$を用いて次のようになります。 \left\{ \begin{align} \frac{dq}{dt} &= \frac{\partial}{\partial p}H(t, q, p) \\ \frac{dp}{dt} &= -\frac{\partial}{\partial q}H(t, q, p) \end{align} \right. これが正準方程式と呼ばれます。ポテンシャル$V(q)$を持ち、非保存力がなく運動量もしくは速度に依存した力もない(もしくは運動エネルギー$K$が$p^2/2m$と表される)場合のハミルトニアンは$H = p^2/{2m} + V(q)$となるので、これを上の正準方程式に代入すると次のようになります。 \left\{ \begin{align} \frac{dq}{dt} &= p/m\\ \frac{dp}{dt} &= -\frac{\partial V}{\partial q} \end{align} \right. 非保存力$F$があるならおそらく非保存力がある場合のラグランジュの運動方程式をルジャンドル変換して$dp/dt = -\partial V/{\partial q} + F$となるはずです(ニュートン方程式と同値ならこうなるはず)。真っ当な数値計算であればさらに$-\partial V/\partial q$を変数$t, q, p$で表しますが、面倒臭がりでしょっちゅう計算ミスする私はこの偏微分計算をPytorchの自動微分に丸投げしてしまいたいというのが本記事の主題です。 Pytorchでの表現  というわけで系全体の運動エネルギー$K$、ポテンシャル$V$、そしてミルトニアン$H$さえ計算できれば、自動微分の力を借りて正準方程式から時間微分を計算し、任意の時間発展アルゴリズムを適用するだけです。なお個々の粒子のハミルトニアンを積み重ねた列ベクトルではなく系全体のハミルトニアンとする主な理由はPytorchの自動微分torch.autogradで微分する対象はスカラーでなければならないからです。  注意点として微分して$d\sqrt{x}/dx = 1/\sqrt{x}$となる部分があった場合、$x=0$が代入されるとtorch.autogradはnanを返します。バネで結ばれた複数粒子のポテンシャルを行列で表現して計算すると対角成分がまさにこれに該当します。これを回避するために以下のコードでは2乗根をとる前に対角成分に1を足しています。該当する要素は$(x_i - x_i)^2$の関数であり微分すると$x_i - x_i$を積に含むため、導係数は必ず0となり計算上の支障はありません。 field.py import torch class Potential: def kinetic_energy(self, t, q, p): k = (p**2/2/self.m).sum() return k def hamiltonian(self, t, q, p): k = self.kinetic_energy(t, q, p) v = self.potential_energy(t, q, p) return k + v class Harmonic(Potential): def __init__(self, m, l, k): self.m = m self.k = k #粒子間の相互作用を記述したクラスを適宜同時に継承すること #self.k_ = self.set_connectivity(k) self.l = l def potential_energy(self, t, q, p): r = ((q.reshape(self.dim, -1, 1) -q.reshape(self.dim, 1, -1)) **2).sum(dim=0) # diff sqrt(x)|x=0 causes Error # and torch returns nan r = (r + torch.eye(r.shape[0], device=self.device)).sqrt() V = ((1/2*self.k*(r - self.l)**2)/2).sum() return V class LJ(Potential): def __init__(self, m, e, s, p=12, q=6): self.m = m self.e = e self.s = s #self.e_ = self.set_connectivity(e) #self.s_ = self.set_connectivity(s) self.p = p self.q = q def potential_energy(self, t, q, p): xi = q.reshape(3, -1, 1) xj = q.reshape(3, 1, -1) r = ((xi-xj)**2).sum(dim=0) r = (r + torch.eye(r.shape[0],device=self.device)).sqrt() v = (4*self.e*((self.s/r)**self.p - (self.s/r)**self.q) ).sum()/2 return v class Morse(Potential): def __init__(self, m, d, a, l): self.m = m self.d = self.set_connectivity(d) self.a = self.set_connectivity(a) self.l = self.set_connectivity(l) def potential_energy(self, t, q, p): xi = q.reshape(3, -1, 1) xj = q.reshape(3, 1, -1) r = ((xi-xj)**2).sum(dim=0) r = (r + torch.eye(r.shape[0],device=self.device)).sqrt() v = (self.d *(1 - torch.exp(-self.a*(r - self.l)))**2 ).sum()/2 return v  調子に乗ってLJポテンシャルやモースポテンシャルも作りましたが動作確認は全く行なってません。これらポテンシャルを用いて実際に正準方程式を計算するには一緒に次のclass Hamiltonianを継承したクラスをmain.pyで定義します。 hamiltonian.py import torch def requires_grad(func): def wrapper(self, t, q, p, **kwargs): q.requires_grad = True q.grad = None p.requires_grad = True p.grad = None return func(self, t, q, p, **kwargs) return wrapper class Hamiltonian: def __call__(self, t, q, p): return self.hamiltonian(t, q, p) @requires_grad def dqdt(self, t, q, p): h = self.hamiltonian(t, q, p) g = torch.autograd.grad(h, p) return g[0] @requires_grad def dpdt(self, t, q, p): h = self.hamiltonian(t, q, p) g = torch.autograd.grad(h, q) return -g[0] @requires_grad def gradient(self, t, q, p): h = self.hamiltonian(t, q, p) h.backward() return torch.stack([p.grad, -q.grad])  また格子点を生成するのに便利なclass Latticeも作っておきます。 points_design.py import torch import numpy as np class Lattice: def __init__(self, N, l, device='cpu'): ''' Base class of lattice. Parameters ---------- N: Number(s) of point(s), int or array(int,) l: distance between points, float or array(float,) ''' self.N = N self.Nx = self.Ny = self.Nz = 1 if type(self.N) is int: self.Nx = self.N self.dim = 1 elif len(self.N) == 3: self.Nx, self.Ny, self.Nz = self.N self.dim = 3 elif len(self.N) == 2: self.Nx, self.Ny = self.N self.dim = 2 elif len(self.N) == 1: self.Nx, = self.N self.dim = 1 self.l = l self.device = device def set_connectivity(self, *args): Nx = Ny = Nz = 1 if type(self.N) is int: Nx = self.N elif len(self.N) == 3: Nx, Ny, Nz = self.N elif len(self.N) == 2: Nx, Ny = self.N elif len(self.N) == 1: Nx, = self.N if len(args) == 3: kx, ky, kz = args else: kx = ky = kz = args[0] items = Nz*Ny*Nx E = torch.eye(items, device=self.device, dtype=torch.float64) c = [[0, None, None, -(Nx*Ny), kz], [-1, None, None, Nx*Ny, kz], [torch.arange(Nz), 0, None, -Nx, ky], [torch.arange(Nz), -1, None, Nx, ky], [torch.arange(Nz), torch.arange(Ny), 0, -1, kx], [torch.arange(Nz), torch.arange(Ny), -1, 1, kx] ] C = torch.zeros_like(E) for i, j, k, roll, kk in c: I = torch.ones(Nz, Ny, Nx, device=self.device, dtype=torch.float64) I[i, j, k] = 0. I = I.reshape(-1,1)*kk C += torch.roll(E*I, roll, dims=1) return C def set_points(self): device = self.device lx = ly = lz = 1. if type(self.l) is float: lx = self.l elif len(self.l) == 3: lx, ly, lz = self.l elif len(self.l) == 2: lx, ly = self.l elif len(self.l) == 1: lx, = self.l Nx = Ny = Nz = 1 if type(self.N) is int: Nx = self.N x = torch.arange(0,Nx, dtype=torch.float64, device=device)*lx elif len(self.N) == 3: Nx, Ny, Nz = self.N x = torch.arange(0,Nx, dtype=torch.float64, device=device)*lx y = torch.arange(Ny-1,-1,-1, dtype=torch.float64, device=device)*ly z = torch.arange(Nz-1,-1,-1, dtype=torch.float64, device=device)*lz x, y, z = torch.meshgrid(x, y, z) x = torch.stack([x.T, y.T, z.T]) elif len(self.N) == 2: Nx, Ny = self.N x = torch.arange(0,Nx, dtype=torch.float64, device=device)*lx y = torch.arange(Ny-1,-1,-1, dtype=torch.float64, device=device)*ly elif len(self.N) == 1: Nx, = self.N x = torch.arange(0,Nx, dtype=torch.float64, device=device)*lx v = torch.zeros_like(x) return x, v  これで物理的情報は全て記述できたので次は時間発展アルゴリズムの実装です。以前のミスってる記事では4次のルンゲクッタ法のみでしたが、今回は速度ベルレ法と特殊なオイラー法も実装します。ただしオイラー法はコードしただけで力尽きて動作確認してません。追加した2つはルンゲクッタ法と比べてSymplectic性14という性質によってハミルトニアン(=エネルギー)が保存されやすいそうで、計算量とコード量も少なく済みます。  が、今回のバネで繋いだ格子点の運動では速度ベルレ法よりルンゲクッタ法の方がエネルギーが安定していました。またミスってるのか?詳細は後述しますが、解析的に解ける2粒子系の運動で0.01秒刻みで1000秒まで試すと速度ベルレ法の全エネルギーが(4.0499±0.0001)e-1の間で振動していたのに対して、ルンゲクッタ法は4.05e-1から4.5e-9だけ単調減少しました。 solver.py from pathlib import Path from datetime import datetime import numpy as np import torch class Solver(object): def __init__(self, t0, dt, dydt, *args, set_boundary=None, wd=None, fps=15, cashe=False): ''' Parameters ---------- dydt: Python function t0: start time dt: time step args: parameters for dydt ''' self.t = t0 self.dt = dt self.dydt = dydt self.args = args self.results = {'t':[t0], 'args':{}} # 保存タイミングを制御するためにリストで保持させたけど # メモリ不足で活用できそうになかった for i in range(len(args)): self.results['args'][i] = [args[i].detach().to('cpu')] if wd is None: self.wd = Path.cwd() else: self.wd = wd self.fps = fps self.cashe = cashe def step(self): steps = self.alg() for y, s in zip(self.args, steps): y.data += s self.t += self.dt self._to_results() return self.t def _to_results(self): self.results['t'].append(self.t) for i in range(len(self.args)): self.results['args'][i].append( self.args[i].detach().to('cpu') ) def save(self): t = np.array(self.results['t']) with open(self.wd/'result_time.csv', 'ab') as f: np.savetxt(f, t) self.results['t'] = [] for i in range(len(self.args)): with open(self.wd/f'results_args_{i}.csv', 'ab') as f: arg = torch.stack( self.results['args'][i] ).detach().reshape(1,-1).to('cpu').numpy() np.savetxt(f, arg) self.results['args'][i] = [] class RungeKutta(Solver): def alg(self): dydt = self.dydt t = self.t param = self.args k1 = dydt(t, *param) t = self.t + self.dt/2 param = [a.detach() + self.dt/2*k for a, k in zip(self.args, k1)] k2 = dydt(t, *param) t = self.t + self.dt/2 param = [a.detach() + self.dt/2*k for a, k in zip(self.args, k2)] k3 = dydt(t, *param) t = self.t + self.dt param = [a.detach() + self.dt*k for a, k in zip(self.args, k3)] k4 = dydt(t, *param) return self.dt/6*(k1 + 2*k2 + 2*k3 + k4) class VelocityVerlet(Solver): def step(self): a = self.dydt x = self.args[0] v = self.args[1] t = self.t dt = self.dt at = a(t, *self.args) self.args[0].data += v*dt + at/2*dt**2 t += dt self.args[1].data += (at + a(t,*self.args))/2*dt self.t = t self._to_results() return self.t class SymplecticEuler(Solver): def step(self): self.args[1].data += self.dydt(self.t,*self.args)*self.dt self.args[0].data += self.args[1]*self.dt self.t += self.dt self.results['t'].append(self.t) self.results['args'].append(self.args) self._to_results() return self.t  長々とお膳立てしてやっと本体。ポテンシャルとハミルトニアンと粒子の配置を継承させて座標変換を定義して系を完成させてSolverで解く。 main.py from pathlib import Path import os from datetime import datetime import numpy as np import torch import pandas as pd from matplotlib import pyplot as plt import seaborn as sns from field import Harmonic from hamiltonian import Hamiltonian from points_design import Lattice from solver import RungeKutta, VelocityVerlet class LatticeHarmonic(Lattice, Harmonic, Hamiltonian): def __init__(self, m, N, l, k, device): Lattice.__init__(self, N, l, device) Harmonic.__init__(self, m, l, k) @staticmethod def x2q(t, x, v): return t, x, v @staticmethod def q2x(t, q, v): return t, q, v def v2p(self, t, q, v): return t, q, self.m*v def p2v(self, t, q, p): return t, q, p/self.m def cart2gen(self, t, x, v): t_, q, v_ = self.x2q(t, x, v) t_, q, p = self.v2p(t, q, v) return t, q, p def gen2cart(self, t, q, p): t_, q, v = self.p2v(t, q, p) t_, x, v = self.q2x(t, q, v) return t, x, v def test_RK(m=1., N=2, l=1., k=1., dt=1.e-2, te=10.): device = torch.device( 'cuda:0' if torch.cuda.is_available() else 'cpu') lattice = LatticeHarmonic(m, N, l, k, device) x, v = lattice.set_points() if lattice.Nz == lattice.Ny == 1: x[-1] *= 1.9 elif lattice.Nz == 1: x[0, -1, -1] *=1.9 else: x[0, -1, -1, -1] *= 1.9 t = t0 = 0. t, q, p = lattice.cart2gen(t, x, v) Nx = Ny = Nz = 1 if type(N) is int: Nx = N elif len(N) == 2: Nx, Ny = N elif len(N) == 3: Nx, Ny, Nz = N wd = Path.cwd()/'loop'/'RK4'/f'm{m}_Nx{Nx}_Ny{Ny}_Nz{Nz}_l{l}_k{k}_{datetime.now():%Y%m%d%H%M}' wd.mkdir(parents=True) solver = RungeKutta( t0, dt, lattice.gradient, q, p, wd=wd ) h = [] with torch.no_grad(): h.append(lattice(t, q, p)) solver.save() h = (torch.stack(h).detach() .reshape(1,-1).to('cpu').numpy() ) with open(wd/'result_h.csv', 'ab') as f: np.savetxt(f, h) h = [] while t < te: t = solver.step() q = solver.args[0] p = solver.args[1] h.append(lattice(t, q, p)) # 1/freqの頻度で保存 if int((t-t0)/dt)%freq == 0: solver.save() h = (torch.stack(h).detach() .reshape(1,-1).to('cpu').numpy() with open(wd/'result_h.csv', 'ab') as f: np.savetxt(f, h) h = [] return wd if __name__=='__main__': test_RK()  そろそろかなり記事が長くなってしまっているので速度ベルレ法の実行関数は省略しましたが、a = lambda t,q,p: lattice.dpdt(t,q,p)/mをsolver.VelocityVerletに与えればルンゲクッタ法と同じです。 解析解との差  上記をバネで繋いだ2粒子に対して実行して解析解とのズレと全エネルギーの変動を見たのが次です。1000秒間を0.01秒刻みで全て描画しようものなら本体の計算より遥かに時間がかかって終わらないため、描画は1秒刻みに間引きました。お前高速とか言ってPyQtgraphの記事書いてるなら使えよ。2粒子間の距離を上、エネルギーを下の行にそれぞれプロットしています。重なっているので分かりにくいですが、青点が数値計算、橙点が解析解を表しています。 まずルンゲクッタ法 次に速度ベルレ法  ある範囲内に収まるという意味では確かに速度ベルレ法の方が安定していますが、誤差という意味では1e8秒くらいまで計算しない限りはルンゲクッタ法の方が正確でした。   数値計算のための解析力学, 神戸大学 陰山 聡 ↩ 物理のかぎしっぽ  ↩ EMANの解析力学 ↩ 数値解析と物理学 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(Mac)PythonのSeleniumで「chromedriveのpathの場所が見つからない」時の対応

概要 chromedriverを無事インストール出来て、seleniumを使ってプログラムの動作テストを行った処、以下のエラーで悩まされました。 selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home google翻訳のところ selenium.common.exceptions.WebDriverException:Message:「chromedriver」実行可能ファイルはPATHに含まれている必要があります。 https://sites.google.com/a/chromium.org/chromedriver/homeをご覧くださいということで、 これはchromedriverの実行ファイルまでのパスを通して下さいというエラーメッセージらしいです。 folderの検索窓に「chromedriver」を記述して検索しても、あるのは「chromedriver.rb」だけで、「chromedriver」は見つかりませんでした。 driverの場所が探せなくて苦労してググている時、このリンクで教えてもらいました。 「Python/ChromeDriverインストールとパスの通し方」 https://watlab-blog.com/2019/08/10/chromedriver-path/ ※ちなみに、解決策2の「pip install chromedriver-binary-auto」については、実施していません。(解決策1で対処した為です) これであっという間に解決しました。!!感謝を込めて記録しておこうと思います。 構築環境 ・macOSBig Sur 11.5.2 ・Python 3.7.7 ・機種名: iMac18,3 ・ロセッサ名: Intel Core i5 Google Chromeをインストール まずは、Google Chromeをインストール <参考> Google Chrome をダウンロードしてインストールする https://support.google.com/chrome/answer/95346?hl=ja&co=GENIE.Platform%3DDesktop pipでseleniumおよびchromedriver-binaryをインストール 次にseleniumモジュールとchromedriver-binaryモジュールをインポートします。 $ python3 -m pip install selenium $ python3 -m pip install chromedriver-binary # (※1)パスを通すためのコード <参考> 「Pythonのpipコマンドでうまくパッケージがインストールできない場合がある理由と対処法」 https://news.mynavi.jp/article/zeropython-77/ 検証 これで、下のテストを行ったところ無事に動きました。 決め手は、import chromedriver_binary でした。 #tast_proguram.py from selenium import webdriver # Webブラウザを自動操作する(python -m pip install selenium) import chromedriver_binary # (※A)パスを通すためのコードをimport import time # スリープを使うために必要 # ヘッドレスモード #画面を表示しないでprogramだけを実行させるヘッドレスモードは(※1)を追記して,(※2)の引数をに(options=options)記述する from selenium.webdriver.chrome.options import Options #(※1) options = Options() #(※1) # ヘッドレスモードにしない場合は次の行(options.add_argument(‘--headless'))をコメントアウトする #options.add_argument('--headless') #(※1) ''' #(※A)を記述することによって、下記 "DRIVER_PATH=" のpathの場所を記述する必要がない。 (一般的には、下の2行を記述するとの説明するHPはあるが、この'DRIVER_PATH'が見つからない場合の対処方法を説明しています。) DRIVER_PATH = '/Users/hoghog/Library/Caches/pip/wheels/d3/f1/b9/695dc4bfab6fcf6064915666c2d30d154ba038f7e1579be809/chromedriver_binary-93.0.4577.15.0-py3-none-any.whl' driver = webdriver.Chrome(DRIVER_PATH) ''' print('='*40) print('少々お持ちください') print('='*40) # Chromeを準備 driver = webdriver.Chrome(options=options) #(※2) # Googleを開く driver.get('https://www.google.com/') # 3秒待機 time.sleep(3) # 検索ボックスのオブジェクトを取得 search_bar= driver.find_element_by_name('q') # キーを検索ボックスに送信 search_bar.send_keys('seleniumについて') # フォームを送信 search_bar.submit() # 10秒待機 time.sleep(10) print('リストアップします。') print('='*40) time.sleep(3) #ターミナルへ表示 for elem_h3 in driver.find_elements_by_xpath('//a/h3'): elem_a = elem_h3.find_element_by_xpath('..') print(elem_h3.text) print(elem_a.get_attribute('href')) # このドライバを終了し、開かれていたすべての関連ウィンドウを閉じる driver.quit() 出力結果
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

opencvを使ってライフゲームを実装した(18行!!)

はじめに  暇つぶしにライフゲームを普通とは違う方法で実装しました。せっかくなので記事にしました。 実装  実装したコードはたった18行!!モジュールはnumpyとopencvを使っています。 lifegame.py import cv2 import numpy as np weight = np.array([ [2.0, 2.0, 2.0], [2.0, 1.0, 2.0], [2.0, 2.0, 2.0] ], dtype=np.uint8) _ = cv2.VideoCapture(-1) size = 256 s = (np.random.rand(size, size) > 0.5).astype(np.uint8) while True: s_ = cv2.filter2D(s, -1, kernel=weight) s = (s_ > 4).astype(np.uint8) * (s_ < 8).astype(np.uint8) cv2.imshow('LifeGame', 255 * s.reshape(size, size)) if cv2.waitKey(1) & 0xFF == ord('q'): break 使用したルールは23/3です。以下のような場合に次の状態が1になります 対象セルが1かつ周辺2つが1(畳み込みの値は5) 対象セルが0かつ周辺3つが1(畳み込みの値は6) 対象セルが1かつ周辺3つが1(畳み込みの値は7) 対象セルの値と周辺で活性化しているセルの数に対して畳み込んだ値は一対一で対応してるので、畳み込みの値によって次のセルの状態が決定することになります。 次の活性化セルの条件を書き換えることでルールを簡単に変更できます。 結果 実際に動かした結果がこちらです。ランダムで生成します。 最後に  私が使用しているPCはMacbookAirですが1024×1024でも余裕で動きます。ぜひ遊んでみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ようやく vim-lsp に乗り換えた話 (pylsp-mypy で苦労した話)

RubyKaigi Takeout 2021 に参加して、typeprof などをフルに利用するには LSP を使えるようにしておく必要があるな、と気づきました。 思い立ったが吉日ということで、自分の vim 環境を ALE から LSP に乗り換えることにしました。 その過程で、mypy による lint を有効にするのに苦労したので、備忘録としてメモを残します。 前提 python 使い ALE は lint 用に使っていて、formatter としては使っていない flake8, isort, mypy を linter として使っている vim の plug-in manager には dein を使っている LSP を有効にする 本体である vim-lsp を使います。 細かい設定をやってくれる mattn/vim-lsp-settings も合わせて入れます。 # LSP (Language Server Protocol) [[plugins]] repo = 'prabirshrestha/vim-lsp' # LSP settings [[plugins]] repo = 'mattn/vim-lsp-settings' これで基本的な動きはオッケーです。 このタイミングで ALE をリストから削除しています。 python の linter を pycodestyle から flake8 に変更する python 向けの設定をしていきます。 これまで flake8 を使って lint をしていたので、vim-lsp-settings のデフォルトである pycodestyle から flake8 を利用するように変更します。いずれ乗り換えたいですね (遠い目)。 g:lsp_settings 経由で設定をします。vim-lsp-settings はデフォルトで pylsp-all という設定(?)を使っているので pylsp-all.workspace_config.pylsp.configurationSources に設定を書き加えます。 # LSP settings [[plugins]] repo = 'mattn/vim-lsp-settings' hook_add = ''' " Enable flake8 let g:lsp_settings = { \ 'pylsp-all': { \ 'workspace_config': { \ 'pylsp': { \ 'configurationSources': ['flake8'], \ } \ } \ } \} ''' ※ 僕は設定を一箇所にまとめたいので、プラグインの設定を hook_add を使って plug-in のロードの隣に定義を書いていますが、 .vimrc に直接書いてもよいはずです。 余談: pyls (python-language-server) と pylsp vim-lsp-settings の設定を調べていて混乱したのが pyls と pylsp の違いです。 なぜ似たような設定が2つあるのか、何が違うのか、というのがわからなくてググったりコード読んだりして、しばらくぐるぐるしていました。 どうやら、昨年末までは python-language-server がよく利用されていたようなのですが、メンテナンスが停止してしまったためにプロジェクトが fork されて、現在では python-lsp-server として再公開されているようです。 vim-lsp-settings では前者を pyls、後者を pylsp と呼んでいるようです。 なお、この他にも Microsoft が管理している microsoft/python-language-server というものもあるようですが、深追いしていないのでここでは割愛します。 mypy を有効にする 続いて mypy を有効にします。ここがハマりポイントでした。 ググって出てくる記事にも、python-lsp-server の README にも、mypy を有効にするには pyls-mypy を使えと書いてあるのですが、現時点(2021/9/11)時点ではこれは誤りです。 python-lsp-server で mypy を有効にするには pylsp-mypy をインストールしてください (一文字違い)。 $ /Users/tkomiya/.local/share/vim-lsp-settings/servers/pylsp-all/venv/bin/pip install pylsp-mypy その後、 g:lsp_settings の plug-in リストに pylsp_mypy を追加します。 hook_add = ''' " Enable flake8 and mypy let g:lsp_settings = { \ 'pylsp-all': { \ 'workspace_config': { \ 'pylsp': { \ 'configurationSources': ['flake8'], \ 'plugins': { \ 'pylsp_mypy': { \ 'enabled': 1 \ } \ } \ } \ } \ } \} ''' 課題: pylsp-mypy のインストールを自動化する 今回は pylsp-mypy を手動でインストールしていますが、このアプローチでは環境を作り直すたびに手動でインストールする必要があります。 この部分を自動化できるとよいのですが、やり方が分かっていません。 そのため、 LspInstallServer を実行するたびに pylsp-mypy が消えてしまうんですよね。 課題: isort を有効化する isort を使ってソースコードを lint しようと思ったのですが、今の所実現できていません。 pyls-isort は linter としては動かない(フォーマッタ専用)ようなので、いまのところ有効になっていません。 flake8-isort を入れても意図したように動いていないので、もう少し試行錯誤が必要そうです。実際のところ、もう手癖になってしまったので isort なしでも困らないのですが、つまらないミスで CI でこけないようにするためにもチェックしておきたいところです。 LSP のエラー、警告を lightline に表示する LSP が検知した文法エラーや警告などを lightline に表示します。 必要な定義は lightline-lsp としてパッケージングされているので、これを入れます。 # LSP-lightline bridge [[plugins]] repo = 'halkn/lightline-lsp' あとは、この定義を lightline から参照するだけです。 lightline_lsp#warnings, lightline_lsp#errors などを参照すればオッケーです。 # Customize status line [[plugins]] repo = 'itchyny/lightline.vim' hook_add = ''' let g:lightline = { \ 'mode_map': {'c': 'NORMAL'}, \ 'active': { \ 'right': [ \ [ 'lsp_errors', 'lsp_warnings' ], \ ] \ }, \ 'component_expand': { \ 'lsp_warnings': 'lightline_lsp#warnings', \ 'lsp_errors': 'lightline_lsp#errors', \ }, \ 'component_type': { \ 'lsp_warnings': 'warning', \ 'lsp_errors': 'errors', \ }, \} ''' いままで lightline-ale を使っていたので、参照する関数を差し替えるだけだったのでかんたんでした。 vim-lsp の表示を変更する (ハイライト) vim-lsp はデフォルトでは signcolumns を使って lint の警告/エラーを表示します。 僕はエラー行をハイライト表示するのに慣れてしまったので、この表示には違和感があります (ALE でも同様の設定をしていた)。 そのため、以下のような設定を加えました。 # LSP settings [[plugins]] repo = 'mattn/vim-lsp-settings' hook_add = ''' " Hide signcolumn. let g:lsp_diagnostics_signs_enabled = 0 ''' hook_post_source = ''' " Highlight LSP warnings strongly (like errors) highlight link LspWarningHighlight Error ''' g:gls_diagnostics_sign_enabled で signcolumns を無効化しています。 そして LspWarningHighlight の色を変えることでハイライト表示にしています。 ハイライトの色などはもう少しいい感じのものに調整したほうが良さそうですが、いまのところ Error と同じにしています。 他にもいくつかハイライトが定義されているので、highlight コマンドで眺めつついい感じの色を設定すると良さそうです。 いまのところ LspWarningHighlight だけアップデートすればよさそうでした。 エラーの詳細をステータス行に表示する lint で警告/エラーが発生している行にカーソルを当てたらエラーの詳細を知りたいですよね。 g:lsp_diagnostics_echo_cursor を有効にします。 # LSP settings [[plugins]] repo = 'mattn/vim-lsp-settings' hook_add = ''' " Show diagnostics message to status line let g:lsp_diagnostics_echo_cursor = 1 ''' オートコンプリートを有効にする ついでにオートコンプリートを LSP 経由のものに置き換えます。 asyncomplete と asyncomplete-lsp を入れるだけで有効になります。 # Auto completion [[plugins]] repo = 'prabirshrestha/asyncomplete.vim' # Auto completion for LSP [[plugins]] repo = 'prabirshrestha/asyncomplete-lsp.vim' ※ deoplete.nvim + deoplete-vim-lsp を使う方法もあるようですが、試していないため割愛します。 まとめ 最終的には以下の設定になりました。 # LSP (Language Server Protocol) [[plugins]] repo = 'prabirshrestha/vim-lsp' # LSP settings [[plugins]] repo = 'mattn/vim-lsp-settings' hook_add = ''' " Hide signcolumn. let g:lsp_diagnostics_signs_enabled = 0 " Show diagnostics message to status line let g:lsp_diagnostics_echo_cursor = 1 " Enable flake8 and mypy let g:lsp_settings = { \ 'pylsp-all': { \ 'workspace_config': { \ 'pylsp': { \ 'configurationSources': ['flake8'], \ 'plugins': { \ 'pylsp_mypy': { \ 'enabled': 1 \ } \ } \ } \ } \ } \} ''' hook_post_source = ''' " Highlight LSP warnings strongly (like errors) highlight link LspWarningHighlight Error ''' # LSP-lightline bridge [[plugins]] repo = 'halkn/lightline-lsp' # Auto completion [[plugins]] repo = 'prabirshrestha/asyncomplete.vim' # Auto completion for LSP [[plugins]] repo = 'prabirshrestha/asyncomplete-lsp.vim' # Customize status line [[plugins]] repo = 'itchyny/lightline.vim' hook_add = ''' let g:lightline = { \ 'mode_map': {'c': 'NORMAL'}, \ 'active': { \ 'right': [ \ [ 'lsp_errors', 'lsp_warnings' ], \ ] \ }, \ 'component_expand': { \ 'lsp_warnings': 'lightline_lsp#warnings', \ 'lsp_errors': 'lightline_lsp#errors', \ }, \ 'component_type': { \ 'lsp_warnings': 'warning', \ 'lsp_errors': 'errors', \ }, \} '''
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kivyMDチュートリアル其の参什肆 バージョンアップ篇

ハロー、Qiita。いかがお過ごしでしょうか。 やっぱりこの入りがいいですかね(しらない)。ということで 今週も始まりました、KivyMDのお時間です。 もう涼しくなってきたかと思いきや、後半からは暑くなってきたり、 体調は安定しない季節となりました。でもこんな季節は個人的には割と 好きですね。みなさんはどの季節が好きでしょうか。 今週は特に変わったこともないので、遠回りせず本題に入っていきます。 バージョンアップ さて、さっそく今日のメインテーマに入ることとします。今日は最速で 終わるかもしれません。 ということで、前回までのおさらいですが先週のTooltip篇でComponentsは 終わっている状況を醸し出していました。ですが、実は毎週見ている方は分かる かと思われますが、実は終わっておりません。所々、端折って端折って端折りまく ってました。 一通り終わっているはいるのだけれども、現バージョン(v0.104.1)で動かないこと ところが少々ありましたので、まずはバージョンアップをしてそれらが次バージョン (v0.104.2)でどれだけ改善されているかどうか見てみなければいけません。 じゃ、どこが動かないのよということですが、以前動かなかったところをまとめた ことがありました。以下から必要なものを抜粋して再度リストアップしておきます。 MDSwiper Menu Navigation Drawer Navigation Rail Pickers Selection Snackbar Tabs これらの中には一部動かないというものもあるので、悪しからず。まだ、これだけでは ありません。これをまとめたのはPickers篇であったので、実はそれ以降動かなかった コンポーネント(これも一部動かないなどあり)が発覚したものもありました。(というか Pickersパッケージがそもそも改変されたりして動かなかったり・・・) それらが以下になります。 * ProgressBar * Slider(Customのみ) * Spinner(色) とまぁざっと3点ほどと意外と見直すと少ない結果となりました。バージョンアップが 済んだときにさっと見てみてもよいかもしれません。 また、今回上げるバージョンの履歴としては以下が詳しいので、詳細は以下をご参照頂け ればと思います。 Usage いつもあるように、紐づけて章分けしてみました。まぁそこまで大そうなことはないの ですが・・・ 色々方法論としてはあると思いますが、一旦自分はパッケージを削除してみました。チュート リアルには当たり前ですがアンインストール方法までは書いてもらえてないので、ここはStack- OverFlowとかで実績のありそうな方法を採用してみます。効率悪くね?と思われる方は別の方法で トライしてみても良いかもしれません。一旦ここは、削除というかアンインストールでどのような 結果となったかをコマンドと共に書き留めてみます。 Uninstall Command $ pip install --upgrade pip $ pip uninstall kivymd 結果 Found existing installation: kivymd 0.104.1 Uninstalling kivymd-0.104.1: Would remove: (略) Proceed (y/n)? y Successfully uninstalled kivymd-0.104.1 まずは削除が完了したようです。では次にバージョン指定をしてインストールを してみましょう。 Install Command $ pip install kivymd==0.104.2 結果 Collecting install Downloading install-1.3.4-py3-none-any.whl (3.1 kB) Collecting kivymd==0.104.2 Downloading kivymd-0.104.2-py3-none-any.whl (2.3 MB) |████████████████████████████████| 2.3 MB 2.9 MB/s Requirement already satisfied: kivy>=2.0.0 in /UsersFolder/.pyenv/versions/3.7.6/lib/python3.7/site-packages (from kivymd==0.104.2) (2.0.0) (略) Installing collected packages: kivymd, install Successfully installed install-1.3.4 kivymd-0.104.2 少し1.3.4とあるバージョンが気になりましたが、一旦はインストールできた ようです。まずは第一段階の任務完了となりました。 お試し 今回はこの章を試すというのは、ありはしますが臨機応変に試していくという ことでお試しというお題を付けています。 とりあえず、インストールはしたけどライブラリに不備があって動かないという ことがあれば一大事ですので(まず考えられはしませんが)、試しに以前動かした ものを動かしてみます。 自分の手元にはIcon Definitions篇で試したicon.pyがあるので、それを みてみたいと思います。なんのこっちゃという方は以下リンクを見てもらえれば と思います。 結果①(icon.py) 出力メッセージ $ python icon.py [INFO ] [Logger ] Record log in /UsersFolder/.kivy/logs/kivy_21-09-11.txt [INFO ] [Kivy ] v2.0.0 (略) [INFO ] [KivyMD ] 0.104.2, git-bc7d1f5, 2021-06-06 (installed at "/UsersFolder/.pyenv/versions/3.7.6/lib/python3.7/site-packages/kivymd/__init__.py") (略) [INFO ] [Base ] Start application main loop [INFO ] [Base ] Leaving application in progress... メッセージは問題ないようです。 実行結果 全然問題ありませんでしたね。最初は詰まるんじゃねみたいに疑ってごめんなさい。 お試し2 最初の方で言っていた以下の3点もここで確認を取りたいと思います。 ProgressBar Slider(Customのみ) Spinner(色) どうなっているか確認してみましょう。 結果②(xxxiv/customcolor_indeterminate_progressbar.py) まずはProgressBarからです。こちらは以下リンクの「With custom color」と 「Indeterminate」を組み合わせたコードを実行しています。こちらはここに書くと 長くなってしまうので、上記節の名前にもあるところようにGitHubへコードを上げて おくのでそちらをご参照ください。色々自分で書いて試してみるのもよいかもしれません。 実行結果 なんということでしょう。改善されているではありませんか。 という感じでさくさく見ていきましょう。ちなみに余談ですが、 単にdeterminateモードだとバーが全て埋め尽くされる挙動が 伺えました。これは試してもらったほうがいいかもしれません。 結果③(xxviii/slider_customcolor.py) 続いて、Slider Componentとなります。 こちらは以前にGitHubで配置していたコードとなります。詳細の方は以下をご参照 してもらえればと思います。 ただし、注意点が1つありマニュアルから経緯は忘れていましたが、少し変更をして います。具体的には以下のようになります。 - value: 40 - color: app.theme_cls.accent_color + value: 50 + color: 0, 0, 1, 1 おそらく自分でカスタムできるかなみたいに軽く修正したのだと思います。 実行結果 こちらも問題ない結果となりましたね。少し個人的な感想になりそうですが、 紫に近い青色が表示されています。 結果④(xxix/spinner.py) 最後にSpinnerを見てみます。 こちらについては先ほどと同様、以前に試していたコードを再度引っ張りだして みましょう。なければチュートリアルからSpinnerページの「Spinner palette」を 実装してみるのも手ではあります。 実行結果 最初少し止まっていることもありますが(動画撮るのヘタですみません)、ちゃんと 動いているのは間違いありません。すべてバッチリでしたね。 まとめ さぁ、いかがだったでしょうか。 バージョンアップするのにそれほど苦労することはないということが分かられたら、 私からは何も言うことはありません。もっとこうすればいいのにということもある かと思われますが、それは次回に持ち越しということで。 ※ 「pip install -U ~」を使えば1コマンドでことは足りそうです ということで来週からは一旦少し戻って、冒頭にもある通りMDSwiper篇からリスタート したいと思います。あと8個なので、期間としては2ヶ月くらいかな。 それでは、来週もお楽しみにー と言いたかったところですが、少し留意事項があります。他のPythonとかのバージョン や環境のことですね。2回目の投稿のときにこのような環境でやっていくと宣言をしてい ました。 [開発環境] macOS Mojave 10.14.6 [バージョンなど] Python 3.7.6 Kivy 2.0.0 kivymd 0.104.1 kivymdだけのバージョンを変更かと思いきや、macOSのバージョンも変わっていることを お伝えできていませんでした。完全に忘れていました、すみません。あと、いつ変えたかも 忘れたw # まぁOSのバージョンがそれほど影響してくるとは思いませんではい ということで一旦以下のように変更点があります。今後はこのようになると思ってもらえれば と思います。 - macOS Mojave 10.14.6 - kivymd 0.104.1 + macOS Big Sur 11.2.3 + kivymd 0.104.2 はい、以上となります。 ということで改めて、来週もよろしくお願いしまーす。 それでは、ごきげんよう。 参照 Components » ProgressBar https://kivymd.readthedocs.io/en/latest/components/progressbar/ Kivy MDDatePicker - TypeError: init() missing 1 required positional argument: 'callback' https://stackoverflow.com/questions/66706415/kivy-mddatepicker-typeerror-init-missing-1-required-positional-argument
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

slackのモーダルからlambda関数を実行できるようにする

やりたいこと slackのモーダルからlambda関数を実行できるようにします やったこと 今回のコードでは下記アプローチでモーダルを起動できるようにします。 グローバルショートカットで起動 全てAPIを投げることで実行 views.openとviews.update(とchat.postmessage) views.pushではなく、views.updateを使う pushの場合、複数のダイアログを開いたときに、閉じる操作を明示的に行う必要があり手間 というかダイアログを閉じさせるやり方がよくわからなかった。。。  コード import base64 import json import os import urllib.parse import boto3 import requests headers = { "content-type": "application/json", "Authorization": f"Bearer {os.environ['token']}", } def post_text(text): r = requests.post( url="https://slack.com/api/chat.postMessage", headers=headers, json={"channel": "#general", "text": text}, ) return r def open_view(trigger_id, view): r = requests.post( url="https://slack.com/api/views.open", headers=headers, json={"trigger_id": trigger_id, "view": view}, ) return r def update_view(hash, view_id, view): r = requests.post( url="https://slack.com/api/views.update", headers=headers, json={"hash": hash, "view": view, "view_id": view_id}, ) return r def parse_value(d): if "selected_option" in d: return d["selected_option"]["value"] elif "selected_date" in d: return d["selected_date"] def handler(event, context): decoded = base64.b64decode(event["body"]).decode() param = urllib.parse.parse_qs(decoded) payload = json.loads(param["payload"][0]) print(payload) if payload["type"] == "shortcut": # ショートカット起動時 # userごとに実行可能なタスクを制御したい場合はuser_idが使えそう user_id = payload["user"]["id"] view = { "type": "modal", "callback_id": "modal-select-task", "title": {"type": "plain_text", "text": "実行するタスクを選択"}, "blocks": [ { "type": "section", "block_id": "ecs-task-block", "text": {"type": "mrkdwn", "text": "select action"}, "accessory": { "type": "static_select", "placeholder": {"type": "plain_text", "text": "Select an item"}, "options": [ { "text": {"type": "plain_text", "text": "taskA"}, "value": "taskA", }, { "text": {"type": "plain_text", "text": "taskB"}, "value": "taskB", }, { "text": {"type": "plain_text", "text": "taskC"}, "value": "taskC", }, ], "action_id": "select-task", }, } ], } trigger_id = payload["trigger_id"] r = open_view(trigger_id, view) elif ( payload["type"] == "block_actions" and payload["actions"][0]["action_id"] == "select-task" ): # タスクを選んだ時 select = payload["actions"][0]["selected_option"]["value"] view = { "type": "modal", "callback_id": "modal-parameter-task", "title": {"type": "plain_text", "text": "パラメータを指定してください"}, "submit": {"type": "plain_text", "text": "選択"}, "blocks": [ { "type": "section", "text": {"type": "mrkdwn", "text": f"実行タスク: `{select}`"}, }, { "type": "section", "block_id": "date", "text": {"type": "mrkdwn", "text": "date"}, "accessory": {"type": "datepicker", "action_id": "date"}, }, { "type": "section", "block_id": "parameter1", "text": { "type": "mrkdwn", "text": "Pick an item from the dropdown list", }, "accessory": { "type": "static_select", "placeholder": {"type": "plain_text", "text": "Select an item"}, "options": [ { "text": {"type": "plain_text", "text": "value-0"}, "value": "value-0", }, { "text": {"type": "plain_text", "text": "value-1"}, "value": "value-1", }, { "text": {"type": "plain_text", "text": "value-2"}, "value": "value-2", }, ], "action_id": "parameter1", }, }, ], } trigger_id = payload["trigger_id"] view_id = payload["container"]["view_id"] hash = payload["view"]["hash"] r = update_view(hash, view_id, view) elif payload["type"] == "view_submission": # 送信を押した時 user_id = payload["user"]["id"] values = payload["view"]["state"]["values"] # block-idとaction-idが一致している前提のコード params = {k: parse_value(values[k][k]) for k in values.keys()} # この辺りに実行したい内容を記載 # 実行ユーザの情報を投稿 text = f"""実行者: <@{user_id}> {json.dumps(params, indent=2, ensure_ascii=False)} """ r = post_text(text) else: # 他の通信は無視 r = None if r: print(r.text) return {"statusCode": 200} 説明 Lambda関連 上記のコードはAWS Lambda上で実行させることを前提にしています。 外部ライブラリ(requests)を使用しているため、zipでアップロードするかECR経由でコンテナとしてアップロードする必要があります。 筆者は後者のやり方を利用しました。 この辺りは解説記事等があるかと思いますので省略し、Dockerfileだけ記載しておきます。 FROM public.ecr.aws/lambda/python:3.8 COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY app.py ./ CMD ["app.handler"] slack関連 前提 まずは公式のドキュメントを読むことをお勧めします https://api.slack.com/surfaces/modals/using#pushing_response 事前準備 Interactive Components slackアプリの設定のInteractivity & Shortcutsを有効にして、RequestURLを取得。 ショートカット起動時とモーダル操作時に通信がそのURLに飛んでくるので、API Gateway -> Lambdaでその通信を受け取れるようにしておく Permissions botユーザのトークンが必要なので、有効化しておく。モーダル表示は権限なしで実行可能なので、それ以外のタスクで必要なものだけ有効にしておく。 例: 最後に何かしら投稿させたいなら chat:write とか。 Bots Permissionsを有効にした時点で有効になってるはず。。。 コードについて viewの作り方 公式のwebツールを見ながら生成すれば問題なく作れるはず https://app.slack.com/block-kit-builder/ データの取得の仕方 payloadの中身を見れば下記がわかるので、条件分岐や値の保持に使う。 結構ネストが深いところにあるので頑張る どの操作が成された時の通信かを把握する stateを見てモーダルの選択状況を取得しているか把握する 個人的なハマりポイント 全ての操作に対して通信が飛んでくるので、不要な通信を無視するコード書く必要がある モーダル閉じるときは飛んでこない モーダルに備わっている送信ボタンに対して、views.updateを適用してはいけない モーダルが閉じちゃうので、うんともすんとも言わなくなる 送信ボタンは最後に押させるようなフローにするべき なお、views.pushも使えない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Splunkとkerasを用いた不正アクセス予測検知の仕組み(教師データ作成編)

やりたいこと アクセスログをもとに機械学習(ディープラーニング)で不正アクセスを予測する仕組み作る。 1.Splunkを使い、access.logをCSVに変換 2.CSVファイルをもとにJupyterLab上で教師データを作る ⇦今回の内容 3.機械学習を行い、予測精度を確かめる 前回 Splunkとkerasを用いた不正アクセス予測検知の仕組み(前処理編) 環境 ・JupyterLab 2.1.4 @ AWS Cloud9 ・Python 3 ・Splunk Enterprise 8.1 @ AWS EC2 教師データ(答え合わせ用)作成 初期設定を行う。 import numpy as np import matplotlib.pyplot as plt import pandas as pd import seaborn as sns from distutils.util import strtobool from sklearn.model_selection import train_test_split from sklearn.ensemble import IsolationForest # DataFrameで全ての行・列を表示する設定 pd.options.display.max_columns = None Splunkで作成したCSVファイルを読み込む。 dataset_access_log = pd.read_csv('access-log_200MB.csv') データを確認する。 dataset_access_log.head() dataset_access_log.shape dataset_access_log.info() dataset_access_log.columns dataset_access_log.isnull().sum() データのない行を削除する。 dataset_access_log_2 = dataset_access_log.drop(['referer_domain','referer'],axis=1).dropna() dataset_access_log_2.isnull().sum() カラムの中から不正アクセスとして認識させる特徴量をピックアップする。 今回は暫定で以下のパラメータから抽出したが、精度についてはご愛嬌部分多目。 dataset_access_log_2['uri_find_%'] = dataset_access_log_2['uri'].str.find('%') + 1 #1 dataset_access_log_2['uri_find_:'] = dataset_access_log_2['uri'].str.find(':') + 1 #2 dataset_access_log_2['uri_count_:'] = dataset_access_log_2['uri'].str.count('\:') #3 dataset_access_log_2['uri_count_('] = dataset_access_log_2['uri'].str.count('\(') #4 dataset_access_log_2['uri_count_;'] = dataset_access_log_2['uri'].str.count(';') #5 dataset_access_log_2['uri_count_%'] = dataset_access_log_2['uri'].str.count('%') #6 dataset_access_log_2['uri_count_/'] = dataset_access_log_2['uri'].str.count('/') #7 dataset_access_log_2['uri_count_'] = dataset_access_log_2['uri'].str.count('\'') #8 dataset_access_log_2['uri_count_<'] = dataset_access_log_2['uri'].str.count('<') #9 dataset_access_log_2['uri_count_?'] = dataset_access_log_2['uri'].str.count('\?') #10 dataset_access_log_2['uri_count_.'] = dataset_access_log_2['uri'].str.count('.') #11 dataset_access_log_2['uri_count_#'] = dataset_access_log_2['uri'].str.count('#') #12 dataset_access_log_2['uri_count_%3d'] = dataset_access_log_2['uri'].str.count('%3d') #13 dataset_access_log_2['uri_count_%2f'] = dataset_access_log_2['uri'].str.count('%2f') #14 dataset_access_log_2['uri_count_%5c'] = dataset_access_log_2['uri'].str.count('%5c') #15 dataset_access_log_2['uri_count_%25'] = dataset_access_log_2['uri'].str.count('%25') #16 dataset_access_log_2['uri_count_%20'] = dataset_access_log_2['uri'].str.count('%20') #17 dataset_access_log_2['uri_azAZ09'] = dataset_access_log_2['uri'].str.replace('[a-zA-Z0-9_]','').str.len() #19 dataset_access_log_2['uri_count_/%'] = dataset_access_log_2['uri'].str.count('/%') #23 dataset_access_log_2['uri_count_//'] = dataset_access_log_2['uri'].str.count('//') #24 dataset_access_log_2['uri_count_./.'] = dataset_access_log_2['uri'].str.count('./.') #25 dataset_access_log_2['uri_count_..'] = dataset_access_log_2['uri'].str.count('..') #26 dataset_access_log_2['uri_count_=/'] = dataset_access_log_2['uri'].str.count('=/') #27 dataset_access_log_2['uri_count_./'] = dataset_access_log_2['uri'].str.count('./') #28 dataset_access_log_2['uri_count_/?'] = dataset_access_log_2['uri'].str.count('/?') #29 dataset_access_log_2['method_POST'] = dataset_access_log_2['method'].str.contains('POST') #18 dataset_access_log_2['method_POST_num'] = dataset_access_log_2['method_POST'].where(dataset_access_log_2['method_POST'] == 1,0) #18 dataset_access_log_2['uri_len'] = dataset_access_log_2['uri'].str.len() dataset_access_log_2.head() 必要なカラムだけピックアップ dataset_access_log_3 = dataset_access_log_2.drop(dataset_access_log_2.columns[1:8],axis=1) dataset_access_log_3.head() "method_POST"が邪魔なので削除 dataset_access_log_4 = dataset_access_log_3.drop(dataset_access_log_3.columns[len(dataset_access_log_3.columns) - 3 ],axis=1) dataset_access_log_4.head() 再びデータの確認 dataset_access_log_4.info() "method_POST_num"の型がObjectなのでintに変換 dataset_access_log_5 = dataset_access_log_4.astype({'method_POST_num': 'int64'}) dataset_access_log_5.info() IsolationForestを用いて異常値を抽出して教師データにする。 clf = IsolationForest(random_state=0).fit_predict(X) clf 配列(clf)をデータフレームに追加。 負の値があるとKerasでエラーになるので0と1に変換しておく。 dataset_access_log_5['clf'] = clf dataset_access_log_5.loc[dataset_access_log_5['clf'] < 0, 'clf'] = 0 dataset_access_log_5.head() csvファイルに出力。 ※10文字以下のuriを見てみると、不正アクセスチックなものがほぼないので除外。 dataset = dataset_access_log_5[dataset_access_log_5['uri_len'] >10] dataset.to_csv('dataset.csv') 以上。   【次回】:Splunkとkerasを用いた不正アクセス予測検知の仕組み(ディープラーニング構築編) 参考 https://www.scutum.jp/information/waf_tech_blog/2021/01/waf-blog-077.html https://data-analysis-stats.jp/%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92/%E7%95%B0%E5%B8%B8%E6%A4%9C%E5%87%BA%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0-isolation-forest/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python3] 初心者の初心者による「リスト」基礎解説

リストとは 複数の値を格納することができる機能です。 VBAでいえば、配列と同じようなものです。 リストの作成方法 リストの作成 # 以下がリストの書式です [値1,値2,値3,....] # 以下が実際のリスト作成方法です RandomNumbers = [1,2,3,4,10,15] # 数値の場合 Fruits = ["orange" , "peach" , "banana" , "apple"] # 文字列の場合 Mixed = ["aaaa" , 10 , "bbbb" , 20] # 混在の場合 リストの話とは関係ありませんが、文字列を格納する場合は""の中に記載することを忘れずに!! 上記以外に、組み込み関数のlist()を使うことでもリストを作成することができます。 list()を使用する場合 NumberList = list(range(-5,6)) # 出力結果 [-5,-4,-3,-2,-1,0,1,2,3,4,5] # 空のリストを作成する場合 EmptyList = list() # 出力結果 [] リストの要素参照 次は、実際にリストに格納した値を出力してみましょう。 リスト内の値出力 # リストの作成 numbers = [10 , 20 , 30 , 40 , 50] # 10を出力する場合 print(numbers[0]) # 30を出力する場合 print(numbers[2]) 上記の様に、 変数[インデックス番号] で指定することができます。 ※インデックス番号は先頭から0,1,2...の様に数えます。  その為、最初の値を取得したい場合のインデックス番号は1ではなく0になります。 リスト内の要素変更 リスト内の値を指定し出力が可能になったので、 次はリスト内の要素を変更する方法を紹介いたします。 リスト内の要素変更 # リストの作成 Fruits = ["orange" , "peach" , "banana" , "apple"] # orangeをgrapeに変更する場合 Fruits[0] = "grape" # 出力結果 Fruits = ["grape" , "peach" , "banana" , "apple"] 上記の様に変更したい要素のインデックス番号を指定し代入することで 要素の変更が可能になります。 最後に 今回はリストの作成、要素取得、要素変更を記事にしました。 リストでは様々なことが行えます。 今回紹介できなかった、「要素の追加・削除」「リストのスライス」「内包表記」などは 次回の記事にて説明いたします。 まだまだ初心者ですが、皆様の役に立てれば幸いです。 間違い等ありましたら、ご教授お願いいたします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【プロ野球】今年の新人王をLightGBMで予測してみた

概要 プロ野球も今シーズンは残り30試合ほどとなり、現時点でのデータを用いて新人王が誰になるのかをLightGBMにて予測してみようと思いました。(9/10時点でのデータ) 個人的には、球団新人最多本塁打記録を塗り替えた阪神の佐藤輝明選手に獲って欲しいと思っています。(希望的観測) LightGBMとは 近年編み出された機械学習アルゴリズム。精度が高く、実行時間も短いらしい。 また、欠損値もそのまま扱え、特徴量のスケーリングも必要ない。 詳しくはこちら(公式ドキュメント) https://lightgbm.readthedocs.io/en/latest/ 流れ 2009年から2020年までの新人王有資格者の成績をLightGBMを用いて学習させ、2021年のデータから、新人王は誰になるのかを予測する。 歴代新人王 年度 2020年 2019年 2018年 2017年 2016年 2015年 2014年 2013年 2012年 2011年 2010年 2009年 セ・リーグ新人王 森下 暢仁(広島) 村上 宗隆(ヤクルト) 東 克樹(DeNA) 京田 陽太(中日) 高山 俊(阪神) 山崎 康晃(DeNA) 大瀬良 大地(広島) 小川 泰弘(ヤクルト) 野村 祐輔(広島) 澤村 拓一(巨人) 長野 久義(巨人) 松本 哲也(巨人) パ・リーグ新人王 平良 海馬(西武) 高橋 礼(ソフトバンク) 田中 和基(楽天) 源田 壮亮(西武) 高梨 裕稔(日本ハム) 有原 航平(日本ハム) 石川 歩(ロッテ) 則本 昂大(楽天) 益田 直也(ロッテ) 牧田 和久(西武) 榊原 諒(日本ハム) 攝津 正(ソフトバンク) ライブラリのimport import pandas as pd import numpy as np from tqdm.notebook import tqdm import lightgbm as lgb import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split # データセット分割用 from sklearn.metrics import roc_auc_score # モデル評価用(auc) データ入手 データは、プロ野球データFREAK様から拝借させていただきました。 以下の関数で2009年~2020年までの野手(Batter)と投手(Pitcher)の新人王有資格者のデータをスクレイプ。 def scrape_B(year_list): batter_results = {} for year in tqdm(year_list): time.sleep(1) url = "https://baseball-data.com/" + year + "/stats/hitter-all/tpa-7.html" df = pd.read_html(url) df = df[0].droplevel(1, axis=1) batter_results[year] = df batter_results[year].index = [year] * len(batter_results[year]) return batter_results def scrape_P(year_list): pitcher_results = {} for year in tqdm(year_list): time.sleep(1) url = "https://baseball-data.com/" + year + "/stats/pitcher-all/ip3-7.html" df = pd.read_html(url) df = df[0].droplevel(1, axis=1) pitcher_results[year] = df pitcher_results[year].index = [year] * len(pitcher_results[year]) return pitcher_results 09年から20年までのリストを作成。 year_list = [] for y in range(9, 21, 1): year = str(y).zfill(2) year_list.append(year) スクレイプ関数を実行した後、野手データ、投手データをデータフレーム型に直し、2つを結合する。(2つのcolumnsが違うため欠損値NaNが出てきてしまうが、とりあえずやってみる。) batter_09_20 = scrape_B(year_list) pitcher_09_20 = scrape_P(year_list) batter_09_20 = pd.concat([batter_09_20[key] for key in batter_09_20], sort=False) pitcher_09_20 = pd.concat([pitcher_09_20[key] for key in pitcher_09_20], sort=False) #野手と投手を結合 results_09_20 = pd.concat([batter_09_20, pitcher_09_20]) 前処理等 この時、以下のように試合が'-'となっている行があるが、これは1試合も出ていないということなので、これらの行を省く。 results_09_20 = results_09_20[results_09_20['試合'] != '-'] 新たにKINGという列を加え、新人王を獲っているなら1、そうでないなら0とする。コレが目的変数になる。 results_09_20['KING'] = 0 としてKING列を0で初期化して、あとはこれをcsvファイルにして外部操作で歴代新人王での値を1とした。(他にいい方法があるはず) 予測に使わない列「順位」「選手名」「チーム」を削除する。 #preprocessing(前処理) results_09_20_p = results_09_20.drop(['順位', '選手名', 'チーム'], axis=1) results_09_20_p to_numeric関数を用いて、各列を数値型に変換する。 columns = results_09_20_p.columns.tolist() for column in columns: results_09_20_p[column] = pd.to_numeric(results_09_20_p[column], errors = 'coerce') ここまでで、筆者環境で 1675 rows × 36 columns のresults_09_20_pが出来た。 データの分割 説明変数と目的変数に分ける。 #説明変数  X = results_09_20_p.drop(['KING'], axis=1).values #目的変数 y = results_09_20_p['KING'].values train_test_split関数を使ってデータを分割する。 ここでは訓練データ7割、テストデータ3割となるようにした。 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30) LightGBMで学習 いよいよ学習。パラメータは以下のように簡単にした。 (ここはOptunaを用いてパラメータを調節する手もあるが省略。) #LightGBM parameters params = { 'objective': 'binary', # 目的 : 二値分類 'metric': 'auc', #AUCの最大化を目指す } #モデルの学習 model = lgb.LGBMClassifier(**params) model.fit(X_train, y_train) モデルの評価 予測値と予測確率 y_pred = model.predict(X_test) y_pred_prob = model.predict_proba(X_test) 正答率計算 #正答率 acc = accuracy_score(y_test, y_pred) acc 実行結果 0.9860834990059643 AUC計算 #AUC auc = roc_auc_score(y_test,y_pred_prob[:,1]) auc 実行結果 0.989558232931727 各特徴量の重要度をプロット 各特徴量は以下のようになる。 model.feature_importances_ 実行結果 array([ 34, 451, 82, 51, 47, 71, 10, 47, 81, 0, 2, 8, 84, 12, 33, 62, 8, 10, 236, 163, 19, 40, 87, 44, 119, 34, 54, 12, 75, 47, 99, 48, 50, 320, 85]) これをグラフで可視化してみる。 importance = model.feature_importances_.tolist() #columnsから'KING'を削除 del columns[-1] #Dataframe化 imp = pd.DataFrame(model.feature_importances_, index=columns, columns={'importance'}) #降順にソート imp_s = imp.sort_values('importance', ascending=False) imp_s 以下のようなデータフレームが得られる。 これのindexを縦軸、importanceを横軸とし横棒グラフにする。 plt.figure(figsize=(10,10)) plt.barh(imp_s.index.tolist(),imp_s['importance'].tolist()) plt.show() 重要度としては、出場試合数が最も高く、次点でWHIP,防御率,勝利数,対戦打者数,奪三振などの投手用の特徴量が続いている。 2021年新人王の予測 本題の新人王予想に入る。 まず2021年のデータを取ってくる。 batter_21 = pd.read_html('https://baseball-data.com//stats/hitter-all/tpa-7.html') batter_21 = batter_21[0].droplevel(1, axis=1) pitcher_21 = pd.read_html('https://baseball-data.com//stats/pitcher-all/ip3-7.html') pitcher_21 = pitcher_21[0].droplevel(1, axis=1) #打者データと投手データ結合 results_21 = pd.concat([batter_21, pitcher_21]) 続いて前処理 #試合が - の行は省く results_21 = results_21[results_21['試合'] != '-'] #使わない列を落とす results_21_p = results_21.drop(['順位', '選手名', 'チーム'], axis=1) for column in columns: #columnsは'打率'から'DIPS'まで35個 results_21_p[column] = pd.to_numeric(results_21_p[column], errors = 'coerce') 最後に予測値predと予測確率pred_probを表示 pred = model.predict(results_21_p) pred_prob = model.predict_proba(results_21_p) print(pred) print(pred_prob) predは、1だったら予測確率50%以上で新人王になるということである。 pred_probは[0と予想する確率,1と予測する確率]を表している。 パッと見たところ、0番目の選手が68.4%という確率で新人王になると予測されている。 何番目の選手が新人王と予測されたかを見てみる [i for i, x in enumerate(pred.tolist()) if x == 1] 実行結果 [0, 67] 0番目と67番目の選手を見てみると前者は佐藤輝明選手(阪神)、後者は宮城大弥投手(オリックス)であった。 results_21[0:1] 佐藤選手の成績 results_21[67:68] 宮城投手の成績 さらにpred_prob(新人王と予測する確率)を見ると、佐藤選手68.4%,栗林投手6.8%,宮城投手に至っては99.97%という数値を叩き出した。 (確率が1%以上になる所を取り出したもの。左から佐藤、宮城、栗林選手) 宮城投手の新人王は確定か 現時点でほぼ100%と言える確率を叩き出した宮城投手は、大崩れしない限りは新人王を手にすると思われる。 一方でセ・リーグだが、佐藤選手に引けを取らないDeNAの牧選手の新人王予測確率が1%未満というのに驚いた。 もしかしたらどこかでミスを犯しているのかもしれない。。。それともホームラン数の差によるものなのだろうか。 また、広島の栗林投手は防御率0.47、 23Sと素晴らしい成績を残しているが、予測確率が6.8%と低いのも気になった。 目的変数が1に比べ0が圧倒的に多いので、もしかしたらアンダーサンプリングを実施すれば、、みたいな話かもしれない。 次に向けて シーズン終了時にフルシーズンのデータが揃った段階でまた予測したいと思う。 また、パラメータチューニングやアンダーサンプリングなども検討する。 野手と投手を混ぜて考えたが、別々でやるのもいいかもしれない。 セ・リーグ、パ・リーグで分けるということもやってみてもいいかも。 参考資料 プロ野球データFreak
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「”すぐ” このデータまとめて欲しい」に ”すぐ”に超簡単に応えられる Python の・・・(その2)

~手元データの要約、視覚化を超簡単に実現して、探索的データ解析(EDA)しよう(AutoViz編)~ はじめに   「おい、悪いが”すぐに”このデータまとめてくれ」   緊急!となれば、そりゃ”すぐに”取りかかるが、”すぐに”まとめることができるかは別の話・・・これはデータ項目が多いときほど悩ましいものです。 じつは、Pythonは、このようなデータセットでも”すぐに”まとめることができる。 前回は pandas-profiling を記事にしました。 今回は Autoviz というライブラリで同じことをやってみたという記事です。 私は、見た目、コードもシンプルで簡単に思えた Autoviz の方が気に入りました。 「Pythonで相関係数行列グラフとか描けるのは知ってるけど、チマチマやってる時間ないんです」 といった方にとっても、まとめを頼んだ上司にとっても、これらによるアウトプットはまちがいなくありがたいと喜んでくれるはず。 この記事は、私の備忘含め、 Autoviz が誰でも簡単に実行できるように記録するものです。   実行条件など ・Google colabで実行 ・ボストン住宅価格のデータセットで実行 ※手元データを読込んで実行する場合も記載していますので、簡単にできるはずです。       ボストン住宅価格のデータセットについて 以下サイト(Kaggle)の「Boston.csv」を使わせていただいた。 データ数:506, 項目数:14のデータセットで、住宅価格を示す「MEDV」という項目と、住宅価格に関連するであろう項目が「CRIM:犯罪率」「RM:部屋数」「B:町の黒人割合」「RAD:高速のアクセス性」・・・等、13項目で構成されたデータとなっています。 これだけ項目があると、データ傾向を掴むだけでも、なかなか骨が折れるだろうと想像できますね。 ボストン住宅価格データの項目と内容 項目 内容 CRIM 町ごとの一人当たり犯罪率 ZN 25,000平方フィート以上の住宅地の割合 INDUS 町ごとの非小売業の面積の割合 CHAS チャールズ川のダミー変数(川に接している場合は1、そうでない場合は0) NOX 窒素酸化物濃度(1,000万分の1) RM 1住戸あたりの平均部屋数 AGE 1940年以前に建てられた持ち家の割合 DIS ボストンの5つの雇用中心地までの距離の加重平均 RAD 高速道路(放射状)へのアクセス性を示す指標 TAX 10,000ドルあたりの固定資産税の税率 PTRATIO 町ごとの生徒数と教師数の比率 B 町ごとの黒人の割合 LSTAT 人口の下層階級の比率 MEDV 住宅価格の中央値(1000㌦単位)    プロファイリング(Autoviz)してみよう! ライブラリのインストールおよびインポート ライブラリのインストールおよびインポート !pip install autoviz # 必要なライブラリーのインポート import pandas as pd import numpy as np from autoviz.AutoViz_Class import AutoViz_Class データの読み込み(これはスキップしてもいいです) ※手元データを読込む場合は、読み込むファイル名を変更してください。 ※Excelファイルを読込む場合は、pd.read_excel('ファイル名.xlsx')となります。 # データセットの読込み df = pd.read_csv("Boston.csv",index_col=0) df.head() **プロファイルを出力 autoviz = AutoViz_Class().AutoViz('Boston.csv') **出力イメージ プロファイルから読み取った概要に、HTML出力結果にをアタッチして報告すれば、きっと喜んでいただけるでしょう。 最後に Pythonには「こんなことまでできるのか」と驚かされることがいろいろある。 この Autoviz もそのひとつ。使わない手はない。   参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

goatoolsによるGO解析方法まとめ

概要 GO(Gene Ontology)は、さまざまな生物の遺伝子(がコードするタンパク質)の機能に関する情報を統一化された語彙により表現したもの。 生物医学研究をする上でGOは重要な役割を果たしており、オミックス(ゲノミクス・トランスクリプトミクス・プロテオミクス・メタボロミクス等の総称)実験にて、生物の構造・機能・ダイナミクスを解明するため、一般的によく利用される。 本記事では、PythonのGO解析ツールである「goatools」を利用して、特定コントロール実験条件下での発現変動遺伝子のような遺伝子データセットにおいて統計的有意に多く含まれる機能情報を発見する手法である「GOenrichment解析」や「GOデータ可視化」について記載する。 実行環境設定 下記pipコマンドにより、goatoolsインストール pip install goatools GOデータの可視化を行う場合は、pygraphvizのインストールも必要 pip install pygraphviz またpygraphvizはグラフ構造可視化ツール「graphviz」のラッパーライブラリのため、 本体のgraphvizのインストールが合わせて必要 Ubuntuの場合、下記コマンドで簡単にインストールが可能 sudo apt install -y graphviz libgraphviz-dev pkg-config Windowsの場合のインストール参考記事も記載しておく。 【Windows10】Graphvizのインストール 本記事では、Ubuntu20.04 & Python3.8.10 にて実行環境構築を実施 GO解析用データ取得 GO解析に必要なGOデータを定義したOBOファイルのダウンロードを行う。 以降の解析時には、ダウンロードファイルが常にカレントディレクトリに存在する必要があるので注意。 download_obo_file wget http://geneontology.org/ontology/go-basic.obo wget http://www.geneontology.org/ontology/subsets/goslim_generic.obo go-basic.obo GOの機能や階層構造の定義等を記述したファイル goslim_generic.obo go-basic.oboの縮小版であるGOslim(GO subsets)ファイル。 GOslimを利用することでGO解析結果が簡素化され、全体像の効率的な把握に役立つ。 GO解析結果の情報量が非常に多い場合に利用するが、個人的にあまり使う機会はない。 GOenrichment解析 シロイヌナズナ(Arabidopsis thaliana)のストレス環境条件下における発現変動遺伝子リストが手元にあり、これから機能解析を実行する状況を想定したGOenrichment解析デモについて記載する。 以降のデータ解析を自身で実行する場合は、GitHubに格納したデモ用データをダウンロードしてしてください。 GO解析デモ用データ一覧 at_stress_deg_list.txt シロイヌナズナのストレス環境条件下での発現変動遺伝子(DEG:Differencial Expression Gene)IDリストファイル at_all_gene_list.txt シロイヌナズナの全遺伝子IDリストファイル (1.に記載された発現変動遺伝子IDを全て包括する) at_go_association.txt シロイヌナズナの各遺伝子とGO情報の紐づきを記載したファイル (2.に記載された遺伝子IDを含む) GOenrichment解析実行コマンド find_enrichment.pyにより「① 発現変動遺伝子」と「② 全遺伝子セット」のGOアノテーション割合を算出・比較することで、①のGOアノテーションが有意に多く観測されるかどうかの検定を行う。 複数回検定に起因する偽陽性(FDR:False Discovery Rate)の発生を制御するため、一般的によく利用されるBH(Benjamini-Hochberg)法による多重検定補正を行う設定で実行する。 find_enrichment.py ./data/at_stress_deg_list.txt ./data/at_all_gene_list.txt ./data/at_go_association.txt \ --pval=0.05 --method=fdr_bh --pval_field=fdr_bh \ --outfile=at_go_enrichment.tsv,at_go_enrichment.xlsx コマンドオプション概要 「find_enrichment.py」の最初の引数には順番通りに3つのファイルを指定する。 ターゲット遺伝子IDリストファイル 全遺伝子IDリストファイル 遺伝子IDとGO情報の紐づきを記載したファイル オプション 内容 pval P-value閾値設定。閾値未満(P-value < 0.05)のGO機能を出力。 method 多重検定補正の手法を指定 pval_field 閾値未満(指定した多重検定補正済P-Value < 0.05)のGO機能を出力。 outfile 出力ファイル設定。tsv形式とxlsx形式の両方に対応。同時出力可。 出力結果 tsv/xlsxフォーマットで下記のように有意に多い(p_fdr_bhが充分に低い)発現変動遺伝子のGO機能が出力される。 出力結果からストレス環境要因に関連する機能(response to cold, abiotic stimulus, stress, etc...)が統計的有意に多く観測されていることが分かる。 at_go_enrichment.tsv # GO NS enrichment name ratio_in_study ratio_in_pop p_uncorrected depth study_count p_fdr_bh study_items GO:0009409 BP e response to cold 21/168 241/31819 1.2953970111720632e-19 4 21 4.425076190163768e-16 AT1G09350, AT1G20440, AT1G20450, AT1G76180, AT2G15970, AT2G17840, AT2G17870, AT2G42530, AT2G45660, AT3G22840, AT3G50970, AT3G53990, AT4G03430, AT4G13850, AT4G24960, AT4G36020, AT5G15960, AT5G15970, AT5G20830, AT5G52310, AT5G58070 GO:0009628 BP e response to abiotic stimulus 36/168 1185/31819 1.1559257428995517e-17 2 36 1.703993929100776e-14 AT1G01470, AT1G09350, AT1G20440, AT1G20450, AT1G76180, AT1G77120, AT2G15970, AT2G17840, AT2G17870, AT2G21620, AT2G39800, AT2G42530, AT2G45660, AT3G02230, AT3G03250, AT3G14440, AT3G22840, AT3G50970, AT3G53990, AT3G55610, AT4G03430, AT4G11600, AT4G13850, AT4G15480, AT4G24960, AT4G27410, AT4G36020, AT4G38580, AT5G15650, AT5G15960, AT5G15970, AT5G20830, AT5G40390, AT5G52310, AT5G58070, AT5G62640 GO:0006950 BP e response to stress 45/168 1943/31819 1.4964817878519694e-17 2 45 1.703993929100776e-14 AT1G01470, AT1G02820, AT1G09350, AT1G20440, AT1G20450, AT1G76180, AT1G77120, AT2G02100, AT2G15970, AT2G17840, AT2G17870, AT2G21620, AT2G22080, AT2G39800, AT2G42530, AT2G45660, AT3G02230, AT3G03250, AT3G05660, AT3G14440, AT3G22840, AT3G50970, AT3G53990, AT3G55610, AT4G03430, AT4G11600, AT4G12000, AT4G12470, AT4G13850, AT4G24220, AT4G24960, AT4G27410, AT4G36020, AT4G38580, AT5G13160, AT5G15650, AT5G15960, AT5G15970, AT5G19875, AT5G20830, AT5G25110, AT5G40390, AT5G47120, AT5G52310, AT5G58070 GO:0009266 BP e response to temperature stimulus 22/168 358/31819 3.02332975446427e-17 3 22 2.581923610312487e-14 AT1G09350, AT1G20440, AT1G20450, AT1G76180, AT2G15970, AT2G17840, AT2G17870, AT2G42530, AT2G45660, AT3G22840, AT3G50970, AT3G53990, AT4G03430, AT4G13850, AT4G24960, AT4G36020, AT4G38580, AT5G15960, AT5G15970, AT5G20830, AT5G52310, AT5G58070 GO:0009415 BP e response to water 17/168 178/31819 9.36720486046985e-17 4 17 6.399674360673002e-14 AT1G01470, AT1G20440, AT1G20450, AT1G76180, AT2G15970, AT2G17840, AT2G21620, AT2G39800, AT3G14440, AT3G50970, AT4G24960, AT4G27410, AT5G15960, AT5G15970, AT5G20830, AT5G40390, AT5G52310 〜〜〜(以下省略)〜〜〜 GOenrichment解析実行コマンド(GOslim) map_to_slim.pyにより「at_go_association.txt」の各遺伝子とGO情報の紐づきを縮小(GOslim)化する。 縮小(GOslim)化したファイルを利用して、前述と同様にGOenrichment解析を実行する。 map_to_slim.py --association_file=./data/at_go_association.txt go-basic.obo goslim_generic.obo > ./data/at_go_association_slim.txt find_enrichment.py ./data/at_stress_deg_list.txt ./data/at_all_gene_list.txt ./data/at_go_association_slim.txt \ --pval=0.05 --method=fdr_bh --pval_field=fdr_bh \ --outfile=at_go_enrichment_slim.tsv,at_go_enrichment_slim.xlsx 出力結果(GOslim) GOslimを利用した出力結果において、ストレス環境要因に関連する機能は2つ(response to stress, stimulus)のみとなり、GOslimを利用しない場合と比較してGO情報量が大幅に削減された。 今回のサンプル程度のGO情報量であれば、GOslimは利用しないほうがデータ解析に適していると考えられる。 at_go_enrichment_slim.tsv # GO NS enrichment name ratio_in_study ratio_in_pop p_uncorrected depth study_count p_fdr_bh study_items GO:0006950 BP e response to stress 45/168 1943/31819 1.4964817878519694e-17 2 45 1.137326158767497e-15 AT1G01470, AT1G02820, AT1G09350, AT1G20440, AT1G20450, AT1G76180, AT1G77120, AT2G02100, AT2G15970, AT2G17840, AT2G17870, AT2G21620, AT2G22080, AT2G39800, AT2G42530, AT2G45660, AT3G02230, AT3G03250, AT3G05660, AT3G14440, AT3G22840, AT3G50970, AT3G53990, AT3G55610, AT4G03430, AT4G11600, AT4G12000, AT4G12470, AT4G13850, AT4G24220, AT4G24960, AT4G27410, AT4G36020, AT4G38580, AT5G13160, AT5G15650, AT5G15960, AT5G15970, AT5G19875, AT5G20830, AT5G25110, AT5G40390, AT5G47120, AT5G52310, AT5G58070 GO:0050896 BP e response to stimulus 45/168 1943/31819 1.4964817878519694e-17 1 45 1.137326158767497e-15 AT1G01470, AT1G02820, AT1G09350, AT1G20440, AT1G20450, AT1G76180, AT1G77120, AT2G02100, AT2G15970, AT2G17840, AT2G17870, AT2G21620, AT2G22080, AT2G39800, AT2G42530, AT2G45660, AT3G02230, AT3G03250, AT3G05660, AT3G14440, AT3G22840, AT3G50970, AT3G53990, AT3G55610, AT4G03430, AT4G11600, AT4G12000, AT4G12470, AT4G13850, AT4G24220, AT4G24960, AT4G27410, AT4G36020, AT4G38580, AT5G13160, AT5G15650, AT5G15960, AT5G15970, AT5G19875, AT5G20830, AT5G25110, AT5G40390, AT5G47120, AT5G52310, AT5G58070 GO:0016020 CC e membrane 31/168 1892/31819 1.6725763607803712e-08 2 31 3.846925629794854e-07 AT1G11960, AT1G30360, AT1G58360, AT1G76180, AT1G77120, AT2G02100, AT2G15970, AT2G17840, AT2G21130, AT2G23120, AT2G38465, AT2G45820, AT3G02230, AT3G03250, AT3G15730, AT3G23810, AT3G27210, AT3G45600, AT3G57340, AT4G02710, AT4G05520, AT4G11600, AT4G20890, AT4G23630, AT4G27520, AT4G35300, AT4G38580, AT5G15970, AT5G41600, AT5G57110, AT5G58070 GO:0005886 CC e plasma membrane 31/168 1892/31819 1.6725763607803712e-08 3 31 3.846925629794854e-07 AT1G11960, AT1G30360, AT1G58360, AT1G76180, AT1G77120, AT2G02100, AT2G15970, AT2G17840, AT2G21130, AT2G23120, AT2G38465, AT2G45820, AT3G02230, AT3G03250, AT3G15730, AT3G23810, AT3G27210, AT3G45600, AT3G57340, AT4G02710, AT4G05520, AT4G11600, AT4G20890, AT4G23630, AT4G27520, AT4G35300, AT4G38580, AT5G15970, AT5G41600, AT5G57110, AT5G58070 GO:0110165 CC e cellular anatomical entity 83/168 9528/31819 1.670443498419094e-07 1 83 2.561346697575944e-06 AT1G08880, AT1G11960, AT1G14580, AT1G20450, AT1G21670, AT1G30360, AT1G47710, AT1G53910, AT1G55110, AT1G58360, AT1G61730, AT1G67310, AT1G67360, AT1G73630, AT1G76180, AT1G77120, AT2G02100, AT2G15970, AT2G17840, AT2G17870, AT2G20370, AT2G21130, AT2G22080, AT2G22500, AT2G22860, AT2G23120, AT2G23760, AT2G37520, AT2G38465, AT2G39800, AT2G42530, AT2G45660, AT2G45820, AT3G02230, AT3G02750, AT3G03250, AT3G05660, AT3G15730, AT3G23810, AT3G24520, AT3G24840, AT3G25140, AT3G27210, AT3G29360, AT3G45600, AT3G45960, AT3G49220, AT3G53620, AT3G55610, AT3G55760, AT3G57010, AT3G57340, AT3G59820, AT4G02710, AT4G03430, AT4G05520, AT4G11600, AT4G13850, AT4G20880, AT4G20890, AT4G22590, AT4G23630, AT4G27410, AT4G27520, AT4G32400, AT4G34740, AT4G35300, AT4G38580, AT5G11420, AT5G15190, AT5G15650, AT5G15970, AT5G16010, AT5G17490, AT5G40390, AT5G41600, AT5G47120, AT5G56630, AT5G57110, AT5G57660, AT5G58070, AT5G62190, AT5G62640 〜〜〜(以下省略)〜〜〜 その他TIPS GOカテゴリー(BP/CC/MF)毎に結果を出力 --nsオプションを利用することで各GOカテゴリー毎に結果の出力が可能 3つのカテゴリー(BP:Biological Process, CC:Cellular Component, MF:Molecular Function)の詳細はこちらを参照 for category in BP CC MF do find_enrichment.py ./data/at_stress_deg_list.txt ./data/at_all_gene_list.txt ./data/at_go_association.txt \ --pval=0.05 --method=fdr_bh --pval_field=fdr_bh --ns=$category \ --outfile=at_go_enrichment_$category.tsv,at_go_enrichment_$category.xlsx done Pvalueの大小に関わらず全結果を出力 --pval=-1を指定することで全結果を出力することが可能 find_enrichment.py ./data/at_stress_deg_list.txt ./data/at_all_gene_list.txt ./data/at_go_association.txt \ --pval=-1 --method=fdr_bh --pval_field=fdr_bh \ --outfile=at_go_enrichment_all.tsv,at_go_enrichment_all.xlsx GOデータ可視化 plot_go_term.pyを利用した単体GO定義の親子関係可視化、及び go_plot.pyを利用した複数GO定義の関係可視化の手法について記載する。 単体GO定義の親子関係可視化 plot_go_term.pyにより指定したGOの親子(is-a)関係を可視化した画像を出力可能(対応フォーマット:pdf, svg, png, jpg) ※ 指定したGOの階層レベルが高い場合は、情報量が多くなり画像表示出来ない場合があるので注意 plot_go_term.py --term=GO:0009415 --output=response_to_water.jpg 親または子の関係を除外して可視化した画像出力も可能 plot_go_term.py --term=GO:0009415 --output=response_to_water_noparents.jpg --disable-draw-parents plot_go_term.py --term=GO:0009415 --output=response_to_water_nochildren.jpg --disable-draw-children response_to_water_noparents.jpg response_to_water_nochildren.jpg 複数GO定義の関係可視化 go_plots.pyにより指定したGO定義間における親(root)までの関係(is-a, part-of)を可視化した画像を出力可能 ※ GO定義のRelation(関係)の詳細はこちらを参照 go_plot.py GO:0003304 GO:0061371 --outfile=multi_plot.png -r (--relationship)オプションにより、橙破線のpart-of関係を含むGO情報も出力される。 またGOにカラー情報(#ff0000=赤)を追記することで、対象GOの色を変更することができる。 go_plot.py GO:0003304 GO:0061371#ff0000 -r --outfile=multi_plot_all_relation.png GOenrichment結果のGO定義関係可視化 例:「GOカテゴリー=BP」に属する上位(Pvalue昇順)10個のGO機能の可視化 # BPを対象としたGOenrichment結果のトップ10のGO記載のファイル作成 grep GO: at_go_enrichment.tsv | grep BP | head -n 10 | cut -f 1 > go_enrichment_list.txt go_plot.py --go_file=go_enrichment_list.txt --outfile=plot_go_enrichment_BP.png また下記のような色情報を含むGO定義ファイルを入力とすることで、発現変動遺伝子とGO機能の関連性の強さ(Pvalueの大小関係)を段階色で表現するようなことも可能となる。 go_enrichment_color_list.txt #ff2200 GO:0009409 #ff4400 GO:0009628 #ff6600 GO:0006950 #ff8800 GO:0009266 #ffaa00 GO:0009415 #ffbb00 GO:0001101 #ffcc00 GO:0050896 #ffdd00 GO:0009414 #ffee00 GO:0010035 #ffff00 GO:0006970 echo -e "#ff2200\tGO:0009409\n#ff4400\tGO:0009628\n#ff6600\tGO:0006950\n#ff8800\tGO:0009266\n#ffaa00\tGO:0009415\n#ffbb00\tGO:0001101\n#ffcc00\tGO:0050896\n#ffdd00\tGO:0009414\n#ffee00\tGO:0010035\n#ffff00\tGO:0006970" \ > go_enrichment_color_list.txt go_plot.py --go_file=go_enrichment_color_list.txt --outfile=plot_color_go_enrichment_BP.png 最後に 本記事で紹介したgoatoolsを利用することでGOenrichment解析やGOデータ可視化を手元で手軽に実行することができた。 しかし、近年はWebベースのGO解析ツールも充実してきているため、「自動化されたデータ解析パイプライン構築」や「繰り返しの大量データ解析」等の必要性がない状況では、agriGOやg:ProfilerのようなWebベースのGO解析ツールの利用で十分に解析が可能である。 目的に応じてGO解析のコマンドツールとWebツールを使い分けると良さそう。 参考 GOATOOLS: A Python library for Gene Ontology analyses - scientific reports - goatools論文 goatools - GibHub - goatoolsのGitHubサイト GENE ONTOROGY Unifying Biology - GO管理団体の公式サイト GO解析 biopapyrus - GO解析の詳細が記載されたサイト その他GO解析ツール GOstats - RによるGO解析ツール。Rユーザーはgoatoolsの代替で利用できる。 GO解析Webツール agriGO - 農学系の生物が充実したGO解析ツール。UI・出力結果ともに分かりやすい。 g:Profiler - 遺伝子ID変換ツール等も提供しているGO解析ツール。UI・出力結果ともに分かりやすい。 DAVID - 昔から利用されてきた定番GO解析ツール。しかし、UIが残念でデータ更新が遅い印象。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonインストール【Windows,Mac両用】

Pythonインストール メモメモ。。。 Pythonダウンロード こちらからPythonをダウンロード Windows版かMac版か確認してダウンロード インストール Windows版はPATHが必要なのでチェックを入れてからインストールする。 ※画像はバージョンが低いですが、そこは無視で。。。 バージョン確認 正常にインストールできたか確認 インストールしたバージョンと同じバージョンになっていれば成功です。 $ python -V Windowsの場合、Microsoft Storeが邪魔してる場合がある。 $ python コマンドプロンプトでpythonとコマンドしたとき、Microsoft Storeが開いたらオフにしてあげましょう。 Windowsボタンで設定画面開く アプリと機能を開く アプリ実行エイリアス選択 アプリインストーラーをオフ 確認 pythonとコマンドして下記が表示されればOKです。 $ python Python 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pandasチュートリアル

pandasチュートリアル 本書はpandsのチュートリアルを翻訳及び加筆したものです。 ※筆者が所属するNPO法人の勉強用にメモしたものですので、誤りや不足、加筆修正すべきことろがありましたらご指摘ください。継続してブラッシュアップしていきます。 @2021 NPO法人AI開発推進協会 BSD 3-Clause License Copyright (c) 2008-2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team All rights reserved. Copyright (c) 2011-2021, Open source contributors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.pandas概要 pandasは、高速かつ柔軟で表現力豊かなデータ構造を提供するPythonパッケージで、「リレーショナル」または「ラベル付き」データを簡単かつ直感的に扱えるように設計されています。pandasは、Pythonで実用的なデータ分析を行うための基本的な高レベルなビルディングブロックとなることを目指しています。さらに、あらゆる言語で利用可能な最もパワフルで柔軟なオープンソースのデータ分析/操作ツールになるという大きな目標を持っています。この目標に向けて、すでに順調に進んでいます。 pandasは、さまざまな種類のデータに適しています。 SQLテーブルやExcelスプレッドシートのような、不均一なタイプの列を持つ表形式データ 順番に並べられた時系列データ、または順番に並べられていない時系列データ(必ずしも固定頻度ではない)。 行と列のラベルを持つ任意の行列データ(同種または異種)。 その他、あらゆる形式の観察/統計データセット。pandasのデータ構造に入れるためには、データにラベルが付いている必要はありません。 pandasの2つの主要なデータ構造、Series(1次元)とDataFrame(2次元)は、金融、統計、社会科学、エンジニアリングの多くの分野における典型的なユースケースの大部分を扱います。pandasは、NumPyの上に構築されており、他の多くのサードパーティのライブラリと科学的な計算環境の中でうまく統合することを目的としています。 ここでは、pandasが得意とする機能のいくつかを紹介します。 浮動小数点データや非浮動小数点データの欠損データ(NaNと表現される)を容易に扱うことができる サイズの変更可能性:DataFrameや高次元のオブジェクトにカラムを挿入・削除可能 自動および明示的なデータ整列:オブジェクトをラベルのセットに明示的に整列させることができます。また、ユーザがラベルを無視して、計算の際にSeriesやDataFrameなどが自動的にデータを整列させることもできる パワフルで柔軟なグループ化機能により、データセットの分割・適用・結合を行い、データの集計・変換を行うことができる 他のPythonやNumPyのデータ構造に含まれる、不規則で異なるインデックスのデータを、簡単にDataFrameオブジェクトに変換することが可能 大規模データセットのインテリジェントなラベルベースのスライス、ファンシーインデックス、およびサブセット化 直感的なデータセットのマージと結合 データセットの柔軟なリシェイピングとピボット 階層的な軸のラベル付け(1つの目盛りに複数のラベルを付けることが可能 フラットファイル(CSVおよび区切り線)、Excelファイル、データベースからのデータの読み込み、および超高速HDF5フォーマットからのデータの保存/読み込みのためのロバストなIOツール 時系列に特化した機能:日付範囲の生成と頻度の変換、移動ウィンドウ(moving window)の統計、日付のシフト、そしてラギング これらの原則の多くは、他の言語や科学研究環境でよく経験する欠点を解決するために設けられています。データサイエンティストにとって、データを扱う作業は、データの収集とクリーニング、分析とモデル化、そして分析結果をプロットや表形式での表示に適した形に整理するという、複数の段階に分けられます。 pandasは、これらの作業に最適なツールです。 データ構造 次元 Name 説明 1 Series 1次元ラベル付き同型配列 2 DataFrame 一般的な2Dラベル付き、サイズ変更可能な表形式で、潜在的に異質なタイプのカラムを持つ なぜ複数のデータ構造があるのか? pandasのデータ構造については、低次元のデータのための柔軟なコンテナとして考えるのが一番です。例えば、DataFrameはSeriesのコンテナであり、Seriesはスカラーのコンテナでもあります。辞書のような方法で、これらのコンテナにオブジェクトを挿入したり削除したりできるようにしたいと考えています。 また、時系列や断面のデータセットの典型的な向きを考慮した、一般的なAPI関数の賢明なデフォルト動作が必要です。2次元や3次元のデータを格納するためにN次元配列(ndarrays)を使用する場合、関数を書くときにデータセットの向きを考慮することはユーザーに負担をかけます。軸は多かれ少なかれ同等とみなされます(C言語やFortranの連続性がパフォーマンスに影響する場合を除く)。pandasでは、軸はデータにより多くの意味的な意味を持たせることを目的としています。つまり、特定のデータセットに対して、データの向きを変える「正しい」方法があると考えられるからです。つまり、下流の機能でデータ変換をコード化する際の精神的な負担を軽減することを目的としています。 例えば、表形式のデータ(DataFrame)では、軸0と軸1ではなく、インデックス(行)と列を考えた方が意味的にわかりやすいです。 for col in df.columns: series = df[col] # do something with series データの変更可能性とコピー すべてのpandasのデータ構造は値の変更が可能です(含まれる値は変更できます)が、常にサイズの変更が可能であるとは限りません。例えば、Seriesの長さは変更できませんが、DataFrameに列を挿入することは可能です。しかし、ほとんどのメソッドは新しいオブジェクトを生成し、入力データはそのままにしておきます。一般的には、賢明な場合は不変性を優先します。 サポートを受ける pandasの問題やアイデアは、まずGithub Issue Trackerに投稿されます。一般的な質問については、Stack Overflow を通じて pandas コミュニティのエキスパートが回答します。 コミュニティ pandasは、オープンソースのpandasを実現するために貴重な時間とエネルギーを提供してくれる世界中の志を持った人たちのコミュニティによって、今日も積極的にサポートされています。貢献者の皆さんに感謝します。 貢献に興味のある方は、貢献ガイドをご覧ください。 pandasは、NumFOCUSのスポンサープロジェクトです。これにより、世界的なオープンソース・プロジェクトとしてのpandasの開発を成功させることができ、プロジェクトへの寄付も可能になります。 プロジェクトガバナンス 2008年の発足以来、pandasプロジェクトが非公式に使用してきたガバナンス・プロセスは、プロジェクト・ガバナンス・ドキュメントで公式化されています。この文書では、オープンソースの共同開発と、営利・非営利団体からの資金提供を受けた作業との関係を含め、意思決定の方法やコミュニティのさまざまな要素がどのように相互作用するのかを明確にしています。 Wes McKinneyはBenevolent Dictator for Life (BDFL)です。 開発チーム コアチームのメンバーのリストと詳細な情報は、governance repoのpeople's pageに掲載されています。 機関投資家 現在の機関パートナーについての情報は、pandasのウェブサイトのページにあります。 2.pandasはどのようなデータを扱うのでしょうか? 【ユースケース1】 pandasを使うには import pandas as pd pandasパッケージをロードして作業を開始するには、パッケージをインポートします。コミュニティで合意されたpandasのエイリアスはpdで、pandasをpdとして読み込むことは、すべてのpandasドキュメントの標準的な方法とされています。 pandasのテーブル表現 【ユースケース2】 タイタニック号の乗客データを保存したいと考えています。何人かの乗客について、名前(文字)、年齢(整数)、性別(男性/女性)のデータがわかっています。 df = pd.DataFrame( { "Name": [ "Braund, Mr. Owen Harris", "Allen, Mr. William Henry", "Bonnell, Miss. Elizabeth", ], "Age": [22, 35, 58], "Sex": ["male", "male", "female"], } ) df データをテーブルに手動で格納するには、DataFrame を作成します。Pythonのリストの辞書を使用する場合、辞書のキーが列のヘッダーとして使用され、各リストの値がDataFrameの列として使用されます。 DataFrameは2次元のデータ構造で、さまざまな種類のデータ(文字、整数、浮動小数点値、カテゴリーデータなど)を列に格納することができます。スプレッドシートやSQLテーブル、Rのdata.frameに似ています。 テーブルには3つの列があり、それぞれの列には列ラベルが付いています。列ラベルは、それぞれName、Age、Sexです。 Name列は各値が文字列のテキストデータ、Age列は数字、Sex列はテキストデータで構成されています。 表計算ソフトでは、このデータを表にして表現すると、とてもよく似ています。   DataFrameの各列は、「シリーズ」です。 【ユースケース3】 「年齢(age)」列のデータを扱いたい df["Age"] pandas DataFrameの1列を選択すると、結果はpandas Seriesとなります。列を選択するには、角括弧[]で囲まれた列ラベルを使用します 。 [ノート] Pythonの辞書に慣れている方であれば、単一のカラムを選択することは、キーに基づいて辞書の値を選択することと非常によく似ています。 Seriesも同様に最初から作ることができます。 ages = pd.Series([22, 35, 58], name="Age") ages pandas Seriesは、DataFrameの単一列であるため、列ラベルはありません。Seriesには行ラベルがあります。 DataFrameやSeriesを使って何かをする 【ユースケース4】 乗客の最大年齢を知りたい。。。 Age列を選択してmax()を適用することで、DataFrameでこれを行うことができます。 df["Age"].max() または、Seriesに適用して ages.max() max()メソッドのように、pandasは多くの機能を提供しており、それらの機能はそれぞれDataFrameやSeriesに適用できるメソッドとなっています。メソッドは関数であるため、括弧()を使うことを忘れないでください。 【ユースケース5】 データテーブルの数値データの基本的な統計に興味があります。 df.describe() describe()メソッドは、DataFrameの数値データの概要を簡単に表示します。Name および Sex カラムはテキストデータであるため、デフォルトでは describe() メソッドはこれらを考慮しません。 多くのpandas操作は、DataFrameまたはSeriesを返します。describe()メソッドは、pandas Seriesまたはpandas DataFrameを返すpandasオペレーションの一例です。 ※describeのオプションについては、ユーザーガイドのdescribeを使った集約の項を参照してください。 [ノート] これはあくまでも出発点です。表計算ソフトと同様に、pandasはデータを列と行で構成されたテーブルとして表現します。表計算ソフトで行うようなデータ操作や計算も、pandasではサポートされています。次のチュートリアルに進んでください。 2章で覚えたこと パッケージをインポートする、import pandas as pd   データのテーブルは、pandas DataFrameとして格納される   DataFrameの各列はSeries   DataFrameやSeriesにメソッドを適用することで、様々なことができる 3.表形式のデータを読み書きするにはどうすればいいですか? ※この章では、CSV形式で保存されたチュートリアルサイトにあるタイタニックのデータセットを使用します。データは以下のデータ列で構成されています。 PassengerId: すべての乗客のID。 Survived: 0と1の値を持ちます。0は非生存、1は生存です。 Pclass: 3つのクラスがあります。クラス1、クラス2、クラス3です。 Name: 搭乗者の名前です。 Sex: 搭乗者の性別 Age: 搭乗者の年齢 SibSp: 搭乗者に兄弟姉妹や配偶者がいることを示します。 Parch: 搭乗者が一人であるか、家族がいるかを示します。 Ticket:搭乗者のチケット番号です。 Fare: 運賃を表示します。 Cabin: 搭乗者のキャビンです。 Embarked: 乗車したカテゴリーを表します。 【ユースケース6】 CSVファイルで提供されているタイタニック号の乗客データを分析したい # Githubに公開されているデータを直接読み込む url = 'https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/titanic.csv' titanic = pd.read_csv(url) pandasは、csvファイルとして保存されているデータをpandas DataFrameに読み込むためのread_csv()関数を提供しています。pandasは、様々なファイル形式やデータソースをサポートしており、それぞれにread_*という接頭辞がついています。 データを読み込んだ後は、必ずチェックを入れてください。DataFrameを表示する際、デフォルトでは最初と最後の5行が表示されます。 titanic 【ユースケース7】 pandasのDataFrameの最初の8行を見たい titanic.head(8) DataFrameの最初のN行を表示するには、必要な行数(ここでは8行)を引数に指定してhead()メソッドを使用します。 [ノート] 代わりに最後のN行の興味がありますか? pandasにはtail()メソッドもあります。例えば、titanic.tail(10)は、DataFrameの最後の10行を返します。 [補足]引数を省略した場合「 head()、tail() 」は、5行表示されます pandasが各カラムのデータタイプをどのように解釈したかを確認するには、pandas dtypes属性をリクエストします。 titanic.dtypes 各列には、使用されているデータタイプが列挙されています。このDataFrameのデータ型は、整数(int64)、浮動小数点(float64)、文字列(object)です。 [ノート] dtypesを求めるときには、括弧は使用されません! dtypesはDataFrameとSeriesの属性です。DataFrameやSeriesの属性には、括弧は必要ありません。属性はDataFrameやSeriesの特徴を表していますが、最初のチュートリアルで紹介したように、メソッド(括弧が必要)はDataFrameやSeriesに対して何かを行います。 【ユースケース8】 タイタニックのデータをスプレッドシートとして出力する titanic.to_excel("titanic.xlsx", sheet_name="passengers", index=False) [補足] 上記実行時に「openpyxlモジュール」がないエラーが出た場合、セルを追加し下記でモジュールのインストールをしてください。 ※「!pip」はシステムコマンドでpipをipython上で実行します `!pip install openpyxl` read_* 関数がデータをpandasに読み込むために使用されるのに対し、 to_* メソッドはデータを保存するために使用されます。 to_excel() メソッドは、データをエクセルファイルとして保存します。ここでの例では、シート名をデフォルトのSheet1ではなく、passengersとしています。index=Falseを設定すると、行のインデックスラベルはスプレッドシートに保存されません。 同等の読み込み関数であるread_excel()は、データをDataFrameに再読み込みします。 titanic = pd.read_excel("titanic.xlsx", sheet_name="passengers") titanic.head() 【ユースケース9】 DataFrameのテクニカルサマリーに興味がある titanic.info() info()メソッドはDataFrameに関する技術的な情報を提供しますので、出力内容をより詳しく説明しましょう。 これは確かに DataFrame です。 891 個のエントリ、つまり 891 行があります。 各行には、0 から 890 までの値を持つ行ラベル(別名インデックス)があります。 表には12の列があります。ほとんどの列には、各行の値があります(891 個の値はすべて非ヌル)。いくつかの列では、値が欠落しており、非ヌルの値が891未満のものもあります。 Name(名前)、Sex(性別)、Cabin(船室)、Embarked(乗船)の列は、テキストデータ(文字列、別名オブジェクト)で構成されています。その他の列は数値データであり、整数のものと実数(浮動小数点)のものがあります。 各列のデータの種類(文字、整数、...)は、dtypesのリストにまとめられています。 また、DataFrameを格納するために使用されるRAMのおおよその量も提供されます。 3章で覚えたこと 多くの異なるファイル形式やデータソースからpandasにデータを取り込むことは、「 read_* 」関数でサポートされている   pandasからデータをエクスポートするには、さまざまな「 to_* 」メソッドが用意されている   head/tail/infoメソッドとdtypes属性は、最初のチェックに便利 pandasとの間で可能な入出力の概要については、ユーザーガイドのリーダーとライター機能のセクションを参照してください。 4.DataFrameのサブセットを選択するにはどうすればいいですか? DataFrameから特定の列を選択するにはどうすればいいですか? 【ユースケース10】 タイタニックの乗客の年齢に興味がある ages = titanic["Age"] ages.head() 単一のカラムを選択するには、角括弧 [ ] に対象のカラム名を使用します。 DataFrameの各列はSeriesです。単一の列が選択されると、返されるオブジェクトはpandas Seriesになります。これは、出力のタイプを確認することで確認できます。 type(titanic["Age"]) そして、出力の形を見てみてください。 titanic["Age"].shape DataFrame.shapeは、pandas Seriesの属性(読み書きのチュートリアルを思い出してください、属性に括弧を使ってはいけません)であり、行数と列数(nrows, ncolumns)を含むDataFrameです。 pandas Seriesは1次元であり、行数のみが返されます。 【ユースケース11】 タイタニックの乗客の年齢と性別に興味がある。 age_sex = titanic[["Age", "Sex"]] age_sex.head() 複数の列を選択するには、括弧[]の中に選択する列名をリストします。 [ノート] 内側の角括弧は、Pythonのリストを列名で定義しているのに対し、外側の角括弧は、前の例で見たように、pandasのDataFrameからデータを選択するために使用されます。 返されるデータタイプは、pandas DataFrameです。 type(titanic[["Age", "Sex"]]) titanic[["Age", "Sex"]].shape 選択した結果、891の行と2つの列を持つDataFrameが返されました。DataFrameは、行と列の両方の次元を持つ2次元であることを覚えておいてください。 インデックス作成の基本情報については、ユーザーガイドの「インデックス作成とデータ選択」の項を参照してください。 DataFrameから特定の行をフィルタリングするにはどうしたらいいですか? 【ユースケース12】 35歳以上の乗客に興味がある above_35 = titanic[titanic["Age"] > 35] above_35.head() 条件式に基づいて行を選択するには、括弧 [] の中に条件を入れます。 括弧内の条件 titanic["Age"] > 35 は、Age 列の値が 35 より大きい行をチェックします。 titanic["Age"] > 35 条件式の出力(>だけでなく、==, !=, <, <=,...でも動作します)は、実際には、元のDataFrameと同じ行数を持つ、ブーリアン値(TrueまたはFalseのいずれか)のpandasシリーズです。このようなブーリアン値の系列は、括弧 [] の間に置くことで、DataFrameのフィルタリングに使用できます。値が True の行のみが選択されます。 先ほどから、元のタイタニックのDataFrameは891行で構成されていることがわかっています。それでは、結果として得られた DataFrame above_35 のshape属性を確認して、条件を満たす行数を見てみましょう。 above_35.shape 【ユースケース13】 タイタニックの客室クラス2と3の乗客に興味がある class_23 = titanic[titanic["Pclass"].isin([2, 3])] class_23.head() 条件式と同様に、条件関数 isin() は、指定されたリストに値がある各行に対して True を返します。このような関数に基づいて行をフィルタリングするには、括弧 [] の中で条件関数を使用します。この例では、括弧内の条件の titanic["Pclass"].isin([2, 3]) は、Pclass列が2または3のいずれかである行をチェックします。 上記は、クラスが2または3である行でフィルタリングし、2つの文を|(or)演算子で結合することと同じです。 class_23 = titanic[(titanic["Pclass"] == 2) | (titanic["Pclass"] == 3)] class_23.head() [ノート] 複数の条件文を組み合わせる場合は、それぞれの条件を括弧()で囲む必要があります。また、or/andは使用できず、or演算子 '|' とand演算子 '&' が必要です。 ブーリアンインデックスやisin関数については、ユーザーガイドの専用セクションを参照してください。 【ユースケース14】 年齢がわかっている乗客データを扱いたい age_no_na = titanic[titanic["Age"].notna()] age_no_na.head() notna()条件付き関数は、値がNull値でない各行に対してTrueを返します。この関数は、括弧 [] と組み合わせて、データテーブルをフィルタリングすることができます。 最初の5行は同じ値のままなので、実際に何が変わったのか疑問に思うかもしれません。それを確かめる一つの方法は、形状が変わったかどうかを確認することです。 age_no_na.shape 欠損値に関する専用機能については、ユーザーガイドの欠損データの取り扱いの項を参照してください。 DataFrameから特定の行や列を選択するには? 【ユースケース15】 35歳以上の乗客の名前に興味がある adult_names = titanic.loc[titanic["Age"] > 35, "Name"] adult_names.head() この場合、行と列の両方のサブセットを一度に作成するので、括弧[]で条件を指定するだけでは十分ではありません。括弧[]の前に、 loc / iloc演算子 が必要です。loc / iloc を使用する場合、コンマの前の部分が選択したい行、コンマの後の部分が選択したい列になります。 [補足]locメソッドはインデックス名・カラム名での指定、ilocメソッドはインデクス番号・カラム番号での指定になります。 列名、行ラベル、条件式を使用する場合は、括弧 [] の前にloc演算子を使用します。コンマの前後には、単一のラベル、ラベルのリスト、ラベルのスライス、条件式、コロン( : )を使用できます。コロン( : )を使うと、すべての行または列を選択することになります。 【ユースケース16】 10行目から25行目までと、3列目から5列目に興味がある titanic.iloc[9:25, 2:5] 繰り返しになりますが、行と列の両方のサブセットが一度に作成されるため、括弧 [] を使用するだけでは不十分です。テーブル内の位置に基づいて特定の行や列に特別な関心を持つ場合は、括弧 [] の前にiloc演算子を使用します。 loc または iloc で特定の行や列を選択すると、選択したデータに新しい値を割り当てることができます。例えば、3列目の最初の3つの要素にanonymousという名前を割り当てることができます。 titanic.iloc[0:3, 3] = "anonymous" titanic.head() loc と iloc の使い方については、ユーザーガイドの「インデックス作成のためのさまざまな選択肢」を参照してください。 4章で覚えたこと データのサブセットを選択する場合は、角括弧 [] を使用する   角括弧の中には、単一の列/行ラベル、列/行ラベルのリスト、ラベルのスライス、条件式、コロンを使用できる   行名や列名を使った loc による特定の行や列の選択   テーブル内の位置を利用した iloc による特定の行や列の選択   loc / iloc に基づいて、選択範囲に新しい値を割り当てることができる インデックス作成の概要については、ユーザーガイドの「インデックス作成とデータ選択」のページをご覧ください。 5.pandasでプロットを作成するには? ※この章では、py-openaqパッケージを使用してopenaqから提供されているNO2に関する大気質データを使用します。air_quality_no2.csvデータセットは、パリ、アントワープ、ロンドンにあるFR04014、BETR801、London Westminsterの各測定ステーションのNO2値を提供します。。 url = "https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/air_quality_no2.csv" air_quality = pd.read_csv(url, index_col=0, parse_dates=True) air_quality.head() [ノート] read_csv 関数の index_col および parse_dates パラメータを使用して、最初の(0 番目の)列を結果の DataFrame のインデックスとして定義し、列内の日付をそれぞれ Timestamp オブジェクトに変換します。 【ユースケース17】 データを素早く視覚的に確認したい air_quality.plot() DataFrameの場合、pandasはデフォルトで、数値データを持つ各列に1つのラインプロットを作成します。 【ユースケース18】 データテーブルのパリのデータ列だけでプロットしたい。 air_quality["station_paris"].plot() 特定の列をプロットするには、サブセット・データ・チュートリアルでの選択メソッドとplot()メソッドを組み合わせて使用します。したがって、plot()メソッドはSeriesとDataFrameの両方で動作します。 【ユースケース19】 ロンドンとパリで測定されたN02の値を視覚的に比較してみたい air_quality.plot.scatter(x="station_london", y="station_paris", alpha=0.5) plot 関数を使用したデフォルトの折れ線グラフ以外にも、データをプロットするための様々な方法が用意されています。ここでは、標準的なPythonを使って、利用可能なプロット方法の概要を説明します。 [ method_name for method_name in dir(air_quality.plot) if not method_name.startswith("_") ] [ノート] PythonやJupyter Notebookだけでなく、多くの開発環境では、TABボタンを使って利用可能なメソッドの概要を知ることができます。 例えば、 air_quality.plot. + TABキー です。 オプションの1つにDataFrame.plot.box()がありますが、これは「箱ひげ図」を意味しています。boxメソッドは、大気質の例のデータに適用できます。 air_quality.plot.box() デフォルトのラインプロット以外のプロットについては、ユーザーガイドの「サポートされているプロットスタイル」のセクションを参照してください。 【ユースケース20】 それぞれの列を別のサブプロットにしたい。 axs = air_quality.plot.area(figsize=(12, 4), subplots=True) プロット関数のsubplots引数では、データ列ごとの個別のサブプロットがサポートされています。pandasの各プロット関数に用意されている組み込みオプションを確認しておきましょう。 さらにいくつかのフォーマットオプションについては、ユーザーガイドの「プロットのフォーマット」の項で説明しています。 【ユースケース21】 できあがったプロットをさらにカスタマイズしたり、拡張したり、保存したりしたい。 import matplotlib.pyplot as plt # matplotlibをインポート fig, axs = plt.subplots(figsize=(12, 4)) air_quality.plot.area(ax=axs) # matplot上のオブジェクト上に描画 axs.set_ylabel("NO$_2$ concentration") # y軸のラベルを設定、その他さまざまなmatplotlibの機能が使えます fig.savefig("no2_concentrations.png") # matplotlibの機能を使って画像の保存、DIR直下にpngファイルが生成されます pandasによって作成されるプロットオブジェクトのそれぞれは、matplotlibオブジェクトです。Matplotlibはプロットをカスタマイズするための多くのオプションを提供しているので、pandasとMatplotlibの間のリンクを明示することで、matplotlibのすべての機能を作成するプロットに反映させることができます。この戦略はこの例で適用しています。 fig, axs = plt.subplots(figsize=(12, 4)) # Create an empty matplotlib Figure and Axes air_quality.plot.area(ax=axs) # Use pandas to put the area plot on the prepared Figure/Axes axs.set_ylabel("NO$_2$ concentration") # Do any matplotlib customization you like fig.savefig("no2_concentrations.png") # Save the Figure/Axes using the existing matplotlib method. 5章で覚えたこと .plot.* メソッドはSeriesとDataFramesの両方に適用できる   デフォルトでは,列のそれぞれが異なる要素(line, boxplot,...)としてプロットされる   pandasで作成されたプロットは,すべてMatplotlibオブジェクトである pandasでのプロッティングの概要は、visualizationのページに記載されています。 6.既存のカラムから派生して新しいカラムを作成するには? 【ユースケース22】 ロンドンの駅の NO2 濃度を mg/m3 で表現したい (温度を 25℃、圧力を1013 hPaとすると、換算係数は1.882) air_quality["london_mg_per_cubic"] = air_quality["station_london"] * 1.882 air_quality.head() 新しいカラムを作成するには、左辺の括弧 [] 内に新しいカラム名を指定します。 [ノート] 値の計算はelement_wiseで行われます。つまり、指定された列のすべての値に、一度に1.882という値が掛けられます。ループを使って各行を反復する必要はありません。 【ユースケース23】 パリとアントワープの値の比率を調べて、その結果を新しい列に保存したい。 air_quality["ratio_paris_antwerp"] = ( air_quality["station_paris"] / air_quality["station_antwerp"] ) air_quality.head() 計算は同様に要素ごとに行われるので、各行の値に対して / が適用されます。 また、他の数学演算子(+、-、*、/)や論理演算子(<、>、=、...)も要素ごとに動作します。後者は、条件式を使用してテーブルの行をフィルタリングするために、サブセットデータのチュートリアルですでに使用しています。 より高度なロジックが必要な場合は、apply()メソッドで任意のPythonコードを使用することができます。 【ユースケース24】 データカラムの名前を、openAQで使用される対応するステーション識別子に変更したい air_quality_renamed = air_quality.rename( columns={ "station_antwerp": "BETR801", "station_paris": "FR04014", "station_london": "London Westminster", } ) air_quality_renamed.head() rename()関数は、行ラベルと列ラベルの両方に使用することができます。現在の名前をキーに、新しい名前を値に持つ辞書を用意して、対応する名前を更新します。 マッピングは固定名のみに限定されるべきではなく、マッピング関数を使うことも可能です。例えば、カラム名を小文字に変換することも関数を使って行うことができます。 air_quality_renamed = air_quality_renamed.rename(columns=str.lower) air_quality_renamed.head() 列や行のラベルの名前の変更については、ユーザーガイドの「ラベルの名前の変更」の項を参照してください。 6章で覚えたこと 出力をDataFrameに割り当て、[] の間に新しいカラム名を入れて、新しいカラムを作成する 操作は要素ごとに行われるので、行をループする必要はない 行ラベルや列の名前を変更するには、辞書や関数と一緒に rename 関数を使用する ユーザーガイドには、カラムの追加と削除に関する別のセクションがあります。 7.要約統計量を計算するには? 統計情報の集計 【ユースケース25】 タイタニックの乗客の平均年齢は何歳? titanic["Age"].mean() さまざまな統計情報が用意されており数値データのある列に適用することができます。一般的な操作では欠損データは除外され、デフォルトでは行単位で操作されます。 【ユースケース26】 タイタニックの乗客の年齢とチケット料金の中央値を知りたい titanic[["Age", "Fare"]].median() 集計統計量は、複数の列に対して同時に計算することができます。最初のチュートリアルで説明したdescribe関数を覚えていますか? titanic[["Age", "Fare"]].describe() 事前に定義された統計値の代わりに、DataFrame.agg()メソッドを使用して、指定された列の統計値を集約する特定の組み合わせを定義することができます。 titanic.agg( { "Age": ["min", "max", "median", "skew"], "Fare": ["min", "max", "median", "mean"], } ) 記述統計量の詳細は、ユーザーガイドの記述統計量の項に記載されています。 カテゴリー別に分類された統計情報を集約 【ユースケース27】 タイタニックの乗客の男性と女性の平均年齢は? titanic[["Sex", "Age"]].groupby("Sex").mean() 今回の目的は各性別の平均年齢なので、まずこの2つの列をサブセレクションします:titanic[["Sex", "Age"]]。次に、Groupby()メソッドをSexカラムに適用して、カテゴリーごとのグループを作ります。そして、各性別の平均年齢を計算して返します。 与えられた統計量(平均年齢など)を、列の各カテゴリー(Sex列の男性/女性など)について計算することは、よくあるパターンです。groupbyメソッドはこの種の操作をサポートするために使用されます。より一般的には、split-apply-combineパターンに当てはまります。 データをグループに分ける 各グループに独立して関数を適用する 結果を一つのデータ構造にまとめる 適用と結合のステップは、pandasでは通常一緒に行われます。 先ほどの例では、まず2列を明示的に選択しました。そうでない場合は、数値列を含む各列に meanメソッド が適用されます。 titanic.groupby("Sex").mean() Pclassの平均値を求めてもあまり意味がありません。各性別の平均年齢にのみ興味がある場合、列の選択(通常通り角括弧 [] )はグループ化されたデータでもサポートされます。 titanic.groupby("Sex")["Age"].mean() [ノート] Pclass列には数値データが含まれていますが、実際には「1」、「2」、「3」というラベルを持つ3つのカテゴリー(またはファクター)を表しています。これらの統計値を計算してもあまり意味がありません。そこでpandasでは、このようなデータを扱うためにCategoricalデータ型を用意しています。詳細は、ユーザーガイドの[カテゴリーデータ](https://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html#categorical) の項を参照してください。 【ユースケース28】 性別とキャビンクラスの組み合わせごとに、航空券の平均運賃価格は? titanic.groupby(["Sex", "Pclass"])["Fare"].mean() グループ化は、複数の列で同時に行うことができます。groupby()メソッドにカラム名をリストとして与えます。 split-apply-combineアプローチの詳細については、ユーザーガイドのgroupby operationsの項を参照してください。 カテゴリ別のレコード数をカウントする 【ユースケース29】 それぞれのキャビンクラスの乗客数は? titanic["Pclass"].value_counts() value_counts()メソッドは、カラム内の各カテゴリーのレコード数をカウントします。 この関数は、実際には groupby 演算と、各グループ内のレコード数のカウントを組み合わせたものであるため、ショートカットになっています。 titanic.groupby("Pclass")["Pclass"].count() [ノート] sizeとcountはどちらもgroupbyと組み合わせて使うことができます。sizeはNaN値を含み、単に行数(テーブルのサイズ)を提供するのに対し、countは欠損値を除外します。value_countsメソッドでは、dropna引数を使用して、NaN値を含むかどうかを指定します。 ユーザーガイドにはvalue_countsに関する専用のセクションがあり、離散化に関するページを参照してください。 7章で覚えたこと 列や行全体を対象とした集約統計が可能   groupby は、分割・適用・結合のパターンを提供する   value_countsは、変数の各カテゴリのエントリ数をカウントする便利なショートカット split-apply-combineアプローチの詳細については、ユーザーガイドの groupby操作 のページを参照してください。 8.テーブルのレイアウトを変更するには? テーブルの行を並べ替える 【ユースケース30】 タイタニック号のデータを乗客の年齢に応じてソートしたい。 titanic.sort_values(by="Age").head() ```>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413278/35c32450-7861-b80f-b494-b28e4be61185.png) 【ユースケース31】 タイタニック号のデータを、キャビンクラスと年齢に応じて降順に並べ替えたい。 titanic.sort_values(by=['Pclass', 'Age'], ascending=False).head() Series.sort_values()を使用すると、テーブルの行は定義されたカラムに基づいてソートされます。インデックスは、行の順序に従います。 テーブルのソートについての詳細は、使用ガイドの「データのソート」の項を参照してください。 ロング形式からワイド形式のテーブルフォーマット ここでは、大気質データセットの小さなサブセットを使用してみましょう。ここでは、NO2データに焦点を当て、各場所の最初の2つの測定値(つまり、各グループの先頭)のみを使用します。このデータのサブセットはno2_subsetと呼びます。 # データ(air_quality_long.csv)のロード  url = "https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/air_quality_long.csv" air_quality = pd.read_csv(url, index_col="date.utc", parse_dates=True) air_quality.head() # filter for no2 data only no2 = air_quality[air_quality["parameter"] == "no2"] # use 2 measurements (head) for each location (groupby) no2_subset = no2.sort_index().groupby(["location"]).head(2) no2_subset 【ユースケース32】 3つの観測所の値を隣り合った別の列にしたい no2_subset.pivot(columns="location", values="value") pivot()関数は、純粋にデータを再形成するもので、インデックスと列の組み合わせごとに1つの値が必要となります。 pandasは複数列のプロッティングをサポートしているので(プロッティングのチュートリアルを参照)、long table形式からwide table形式に変換することで、異なる時系列を同時にプロッティングすることができます。 no2.head() no2.pivot(columns="location", values="value").plot() [ノート] indexパラメータが定義されていない場合は、既存のインデックス(行ラベル)が使用されます。 pivot() の詳細については、ユーザーガイドの「DataFrameオブジェクトのピボット」の項を参照してください。 Pivotテーブル 【ユースケース33】 各観測点におけるNO2 と PM2.5 の平均濃度を表形式で求めたい。 air_quality.pivot_table( values="value", index="location", columns="parameter", aggfunc="mean" ) pivot()の場合、データは並べ替えられるだけです。複数の値を集約する必要がある場合(この例では、異なるタイムステップの値)、pivot_table()を使用して、これらの値を結合する方法に関する集約関数(例:mean)を提供しています。 ピボットテーブルは、表計算ソフトではよく知られた概念です。各変数のサマリー欄も個別に表示させたい場合は、marginパラメータをTrueにします。 air_quality.pivot_table( values="value", index="location", columns="parameter", aggfunc="mean", margins=True, ) pivot_table() の詳細については、ユーザーガイドのピボット・テーブルの項を参照してください。 [ノート] 因みに、pivot_table()は確かにgroupby()に直結しています。パラメータとロケーションの両方でグループ化しても、同じ結果が得られます。 `air_quality.groupby(["parameter", "location"]).mean()` groupby() とunstack() の組み合わせについては、ユーザーガイドの「statsとgroupbyの組み合わせ」のセクションをご覧ください。 ワイド形式からロング形式フォーマット 前節で作成したワイドフォーマットの表から再開します。 no2_pivoted = no2.pivot(columns="location", values="value").reset_index() no2_pivoted.head() 【ユースケース34】 すべての大気中のNO2測定値を1つのカラムに集めたい(ロングフォーマット) no_2 = no2_pivoted.melt(id_vars="date.utc") no_2.head() DataFrameのpandas.melt()メソッドは、データテーブルをワイドフォーマットからロングフォーマットに変換します。カラムのヘッダは、新しく作成されたカラムの変数名になります。 解決策は、pandas.melt()を適用する方法の短いバージョンです。このメソッドは、id_varsに記載されていないすべての列を2つの列にまとめます。カラムヘッダの名前を持つカラムと、値そのものを持つカラムです。後者の列はデフォルトで名前の値を取得します。 pandas.melt()メソッドは、より詳細に定義することができます。 no_2 = no2_pivoted.melt( id_vars="date.utc", value_vars=["BETR801", "FR04014", "London Westminster"], value_name="NO_2", var_name="id_location", ) no_2.head() 結果は同じですが、より詳細に定義されています。 value_vars は、どのカラムを一緒に溶かすかを明示的に定義します。 value_name は、デフォルトのカラム名 value の代わりに、values カラムのカスタムカラム名を提供します。 var_name は、カラムヘッダ名を収集するカラムのカスタムカラム名を提供します。それ以外の場合は、インデックス名またはデフォルトの変数を取ります。 したがって、引数value_nameとvar_nameは、生成された2つのカラムに対する単なるユーザー定義名です。溶かすカラムはid_varsとvalue_varsで定義されます。 pandas.melt() によるワイドフォーマットからロングフォーマットへの変換については、ユーザーガイドの「meltによるリシェイピング」のセクションで説明されています。 8章で覚えたこと 1つまたは複数の列でのソートはsort_valuesでサポート   ピボット関数は純粋にデータを再構築するものですが、pivot_tableは集約をサポート   pivotの逆(ロング形式からワイド形式)はmelt(ワイド形式からロング形式) 概要については、ユーザーガイドのリシェイピングとピボットのページを参照してください。 9.複数のテーブルのデータを結合するには? この章では、NO2 に関する大気質データを使用します。 air_quality_no2_long.csvデータセットは、パリ、アントワープ、ロンドンにあるFR04014、BETR801、London Westminsterの各測定ステーションのNO2値を提供します。 また、2.5マイクロメートル以下の粒子状物質に関する大気質データを使用しています。 air_quality_pm25_long.csvデータセットは、FR04014、BETR801、London Westminsterの各測定ステーション(Paris、Antwerp、London)のPM25 値を提供します。 url = "https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/air_quality_no2_long.csv" air_quality_no2 = pd.read_csv(url, parse_dates=True) air_quality_no2 = air_quality_no2[["date.utc", "location", "parameter", "value"]] air_quality_no2.head() url = "https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/air_quality_pm25_long.csv" air_quality_pm25 = pd.read_csv(url, parse_dates=True) air_quality_pm25 = air_quality_pm25[["date.utc", "location", "parameter", "value"]] air_quality_pm25.head() 複数のテーブルのデータを結合するには? オブジェクトの連結 【ユースケース35】 似たような構造を持つ2つのテーブル、NO2 と PM25 の測定値を1つのテーブルにまとめたい air_quality = pd.concat([air_quality_pm25, air_quality_no2], axis=0) air_quality.head() concat()関数は、複数のテーブルをいずれかの軸(行単位または列単位)に沿って連結する操作を行います。 デフォルトでは、連結は軸0に沿って行われるので、結果のテーブルは入力テーブルの行を組み合わせたものになります。元のテーブルと連結されたテーブルの形状を確認して、動作を確認してみましょう。 print('Shape of the ``air_quality_pm25`` table: ', air_quality_pm25.shape) print('Shape of the ``air_quality_no2`` table: ', air_quality_no2.shape) print('Shape of the resulting ``air_quality`` table: ', air_quality.shape) したがって、結果として得られるテーブルは、3178行(= 1110 + 2068)となります。 [ノート] axis 引数は、軸に沿って適用可能な多くの pandas メソッドを返します。DataFrameには、2つの対応する軸があります。1つ目は、行を垂直に下向きに走る軸(軸0)で、2つ目は、列を水平に走る軸(軸1)です。連結や要約統計などのほとんどの操作は、デフォルトでは行(軸0)に沿って行われますが、列に沿っても適用できます。 datetime情報でテーブルをソートすると、両テーブルの組み合わせも図示され、パラメータカラムでテーブルの起源が定義されます(テーブルair_quality_no2からのno2またはテーブルair_quality_pm25からのpm25のいずれか)。 air_quality = air_quality.sort_values("date.utc") air_quality.head() この例では、dataが提供するパラメータ列により、元のそれぞれのテーブルが確実に特定されます。しかし、必ずしも特定されるとは限りません。concat関数は、keys引数を使って、追加の(階層的な)行インデックスを追加する便利なソリューションを提供しています。 例えば、 air_quality_ = pd.concat([air_quality_pm25, air_quality_no2], keys=["PM25", "NO2"]) air_quality_.head() [ノート] これらのチュートリアルでは、複数の行/列のインデックスが同時に存在することについては言及されていません。階層的インデックスやマルチインデックスは、高次元のデータを分析するための高度で強力なpandasの機能です。 マルチインデックスについては、今回のpandasの紹介では触れません。とりあえず、関数reset_indexを使って、インデックスの任意のレベルを列に変換できることを覚えておいてください(例:air_quality.reset_index(level=0) ) ユーザーガイドの[「高度なインデックス作成」](https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html#advanced)では、マルチインデックスの世界をご紹介していますので、ぜひご覧ください。 テーブルの連結(行および列単位)に関するオプションや、他の軸上のインデックスの論理(結合または交差)を定義するために concat を使用する方法については、「オブジェクトの連結」のセクションで説明しています。 共通の識別子でテーブルを結合 【ユースケース36】 ステーションのメタデータ・テーブルで提供されるステーションの座標を、測定値テーブルの対応する行に追加します。 ※大気質測定ステーションの座標は、py-openaqパッケージでダウンロードしたデータファイルair_quality_stations.csvに格納されています。 stations_coord = pd.read_csv("https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/air_quality_stations.csv") stations_coord.head() [ノート] この例で使用したステーション(FR04014、BETR801、London Westminster)は、メタデータテーブルに列挙された3つのエントリーに過ぎません。この3つの座標を測定値テーブルに追加し、それぞれをair_qualityテーブルの対応する行に追加するだけです。 air_quality.head() air_quality = pd.merge(air_quality, stations_coord, how="left", on="location") air_quality.head() merge()関数を使用して、air_qualityテーブルの各行に、対応する座標をair_quality_stations_coordテーブルから追加します。両テーブルには共通してlocationという列があり、これが情報を結合するためのキーとして使用されます。左結合を選択すると、air_quality(左)テーブルで利用可能なロケーション、すなわちFR04014、BETR801、London Westminsterのみが結果テーブルに入ります。マージ機能は、データベーススタイルの操作と同様に、複数の結合オプションをサポートしています。 【ユースケース37】 パラメータメタデータテーブルで提供されるパラメータの完全な説明と名前を、測定値テーブルに追加する 大気質パラメータのメタデータは、py-openaqパッケージでダウンロードしたデータファイルair_quality_parameters.csvに格納されています。 air_quality_parameters = pd.read_csv("https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/air_quality_parameters.csv") air_quality_parameters.head() air_quality = pd.merge(air_quality, air_quality_parameters, how='left', left_on='parameter', right_on='id') air_quality.head() 先ほどの例と比べると、共通のカラム名がありません。しかし、air_qualityテーブルのparameterカラムとair_quality_parameters_nameのidカラムは、どちらも測定された変数を共通のフォーマットで提供しています。ここでは、2つのテーブル間のリンクを作るために、(単なるonではなく)left_onとright_onの引数が使われています。 pandasは、内側結合、外側結合、右結合もサポートしています。テーブルの結合/マージについての詳細は、ユーザーガイドの「データベーススタイルのテーブルのマージ」のセクションに記載されています。また、SQLとの比較のページも参照してください。 9章で覚えたこと 複数のテーブルを列単位、行単位で連結するには、concat関数を使用する   データベースのようにテーブルを結合するには、merge関数を使用する データテーブルを組み合わせるためのさまざまな機能の詳細については、ユーザーガイドを参照してください。 10.時系列データを簡単に扱うには? ※この章では、NO2と2.5マイクロメートル以下の粒子状物質に関する大気質データを使用しています。air_quality_no2_long.csv "データセットは、パリ、アントワープ、ロンドンにあるFR04014、BETR801、London Westminsterの各測定ステーションのNO2値を提供します。 #データ(air_quality_long.csv)の再ロード  url = "https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/air_quality_no2_long.csv" air_quality = pd.read_csv(url) air_quality = air_quality.rename(columns={"date.utc": "datetime"}) air_quality.head() pandasのdatetimeプロパティの使用 【ユースケース38】 datetime列の日付を、プレーンテキストではなくdatetimeオブジェクトとして扱いたい air_quality["datetime"] = pd.to_datetime(air_quality["datetime"]) air_quality["datetime"] 初期状態では、datetimeの値は文字列であり、datetimeの操作(年の抽出、曜日の抽出...など)はできません。to_datetime関数を適用することで、pandasは文字列を解釈し、これらをdatetime(すなわちdatetime64[ns, UTC])オブジェクトに変換します。pandasでは、これらのdatetimeオブジェクトを、標準ライブラリのdatetime.datetimeと同様に、pandas.Timestamp と呼んでいます。 [ノート] 多くのデータセットでは、カラムの1つに日付情報が含まれています。pandas.read_csv()やpandas.read_json()などのpandas入力関数では、parse_datesパラメータにTimestampとして読み込むカラムのリストを指定することで、データを読み込む際に日付への変換を行うことができます。 `pd.read_csv("../data/air_quality_no2_long.csv", parse_dates=["datetime"])` これらの pandas.Timestampオブジェクトはなぜ有用なのでしょうか?いくつかの例を挙げて、その付加価値を説明しましょう。 扱っている時系列データセットの開始日と終了日を教えてください? air_quality["datetime"].min(), air_quality["datetime"].max() datetimesに pandas.Timestampを使用すると、日付情報を使って計算し、それらを比較することができます。そのため、これを使って時系列の長さを求めることができます。 air_quality["datetime"].max() - air_quality["datetime"].min() 結果は pandas.Timedeltaオブジェクトで、Python標準ライブラリのdatetime.timedeltaに似ており、時間の継続時間を定義します。 pandasでサポートされている様々な時間の概念は、ユーザーガイドの「時間に関する概念」のセクションで説明されています。 【ユースケース39】 計測した月のみを含む新しい列をDataFrameに追加したい。 air_quality["month"] = air_quality["datetime"].dt.month air_quality.head() 日付にTimestampオブジェクトを使用することで、多くの時間関連のプロパティがpandasで提供されます。例えば、月はもちろん、年、今年の週、四半期などです。これらのプロパティはすべて dt アクセサでアクセスできます。 既存の日付プロパティの概要は、「時刻と日付のコンポーネントの概要」の表に記載されています。datetime のようなプロパティを返す dt アクセサの詳細については、dt アクセサの項で説明します。 【ユースケース40】 各測定場所の各曜日の平均NO2濃度は? air_quality.groupby( [air_quality["datetime"].dt.weekday, "location"])["value"].mean() 統計計算のチュートリアルで紹介したgroupbyによるsplit-apply-combineパターンを覚えていますか?ここでは、平日ごと、測定場所ごとに、与えられた統計量(例:NO2の平均値)を計算したいとします。平日にグループ化するために、pandas Timestampのdatetimeプロパティweekday(月曜=0、日曜=6)を使用していますが、これはdtアクセサでもアクセス可能です。場所と平日の両方でグループ化することで、これらの組み合わせごとに平均値の計算を分割することができます。 これらの例では非常に短い時系列を扱っているため、この分析は長期的な代表結果を提供するものではありません。 【ユースケース41】 すべての観測所の時系列をまとめて、日中の典型的なNO2のパターンをプロットする。言い換えれば、1日の各時間の平均値はどのくらいかを見たい fig, axs = plt.subplots(figsize=(12, 4)) air_quality.groupby(air_quality["datetime"].dt.hour)["value"].mean().plot( kind='bar', rot=0, ax=axs ) plt.xlabel("Hour of the day") # custom x label using matplotlib plt.ylabel("$NO_2 (µg/m^3)$") 先ほどのケースと同様に、1日の各時間ごとに与えられた統計値(例:NO2の平均値)を計算したいので、再びsplit-apply-combineアプローチを使用することができます。ここでは、pandas Timestamp の datetime プロパティ hour を使用していますが、dt accessorでもアクセスできます。 インデックスとしてのDatetime リシェイピングのチュートリアルでは、pivot()を使って、各測定場所を個別の列としてデータテーブルをリシェイピングしました。 no_2 = air_quality.pivot(index="datetime", columns="location", values="value") no_2.head() [ノート] データをピボットすることで、datetimeの情報がテーブルのインデックスになりました。一般に、列をインデックスとして設定するには、set_index関数を使用します。 datetimeインデックス(DatetimeIndex)を使用すると、強力な機能性が得られます。例えば、時系列のプロパティを取得するための dtアクセサ は必要ありませんが、これらのプロパティはインデックスで直接利用できます。 no_2.index.year, no_2.index.weekday 他の利点としては、時間帯のサブセットが便利であることや、プロット上で時間スケールが適応されることが挙げられます。これを私たちのデータに適用してみましょう。 【ユースケース42】 5月20日から5月21日までの各観測所のNO2値をプロットする no_2["2019-05-20":"2019-05-21"].plot() datetimeに解析される文字列を提供することで、データの特定のサブセットをDatetimeIndexで選択することができます。 DatetimeIndex と文字列を使用したスライスについての詳細は、時系列インデックスのセクションを参照してください。 時系列を別の周波数に再サンプルする 【ユースケース43】 現在の1時間ごとの時系列値を、各観測所の月間最大値に集約する monthly_max = no_2.resample("M").max() monthly_max 日時インデックスを持つ時系列データで非常に強力な方法は、時系列を別の頻度に resample() する機能です(例えば、2秒ごとのデータを5分ごとのデータに変換するなど)。 resample()メソッドはgroupby演算に似ています。 対象となる周波数を定義する文字列(例:M, 5H,...)を使用して、時間ベースのグルーピングを行います。 mean, max,...などの集計関数が必要です。 時系列頻度の定義に使用されるエイリアスの概要は、「オフセット・エイリアスの概要」の表に記載されています。 定義されている場合、時系列の頻度は freq 属性で提供されます。 monthly_max.index.freq 【ユースケース44】 各観測所のNO2の日平均値をプロットする。 no_2.resample("D").mean().plot(style="-o", figsize=(10, 5)) 時系列リサンプリングの詳細については、ユーザーガイドの「リサンプリング」の項を参照してください。 10章で覚えたこと 有効な日付文字列は、to_datetime関数を使ってdatetimeオブジェクトに変換したり、読み取り関数の一部として使用することができる   pandasのDatetimeオブジェクトは、dtアクセサを使用した計算、論理演算、便利な日付関連のプロパティをサポートしている   DatetimeIndexは、これらの日付関連のプロパティを含み、便利なスライシングをサポートしている   Resampleは、時系列の頻度を変更する強力な方法 時系列の概要については、時系列と日付機能のページで説明しています。 11.テキストデータを操作するには? ※このチュートリアルでは、CSV形式で保存されたタイタニックのデータセットを使用します。 url = 'https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/titanic.csv' titanic = pd.read_csv(url) 【ユースケース45】 名前の文字をすべて小文字にする。 titanic["Name"].str.lower() Name列の各文字列を小文字にするには、「Name」列を選択し(データの選択に関するチュートリアルを参照)、strアクセサを追加して、lowerメソッドを適用します。このように、各文字列は要素ごとに変換されます。 時系列チュートリアルのdatetimeオブジェクトがdtアクセサを持つのと同様に、strアクセサを使うと、いくつかの特殊な文字列メソッドが利用できます。これらのメソッドは,一般的には,単一要素に対応する組み込みの文字列メソッドと同じ名前ですが,列の各値に対して要素ごとに適用されます(要素ごとの計算を思い出してください)。 【ユースケース46】 カンマの前の部分を抽出して、乗客の姓を含む新しい列「Surname」を作成する titanic["Name"].str.split(",") Series.str.split()メソッドを使うと、それぞれの値が2つの要素からなるリストとして返されます。最初の要素はコンマの前の部分で、2番目の要素はコンマの後の部分です。 titanic["Surname"] = titanic["Name"].str.split(",").str.get(0) titanic["Surname"] 苗字を表す最初の部分(要素0)にのみ興味があるので、ここでもstrアクセサを使用し、Series.str.get()を適用して該当部分を抽出します。このように、文字列関数を連結することで、複数の関数を一度に組み合わせることができます。 文字列の一部を抽出する方法については、ユーザーガイドの「文字列の分割と置換」の項で詳しく説明しています。 【ユースケース47】 タイタニック号に乗船していた伯爵夫人の乗客データを抽出する titanic["Name"].str.contains("Countess") titanic[titanic["Name"].str.contains("Countess")] (彼女の話に興味がある方は、Wikipediaをご覧ください!) 文字列メソッド Series.str.contains() は、列 Name の各値について、文字列に Countess という単語が含まれているかどうかを確認し、各値について True (Countess は名前の一部) または False (Countess は名前の一部ではない) を返します。この出力は、データのサブセットのチュートリアルで紹介した条件付き(ブーリアン)インデックスを使用して、データをサブセレクトするのに使用できます。タイタニック号にはCountessが1人しかいないので、結果として1つの行が得られます。 [ノート] Series.str.contains()メソッドやSeries.str.extract()メソッドが正規表現を受け付けるので、より強力な文字列の抽出が可能ですが、このチュートリアルでは対象外としています。 文字列の一部を抽出する方法については、ユーザーガイドの「文字列のマッチングと抽出」の項を参照してください。 【ユースケース48】 タイタニックの乗客の中で、最も長い名前を持つ人を抽出する titanic["Name"].str.len() 最長の名前を取得するためには、まずName列の各名前の長さを取得する必要があります。pandasの文字列メソッドを使って、Series.str.len()関数を各名前にごと(要素ごと)に適用されます。 titanic["Name"].str.len().idxmax() 次に、名前の長さが最大となるテーブルの対応する位置、できればインデックスラベルを取得する必要があります。idxmax()メソッドはまさにそれを行います。これは文字列メソッドではなく、整数に適用されるため、strは使用されません。 titanic.loc[titanic["Name"].str.len().idxmax(), "Name"] 行のインデックス名(307)と列のインデックス名(Name)をもとに、サブセットのチュートリアルで紹介したloc演算子を使って選択を行います。 【ユースケース49】 「性別(sex)」の列で、「男性(male)」の値を「M」に、「女性(female)」の値を「F」に置き換えます。 titanic["Sex_short"] = titanic["Sex"].replace({"male": "M", "female": "F"}) titanic["Sex_short"] replace()は文字列メソッドではありませんが、マッピングやボキャブラリを使って特定の値を変換する便利な方法を提供します。このメソッドには、マッピング {from : to} を定義するための辞書が必要です。 特定の文字を置き換えるreplace()メソッドも用意されています。しかし、複数の値のマッピングを持つ場合、これは次のようになります。 `titanic["Sex_short"] = titanic["Sex"].str.replace("female", "F")` `titanic["Sex_short"] = titanic["Sex_short"].str.replace("male", "M")` これでは面倒だし、間違いも起こりやすいです。もし、この2つの文を逆の順序で適用したらどうなるか考えてみてください(あるいは自分で試してみてください)。 11章で覚えたこと 文字列メソッドは、strアクセサを使って利用できる   文字列メソッドは、要素単位で動作し、条件付きインデックスに使用することができる   replaceメソッドは、指定された辞書に従って値を変換する便利なメソッド テキストデータの扱い方については、ユーザーガイドのページに詳しい説明があります。 おわりに NPO法人AI開発推進協会では、本チュートリアルをiPythonやGoogleコラボ上で動かしながら学習するオンランセミナーや、さまざまなAIモデルをボタンを押すだけでAIを体験できるGUIツールを用いたセミナーも定期的に開催しています。 興味がありましたら当法人のホームページをご覧ください。 @2021 NPO法人AI開発推進協会
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

好きな画像を実際に物体検知させてみよう!

PyTorch Hubにおいて、学習済みの最先端モデルが簡単にダウンロードできます。 これを初学者でも簡単に使えるようにコードを書きました。GitHubにも公開しています。 コード内の画像URL部分を好きに変えて実行してください! (URLを貼り付けること。URLの最後が.png等の拡張子で終わっていること。) 開発環境 Python : 3.9.0 PyTorch : 1.7.1 画像分類 画像に写っているものは何なのかを分類してくれる技術です。 ResNeXtのモデルを使用します。 import os from PIL import Image import urllib.request import matplotlib.pyplot as plt import torch from torchvision import transforms from utils.imagenet_labels import idx2label # モデル読み込み model = torch.hub.load('facebookresearch/WSL-Images', 'resnext101_32x8d_wsl') model.eval() # 画像 url = "https://github.com/pytorch/hub/raw/master/images/dog.jpg" # <- 自由に変更してね save_dir = "../result/resnext" os.makedirs(save_dir, exist_ok=True) filename = f"{save_dir}/{os.path.basename(url)}" try: urllib.URLopener().retrieve(url, filename) except: urllib.request.urlretrieve(url, filename) input_image = Image.open(filename) #前処理 preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) input_tensor = preprocess(input_image) input_batch = input_tensor.unsqueeze(0) #推論 if torch.cuda.is_available(): input_batch = input_batch.to('cuda') model.to('cuda') with torch.no_grad(): output = model(input_batch) #可視化 idx = torch.nn.functional.softmax(output[0], dim=0).argmax().item() print(idx2label[idx]) plt.imshow(input_image) plt.title(idx2label[idx]) plt.savefig(filename) 物体検知 画像内にうつる物体の場所と分類をしてくれる技術です。 YOLOv5sを使います。 import os from copy import deepcopy import torch # モデル読み込み model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) # 画像 imgs = ['https://ultralytics.com/images/zidane.jpg'] # <- 自由に変更してね! img_paths = deepcopy(imgs) # 推論 results = model(imgs) # 可視化 save_dir = "../result/yolov5" os.makedirs(save_dir, exist_ok=True) results.save(save_dir) xys = results.pandas().xyxy for xy, img in zip(xys, img_paths): basename = os.path.splitext(os.path.basename(img))[0] xy.to_csv(f"{save_dir}/{basename}.csv") print(xy)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PytorchによるDeepLearningの動作環境(PyTorch+Torchvison+OpenCV)をRaspberryPi4に構築する方法

PytorchによるDeepLearningの動作環境(PyTorch+Torchvison+OpenCV)をRaspberryPi4に構築する方法 書籍「つくりながら学ぶ! PyTorchによる発展ディープラーニング」を参考にして作成したDeepLearningのプログラムをRaspberryPi4上で動作させるために、PyTorch、Torchvision、OpenCVの3つをインストールする必要がありました。 この記事には、これら3つのモジュールのインストール手順のメモを掲載します。 ただし、Raspbian上で実行したものであり、Raspberry Pi OS上で実行した場合に動作するかを確かめていない点、ご容赦ください。 環境 2020/4/3時点で最新のRaspbianを使いました。 (このメモを作成時はRaspberry Pi OSが公開されていませんでした・・・) OS:Raspbian Buster(Release date:2020-02-13) Python:3.7.3 インストール 1. Pytorchをインストール GitHubに掲載されている手順にを参考に、インストールします。 ただし、後述する①②に注意してください。 PyTorchのインストール手順 ①最新のバージョンを使用できない 最新のバージョンはインストールできませんでした。(ビルドエラーになりました) Optionally・・・の箇所に記載されている git checkout v1.0.1 を実施すると、無事にインストールできました。 git checkout XXXのXXX部分は、GitHubのBranch名称に対応しています。 ②PyTorchとTorchvisionのバージョンの組み合わせ PyTorchとTorchvisionのバージョンの組み合わせは、以下のリンク先のページから選択する必要があります。 PyTorchとTorchVisionの組み合わせ 今回は、Pytorch:v1.0.1、Torchvision:v0.2.2の組み合わせでインストールすることにしました。 2. Torchvisionをインストール Torchvisionをインストールします。 root で以下を実行してください。 git clone https://github.com/pytorch/vision.git cd vision git checkout v0.2.2_branch python3 setup.py setup build python3 setup.py setup install 参考ページ:https://gist.github.com/akaanirban/621e63237e63bb169126b537d7a1d979 3. OpenCVをインストール 次のコマンドを実行すると、OpenCVがインストールされます。 sudo pip3 install opencv-python 動作確認 ターミナルからpythonを起動し、次のコードを実行してください。 PyTorch、Torchvision、OpenCVのバージョンがそれぞれ表示されればOKです。 import torch import torchvision import cv2 torch.__version__ # Pytorchのバージョンを表示 torchvision.__version__ # Torchvisionのバージョンを表示 cv2.__version__ # OpenCVのバージョンを表示
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】GooglePhotosAPIで写真・動画のバックアップを取得する方法

はじめに 去年娘が生まれてからスマホで写真や動画を取る頻度がとても増えました。 私も妻もAndroidユーザーなので、自動でGoogle Photoに同期するようにしていて、非常に便利なのですが容量が15GBまでの制限があり、バックアップを取ってから削除していくというのを手動で行っておりました。 何とかならないものかと思い、自動で自宅のNASに写真や動画をバックアップするスクリプトを作りました。 tl;dr AndoridのGooglePhotoへの自動アップロードしておく pythonでGoogleのAPIををつかって写真と動画を取得するスクリプトを作成 ローカルサーバーのcronに仕込んで日次実行させる サーバーへの定義とスクリプトのデプロイはAnsible スクリプトのコードはこちら 目次 環境と構成 GCPでAPI有効化とOAuth認証設定 pythonでPhoto Library APIを実行 ReadyNASでのNFS有効化 ローカルサーバーへのデプロイ 結果 環境と構成 HW 開発PC Microsoft Windows 10 Pro (build 19043) Mac Book Pro BigSur(version 11.5.2) 本番環境 NUC (Intel NUC BOXNUC6CAYH) CentOS Linux release 7.9.2009 (Core) NAS (NETGEAR ReadyNAS 214 RN214) RAID 5(3D+1P: 6Gb/s 256MB 5400rpm 4TB*4) 言語 python 3.6.8 google-api-core (2.0.1) google-api-python-client (2.20.0) google-auth (2.0.2) google-auth-httplib2 (0.1.0) google-auth-oauthlib (0.4.6) python-dateutil (2.8.2) その他 VSCode(別に他のIDEでもいいです) Ansible(Dockerイメージとして構築。なのでWindows/MacにはDockerの実行環境が必要) 構成 ざっくりとした構成は以下の通りです。 GCPのコンソールでAPIの設定をしておく ローカルPCからNUCに対してAnsibleで定義更新とGitHubからのクローンを行う 初回実行は対象のユーザーのPC(AndroidのGoogleアカウントと同じユーザーでログインしているPC)でNUCにssh経由で行い、認証を通しておく cronでキックされたらPhoto Libraryで写真と動画のリストを取得し、新しい写真や動画があればdownloadしてNASに保存する API有効化とOAuth認証の設定 Photo Library APIの有効化 Google API Consoleにアクセス プロジェクトを作成していない場合は新規でプロジェクトを作成しておく。今回はgoogle-photo-backupとしました。 上部の検索バーでPhoto Libraryを入力してPhoto Library APIをクリックする。 Photo Library APIの有効にするボタンをクリックし、有効化しておく。 OAuth 2.0 クライアントIDの作成 画面左上のメニューからAPIとサービスを選択して、認証情報をクリックする。 アプリ名、ユーザーサポートメール、メールアドレスを入力し、保存して次へをクリックする。 アプリ名に「-」などが入っていると、エラーが発生します。 メニューからOAuth同意画面を選択し、UserTypeを外部にして作成をクリックする。 スコープは省略して大丈夫です。 画面上部の認証情報を作成をクリックし、OAuth クライアント IDをクリックする。 アプリケーションの種類でデスクトップアプリを選択して作成をクリックする。 作成されたOAuth 2.0 クライアント IDの右側にあるダウンロードボタンをクリックし、jsonファイルをダウンロードする。 pythonでPhoto Library APIを実行 先ほどダウンロードしたclient_secret~~.jsonをclient_secret.jsonにリネームして、スクリプトと同じフォルダに配置しておきます。 初回はこちらを使って認証を行いcredential.jsonを生成し、次回以降はcredential.jsonを用いて認証します。 こちらを参考にしました。 SCOPES = ['https://www.googleapis.com/auth/photoslibrary'] API_SERVICE_NAME = 'photoslibrary' API_VERSION = 'v1' CLIENT_SECRET_FILE = 'client_secret.json' CREDENTIAL_FILE = 'credential.json' def getCredentials(): """ API接続時にクレデンシャルを取得する。 初回はclient_secret.jsonをもとに認証を行い ユーザー操作の後credential.jsonを生成する 次回以降はcredential.jsonをもとに認証を行う Returns ---------- credential : Any クレデンシャル情報 """ if os.path.exists(CREDENTIAL_FILE): with open(CREDENTIAL_FILE) as f_credential_r: credentials_json = json.loads(f_credential_r.read()) credentials = google.oauth2.credentials.Credentials( credentials_json['token'], refresh_token=credentials_json['_refresh_token'], token_uri=credentials_json['_token_uri'], client_id=credentials_json['_client_id'], client_secret=credentials_json['_client_secret'] ) else: flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file( CLIENT_SECRET_FILE, scopes=SCOPES) credentials = flow.run_console() with open(CREDENTIAL_FILE, mode='w') as f_credential_w: f_credential_w.write(json.dumps( vars(credentials), default=support_datetime_default, sort_keys=True)) return credentials def main(): # クレデンシャルを取得 credentials = getCredentials() service = build( API_SERVICE_NAME, API_VERSION, credentials=credentials, static_discovery=False ) このserviceを使ってAPIにアクセスします。 query bodyを渡して、mediaItems = service.mediaItems().search(body=queryBody).execute()のようにアクセスします。 後々ダウンロードするときに、写真と動画を分ける必要があるので、mediaMetadataの中にphotoがあるかないかで判定しています。 def getMediaIds(service): """ APIに接続しMediaItemを取得する Parameters ---------- service : int credentialsから生成したAPI接続用のservice Returns ---------- photos : list id,filename,url videos : list id,filename,url """ photos = [] videos = [] now = datetime.now() logger.debug('datetime.now() : %s', now) nextPageTokenMediaItems = '' while True: queryBody = getQueryBody(nextPageTokenMediaItems, now, QUERY_FILTER) mediaItems = service.mediaItems().search(body=queryBody).execute() logger.debug('mediaItems length = %s', len(mediaItems)) if len(mediaItems) == 0: logger.info('mediaItems nothing') break for mediaItem in mediaItems['mediaItems']: photo = {} video = {} if 'photo' in mediaItem['mediaMetadata']: photo['id'], photo['filename'], photo['url'] = mediaItem['id'], mediaItem[ 'filename'], mediaItem['baseUrl'] photos.append(photo) else: video['id'], video['filename'], video['url'] = mediaItem['id'], mediaItem[ 'filename'], mediaItem['baseUrl'] videos.append(video) if 'nextPageToken' in mediaItems: nextPageTokenMediaItems = mediaItems['nextPageToken'] else: break logger.debug('photos length = %s', len(photos)) logger.debug('videos length = %s', len(videos)) return photos, videos query bodyにはフィルターをかけて期間を絞ることが出来ます。 日付でフィルターをかけれるようにしています。フィルターがいらないときは、pageSizeとpageTokenのみを渡せば良いです。 body = { 'pageSize': 50, "filters": { "dateFilter": { "ranges": [ { "startDate": { "year": startDate.year, "month": startDate.month, "day": startDate.day }, "endDate": { "year": referenceDate.year, "month": referenceDate.month, "day": referenceDate.day, }, }, ], }, }, 'pageToken': nextPageTokenMediaItems } ReadyNASでのNFS有効化 我が家にはNETGEARのReadyNASがあるので、こいつにため込むことにしました。 RAID 5で実行容量が12TBあるのでクォータを1TBにしてNFSとしてexportしておきます。 やり方はこちら。 ローカルサーバーへのデプロイ 我が家のNUCにはCentOS7をインストールしていて、構成はAnsibleで管理しています。 なので、そのままAnsibleでデプロイまで管理することにしました。 Docker上でAnsible環境を作る方法はこちら。 各種設定 google photo backup用にroleを作成しました。 roleのmain.ymlはこんな感じです main.yml # main.yml - include: install_require_package.yml notify: - OS reboot - Waiting for server to down - Waiting for server to up - include: setup_nfs_mount.yml - include: setup_logrotate.yml - include: deploy_app.yml - include: setup_app_config.yml - include: setup_cron_job.yml nfs、logrotate、cron_jobのところは本質的ではないので、それ以外のアプリデプロイに関わる部分をかいつまんで解説します。 install_require_package.yml まず必要なパッケージをインストールします。python36をyumでいれるだけです。 iusリポジトリを追加しているのがミソです。 install_require_package.yml # Install require package - name: install the ius-release-el7 rpm from a remote repo become: yes yum: name: https://repo.ius.io/ius-release-el7.rpm state: present - name: install_require_package(yum) become: yes yum: name: - python36u - python36u-libs - python36u-devel - python36u-pip state: present - name: install_require_package(pip with shell confirm) become: yes shell: pip3 show {{ item }} register: pip3_check loop: - google-auth - google_auth_oauthlib - google-api-python-client - python-dateutil changed_when: no failed_when: no - name: install_require_package(pip with shell install) become: yes shell: pip3 install {{ item.item }} when: "item.rc != 0" loop: "{{ pip3_check.results }}" Ansibleにはpipモジュールが存在するのですが、公式に書いてある方法でexecutable: pip3.3としてもうまくpip3を使ってくれなかったので、しかたなくshellで入れています。 shellモジュールでも冪等性が担保されるように、install_require_package(pip with shell confirm)でインストールされているかどうかを確認し、install_require_package(pip with shell install)でインストールされていないパッケージのみインストールしています。changed_when: noとfailed_when: noを指定してやることで、ドライランも対応できます。 deploy_app.yml リポジトリをpublicにしているので、特に認証等はせずにただクローンしています。 client_secret.jsonはansibleの実行環境側で用意しておいたものを配布しています。 クローンしたソースをjobの数だけユーザー毎に別フォルダに配置しています。 配置はクローンが行われた時にだけ行います。(後で設定ファイル書き換えたりするので) deploy_app.yml # deploy_app.yml # クローン用ディレクトリの作成 - name: clone directory making become: yes file: path: /opt/remote-repo/ state: directory owner: root group: root mode: 0755 # git clone - name: git clone from github become: yes ansible.builtin.git: repo: https://github.com/tokku5552/google-photo-backup.git dest: /opt/remote-repo/google-photo-backup register: clone_result # client_secret.jsonのファイル配置 - name: distribute client_secret.json become: yes copy: src: ../files/client_secret.json dest: /opt/remote-repo/google-photo-backup/src/client_secret.json backup: yes # アプリケーション用ディレクトリの作成 - name: app distribute directory making become: yes file: path: /opt/job/{{ item }}/bin state: directory owner: root group: root mode: 0755 loop: - userA - userB # srcのdirectory移動 - name : update source become: yes shell: cp -pr /opt/remote-repo/google-photo-backup/src/* /opt/job/{{ item }}/bin loop: - userA - userB when: clone_result.changed setup_app_config.yml pythonのスクリプト側でsettings.pyというファイルで可変設定項目を定義しているのですが、そちらをansibleで変更できるようにしています。 もともとGitHubリポジトリ上のsettings.pyがクローンされてくるので、変更したいところだけ変えるようにしています。 replaceモジュールで書き換えているだけです。 setup_app_config.yml # setup_app_config.yml # settingを強制上書きする場合 - name : update source (if force) become: yes shell: cp -pr /opt/remote-repo/google-photo-backup/src/* /opt/job/{{ item }}/bin loop: - userA - userB when: gpbk_setting_update_force # setting log filename - name: setting log filename become: yes replace: dest: /opt/job/{{ item }}/bin/settings.py regexp: LOG_FILENAME = '/var/log/google_photos_backup.log' replace: LOG_FILENAME = '/var/log/google_photos_backup_{{ item }}.log' loop: - userA - userB # バックアップディレクトリの作成 - name: make backup directory become: yes file: path: /gpbk/{{ item }} state: directory owner: nobody group: nobody mode: 0777 loop: - userA - userB # setting log filename - name: setting backup directory become: yes replace: dest: /opt/job/{{ item }}/bin/settings.py regexp: DESTINATION_DIR = '/gpbk' replace: DESTINATION_DIR = '/gpbk/{{ item }}' loop: - userA - userB # setting query filter - name: setting query filter become: yes replace: dest: /opt/job/{{ item }}/bin/settings.py regexp: QUERY_FILTER = True replace: QUERY_FILTER = {{ gpbk_query_filter }} loop: - userA - userB # setting debug mode - name: setting debug mode become: yes replace: dest: /opt/job/{{ item }}/bin/settings.py regexp: LOGGING_LEVEL = 20 # DEBUG=10,INFO=20 replace: LOGGING_LEVEL = {{ gpbk_log_level }} # DEBUG=10,INFO=20 loop: - userA - userB 各可変項目はgroup_varsで定義しています。 group_vars/nuc.yml gpbk_setting_update_force: False gpbk_log_level: 10 # DEBUG=10,INFO=20 gpbk_query_filter: False 結果 ログの一部 /var/log/google_photos_backup.log 2021-09-10 04:00:31,295 : [DEBUG] [google_photos_backup.py] mediaItems length = 1 2021-09-10 04:00:31,295 : [DEBUG] [google_photos_backup.py] photos length = 939 2021-09-10 04:00:31,295 : [DEBUG] [google_photos_backup.py] videos length = 33 2021-09-10 04:00:31,296 : [INFO] [google_photos_backup.py] end : get media ids 2021-09-10 04:00:31,296 : [INFO] [google_photos_backup.py] start : remove aquired media ids 2021-09-10 04:00:31,298 : [DEBUG] [google_photos_backup.py] loadIdsJson : aquired media list size = 973 2021-09-10 04:00:31,341 : [DEBUG] [google_photos_backup.py] removeAquiredMedia : result list counts = 0 2021-09-10 04:00:31,344 : [DEBUG] [google_photos_backup.py] removeAquiredMedia : result list counts = 0 2021-09-10 04:00:31,345 : [INFO] [google_photos_backup.py] end : remove aquired media ids 2021-09-10 04:00:31,345 : [INFO] [google_photos_backup.py] start : download media 2021-09-10 04:00:31,345 : [INFO] [google_photos_backup.py] download photos 2021-09-10 04:00:31,345 : [INFO] [google_photos_backup.py] 0 media downloads completed 2021-09-10 04:00:31,345 : [INFO] [google_photos_backup.py] download videos 2021-09-10 04:00:31,346 : [INFO] [google_photos_backup.py] 0 media downloads completed 2021-09-10 04:00:31,346 : [INFO] [google_photos_backup.py] end : download media 2021-09-10 04:00:31,346 : [INFO] [google_photos_backup.py] start : move files 2021-09-10 04:00:31,347 : [INFO] [google_photos_backup.py] done file moving 2021-09-10 04:00:31,347 : [INFO] [google_photos_backup.py] end : move files 2021-09-10 04:00:31,347 : [INFO] [google_photos_backup.py] start : update aquired list json 2021-09-10 04:00:31,347 : [DEBUG] [google_photos_backup.py] aquired_media_list : aquired_list.json 2021-09-10 04:00:31,353 : [INFO] [google_photos_backup.py] end : update aquired list json 2021-09-10 04:00:31,354 : [INFO] [google_photos_backup.py] All Proccess Complete! 無事定期的にバックアップがとられるようになりました! 難点として現在は大量に一度に取得できないので、初期同期は何か別の方法を考えて行う必要があります。(私はテストもかねてfilterの期間を少しずつ広げながら実行していきました。aquired_listに一度登録されたら次回以降は取得しなくなるので、一度全データがリストに登録されてしまえばフィルター外しても大丈夫です。) また、Google Photo APIは削除の処理が提供されていないので、容量を空けるための削除は手動で行う必要があります。 削除処理が実装されて欲しいですね、、、 参考 【Python】Google Photos API でアルバムの画像取得 PythonでGoogle Photoを50GBの無料サーバーにバックアップ Method: mediaItems.search  |  Google Photos APIs  |  Google Developers tokku5552/google-photo-backup: Get a backup of Google Photos Packaging modules — Ansible Documentation RN214 | ReadyNASストレージ | 製品 | ホーム製品 | NETGEAR ReadyNASでのNFS有効化の方法 | インフラエンジニアがもがくブログ DockerでAnsibleの実行環境を作って家のサーバーを管理する | インフラエンジニアがもがくブログ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【勉強記録】Pythonのデータ型について

目次 趣旨 代表的なデータ型 サンプルと型の確認 趣旨 前回の投稿で稚拙なコードにリファクタリング例を頂くというありがたい経験をしました。 ご教示頂いた箇所を再度アウトプットしたいと思い、題材を見つけて投稿するものです。 データ型のまとめ自体は薄い内容で申し訳ありません。 勉強の都度更新いたします。 代表的なデータ型 データ型 説明 サンプル str型 文字列 あいうえお int型 整数 1212 float型 浮動小数点数 3.14 bool型 真偽値 True list型 リスト [1,2,3] tuple型 タプル (1,2,3) dict型 辞書型 {"a":1,"b":2} ※現状、tuple型はimmutable(変更不可能)なlist型という理解 サンプルと型の確認 data-type.py x = ["楽団",1212,3.14,True,[1,2,'3'],(1,2,'3'),{'a':10,'b':20}] def data_type(i): print(type(x[i])) data_type(0) #=> <class 'str'> data_type(1) #=> <class 'int'> data_type(2) #=> <class 'float'> data_type(3) #=> <class 'bool'> data_type(4) #=> <class 'list'> data_type(5) #=> <class 'tuple'> data_type(6) #=> <class 'dict'> リファクタリング data-type.py x = ["楽団",1212,3.14,True,[1,2,'3'],(1,2,'3'),{'a':10,'b':20}] def data_type(i): print(type(i)) for i in x: data_type(i) 振り返り 散らかったコードをひとまず書いてから、リスト、関数の定義、for文と使用してリファクタリングを行いました。動けばいいじゃん、ではないコードを書きたいものです。 (ただ教科書的ではないですね…)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityにpythonをインストールする。

1 この記事は何? Unityにpythonをインストールする方法を説明します。またUnity上で簡単なpythonのコードを実行してみます。 前提条件 Unity2919.4.29f1が導入されているものとします。 2 その方法は? 2-1 必要データのダウンロード まず下記のサイトにアクセスしサイトの筆者が準備したUnity Packageをダウンロードします。 2-2 データの取り込み 前述のPackageをUnityにImportします。 AssetsフォルダにあるPythonExample.prefabを描写エリアにドラッグドロップします。 2-3 コーディング まずは、pythonコードから始めます。Assetsのフォルダにgreeter.pyがあります。 greeter.pyを下記のとおり変更してみます。(Python2.7がインストールされています。Python3.xは使用できないことをご認識ください) greeter.py import random import sys class Greeter(): def __init__(self, name): self.name = name def greet(self): return "Hiii, " + self.name def versionget(self): sysv=sys.version #pythonのverisonを取得します。 return "python version:" + sysv 次にC#をコーディングします。Assetsディレクトリの中に配置されているPythonExample.csを開きます。 下記の通り修正してください。 PythonExample.cs using System.Collections; using System.Collections.Generic; using IronPython.Hosting; using UnityEngine; public class PythonExample : MonoBehaviour { // Use this for initialization void Start() { var engine = Python.CreateEngine(); ICollection<string> searchPaths = engine.GetSearchPaths(); //Path to the folder of greeter.py searchPaths.Add(Application.dataPath); //Path to the Python standard library searchPaths.Add(Application.dataPath + @"\Plugins\Lib\"); engine.SetSearchPaths(searchPaths); dynamic py = engine.ExecuteFile(Application.dataPath + @"\greeter.py"); dynamic greeter = py.Greeter("Mika"); Debug.Log(greeter.greet()); Debug.Log(greeter.versionget()); } // Update is called once per frame void Update () { } } 上記のコードはC#の一連の作業の中でpythonを実行します。pythonにデータを渡し、pythonから返された値をC#が受け取ります。 コーディングか終わりましたら、PythonExample.csをビルドします。 2-4 実行 実際に実行してみます。Gameを選択のうえ、矢印をクリックすると動作が開始されます。 実行結果がConsoleに表示されております。pythonコードでは、「Hii,Mika」と使用しているPython versionの表示を行っているので、C#がそれらのデータを受け取りConsoleに表示しております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flaskで超簡単 奇数偶数判定

構成 odd_or_even / ├ templates / │ └ base.html └ main.py プログラム from flask import Flask from flask import render_template from flask import request app = Flask(__name__) @app.route("/", methods=["GET", "POST"]) def base(): value = {"result": ""} if request.method == "POST": num = int(request.form["num"]) which = lambda x: "偶数" if x % 2 == 0 else "奇数" value["result"] = which(num) return render_template("base.html", values=value) else: return render_template("base.html", values=value) app.run() 流れ "/"GETでアクセス(elseの処理) 数値を入力してEnterでPOST(ifの処理) 辞書の"result"の値更新→表示 Djangoとの比較 app = Flask(__name__) # app_name = "xxx" @app.route("/", methods=["GET", "POST"]) # リストの中で書く部分 この2行はDjangoでのurls.pyに書くやつ それ以外のはviews.pyに書くやつと同じ HTMLの中身 <form action="/" method="post"> <input type="text" name="num"> </form> {{ values.result }} render_templateの第二引数のvaluesの名前
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PySparkにてArrayType内のカラムにドット(.)が含まれるカラムにSELECTする方法

概要 PySparkにてArrayType内のカラムにドット(.)が含まれるカラムにSELECTする方法を共有します。カラム名をそのまま指定すると下記のようなエラーメッセージが表示されます。 AnalysisException: No such struct field strintg in strintg.in.array 対応方法としては、ドットが含まれるカラムをバッククォート(`)で囲むことでSELECTができるようになります。シングルクォーテーション(')ではないことに注意してください。 下記のようなスキーマの場合には、array.`strintg.in.array` と記載することで、データを取得することができます。カラムごとにバッククォートで囲む必要があるため、`array.strintg.in.array` のようにカラム全体を囲むだけではエラーが発生します。 root |-- array: array (nullable = true) | |-- element: struct (containsNull = true) | | |-- strintg.in.array: string (nullable = true) 手順 1. データの準備 json_data =""" { "array":[ { "strintg.in.array": "値①"}, { "strintg.in.array": "値②"}, { "strintg.in.array": "値③"} ] } """ df = spark.read.json(sc.parallelize([json_data])) df.createOrReplaceTempView('tmp_nested_columns') # df.display() df.printSchema() 2. DataframeでSELECTする方法 df_2 = df.select('array.`strintg.in.array`') # df_2.display() 3. SQLでSELECTする方法 %sql SELECT array.`strintg.in.array` FROM tmp_nested_columns
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PySparkにてStructType内のカラムにドット(.)が含まれるカラムにSELECTする方法

概要 PySparkにてStructType内のカラムにドット(.)が含まれるカラムにSELECTする方法を共有します。カラム名をそのまま指定すると下記のようなエラーメッセージが表示されます。 AnalysisException: No such struct field strintg in strintg.in.struct 対応方法としては、ドットが含まれるカラムをバッククォート(`)で囲むことでSELECTができるようになります。シングルクォーテーション(')ではないことに注意してください。 下記のようなスキーマの場合には、struct.`strintg.in.struct` と記載することで、データを取得することができます。カラムごとにバッククォートで囲む必要があるため、`struct.strintg.in.struct` のようにカラム全体を囲むだけではエラーが発生します。 root |-- struct: struct (nullable = true) | |-- strintg.in.struct: string (nullable = true) 手順 1. データの準備 json_data =""" { "struct":{ "strintg.in.struct": "struct_1" } } """ df = spark.read.json(sc.parallelize([json_data])) # df.display() df.createOrReplaceTempView('tmp_nested_columns') df.printSchema() 2. DataframeでSELECTする方法 df_2 = df.select('struct.`strintg.in.struct`') # df_2.display() 3. SQLでSELECTする方法 %sql SELECT struct.`strintg.in.struct` FROM tmp_nested_columns
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ランダム処理

ポイント ➀機械学習などでは、特にランダム処理が必要となる。データに対して相関性など依存関係をもたせるとデータ分析に影響をもたらす可能性があるためである。 ➁ランダム処理には大きく、乱数生成と用意されデータのシャッフルを行う 二つのやり方に分類される。 ランダム処理のまとめ一覧 import random #ランダム処理に必要なライブラリのインポート ・データの準備 #0~9のリスト生成 a = list(range(10)) print(a) >>>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ・データのシャッフル #リストのシャッフル random.shuffle(a) print(a) >>>[0, 1, 9, 5, 6, 4, 2, 8, 3, 7] ・シャッフルしたデータを固定する。 '''データをシャッフルするごとにリストの中身が異なるのは不便なことがある。 seedを用いることで、シャッフルの固定化を行うことができる。''' c = list(range(5)) random.seed(0) random.shuffle(c) print(c) >>>[2, 1, 0, 4, 3] ・データ抽出 #リストから三つのデータを抽出し、新規リストを作成。その後、シャッフルを実行 #母集団からデータの一部を標本として、抽出するときに有効 b = random.sample(a, 3) print(b) >>>[4, 9, 2] ・整数の乱数を生成する sample_list = ['転職', 'あきらめて会社に居残る', '様子をみる'] #0~2の値をランダム生成 index=random.randint(0,2) sample_list[index] >>>様子をみる ・浮動小数の乱数を生成する #floatのランダム生成(1~3) judge=random.uniform(1,3) if judge >1.5: print('乱数は1.5より大きいです') else: print('乱数は1.5以下です') ・文字列のなかから文字をランダムで取得する #文字列の中から、ランダムで文字を取得する value=random.choice('abcdefg') value >>>b ・numpyを用いて、ランダムな行列を生成する import numpy as np #numpyを用いることで、簡単に行列に関するランダム生成を行うことができる。 arr1 = np.random.rand(4, 4) # 4 x 4の配列の乱数 print(arr1) arr2 = np.random.rand(3, 2, 2) # 3 x 2 x 2の配列の乱数 print(arr2) >>> [[0.41656682 0.2629626 0.14942424 0.3543975 ] [0.51200162 0.23385965 0.62659274 0.19056221] [0.27613723 0.45423013 0.46009073 0.50387105] [0.93519298 0.88835726 0.44611031 0.62408758]] [[[0.75928824 0.55752824] [0.50373056 0.88856187]] [[0.97135706 0.12369758] [0.53365778 0.02798405]] [[0.77609176 0.9043028 ] [0.7760245 0.64095079]]]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】SBI証券に自動ログインしてみた

はじめに 皆さんはPythonを使って何を実現したいでしょうか。 私は毎日の単純作業を自動化し、Lineに通知することを実現したいと思っています。 Lineは毎日使うアプリだし、LineNotifyへの連携も容易です。 証券会社のHPへログインした後に毎日確認したことはありますか? そんな第一歩としてまずは自動ログインしてみました。 環境 Python3.9 macOS Catalina10.15.7 Chrome 93.0.4577.63 事前準備 1.Python3.X系をインストールします。(詳細は割愛します) 2.seleniumをインストールします。 ターミナルを起動して以下のコマンドを入力します。管理者パスワードを入れるとインストールできます。 $ sudo pip3 install selenium この時、macOSにpython2.Xが既にインストールされている場合[pip3]ではなく[pip]でインストールした場合少しはまりますのでご注意ください。詳細は以下です。 3.ChromeDriverをダウンロードして好きな場所に配置します。 今回は以下に配置します。 /Users/Hiroki/Info/chromedriver ダウンロード前にまずmacにあるChromeブラウザのバージョンを確認します。 Chromeを開く > 右上の「・・・」(設定) > [ヘルプ] > [Google Chromeについて] 今は「93.0.4577.63」ですね。 ChromeDriverをダウンロードする。以下のサイトにあります。 https://chromedriver.chromium.org/downloads Topページにさっそく「Supports Chrome version 93」と記載のあるChromeDriverがありました。今回はChromeブラウザのバージョンが93なので、これでOKです。 4.IDとPWファイルを用意します。 方法は以下をご参考ください。 ※はまり注意※ IDやPWファイルに「改行」が入っているとSeleniumは[Enter]として認識します。 予期せぬ動きをしますので気をつけて下さい。 プログラム SBI_login.py from selenium import webdriver#←seleniumからwebdriverをインポートします。変更不要です。 #↓IDファイル名や置き場を書き換えて下さい。 ID = open('/Users/Hiroki/Info/ID.txt', 'r', encoding='UTF-8') IDdata = ID.read() #↓PWファイル名や置き場を書き換えて下さい。 PW = open('/Users/Hiroki/Info/PW.txt', 'r', encoding='UTF-8') PWdata = PW.read() #↓chromedriverの置き場を書き換えて下さい。 driver = webdriver.Chrome("/Users/Hiroki/Info/chromedriver") driver.get("https://www.sbisec.co.jp/ETGate") #↓ログインIDの自動入力です。変更不要 elem_id_word = driver.find_element_by_name("user_id")#←現在のSBI証券の要素名 elem_id_word.send_keys(IDdata) #↓ログインPWの自動入力です。変更不要 elem_pw_word = driver.find_element_by_name("user_password")#←現在のSBI証券の要素名 elem_pw_word.send_keys(PWdata) #↓ログインボタンの自動入力です。変更不要 elem_login_btn = driver.find_element_by_xpath("/html/body/table/tbody/tr[1]/td[2]/form/div/div/div/p[2]/a/input")#←現在のSBI証券のログインボタンのxpathです。 elem_login_btn.click() 最後に ログインできましたでしょうか。 これは第一章として、ログイン後何をしたいかが重要かと思います。 私はIPOの有無を確認してLine通知しています。 ご参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

yukicoder contest 313 参戦記

yukicoder contest 313 参戦記 1時間遅れの参加だったけど、ABCが解けて嬉しかった. A 1672 404 Not Found ステータスコードの値を100で割って4か5ならエラーコードですね. S = int(input()) if S // 100 in [4, 5]: print('Yes') else: print('No') 文字列で処理してもいいですけどね. S = input() if S[0] in '45': print('Yes') else: print('No') B 1673 Lamps on a line Ri - Li≦10 という条件なので素直にシミュレーションすればいいです. from sys import stdin readline = stdin.readline N, Q = map(int, readline().split()) c = 0 t = [0] * (N + 1) result = [] for _ in range(Q): L, R = map(int, readline().split()) for i in range(L - 1, R): if t[i] == 0: c += 1 t[i] = 1 elif t[i] == 1: c -= 1 t[i] = 0 result.append(c) print(*result, sep='\n') C 1674 Introduction to XOR ちょっと考えると、どの Ai でも立っていない LSB(Least Significant Bit / 最下位ビット)だと分かります. N, *A = map(int, open(0).read().split()) x = 0 for a in A: x |= a for i in range(x.bit_length() + 1): if (x >> i) & 1 == 0: print(1 << i) break
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む