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

paiza攻略(Cランク)

今回やること paizaのプログラミング問題を解く上で頻出となるロジックをまとめました。 これでCランクは大体いけるはず。言語はpythonです。 標準入力から半角スペース区切りで変数に代入 コンソールに半角区切りで値を入力することで、変数a,b,cに代入されます。 a,b,c = input().split() なお、半角スペース以外で区切りたい場合はsplitメソッドの引数に指定する # 以下の例だと入力値を"+"で区切って入力することができる。 a,b,c = input().split("+") 任意の文字列を切り出す 開始インデックスと終了インデックスをコロン(:)でつなげて指定することで、文字列を切り出せる 以下の例では、hogeにはtの1文字目と2文字目が入り、fugaにはtの3文字目以降が全て入る t = input() hoge = t[0:2] fuga = t[3:] print(hoge) print(fuga) # 実行結果(入力値 abcdefg) # ab # defg 特定の文字列の出現回数を数える 以下の例では入力された文字列のなかに"<"が何文字含まれているかカウントしています。 hoge = input() answer = 0 # count関数で変数hogeに<が何文字含まれているか数える answer += hoge.count("<") print(answer) # 実行結果(入力値 a<<b<c<d<e<f) # 6 複数の変数を数値で入力したい場合 以下のようにして、input().split()で取得したstr型リストの要素をにint型に変換します。 a,b = (int(x) for x in input().split()) print(a+b) # 実行結果(入力値 "1 3") # 4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

paizaのスキルチェック攻略(Cランク, python)

今回やること paizaのプログラミング問題を解く上で頻出となるロジックをまとめました。 これでCランクは大体いけるはず。言語はpythonです。 標準入力から半角スペース区切りで変数に代入 コンソールに半角区切りで値を入力することで、変数a,b,cに代入されます。 a,b,c = input().split() なお、半角スペース以外で区切りたい場合はsplitメソッドの引数に指定する # 以下の例だと入力値を"+"で区切って入力することができる。 a,b,c = input().split("+") 任意の文字列を切り出す 開始インデックスと終了インデックスをコロン(:)でつなげて指定することで、文字列を切り出せる 以下の例では、hogeにはtの1文字目と2文字目が入り、fugaにはtの3文字目以降が全て入る t = input() hoge = t[0:2] fuga = t[3:] print(hoge) print(fuga) # 実行結果(入力値 abcdefg) # ab # defg 特定の文字列の出現回数を数える 以下の例では入力された文字列のなかに"<"が何文字含まれているかカウントしています。 hoge = input() answer = 0 # count関数で変数hogeに<が何文字含まれているか数える answer += hoge.count("<") print(answer) # 実行結果(入力値 a<<b<c<d<e<f) # 6 複数の変数を数値で入力したい場合 以下のようにして、input().split()で取得したstr型リストの要素をにint型に変換します。 a,b = (int(x) for x in input().split()) print(a+b) # 実行結果(入力値 "1 3") # 4 mapを用いた処理 map関数は引数に map(処理内容,繰り返し処理ができるもの) という引数を取る。 下記の処理では半角スペース区切りで入力した値をstr型からint型に変換している。 arr = map(int,input().split(" ")) print(arr) # 実行結果(入力値 "1 2 3 4 5") # [1, 2, 3, 4, 5]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】関数のデフォルト引数の落とし穴

はじめに Pythonでコードを書いていてハマったのでメモ Python使いの人たちには常識かもしれないが多言語使いだと落とし穴になりそう。 関数のデフォルト引数挙動 Pythonではデフォルト引数は関数定義時に一度だけ評価されるという挙動らしいです。 関数定義時であって初めに呼び出された時じゃないという点もポイントです。 下記のコードでは 現在時刻を表示 ↓ print_now関数定義 ↓ 5秒スリープ ↓ print_now関数呼び出し(現在時刻を表示・・・のつもり) ↓ 3秒スリープ ↓ print_now関数呼び出し(現在時刻を表示・・・のつもり) ↓ 3秒スリープ ↓ 現在時刻を表示 を行っています。 import datetime import time print(f'0:{datetime.datetime.now():%Y-%m-%d %H:%M:%S}') def print_now(msg, now = datetime.datetime.now()): print(f'{msg}:{now:%Y-%m-%d %H:%M:%S}') time.sleep(5) print_now('1') time.sleep(3) print_now('2') time.sleep(3) print(f'3:{datetime.datetime.now():%Y-%m-%d %H:%M:%S}') 出力結果 0:2021-04-11 23:17:28 1:2021-04-11 23:17:28 2:2021-04-11 23:17:28 3:2021-04-11 23:17:39 初めて呼び出された時でも呼び出した時でもなく関数定義時に計算された値になっている。 これは知らないとハマるって・・・。 公式によると デフォルト引数の式は関数が定義されるときにただ一度だけ評価され、同じ "計算済みの" 値が呼び出しのたびに使用されることを意味します。 とのこと。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 198 参戦記

AtCoder Beginner Contest 198 参戦記 ABC198A - Div 1分で突破. 書くだけ. N = int(input()) print(N - 1) ABC198B - Palindrome with leading zeros 3分で突破. 書くだけ. N = input() def is_palindrome(s): for i in range(len(s) // 2): if s[i] != s[len(s) - 1 - i]: return False return True for i in range(len(N) + 1): if is_palindrome('0' * i + N): print('Yes') break else: print('No') ABC198C - Compass Walking 9分で突破、WA2. 近すぎるときは2回になるのが盲点だった. from math import ceil, sqrt R, X, Y = map(int, input().split()) c = sqrt((X * X + Y * Y) / (R * R)) if c < 1: print(2) else: print(ceil(c)) ABC198E - Unique Color 30分くらい?で突破. DFS でサクッと突破できた. from sys import setrecursionlimit, stdin readline = stdin.readline setrecursionlimit(10 ** 6) N = int(readline()) C = list(map(int, readline().split())) links = [[] for _ in range(N)] for _ in range(N - 1): A, B = map(lambda x: int(x) - 1, readline().split()) links[A].append(B) links[B].append(A) dp = [(10 ** 18, False)] * N dp[0] = (0, True) def f(frm, to, steps, colors): if steps == dp[to][0]: if C[to] not in colors: dp[to] = (steps, True) elif steps < dp[to][0]: dp[to] = (steps, C[to] not in colors) colors.setdefault(C[to], 0) colors[C[to]] += 1 for nto in links[to]: if nto == frm: continue f(to, nto, steps + 1, colors) colors[C[to]] -= 1 if colors[C[to]] == 0: del colors[C[to]] f(-1, 0, 0, {}) result = [] for i in range(N): if dp[i][1]: result.append(i + 1) print(*result, sep='\n') ABC198D - Send More Money 50分くらい?で突破、WA2TLE3. まずは変数は10個が最大であることに気づく(別の変数は別の値縛りから). 10!=3628800=3.6×107なので、この問題の「実行時間制限: 5 sec」ならなんとか収まりそうと分かる. 実際は PyPy でもギリギリで定数倍削減する間に TLE の山が発生した. from itertools import permutations S1 = input() S2 = input() S3 = input() s1 = [ord(c) - 97 for c in S1] s2 = [ord(c) - 97 for c in S2] s3 = [ord(c) - 97 for c in S3] a = sorted(set(s1 + s2 + s3)) if len(a) > 10: print('UNSOLVABLE') exit() if len(a) < 10: for i in range(30, 30 + (10 - len(a))): a.append(i) b = [0] * 100 l = set([s1[0], s2[0], s3[0]]) for p in permutations(a): if p[0] in l: continue for i in range(10): b[p[i]] = chr(i + 48) N1 = int(''.join(b[c] for c in s1)) N2 = int(''.join(b[c] for c in s2)) N3 = int(''.join(b[c] for c in s3)) if N1 + N2 == N3: print(N1) print(N2) print(N3) exit() print('UNSOLVABLE')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python Tesseract + PyOCRを使って画像データのテキスト化

Python Tesseract + PyOCRを使って画像データのテキスト化 Pythonで画像データをテキスト化はTesseract + PyOCRを使うことで簡単に実現が可能だった 準備 PyOCRインストール $ pip install pyocr Tesseractインストール ※ Windows環境は別方法でインストールしてください $ brew install tesseract $ ls /usr/local/Cellar/tesseract/4.1.1/share/tessdata/ jpn.traineddata取得 $ wget https://github.com/tesseract-ocr/tessdata/raw/4.1.0/jpn.traineddata $ mv jpn.traineddata /usr/local/Cellar/tesseract/4.1.1/share/tessdata/ サンプル 以下の画像をPyOCRを使って解析 from PIL import Image import pyocr import pyocr.builders def main(): # OCRエンジンの取得 tools = pyocr.get_available_tools() tool = tools[0] # OCR実行 builder = pyocr.builders.TextBuilder() with Image.open("images/sample.png") as im: result = tool.image_to_string(im, lang="jpn", builder=builder) print(result) if __name__ == "__main__": main() 出力 ToysCreation ト イ ズ ク リ エ イ シ ョ ン 簡単に画像データからテキストを出力することができました いいね!と思ったら LGTM お願いします 【PR】プログラミング新聞リリースしました! → https://pronichi.com 【PR】週末ハッカソンというイベントやってます! → https://weekend-hackathon.toyscreation.jp/about/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python機械学習プログラミング備忘録 3.1-3.2

はじめに オンライン機械学習講義の予習用。(2021/04/11現在) Python機械学習プログラミング 達人データサイエンティストによる理論と実践 の以下を取り扱います。 第3章 分類問題 (3.1-3.2) ここでは、sklearn を使ってパーセプトロンの学習を実装します。 対象データはIris データセットです。 目次 開発環境 万能な分類器は存在しない Irisデータをロード パーセプトロンの学習 学習結果の可視化 まとめ 参考文献 開発環境 MacBook Air 2017 macOS Catalina 10.15.16 Google Colaboratory sklearn 0.22.2.post1 万能な分類器は存在しない まず前提として、学習アルゴリズムにはそれぞれ癖があるため、 解決したい問題に合わせて モデルを変えていく必要があります。 どんな問題にも通用する万能な手法は存在しないということです。 モデルの性質を見極め、パラメータを調整しなければ、 期待する結果は得られません。 学習モデルの特徴を捉えるためには、 実験を通した練習が最も効率的です。 はじめは単純なモデルである パーセプトロンに着目して話を進めていきます。 以下を colab notebook に入力して sklearn のバージョンを確認します。 ipynb import sklearn print(sklearn.__version__) # 0.22.2.post1 2021/04/11現在、colab notebook ではデフォルトで 上記のバージョンが使用されるようです。 ※ バージョンによっては、これより下のコードの整合性が取れないことがあるかもしれません。 Irisデータをロード 続いて、以下のコードで学習に必要なデータをロードし、 X に特徴量、y にラベルを格納します。 ipynb from sklearn import datasets import numpy as np iris = datasets.load_iris() X = iris.data[:, [2, 3]] # 2: petal length, 3: petal width y = iris.target ちなみに今回はデータをグラフにプロットしたいので、 考慮する特徴量は、 花びらの長さ(petal length)と 花びらの幅(petal width)のみになります。 続いて、どんなラベルが割り振られているのか気になるので、 以下を実行します。 ipynb print("Class labels: ", np.unique(y)) # [0 1 2] 普通、ラベルは人間にとって分かりやすい 名前がついているはずですが、 すでにクラスラベルとして整数に変換されていたようです。 Iris-Setosa: 0, Iris-Versicolor: 1, Iris-Virginica: 2 として、与えられているということです。 パーセプトロンの学習 次にモデルの汎化性能を確保するために、 データ全体から トレーニングデータセットとテストデータセットに 分割します。 以下のコードで、 テストデータの割合を30%(45個のサンプル)に指定します。 ipynb from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=0) 続いて特徴量のスケーリングを行います。 各特徴量の取りうる値を揃えるために、 StandardScalerを使います。 以下のコードで、標準化できます。 ipynb from sklearn.preprocessing import StandardScaler sc = StandardScaler() # fitメソッドで平均と標準偏差を計算 sc.fit(X_train) X_train_std = sc.transform(X_train) # transformメソッドで標準化 X_test_std = sc.transform(X_test) StandardScalerのfitメソッドでを使うと 特徴量ごとの平均 μ と標準偏差 σ を計算できます。 これらのパラメータを使い、 transform メソッドでトレーニングデータを標準化します。 テストデータにも 同じスケーリングパラメータを適用したのは、 相互の基準を揃え、比較できるようにするためです。 これでパーセプトロンをトレーニングできる状態になりました。 以下のコードで、 パーセプトロンのインスタンスを作成し、 トレーニングを実行します。 ipynb from sklearn.linear_model import Perceptron # エポック40, 学習率0.1のパーセプトロンインスタンスを作成 ppn = Perceptron(max_iter=40, eta0=0.1, random_state=0, shuffle=True) # トレーニングデータをモデルに適合させる ppn.fit(X_train_std, y_train) ※ インスタンスを作成するときにエポックと学習率を定義します。 次に予測を行います。 以下の predict メソッドで予測を実行し、 誤分類されたサンプルも表示します。 ipynb # テストデータで予測を実施 y_pred = ppn.predict(X_test_std) # 誤分類のサンプル個数を表示 print("Missclassified samples: {}".format((y_test != y_pred).sum())) # Missclassified samples: 5 せっかく表示した結果ですが、 一般に、機械学習で推奨される報告は、 モデルの誤分類率ではなく、正解率です。 そのため、誤分類を出して終わりではないのです。 以下のコードで、 正解率を計算します。 ipynb # 実装された性能指標を使用 # ここではパーセプトロンの正解率を計算 from sklearn.metrics import accuracy_score print("Acuracy: {:.2f}".format(accuracy_score(y_test, y_pred))) ちなみに正解率は以下の式で定義されています。 正解率 = 1 - 誤分類率 テストデータの誤分類率は 5/45=0.111... なので、上記の式に当てはめると、 正解率 = 1 - 0.11 = 89% になります。 このようにして、学習済みのモデルを評価します。 ※ 今回は簡単な解析だったため、 過学習を防止する手法等は適用していないことに注意。 学習結果の可視化 続いて、 トレーニングしたモデルを可視化します。 モデルの決定領域をプロットし、 未知のデータに対してその程度識別できるか見ていきます。 以下のコードで、描画の定義をします。 ipynb from matplotlib.colors import ListedColormap import matplotlib.pyplot as plt def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02): # マーカーとカラーマップの準備 markers = ('s', 'x', '^', 'v') colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan') cmap = ListedColormap(colors[:len(np.unique(y))]) # 決定領域のプロット x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 # グリッドポイントの生成 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution)) # 各特徴量を1次元配列に変換して予測を実行 Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T) # 予測結果を元にグリッドポイントのデータサイズに変換 Z = Z.reshape(xx1.shape) # グリッドポイントの等高線をプロット plt.contourf(xx1, xx2, Z, alpha=0.5, cmap=cmap) # 軸の範囲を設定 plt.xlim(xx1.min(), xx1.max()) plt.ylim(xx2.min(), xx2.max()) # クラスごとにサンプルをプロット for idx, cl in enumerate(np.unique(y)): plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.6, color=cmap(idx), marker=markers[idx], label=cl) # テストサンプルを目立たせる(黒で描画) if test_idx: X_test, y_test = X[test_idx, :], y[test_idx] plt.scatter(X_test[:, 0], X_test[:, 1], color=(0, 0, 0), alpha=0.6, linewidths=1, marker='o', s=55, label='test set') # 誤分類されたサンプルは黄色でプロット plt.scatter(X_test[y_test != y_pred, 0], X_test[y_test != y_pred, 1], color=(1, 1, 0), alpha=0.6, linewidths=1, marker='o', s=55, label='Missclassified') この関数で、 トレーニングデータとテストデータをプロットします。 下の方にある、 「# 誤分類されたサンプルは黄色でプロット」以下が 誤分類されたサンプルを描画するためのコードです。 以下のコードで、実際に描画してみましょう。 ipynb # トレーニングデータとテストデータを行方向に結合 X_combined_std = np.vstack((X_train_std, X_test_std)) y_combined = np.hstack((y_train, y_test)) # 決定境界のプロット plot_decision_regions(X=X_combined_std, y=y_combined, classifier=ppn, test_idx=range(105, 150)) plt.xlabel('petal length [standardized]') plt.ylabel('petal width [standardized]') plt.legend(loc='upper left') plt.show() これを実行すると、以下の図ような出力が得られるはずです。 テストデータは黒色でプロットしており、 その中でも誤分類したものは黄色になっているはずです。 この結果から、 パーセプトロンアルゴリズムでは Irisデータセットの3つの品種を線形の決定境界では 完全に区切ることができないことが分かります。 一般に、 パーセプトロンアルゴリズムがデータ解析で使用されないのは、 完全な線形分離が要求されるからです。 この条件を満たさない限り、 パーセプトロンアルゴリズムは収束しないため、 期待する結果は得られません。 まとめ 以下に今回のまとめを記載します。 実験を通して、特定の問題に最適なモデルを選択する。 誤分類率ではなく正解率を報告する。 クラスラベルは整数に変換して使用する。 パーセプトロンアルゴリズムは線形分離が不可能だと収束しない。 次回はクラスが完全に線形分離できない場合であっても、 コスト関数が最小値に収束するロジスティック回帰を取り上げます。 参考文献 ImportError: No module named 'sklearn.cross_validation'の対処 sklearn.linear_model.Perceptron のパラメータから n_iter が削除されてた件のメモ Sebastian Raschka (2015). Python Machine Learning-1st Edition, Packt Publishing. (セバスチャン・ラシュカ, 株式会社クイープ(訳) (2016). Python機械学習プログラミング達人データサイエンティストによる理論と実践, 株式会社インプレス, pp.48-53)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

色の単語の分散表現からRBG空間への転写を学習して、単語から色を生成してみる

できたもの はじめに 単語や文章の意味を分散表現のベクトルとして扱うアプローチが一般的になっていますが、単語の意味において色はどういう関係にあるのだろうと思い立ち、試しにモデルを学習させてみました。色にも色空間(RGBやHSVなど)があるので、単語の分散表現空間と色のRGB空間との転写を学習するというアプローチを試してみることにしました 具体的には、単語→意味分散表現→RGB空間→色という流れで今回は単語から色を生成してみます 色の名前とRGB値をセットで取得 こちらのサイトからHTMLカラーと呼ばれる140種類ぐらいの名前のついた色を集めさせていただきました import requests from bs4 import BeautifulSoup import re url = 'https://www.w3schools.com/colors/colors_names.asp' res = requests.get(url) soup = BeautifulSoup(res.content, 'html.parser') color_names_and_codes = {} for name, code in zip(soup.select('.colornamespan'), soup.select('.colorhexspan')): name = re.sub('(.)([A-Z])', r'\1 \2', name.text).lower() # eg: AliceBlue -> alice blue color_names_and_codes[name] = code.text このcolor_names_and_codesには次のようにHTMLカラー名とRGBの組み合わせが入っています {'alice blue': '#F0F8FF', 'antique white': '#FAEBD7', 'aqua': '#00FFFF', 'aquamarine': '#7FFFD4', 'azure': '#F0FFFF', 'beige': '#F5F5DC', 'bisque': '#FFE4C4', 'black': '#000000', 'blanched almond': '#FFEBCD', ... BERTの分散表現学習(huggingface transformers) モデルと分散表現 使用したバージョン:transformers==4.5.0 huggingfaceのtransformersが簡単にモデルを取得し学習・評価できたのでこちらを用います。 事前学習済みの英語のbertのトークナイザーとモデルをまずロードします import torch from transformers.tokenization_bert import BertTokenizer from transformers import BertModel tokenizer = BertTokenizer.from_pretrained('bert-base-cased') # casedは大文字小文字区別なし model = BertModel.from_pretrained('bert-base-cased') とりあえず現状の分散表現空間(768次元)での各単語の分布を見てみたいので、全単語の出力を取得します def encode_texts(texts): encoding = tokenizer.batch_encode_plus(texts, return_tensors='pt', pad_to_max_length=True) return encoding['input_ids'], encoding['attention_mask'] texts = list(color_names_and_codes.keys()) ids, attention_mask = encode_texts(texts) outputs = model(ids) vecs = outputs[0][:, 0, :].tolist() このベクトルを次元圧縮(t-SNE)して見てみます。プロット点の色がその単語の色と対応しています import matplotlib.pyplot as plt from sklearn.manifold import TSNE def plot_tsne(x, y, color): plt.figure(figsize=(8, 6)) plt.clf() tsne = TSNE(n_components=2, random_state=0, perplexity=10) x_embedded = tsne.fit_transform(x) plt.scatter(x_embedded[:, 0], x_embedded[:, 1], c=color) plt.show() plot_tsne(vecs, texts, color_names_and_codes.values()) 時々似ている色が隣り合っていたり集まっている箇所はあるようです 学習 このモデルの最終層をRGBに対応した3次元のものに取り替えます。RGBの3次元の回帰問題として学習します # すべてのパラメータを固定 for param in model.parameters(): param.requires_grad = False n_in_features = model.pooler.dense.in_features n_out_features = 3 model.pooler.dense = torch.nn.Linear(n_in_features, n_out_features) model.pooler.activation = torch.nn.ReLU() カラーコードを0から1の値を持つRGBベクトルに変換し、正解データを作ります。 import numpy as np import torch from sklearn.model_selection import train_test_split def hex_to_rgb(color_code): r = int(color_code[1:3], 16) / 255 g = int(color_code[3:5], 16) / 255 b = int(color_code[5:7], 16) / 255 return [r, g, b] y = np.array([hex_to_rgb(code) for code in color_names_and_codes.values()]) y_tensor = torch.from_numpy(y).float() i_train, i_val = train_test_split(range(len(ids)), test_size=0.25) print(y_tensor[i_train].shape) print(ids[i_val].shape) #=> torch.Size([111, 3]) torch.Size([37, 8]) 損失関数を平均二乗誤差に設定し、モデルの学習を行います optimizer = transformers.AdamW(model.pooler.parameters(), lr=0.00001) criterion = torch.nn.MSELoss() model.train() n_epochs = 1000 train_losses, val_losses = [], [] x_train, x_val, y_train, y_val = ids[i_train], ids[i_val], y_tensor[i_train], y_tensor[i_val] attention_mask_train, attention_mask_val = attention_mask[i_train], attention_mask[i_val] for epoch in tqdm(range(n_epochs)): y_train_pred = model(x_train, attention_mask_train) loss = criterion(y_train_pred.pooler_output, y_train) optimizer.zero_grad() loss.backward() optimizer.step() train_losses.append(loss.data.numpy().tolist()) y_val_pred = model(x_val, attention_mask_val) val_loss = criterion(y_val_pred.pooler_output, y_val) val_losses.append(val_loss.data.numpy().tolist()) 一応それなりにロスは落ちたみたいなので、 データ内の色をどのように生成したかプロットしてみます(以下のページですべて確認可能です) 元のデータの分布からなのか、なんか地味めな青とか緑系の色が多くなりました。あまり上手くいってなさげですが、そもそも単語の意味分散表現から色の生成がどの程度難しいのかよく分からないので、とりあえずこのモデルを使用することにしました herokuへデプロイ 単語に色がついているのが見てみたかったので、どんな単語でもインタラクティブに色の確認ができるように、streamlitを用いてwebアプリ化してみました おわりに 文字に色があるタイプの共感覚を疑似体験することはできた感じがしますが、理想的には「晴れた夏の青空」とか入力すると鮮やかな青色のRGB値を出力するようなものができれば、何かしらに使えるかもしれないですが今回はそこまでは難しかったみたいです ちなみにHSV空間でも試したところ、青か緑しか出さないモデルができて上手くいかなかったのですが、明度や彩度を無視して色相(H)だけを予測するとかでも良かったのかもしれないです 参考 https://qiita.com/ichiroex/items/6e305a5d5bed7d715c2f https://qiita.com/xkumiyu/items/1cc0223486c560062e00#%E5%B1%9E%E6%80%A7%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E3%81%AE%E6%8A%BD%E5%87%BA https://tksmml.hatenablog.com/entry/2019/10/22/215000 https://aidiary.hatenablog.com/entry/20180217/1518833659
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

写真の撮影日時をいい感じに修正するスクリプト

ざっくり 写真をAmazon Photoなどのクラウドサービスにアップした際、 撮影日時が入っていなかったりズレがある写真はあらぬ位置に表示されてしまいます。 ということで、撮影日時をいい感じに修正するスクリプト photo_daytime_fixer を書きました。 実行後はこんな感じ。 ついでに元に戻したい時のためのスクリプト photo_daytime_restorer も書きました。 スクリプトの解説 photo_daytime_fixer.py 概要 修正を行うフォルダを指定してスクリプトを実行 ~~~ python3 photo_datetime_fixer.py <対象フォルダ> ~~~ 指定した指定したフォルダ内のファイルを名前順で確認していく 撮影日時 (DateTimeOriginal) の整合性が前後と取れていないファイルを対象に EXIF 情報を更新する DateTimeOriginal の更新 前後のファイルの撮影日時の間の日時(年〜秒)とする 対象ファイルが複数の場合は均等に配分する 1番目のファイルについてはなにもしない 最後のファイル(たち)が対象の場合は直前の整合性が取れているファイルの日時に1秒ずつ足した日時に設定 UserComment の追記 DateTimeOriginal の更新内容を以下の形式で記録する ~~~ FixedDateTimeOriginal_<このスクリプトを実行したUNIX時間>[<元の日時> -> <変更後の日時>] ~~~ このコメントは後述の photo_daytime_restorer で利用する もともとコメントが存在する場合には半角スペース区切りで追記 動作例 実行前 ファイル名 DateTimeOriginal UserComment 0.JPG 2021:04:01 00:00:00 test0 1.JPG test1 2.JPG 1999:01:01 00:00:00 test2 3.JPG 2021:04:01 03:06:09 test3 4.JPG 2021:04:01 04:08:12 test4 実行 $ python3 photo_datetime_fixer.py /xxx/test +-------------------------+ | Photo Date & Time Fixer | +-------------------------+ Target Dir: /xxx/test Fix ID: 1618139631 0.JPG: Skip: No data before. 1.JPG: Need to fix. 2.JPG: Need to fix. 3.JPG: OK. Fix! 1.JPG: Fixed: -> 2021:04:01 01:22:03 2.JPG: Fixed: 1999:01:01 00:00:00 -> 2021:04:01 02:44:06 4.JPG: OK. +-----------+ | ?Fixed? | +-----------+ 実行後 ファイル名 DateTimeOriginal UserComment 0.JPG 2021:04:01 00:00:00 test0 1.JPG 2021:04:01 01:02:03 test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] 2.JPG 2021:04:01 02:04:06 test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] 3.JPG 2021:04:01 03:06:09 test3 4.JPG 2021:04:01 04:08:12 test4 photo_daytime_restorer.py 概要 修正を行うフォルダと photo_daytime_fixer を実行したUNIX時間を指定してスクリプトを実行 ~~~ python3 photo_datetime_restorer.py <対象フォルダ> ~~~ 指定した指定したフォルダ内のファイルを名前順で確認していく UserComment から対象ファイルを探し、EXIF 情報を更新する DateTimeOriginal の更新 元の日時に更新 UserComment の追記 DateTimeOriginal の更新内容を以下の形式で記録する ~~~ RestoredDateTimeOriginal_<このスクリプトを実行したUNIX時間>[<元の日時> -> <変更後の日時>] ~~~ もともとコメントが存在する場合には半角スペース区切りで追記 動作例 実行前 ファイル名 DateTimeOriginal UserComment 0.JPG 2021:04:01 00:00:00 test0 1.JPG 2021:04:01 01:02:03 test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] 2.JPG 2021:04:01 02:04:06 test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] 3.JPG 2021:04:01 03:06:09 test3 4.JPG 2021:04:01 04:08:12 test4 実行 $ python3 photo_datetime_restorer.py test 1618139631 +----------------------------+ | Photo Date & Time Restorer | +----------------------------+ Target Dir: test Fix ID: 1618139631 Restore ID: 1618140405 0.JPG: Skip. 1.JPG: Restore. 2021:04:01 01:02:03 -> 2.JPG: Restore. 2021:04:01 02:04:06 -> 1999:01:01 00:00:00 3.JPG: Skip. 4.JPG: Skip. +-------------+ | ?Restored? | +-------------+ 実行後 ファイル名 DateTimeOriginal UserComment 0.JPG 2021:04:01 00:00:00 test0 1.JPG 2021:04:01 01:02:03 test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ] 2.JPG 2021:04:01 02:04:06 test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00] 3.JPG 2021:04:01 03:06:09 test3 4.JPG 2021:04:01 04:08:12 test4 exif_manager.py 概要 上記2スクリプトで利用するクラスを定義したもの EXIF 情報の更新 ExifTool (https://exiftool.org/) を利用 時間の計算 留意事項 対象のファイルの EXIF 情報に元から SubSecDateTimeOriginal が含まれていた場合は年〜秒までは一緒に更新される SubSecDateTimeOriginal には ms 以下の時間やタイムゾーンが書かれている 年〜秒以外の部分はオリジナルのままになる 対象フォルダ内に EXIF 情報を更新できないファイル(ExifTool のサポート外、もしくは書き込み権限なし)のファイルがあった場合には、そのファイルについては何もしない ただし、複数ファイルを一気に更新する際の時間間隔計算の分母にはカウントされる 対象のはずなのにうまく動作しないファイルもあった サイズが小さいファイル 大昔のiPhoneのスクショ ガラケー時代の遺産ことデコ文字のファイル 他にもあるかも PNG は EXIF 情報が正しく更新できた場合でも、Amazon Photoでは日付なしに分類されてしまう 悲しい 仕様上、大量にファイルがあり前後の正しいファイルの撮影時間の差が小さいと間隔が1秒未満になってしまい、正しい結果が得られない SubSecDateTimeOriginal でなんとかしようとしましたが、ファイルによってタイムゾーンの表記があったりなかったりだったので断念 まあ、あまり起こり得ない & 起こってもあまり影響はない想定 余談 Google Photo の無料枠が終了するということで Amazon Photo に乗り換えようとしたところ、順序の狂い方が Google のときより酷い気がしてすごくムカついたので2日くらいでバーっと書きました。 なので日時を示す EXIF タグのうち、Amazon Photoで日時として認識されているっぽい DateTimeOriginal を更新することにしました。 あまり Python に触ってこなかった人生ですが今どきそれはさすがにエンジニアとして、、、ということで Python で。 最初は PIL (https://pillow.readthedocs.io/en/stable/#) を使おうとしましたが、HEIC に対応指定なさそうだったので断念。ExifToolを利用することに。 ならシェルスクリプトでよくね感もありましたがせっかくなので、、、 Python 初心者なので超拙いです多分、レビューお待ちしております。 これ使ってもAmazon Photoにアップ済みのファイルは修正できない 一括削除しようとしましたがいつの間にかできない仕様に、、、 3000件までなら一括選択できるスクリプトを書かれている方がいらっしゃったのでそれを利用しました。 https://qiita.com/M_Kagawa/items/9b7dc3fa310a0170b6b2 gitHubプライベートアカウント&Qiitaへの初投稿なのでお手柔らかに。 勝手がアレ。 誤字脱字もめちゃくちゃありそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

写真の撮影日時を超超超いい感じに修正するスクリプト

ざっくり 写真を Amazon Photos などのクラウドサービスにアップした際、 撮影日時が入っていなかったりズレがある写真はあらぬ位置に表示されてしまいます。 ということで、撮影日時をいい感じに修正するスクリプトを書きました。 実行後はこんな感じ。 ついでに元に戻したい時のためのスクリプトと記録した変更履歴を削除するスクリプトも書きました。 スクリプトの解説 exif_manager.py 下記スクリプトで利用するクラスを定義したもの。 ExifTool (https://exiftool.org/) を利用した EXIF 情報の更新を行うので、 ExifTool のインストールが必要。 photo_daytime_fixer.py 日時をいい感じに修正するスクリプト。 処理 修正を行うフォルダを指定してスクリプトを実行 $ python3 photo_datetime_fixer.py <対象フォルダ> 指定した指定したフォルダ内のファイルを名前順で確認していく 撮影日時 (DateTimeOriginal) の整合性が前後と取れていないファイルを対象に EXIF 情報を更新する DateTimeOriginal の更新 前後のファイルの撮影日時の間の日時(年〜秒)とする 対象ファイルが複数の場合は均等に配分する 1番目のファイルについてはなにもしない 最後のファイル(たち)が対象の場合は直前の整合性が取れているファイルの日時に1秒ずつ足した日時に設定 UserComment の追記 DateTimeOriginal の更新内容を以下の形式で記録する FixedDateTimeOriginal_<このスクリプトを実行したUNIX時間>[<元の日時> -> <変更後の日時>] このコメントは後述の photo_daytime_restorer で利用する もともとコメントが存在する場合には半角スペース区切りで追記 動作例 実行前 ファイル名 DateTimeOriginal UserComment 0.JPG 2021:04:01 00:00:00 test0 1.JPG test1 2.JPG 1999:01:01 00:00:00 test2 3.JPG 2021:04:01 03:06:09 test3 4.JPG 2021:04:01 04:08:12 test4 実行 $ python3 photo_datetime_fixer.py /xxx/test +-------------------------+ | Photo Date & Time Fixer | +-------------------------+ Target Dir: /xxx/test Fix ID: 1618139631 0.JPG: Skip: No data before. 1.JPG: Need to fix. 2.JPG: Need to fix. 3.JPG: OK. Fix! 1.JPG: Fixed: -> 2021:04:01 01:22:03 2.JPG: Fixed: 1999:01:01 00:00:00 -> 2021:04:01 02:44:06 4.JPG: OK. +-----------+ | ?Fixed? | +-----------+ 実行後 ファイル名 DateTimeOriginal UserComment 0.JPG 2021:04:01 00:00:00 test0 1.JPG 2021:04:01 01:02:03 test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] 2.JPG 2021:04:01 02:04:06 test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] 3.JPG 2021:04:01 03:06:09 test3 4.JPG 2021:04:01 04:08:12 test4 photo_daytime_restorer.py 日時を元に戻すスクリプト。 処理 修正を行うフォルダと photo_daytime_fixer を実行したUNIX時間を指定してスクリプトを実行 $ python3 photo_datetime_restorer.py <対象フォルダ> <photo_daytime_fixerを実行したUNIX時間> UserComment を参照することで対象ファイルを探し、EXIF 情報を更新する DateTimeOriginal の更新 元の日時に更新 UserComment の追記 DateTimeOriginal の更新内容を以下の形式で記録する RestoredDateTimeOriginal_<このスクリプトを実行したUNIX時間>[<元の日時> -> <変更後の日時>] もともとコメントが存在する場合には半角スペース区切りで追記 動作例 実行前 ファイル名 DateTimeOriginal UserComment 0.JPG 2021:04:01 00:00:00 test0 1.JPG 2021:04:01 01:02:03 test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] 2.JPG 2021:04:01 02:04:06 test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] 3.JPG 2021:04:01 03:06:09 test3 4.JPG 2021:04:01 04:08:12 test4 実行 $ python3 photo_datetime_restorer.py test 1618139631 +-----------------------------+ | Photo Fix/Restore Forgetter | +-----------------------------+ Target Dir: test Fix ID: 1618139631 Restore ID: 1618140405 0.JPG: Skip. 1.JPG: Restore. 2021:04:01 01:02:03 -> 2.JPG: Restore. 2021:04:01 02:04:06 -> 1999:01:01 00:00:00 3.JPG: Skip. 4.JPG: Skip. +-------------+ | ?Restored? | +-------------+ 実行後 ファイル名 DateTimeOriginal UserComment 0.JPG 2021:04:01 00:00:00 test0 1.JPG 2021:04:01 01:02:03 test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ] 2.JPG 2021:04:01 02:04:06 test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00] 3.JPG 2021:04:01 03:06:09 test3 4.JPG 2021:04:01 04:08:12 test4 photo_daytime_forgetter.py 日時の更新履歴を消すスクリプト。 処理 修正を行うフォルダと photo_daytime_fixer もしくは photo_daytime_restorer を実行したUNIX時間を指定してスクリプトを実行 $ python3 photo_datetime_restorer.py <対象フォルダ> <photo_daytime_fixer/restorerを実行したUNIX時間> UserComment を参照することで対象ファイルを探し、EXIF 情報を更新する UserComment の更新 コメント中から指定した実行についての記述のみを削除する 動作例 実行前 ファイル名 UserComment 0.JPG test0 1.JPG test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ] 2.JPG test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00] 3.JPG test3 4.JPG test4 実行 $ python3 photo_datetime_forgetter.py test 1618139631 +--------------------------------+ | Photo Fix or Restore Forgetter | +--------------------------------+ Target Dir: test Fix/Restore ID: 1618139631 0.JPG: Skip. 1.JPG: Forget. test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ] -> test1 RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ] 2.JPG: Forget. test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00] -> test2 RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00] 3.JPG: Skip. 4.JPG: Skip. +---------------+ | ?Forgotten? | +---------------+ 実行後 ファイル名 UserComment 0.JPG test0 1.JPG test1 RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ] 2.JPG test2 RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00] 3.JPG test3 4.JPG test4 留意事項 対象のファイルの EXIF 情報に元から SubSecDateTimeOriginal が含まれていた場合は年〜秒までは一緒に更新される SubSecDateTimeOriginal には ms 以下の時間やタイムゾーンが書かれている 年〜秒以外の部分はオリジナルのままになる 対象フォルダ内に EXIF 情報を更新できないファイル(ExifTool のサポート外、もしくは書き込み権限なし)のファイルがあった場合には、そのファイルについては何もしない ただし、複数ファイルを一気に更新する際の時間間隔計算の分母にはカウントされる 対象のはずなのにうまく動作しないファイルもあった サイズが小さいファイル 大昔のiPhoneのスクショ ガラケー時代の遺産ことデコ文字のファイル 他にもあるかも PNG は EXIF 情報が正しく更新できた場合でも、Amazon Photos では日付なしに分類されてしまう 悲しい 仕様上、大量にファイルがあり前後の正しいファイルの撮影時間の差が小さいと間隔が1秒未満になってしまい、正しい結果が得られない SubSecDateTimeOriginal でなんとかしようとしましたが、ファイルによってタイムゾーンの表記があったりなかったりだったので断念 まあ、あまり起こり得ない & 起こってもあまり影響はない想定 余談 Google フォトの無料枠が終了するということで Amazon Photos に乗り換えようとしたところ、順序の狂い方が Google のときより酷い気がしてすごくムカついたので2日くらいでバーっと書きました。 なので日時を示す EXIF タグのうち、Amazon Photos で日時として認識されているっぽい DateTimeOriginal を更新することにしました。 あまり Python に触ってこなかった人生ですが今どきそれはさすがにエンジニアとして、、、ということで Python で。 最初は PIL (https://pillow.readthedocs.io/en/stable/#) を使おうとしましたが、HEIC に対応指定なさそうだったので断念。ExifToolを利用することに。 ならシェルスクリプトでよくね感もありましたがせっかくなので、、、 Python 初心者なので超拙いです多分、レビューお待ちしております。 これ使っても Amazon Photos にアップ済みのファイルは修正できない 一括削除しようとしましたがいつの間にかできない仕様に、、、 3000件までなら一括選択できるスクリプトを書かれている方がいらっしゃったのでそれを利用しました。 https://qiita.com/M_Kagawa/items/9b7dc3fa310a0170b6b2 gitHubプライベートアカウント&Qiitaへの初投稿なのでお手柔らかに。 勝手がアレ。 誤字脱字もめちゃくちゃありそう。 以上、あざした!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonとAnacondaとTensorflowでGPU環境を整える

メモ書きです. 色々調べて最新版入れればいいだろと思って https://www.tensorflow.org/install/source_windows?hl=ja ここから対応表見てPython3.8とTensorflow-gpu ver2.3でやって見たのですがうまく行かず・・・ どうも,TensorflowにはGPU用のビルドがあるらしく https://medium.com/lsc-psd/tensorflow2-1%E3%81%A7cuda10-1%E3%81%AA%E3%81%AE%E3%81%ABgpu%E3%81%8C%E8%AA%8D%E8%AD%98%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E6%B1%BA%E6%B3%95-6be5137ec216 現在2021年4月11日時点ではTensorflow 2.3をAnacondaのGUIからダウンロードするとmklビルド(CPU用のビルドらしい)が当たるらしく,それでうまく行かなかったようです. それぞれのバージョンをPython3.7とTensorflow_gpu-2.1.0まで落としてするとAnacondaのGUIからのインストールでもGPU用のビルドが適用され何事もなくGPU認識となりました. あとがき 自前でビルドしてしまえば関係ないかもしれないですね.あくまでAnacondaを利用した場合の話です. 使用したバージョン tensorflow_gpu 2.1.0 (AnacondaのGUIより) Python 3.7 (AnacondaのGUIより) MSVC 2019 (参考資料[2]より) cuDNN 7.6 (参考資料[3]より) CUDA 10.1 (参考資料[4]より) システム環境変数のPathに[5]に示してあるようなフォルダのパスを追加すること.(エクスプローラから直接確認コピペすること) 参考資料 [1] https://qiita.com/nemutas/items/c7d9cca91a7e1bd404b6 [2] https://www.kkaneko.jp/tools/win/buildtool.html#S1 [3] https://developer.nvidia.com/rdp/cudnn-archive [4] https://developer.nvidia.com/cuda-10.1-download-archive-base?target_os=Windows&target_arch=x86_64&target_version=10 [5] https://www.tensorflow.org/install/source_windows?hl=ja
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Tensorflowによるカテゴリー分類の、CSVデータのデータセットの作り方

はじめに  こんにちは、新しい技術が大好きなsazanami5です。qiita初投稿ですのでお手柔らかにお願いします。ご意見、ご指摘等ありましたらぜひコメントいただけると嬉しいです。  流行りの深層学習に興味を持ち、「深層学習の教科書」という本を読んでからtensorflowで時々遊んでいます。最近、TensorFlowを使ってカテゴリー分類の機械学習を自前のCSVデータで動かしたのですが、データセットがうまく作れず、少し苦戦してしまいました。データセットを作る方法をまとめた記事があまりなく、TensorFlowチュートリアルも少し読みにくかったため、今回データセットの作り方を中心に投稿させていただくことにしました。 よくあるアヤメの分類のようなカテゴリー分類のためのデータセットを作っています。 環境 私はローカルの仮想環境で実行していますが、Google Colaboratoryを利用すれば環境構築の必要はありません。 python 3.8.6 tensorflow 2.4.1 numpy 1.19.5 pandas1.2.1 matplotlib 3.3.3 使用するCSVデータ このデータは第二外国語を学習したことのある人を対象に行ったものです。 学習したことのある第二外国語(その他を含む8言語の中から選択)を最初に聞き、Q1~Q30では日常や生活習慣などに関する二択の質問を行っています。 質問の内容とラベルの内容が知りたい方は左の▶︎を押してください みづらくてゴメンなさい それぞれの回答を数字に変換したデータは以下のようになっています。 language Q1 Q2 Q3 ... Q30 0 0 1 0 ... 1 1 0 0 0 ... 0 2 1 0 0 ... 1 6 0 0 0 ... 1 4 1 0 0 ... 1 2 1 0 0 ... 0 ... ... ... ... ... ... 2 1 0 0 ... 1 データセットを作る ついに本題のデータセットの作り方について説明します。 上で用意したcsvデータを、分類するラベル(languageのカラム)と学習させるデータ(languageカラム以外の列)に分ける必要があります。 pandas等を使ってコードでも出来るのですが、今回はExcelを使い、以下の4つのファイルを作成しました。 訓練用データ(データの7割) x_train.csv(languageカラム以外の列) y_train.csv(languageカラムの列) 評価用データ(データの3割) x_eval.csv(languageカラム以外の列) y_eval.csv(languageカラムの列) ライブラリのインポート 必要なライブラリをインポートします。 データ処理は私が慣れているPandasを利用しました。 secondLanguageRecommend.ipynb import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Activation from tensorflow.keras.optimizers import Adam import numpy as np import pandas as pd 変数のセット secondLanguageRecommend.ipynb tf.random.set_seed(0) #再現性を持たせるためにランダムの値を固定する num_classes = 8 #ラベルの種類が8つ batch_size = 5 epochs = 20 CSVデータの読み込み pd.read_csvを使って読み込みます。windowsやmacのどちらのデータでも使えるようにするため、念の為エンコードをutf-8に指定しています。 データ構造 secondLanguageRecommend ├ secondLanguageRecommend.ipynb └ data/ ├ x_train.csv ├ x_eval.csv ├ y_train.csv └ y_eval.csv secondLanguageRecommend.ipynb #データの読み込み x_train = pd.read_csv("data/x_train.csv", encoding= "utf-8") x_test = pd.read_csv("data/x_eval.csv", encoding= "utf-8") y_train = pd.read_csv("data/y_train.csv", encoding= "utf-8") y_test = pd.read_csv("data/y_eval.csv", encoding= "utf-8") #一行目のカラムの行を取り除き、数値部分のみにする x_train = x_train.values x_test = x_test.values y_train = y_train.loc[:,"language"] y_test = y_test.loc[:,"language"] print(x_train[:10]) print(type(x_train)) 出力 out [[0 1 0 1 0 0 1 0 1 0 0 0 0 0 1 0 0 1 0 0 0 1 0 1 1 0 1 0 1 1] [0 0 0 0 0 0 1 1 0 0 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 0 1 1 0] [1 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 1 1 0 1 1 0 1 1 1 0 1 0 0 1] [0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 1 1 0 0 0 1 0 1 0 1 0 1 1] [1 0 0 1 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 0 0 1 0 0 1 1] [1 0 0 1 0 1 0 0 1 0 1 0 0 0 0 1 1 0 1 0 1 0 0 0 0 0 0 0 0 1] [0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 1 1 1 1 0 1 1 0 0 0 0 1 1 1 0] [0 0 0 0 0 1 1 0 1 1 0 1 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1] [0 0 0 0 0 1 1 1 0 0 0 0 1 0 0 1 1 1 0 1 1 0 1 0 0 1 0 0 1 0] [0 1 0 1 0 0 1 1 1 1 1 0 0 1 1 0 1 1 1 0 0 1 1 1 0 1 0 0 1 1]] <class 'numpy.ndarray'> ここで型がnumpy.ndarrayになっていることが大事です。 自分はここでDataFrameやlistではできないことを知らなくてつまずきました。ぴえん。 全ての列のデータを取りたい時は.values、特定の列のデータを取りたい時は.loc[:,"カラム名1","カラム名2"]を使ってください。 ラベルをone-hotベクトルにする one-hotベクトルとは One-hot ベクトルとは、(0,1,0,0,0,0) のように、1つの成分が1で残りの成分が全て0であるようなベクトルのことです。 引用元:算数から高度な数学まで、網羅的に解説したサイト 今回は0〜7の8個のカテゴリーがあるので、[[0][4][3]]のようなデータを[[1,0,0,0,0,0,0,0][0,0,0,0,1,0,0,0][0,0,0,1,0,0,0,0]]にするということです。 kerasにあるto_categorical関数を使って変換します。 keras公式ドキュメント secondLanguageRecommend.ipynb # convert class vectors to binary class matrices y_train = tf.keras.utils.to_categorical(y_train, num_classes) y_test = tf.keras.utils.to_categorical(y_test, num_classes) print(y_train[:10]) print(type(y_train)) 出力 out [[0. 1. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 0. 0. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0.]] <class 'numpy.ndarray'> 変換できました。 これでデータセットは完成です。 これをそのままfitの引数に渡すことで学習できます。 まとめ 型はnumpy.ndarray カテゴリー分類はone-hotベクトルに変換 最後に  データセットの作り方は今回の方法の他にも様々なものがあり、どれが正解というものでもないと思います。 一つの選択肢として誰かの役に立てば幸いです。 また、今回のような0と1の回答だけではない場合は正規化という作業も必要ですので、時間があれば追記いたします。 ここまで読んでいただきありがとうございました。 最後に今回のコードを貼っておきます。 secondLanguageRecommend.ipynb import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Activation from tensorflow.keras.optimizers import Adam import numpy as np import pandas as pd tf.random.set_seed(0) num_classes= 8 batch_size= 5 epochs= 20 #load data x_train= pd.read_csv("data/x_train.csv", encoding= "utf-8") x_test= pd.read_csv("data/x_eval.csv", encoding= "utf-8") y_train= pd.read_csv("data/y_train.csv", encoding= "utf-8") y_test= pd.read_csv("data/y_eval.csv", encoding= "utf-8") y_train = y_train.loc[:,"language"] y_test = y_test.loc[:,"language"] x_train = x_train.values print(x_train[:10]) print(type(x_train)) # convert class vectors to binary class matrices y_train = tf.keras.utils.to_categorical(y_train, num_classes) y_test = tf.keras.utils.to_categorical(y_test, num_classes) print(y_train[:10]) print(type(y_train)) # モデル作成 model = Sequential() model.add(Dense(32, activation='relu', input_shape=(30,))) model.add(Dense(32, activation='relu')) model.add(Dense(num_classes, activation='softmax')) model.summary() model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy']) # モデルを学習 history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_test, y_test)) # テストデータに対して誤差と精度を評価 score = model.evaluate(x_test, y_test, verbose=0) print('Test loss:', score[0]) print('Test accuracy:', score[1])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

気象庁のAPIを使って気温を取得する。

気象庁のWebサイトがリニューアルしたのでWebスクレイピングが出来ず気温が取得できない。 2021年2月16日、気象庁発表より引用: 気象庁ホームページを2月24日13時よりリニューアルします。様々な防災気象情報が一つのページで見やすく確認できるようになるほか、スマートフォンでも見やすくなります。 https://www.jma.go.jp/jma/press/2102/16a/20210216_jmahp_renewal.html このリニューアルにより、気象庁がアメダス情報を「表形式」(HTML的にはtableタグ)として表示していたデータが無くなり、Webスクレイピングで読み取れなくなりました。 私はかなり以前より、気象庁のWebサイトから地元の気温を抜き出して自宅Zabbixにデータを貯めていたので、何かしらの対処が必要となりました。 そこで、色々調べてみると非公式のようではありますが、気象庁がAPIを公開(アクセス可能な状態)にしているようでした。 それを使って、埼玉県熊谷市の気温を取得するPythonスクリプトを書いてみました。 outside_tmp.py # -*- coding: utf-8 -*- #!/usr/bin/env python # 注意点: # 例えば12:00にデータが更新される場合、12:00にAPIを叩くとレスポンスjsonが不足している場合がある。 # そのため、crontabなどにより、5分~15分sleepしてから取得した方が良さそう。 import pprint import requests import datetime def main(): # 現在の時刻を取得 now_raw = datetime.datetime.now() now = now_raw.strftime('%Y%m%d%H') # エンドポイントに最新時刻のurlを組み込み url = "https://www.jma.go.jp/bosai/amedas/data/map/" + now + "0000.json" header = {"content-type": "application/json"} # 正しいjsonが返って来た場合のみ処理し、正しくないjsonの場合はエラーとする。 try: response = requests.get(url, headers=header) # 熊谷の tempを表示 data = response.json() temp_arry = data["43056"]["temp"] print temp_arry[0] except KeyError: pass if __name__ == '__main__': main() スクリプト内の「43056」は熊谷のコードです。レスポンスjsonを見れば分かるのですが気温以外にも取得が可能です。 ただ、一つ引っかかった点としては、jsonのファイル名が変動するところです。 https://www.jma.go.jp/bosai/amedas/data/map/20210411050000.json 上記では20210411050000.jsonが以下のように分割されます。 2021年04月11日05時 + 0000 最後の0000は、分・秒と推測されます。今後は毎分・毎秒単位でデータの更新があるのかもしれません。 今のところ特にtokenなどは無いようなのですが、上記を参考にされる方は節度を守った上でのアクセスをご考慮ください。 僕は、crontabで以下のように記載しています。 outside_temp.shは上記pythonスクリプトの実行結果をファイルに書き出すだけのスクリプトです。書き出したファイルをzabbixに読み込ませています。 outside_temp.sh #!/bin/bash python /usr/local/src/outside_tmp.py > /tmp/outside_temp 15 */1 * * * /usr/local/src/outside_temp.sh > /dev/null 2>&1 このスクリプトを各n時間15分に取得しています。1:15 -> 2:15 -> 3:15 ... Zabbixでの取れ方 こんな感じになっています。 まとめ この情報は気象庁より公式的にアナウンスされていないため、APIの仕様が突然変わる可能性があります。 APIは正式に気象庁から外部へ公開されたものではありません。業務で利用する場合は気象庁へお問い合わせください。 Python初心者なので、コードにツッコミがあればガンガンお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SqlAlchemyのコネクションプーリングの効果比較

SqlAlchemyのコネクションプーリングの効果比較 コネクションプーリングのあり・なしを比較してみた。 それぞれ、 psycopg2 と pg8000 で確認してした。 結果は、コネクションプーリングなしが 280~310倍遅いということになったが、それ以上に今回の測定では pg8000 が若干早いという結果になった。 環境 Ubuntu 18.04 LTS ... WSL1 (Windows 10) libpq ... 13.2 Python 3.8 SQLAlchemy 1.4.6 pg8000 1.19.1(Pure Python) psycopg2 2.8.6 (Nativeライブラリを使用) 測定方法 測定方法は、参考1のやり方と合わせ1多重で1000回の接続・切断を行う。 ただし、コードの書き方は厳密に合わせはしない。なるべく今風の書き方をする。 なお、SqlAlchemyではデフォルトで QueuePool というものを使う。 コネクションプーリングを明示的に使わないようにするには NullPool を使う。 趣味の範囲なので、複数測った平均だとかはとらず、一発取り。1000回の試行の合計なので、それだけでバラつきは減る。プログラムの調整で複数回は実行するがバラつきはおよそ±3秒の範囲。 結果(概要) 参考にしたサイトだと 270 倍だったが、同じような水準になった。 なお、PCが貧弱で、かつ WSL1 を使っているので遅い面はあるので、相対値のみ参考にしてください。 (注: 1000で割った数が1コネクションあたりの時間になります。) pg8000 psycopg2 QueuePool 0.078 [s/1000conn] 0.094 [s/1000conn] NullPool 21.747s [s/1000conn] 28.930 [s/1000conn] Ratio 279倍 307倍 検証結果から言えること コネクションにかかるコストは相対的に大きい 1コネクションでみると一番遅いパターンでも 0.028 秒なので体感して遅い訳ではない pg8000(Pure Python) より、psycopg2(ネイティブクライアント使用)のが若干遅い 検証結果に対する補足 psycopg2 の方が遅いのは、この計測の場合 Python → C の呼び出しに関わるオーバヘッド(メモリ確保やデータ変換などの橋渡し)の影響が大きく出るのではないかと思われる 常駐プログラムでない場合は NullPool相当となる その都度処理が終わるなら当然コネクションの使いまわしができないから 検証コード 共通化することはできるのだが、まぁいいや。 pg8000 + NullPool # -*- coding: utf-8 -*- """Example of postgresql+pg8000.""" import os import time import sqlalchemy def main(engine): """Run main.""" t_0 = time.time() for _ in range(1000): with engine.connect(): pass t_1 = time.time() print(f'dt = {(t_1 - t_0):.3f}s') if __name__ == '__main__': engine_pg8000 = sqlalchemy.create_engine( sqlalchemy.engine.URL.create( 'postgresql+pg8000', host=os.environ.get('PGHOST'), port=os.environ.get('PGPORT'), database=os.environ.get('PGDATABASE'), username=os.environ.get('PGUSER'), password=os.environ.get('PGPASSWORD') ), poolclass=sqlalchemy.pool.NullPool ) main(engine_pg8000) engine_pg8000.dispose() # EOF pg8000 + QueuePool(default) # -*- coding: utf-8 -*- """Example of postgresql+pg8000.""" import os import time import sqlalchemy def main(engine): """Run main.""" t_0 = time.time() for _ in range(1000): with engine.connect(): pass t_1 = time.time() print(f'dt = {(t_1 - t_0):.3f}s') if __name__ == '__main__': engine_pg8000 = sqlalchemy.create_engine( sqlalchemy.engine.URL.create( 'postgresql+pg8000', host=os.environ.get('PGHOST'), port=os.environ.get('PGPORT'), database=os.environ.get('PGDATABASE'), username=os.environ.get('PGUSER'), password=os.environ.get('PGPASSWORD') ), pool_size=1, max_overflow=1 ) main(engine_pg8000) engine_pg8000.dispose() # EOF psycopg2 + NullPool # -*- coding: utf-8 -*- """Example of postgresql+psycopg2.""" import os import time import sqlalchemy def main(engine): """Run main.""" t_0 = time.time() for _ in range(1000): with engine.connect(): pass t_1 = time.time() print(f'dt = {(t_1 - t_0):.3f}s') if __name__ == '__main__': engine_psycopg2 = sqlalchemy.create_engine( sqlalchemy.engine.URL.create( 'postgresql+psycopg2', host=os.environ.get('PGHOST'), port=os.environ.get('PGPORT'), database=os.environ.get('PGDATABASE'), username=os.environ.get('PGUSER'), password=os.environ.get('PGPASSWORD') ), poolclass=sqlalchemy.pool.NullPool ) main(engine_psycopg2) engine_psycopg2.dispose() # EOF psycopg2 + QueuePool(default) # -*- coding: utf-8 -*- """Example of postgresql+psycopg2.""" import os import time import sqlalchemy def main(engine): """Run main.""" t_0 = time.time() for _ in range(1000): with engine.connect(): pass t_1 = time.time() print(f'dt = {(t_1 - t_0):.3f}s') if __name__ == '__main__': engine_psycopg2 = sqlalchemy.create_engine( sqlalchemy.engine.URL.create( 'postgresql+psycopg2', host=os.environ.get('PGHOST'), port=os.environ.get('PGPORT'), database=os.environ.get('PGDATABASE'), username=os.environ.get('PGUSER'), password=os.environ.get('PGPASSWORD') ), pool_size=1, max_overflow=1 ) main(engine_psycopg2) engine_psycopg2.dispose() # EOF 参考 測定の参考にしたサイト Python データベースコネクションをプーリング(SQLAlchemy使用) - Symfoware SQLAlchemy関連 Connection Pooling — SQLAlchemy 1.4 Documentation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flask、PyJWTを使用したGoogle OpenID 連携メモ

Flask とPyJWTを使用したGoogleとのOpenID連携方法についてメモする。 大まかな処理の流れ Googleへ認可リクエスト(Authorization Request)を行う。 ユーザー認証/同意を行い、認可レスポンスを受け取る。 認可レスポンスを使ってトークンリクエストを行う。 事前準備 Googleデベロッパーコンソールからプロジェクト、クライアントを登録する。 アプリケーションの種類 ウェブアプリケーション クライアントID/シークレット発行 スコープ設定 リダイレクトURI設定 今回はhttp://localhost:5000/callbackを設定 Pythonライブラリインストール flask ウェブアプリ開発用フレームワーク pyjwt JSON Web Tokenエンコード/デコード用ライブラリ id_token検証に使用する。 id_token署名情報確認 jwks_uri からJWKを確認する。 id_tokenのヘッダー記載のkidと同じJWKを確認する(下記jwk_json)。 JWKに対してpyjwtを使用して公開鍵を取得する。 from jwt.algorithms import RSAAlgorithm jwk_json = { "e": "AQAB", "use": "sig", "n": "q_GoX7XASWstA7CZs3acUgCVB2QhwhupF1WZsIr6FoI-DpLaiTlGLzEJlkLKW2nthUP35lqhXilaInOAN86sOEssz4h_uEycVpM_xLBRR-7Rqs5iXype340JV4pNzruXX5Z_Q4D7YLvm2E1QWivvTK4FiSCeBbo78Lpkr5atiHmWEcLENoquhEHdpij3wppdDlL5eUAy4xH6Ait5IDe66RehBEGfs3MLnCKyGAPIammSUruV0BEmUPfecLoXNhpuAfoGs3TO-5CIt1jmaRL2B-A2UxhPQkpE4Q-U6OJ81i4nzs34dtaQhFfT9pZqkgOwIJ4Djj7HI1xKOmoExMCDLw", "kid": "774573218c6f6a2fe50e29acbc686432863fc9c3", "kty": "RSA", "alg": "RS256" } public_key = RSAAlgorithm.from_jwk(jwk_json) 実装 認可リクエスト ~ トークンリクエスト(id_token検証含む)までのコード import hashlib from flask import Flask, request, redirect, session import urllib.request import urllib.parse import json import base64 from pprint import pprint import os import jwt from jwt.algorithms import RSAAlgorithm # id_token 検証用公開鍵 # https://www.googleapis.com/oauth2/v3/certs jwk_json = { "e": "AQAB", "use": "sig", "n": "q_GoX7XASWstA7CZs3acUgCVB2QhwhupF1WZsIr6FoI-DpLaiTlGLzEJlkLKW2nthUP35lqhXilaInOAN86sOEssz4h_uEycVpM_xLBRR-7Rqs5iXype340JV4pNzruXX5Z_Q4D7YLvm2E1QWivvTK4FiSCeBbo78Lpkr5atiHmWEcLENoquhEHdpij3wppdDlL5eUAy4xH6Ait5IDe66RehBEGfs3MLnCKyGAPIammSUruV0BEmUPfecLoXNhpuAfoGs3TO-5CIt1jmaRL2B-A2UxhPQkpE4Q-U6OJ81i4nzs34dtaQhFfT9pZqkgOwIJ4Djj7HI1xKOmoExMCDLw", "kid": "774573218c6f6a2fe50e29acbc686432863fc9c3", "kty": "RSA", "alg": "RS256" } public_key = RSAAlgorithm.from_jwk(jwk_json) # iss(トークン発行者) issuer = 'https://accounts.google.com' # クライアント情報 # 自身の環境に合わせて設定する。 client_id = <YOUR_CLIENT_ID> client_secret = <YOUR_CLIENT_SECRET> redirect_uri = 'http://localhost:5000/callback' # Google エンドポイント authorization_base_url = 'https://accounts.google.com/o/oauth2/v2/auth' token_url = 'https://www.googleapis.com/oauth2/v4/token' app = Flask(__name__) app.secret_key = 'session_key' # 認可リクエスト用 @app.route("/login") def login(): # nonce、stateの生成と保存 nonce = hashlib.sha256(os.urandom(32)).hexdigest() state = hashlib.sha256(os.urandom(32)).hexdigest() session['nonce'] = nonce session['state'] = state # Googleへの認可リクエスト return redirect(authorization_base_url+'?{}'.format(urllib.parse.urlencode({ 'client_id': client_id, 'scope': 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile', 'redirect_uri': redirect_uri, 'state': state, 'nonce': nonce, 'response_type': 'code' }))) # 認可レスポンス用リダイレクションエンドポイント + トークンリクエスト用 @app.route("/callback") def callback(): # state 検証 state = request.args.get('state') if state != session['state']: print("invalid_redirect") code = request.args.get('code') # トークンリクエスト body = urllib.parse.urlencode({ 'code': code, 'client_id': client_id, 'client_secret': client_secret, 'redirect_uri': redirect_uri, 'grant_type': 'authorization_code' }).encode('utf-8') req = urllib.request.Request(token_url) with urllib.request.urlopen(req, data=body) as f: res = f.read() # id_token 検証 content = json.loads(res) id_token = content['id_token'] claims = jwt.decode(id_token, public_key, issuer=issuer, audience=client_id, algorithms=["RS256"]) # nonce 検証 if claims['nonce'] != session['nonce']: return "invalid id_token" return claims if __name__ == "__main__": app.run(debug=True) 参考情報 OpenID Connect python/flaskでgoogleにOpenIDでログインしてみた。ライブラリ無しで。 [Python] PyJWT で Google OAuth 2.0 API の ID Token を検証
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【メモ】PythonのC++の%sみたいなやつ

※これはメモ用です。 パターン1 moji = "hoge" print = (f"huga:{moji}:thinking") 出力 huga:hoge:thinking パターン2 moji = "hoge" print = ("huga:{}:thinking".format(moji)) 出力 huga:hoge:thinking f"{}"形式でも.format()形式でも出力は同じだがf"{}の方が分かりやすくてよき。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PySide2をゼロから学んでいく~#3 ラベル~

はじめに PythonでGUI開発をするためのライブラリ「PySide2」の基本的な使い方を、いくつかの段階に分けて説明していきます。 当ページではPySide2のラベルについて説明しています。 環境 以下の通りになります。 Windows 10 Python 3.8以降 知っておきたい用語  ラベル ウィンドウ上に表示されている情報やデータのことをラベルといいます。 ウィジェットと似ていますが、ウィジェットはウィンドウ上に機能を持つアイコン(ボタンやメニューなど)、ラベルはウィジェットの中でも人間にとって情報となりうるものだと思ってください(下図のようなイメージ)。 GUI開発の勉強をしていると「ウィンドウに文字を表示するにはラベルを使います」って説明をよく目にしますが、私は『ちょっと説明不足だな』って思います。実はラベルは画像や動画などにも適用できます。そのためラベル=文字ではなくラベル=情報やデータだと思ってください。 ベースプログラム 以下のウィンドウを表示するだけのプログラムをベースにして実装していきます。 # PySide2のモジュールを読み込む from PySide2 import QtWidgets # ウィンドウの見た目と各機能(今はウィンドウだけ) class MainWindow(QtWidgets.QWidget): def __init__(self): super().__init__() # アプリの実行と終了 app = QtWidgets.QApplication() window = MainWindow() window.show() app.exec_() PySide2でラベル 文字を表示 単に文字だけを表示してみます。 サンプルプログラム # PySide2のモジュールを読み込む from PySide2 import QtWidgets # ウィンドウの見た目と各機能 class MainWindow(QtWidgets.QWidget): def __init__(self): super().__init__() label = QtWidgets.QLabel(self) # ラベルオブジェクトの生成 label.setText("こんにちは。") # 文字列としてラベルを表示 # アプリの実行と終了 app = QtWidgets.QApplication() window = MainWindow() window.show() app.exec_() 実行結果 プログラム解説 ウィンドウにラベルを張り付けるにはラベルオブジェクトを取得する必要があります。ラベルオブジェクトとは「ラベル(文字や画像)を張り付ける場所」だと思ってください。ラベルオブジェクトはQtWidgets.QLabelクラスで取得できます。 書式: QtWidgets.QLabel(window_object) 引数: window_object: ラベルの張り付け対象となるウィンドウ ラベルオブジェクトだけでは、単にラベルの場所を確保しただけになります。そこに文字列を表示するにはsetText("string")メソッドを使用します。 書式: QtWidgets.QLabel(window_object).setText("string") 引数: strings: 表示したい文字列 サンプルプログラムでは生成したラベルオブジェクトlabelに「こんにちは」という文字列を張り付けているだけになります。 label = QtWidgets.QLabel(self) # ラベルオブジェクトの生成 label.setText("こんにちは。") # 文字列としてラベルを表示 フォント設定 文字サイズや文字色、背景色などのフォント設定します。 サンプルプログラム 以下のプログラムでは「文字色」「文字サイズ」「背景色」「フォント(文字の輪郭)」を設定しています。 # PySide2のモジュールを読み込む from PySide2 import QtWidgets, QtGui # ウィンドウの見た目と各機能 class MainWindow(QtWidgets.QWidget): def __init__(self): super().__init__() self.setGeometry(300, 300, 650, 300) # Qt Style Sheets (CSS風) で見た目を設定 label_style = """QLabel { color: #FF00AA; /* 文字色 */ font-size: 128px; /* 文字サイズ */ background-color: yellow; /* 背景色 */ font-family: 851CHIKARA-YOWAKU; /* フォント(文字の輪郭) */ }""" label = QtWidgets.QLabel(self) # ラベルオブジェクトの生成 label.setStyleSheet(label_style) # 指定したフォントを反映 label.setText("腹ン中が\nパンパンだぜ") # 適当な文字を表示 # アプリの実行と終了 app = QtWidgets.QApplication() window = MainWindow() window.show() app.exec_() 実行結果 以下のフォントは「851チカラヨワク」というものを使用してます。 プログラム解説 フォントを反映するには以下のsetStyleSheet(qt_style_sheets)メソッドを使用します。 書式: QtWidgets.QLabel(window_object).setStyleSheet(qt_style_sheets) 引数: qt_style_sheets: Qt Style Sheetsで設定したラベルの外見データ setStyleSheet(qt_style_sheets)メソッドにQt Style Sheetsで設定した外見データを引数として渡します。このQt Style Sheetsはラベルに限らず、Qtウィジェット全般の外見を設定できます。基本的にCSSと同じように記述します。 サンプルプログラムでは以下の変数label_styleで外見を設定して、setStyleSheet(qt_style_sheets)メソッドに引数として渡しています。 # Qt Style Sheets (CSS風) で見た目を設定 label_style = """QLabel { /* ラベル -> QLabelを設定 */ color: #FF00AA; /* 文字色 */ font-size: 128px; /* 文字サイズ */ background-color: yellow; /* 背景色 */ font-family: 851CHIKARA-YOWAKU; /* フォント(文字の輪郭) */ }""" Qt Style Sheetsの冒頭QLabelはラベルを指定する際に記述します。ほかにボタンを指定したい場合はQPushButton、メニューならQMenuなどを指定します。ここら辺に関しては、Qt Style Sheetsの公式リファレンスがありますので、以下のURLを参照してください。 Qt Style Sheets Reference Qt Style Sheets Examples ちなみにfont-familyに設定するフォント名はC:\Windows\Fontsで確認できます。後は下図の手順でフォント名を取得し、font-familyに設定します。 画像を表示 私の使用アイコンを表示させてみます。(私の手書きです) サンプルプログラム # PySide2のモジュールを読み込む from PySide2 import QtWidgets, QtGui # ウィンドウの見た目と各機能 class MainWindow(QtWidgets.QWidget): def __init__(self): super().__init__() self.setGeometry(300, 300, 180, 160) # ラベルオブジェクトの生成 label = QtWidgets.QLabel(self) # QPixmap型に変換してウィンドウに画像を表示 label.setPixmap(QtGui.QPixmap(R"C:\Users\からくり\OneDrive\デスクトップ\myicon.PNG")) # アプリの実行と終了 app = QtWidgets.QApplication() window = MainWindow() window.show() app.exec_() 実行結果 プログラム解説 ウィンドウ上に画像を表示するには以下のsetPixmapメソッドを使用しています。 書式: QtWidgets.QLabel(window_object).setPixmap(image_path) 引数: image_path: QPixmap型の画像ファイルパス setPixmap(image_path)メソッドを使用することでウィンドウに画像を表示させることができます。ですが引数の型はQPixmap型でなくてはいけません。 そこでstr型からQPixmap型へ変換するのにQtGui.QPixmap(image_path)を使用します。 importモジュール: PySide2.QtGui 書式: QtGui.QPixmap(image_path) 引数: image_path: str型の画像ファイルパス QtGui.QPixmap(image_path)はimage_pathで指定した画像のファイルパスをQPixmap型に変換して返すメソッドです。 GIFを表示 GIFをウィンドウに表示してみます。マイクラのワールド生成中のシーンを録画して、それをGIFファイルに変換したものを張り付けてみます。 ラベルでmp4の再生を試みましたが分かりませんでした。 PySide2.QMediaPlaylistモジュールを使用すればmp4を再生することは可能ですが、ラベルを使用しないため割愛。 サンプルプログラム 注記 再度記載しますが、以下のプログラムではmp4の再生はできません。 # PySide2のモジュールを読み込む from PySide2 import QtWidgets, QtGui # ウィンドウの見た目と各機能 class MainWindow(QtWidgets.QWidget): def __init__(self): super().__init__() self.setGeometry(300, 300, 1000, 600) # QMovie型でGIFファイルを取得 movie = QtGui.QMovie(R"C:\Users\からくり\OneDrive\デスクトップ\000.gif") # GIFファイルを再生 movie.start() label = QtWidgets.QLabel(self) # ラベルオブジェクトの生成 label.setMovie(movie) # ウィンドウにGIFを表示 # アプリの実行と終了 app = QtWidgets.QApplication() window = MainWindow() window.show() app.exec_() 実行結果 録画解像度を間違えてカクカクなGIFになったのは許して・・・ プログラム解説 GIFファイルはsetMovie(gif_movie)メソッドでラベルとして表示しています。 書式: QtWidgets.QLabel(window_object).setMovie(gif_movie) 引数: gif_movie: QMovie型のGIFファイルのパス mp4は試したところ再生できない setMovie(movie)メソッドの引数には再生させたいGIFファイルのパスを指定します。ですがQMovie型に変換しないとエラーになってしまいます。そこで以下のメソッドを使用してQMovie型に型変換します。 importモジュール: PySide2.QtGui 書式: QtGui.QMovie(movie_filepath) 引数: movie_filepath: 型変換対象のGIFファイルパス QMovie(movie_filepath)クラスで型変換したGIFファイルをそのままでは再生することができません。QMovie型に変換した後、以下のstart()メソッドでGIFファイルを再生させます。 書式: QtGui.QMovie(movie_filepath).start() 引数: なし start()メソッドを実行しないとウィンドウ上には何も表示されないままになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでクラス→JSONにする方法

クラスをJSONにする手順 JavaだとJacksonというくらすをJSONやXMLに変換するライブラリがあるんだけど、 Pythonは一度辞書型に変換しないといけないっぽい JSONへの返還手順 1. クラス変数を辞書型にする 2. json.dump()でJSONに変換 Pythonは全てのクラスに、自インスタンスのフィールド名と値を辞書型にするメソッドがあるので簡単です b こんな感じですね こんな感じの実装 service.py # 認証トークンをJSON化 jsonedToken = json.dumps(casheData.__dict__) DBから取ってきた値をJSONにする で、このjson.dump()メソッドなんですが String型しか変換できないので、 DBの値をJSONへコンバートしたりするときには、日付型やBooleanを文字列に変換しないといけないッス service.py # 認証トークンをJSON化 #DBのエンティティ authenticateToken = T_UserToken() #画面保存用 casheData= T_UserTokenDto() casheData.id = authenticateToken.id jsonedToken = json.dumps(casheData.__dict__) フィールドにバイト型が混ざっていた場合 で、JSONにするマジにハッシュが混ざっていた場合、 Pythonのhashlibが吐き出すハッシュはbyte型なので、 byte型からbase64に変換して、文字列にします。 サンプルはこんな感じ service.py # 認証トークンをJSON化 #DBのエンティティ authenticateToken = T_UserToken() #画面保存用 casheData= T_UserTokenDto() casheData.id = authenticateToken.id casheData.AuthenticateToken = base64.b64encode(casheData.AuthenticateToken).decode('utf-8') jsonedToken = json.dumps(casheData.__dict__) ざっくりこんな感じですね。 バイト文字列が混ざっていたりすると何気に分かりづらいバグになるかなー、と
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

# Windowsの画面操作を録画するPython

pyautoguiをつかって連続的にWindowsの画面キャプチャを行い、その時のマウスの状態を合成したものを連結して動画を作ります。画面操作の説明にお役立てください。ffmpegのインストールが必要です。重要そうなバージョンの情報は下記です。 Jupyterでデバッグしています。 C:\>ver Microsoft Windows [Version 10.0.19042.868] C:\>python --version Python 3.9.1 C:\>jupyter lab --version 3.0.7 C:\>pip show pyautogui Name: PyAutoGUI Version: 0.9.52 Summary: PyAutoGUI lets Python control the mouse and keyboard, and other GUI automation tasks. For Windows, macOS, and Linux, on Python 3 and 2. Home-page: https://github.com/asweigart/pyautogui (以下略) C:\>pip show pillow Name: Pillow Version: 8.1.2 Summary: Python Imaging Library (Fork) (以下略) 素材の準備 アイコン作成 フリー素材を探していたのですが、必要なものが単純すぎるのかなかなか良いものが見当たらず、自分で作ることにしました。 カーソル 上向きのやつを勘で作図し、23度傾けたらだいぶそれっぽくなりました。 import matplotlib.pyplot as plt import numpy as np A = np.array([(-0.7,-3),(-0.7,-0.7),(-3,-2),(0,5.0),(3,-2),(0.7,-0.7),(0.7,-3),(-0.7,-3)]) plt.figure(figsize=(2,2),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*A)),color='white',alpha=1) plt.plot(*list(zip(*A)),color='black',alpha=0.8,linewidth=10) plt.show() from io import BytesIO from PIL import Image def rotate(a,theta): return np.dot(a,np.array([ ( np.cos(theta), np.sin(theta)), (-np.sin(theta), np.cos(theta))])) A = np.array([(-0.7,-3),(-0.7,-0.7),(-3,-2),(0,5.0),(3,-2),(0.7,-0.7),(0.7,-3),(-0.7,-3)]) plt.figure(figsize=(2,2),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*rotate(A,23*np.pi/180))),color='white',alpha=1) plt.plot(*list(zip(*rotate(A,23*np.pi/180))),color='black',alpha=0.8, linewidth=10) buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0) plt.close() cursor = Image.open(buf).convert('RGBA') cursor 左クリック M = np.array([(-1,-1),(-1,1.6),(1,1.6),(1,-1),(-1,-1)]) L = np.array([(-1,0),(-1,1.6),(0,1.6),(0,0),(-1,0)]) R = np.array([(0,0),(0,1.6),(1,1.6),(1,0),(0,0)]) plt.figure(figsize=(2,3),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*M)),color='white',alpha=0.5) plt.plot(*list(zip(*M)),color='black',alpha=0.7,linewidth=10) plt.fill(*list(zip(*L)),color='red',alpha=1) plt.plot(*list(zip(*L)),color='black',alpha=0.5,linewidth=10) plt.plot(*list(zip(*R)),color='black',alpha=0.5,linewidth=10) buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0) plt.close() lclick = Image.open(buf).convert('RGBA') lclick M = np.array([(-1,-1),(-1,1.6),(1,1.6),(1,-1),(-1,-1)]) L = np.array([(-1,0),(-1,1.6),(0,1.6),(0,0),(-1,0)]) R = np.array([(0,0),(0,1.6),(1,1.6),(1,0),(0,0)]) plt.figure(figsize=(2,3),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*M)),color='white',alpha=0.5) plt.plot(*list(zip(*M)),color='black',alpha=0.7,linewidth=10) plt.fill(*list(zip(*R)),color='red',alpha=1) plt.plot(*list(zip(*R)),color='black',alpha=0.5,linewidth=10) plt.plot(*list(zip(*L)),color='black',alpha=0.5,linewidth=10) buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0) plt.close() rclick = Image.open(buf).convert('RGBA') rclick アイコンの背景が透過できているか確認 方眼紙の画像を作って合成してみました。 bg = Image.fromarray(np.array( [[[ 64,128, 64,255] if x%32==0 or y%32==0 else [192,255,240,255] for x in range(256) ] for y in range(256)], dtype=np.uint8), mode='RGBA') bg Jupyterを使ってファイル保存せずに画像を確認しながら作業を進めました。 from IPython.display import HTML import base64 方眼紙の罫線をなぞってみます。 imgs = [] x,y = 128,128 for _ in range(32): x += 1; img = bg.copy(); img.paste(cursor,(x,y),cursor); imgs.append(img) for _ in range(32): y -= 1; img = bg.copy(); img.paste(cursor,(x,y),cursor); imgs.append(img) for _ in range(32): x -= 1; img = bg.copy(); img.paste(cursor,(x,y),cursor); imgs.append(img) for _ in range(32): y += 1; img = bg.copy(); img.paste(cursor,(x,y),cursor); imgs.append(img) buf = BytesIO() imgs[0].save(buf, format='gif', save_all=True, append_images=imgs[1:], duration=20, loop=0) HTML('<img src="data:image/gif;base64,'+base64.encodebytes(buf.getvalue()).decode('utf8')+'"/>') いい感じにアイコンの背景が透けていますし、左上の角に変な空白もできていないようです。 これはplt.savefig(buf, bbox_inches='tight', pad_inches=0)のときのオプションのおかげです。 クリックしたときの画像の合成 img = bg.copy() x,y = 32,32 img.paste(cursor, (x,y), cursor) img.paste(lclick, (x-4,y+17), lclick) img img = bg.copy() x,y = 32,32 img.paste(cursor, (x,y), cursor) img.paste(rclick, (x-4,y+17), rclick) img 絵が下手ですが、マウスのつもりです。個人の感想ですが、使ってみると意外とわかりやすくてアリです。マウスが微妙に透けているのはplt.plotやplt.fillの引数alphaによる効果なので意図的です。 ドラッグしているときの動作を想定したアニメーションです。 imgs = [] for i in range(192): x = bg.copy() x.paste(cursor, (32+i,int(96+32*np.sin(2*np.pi*i/64))), cursor) x.paste(lclick, (32+i-4,int(96+32*np.sin(2*np.pi*i/64))+17), lclick) imgs.append(x) buf = BytesIO() imgs[0].save(buf, format='gif', save_all=True, append_images=imgs[1:], duration=20, loop=0) HTML('<img src="data:image/gif;base64,'+base64.encodebytes(buf.getvalue()).decode('utf8')+'"/>') imgs = [] for i in range(192): x = bg.copy() x.paste(cursor, (32+i,int(96+32*np.sin(2*np.pi*i/64))), cursor) x.paste(rclick, (32+i-4,int(96+32*np.sin(2*np.pi*i/64))+17), rclick) imgs.append(x) buf = BytesIO() imgs[0].save(buf, format='gif', save_all=True, append_images=imgs[1:], duration=20, loop=0) HTML('<img src="data:image/gif;base64,'+base64.encodebytes(buf.getvalue()).decode('utf8')+'"/>') 画面制御の基本 pyautogui 画面のタイトルを指定してそのスクリーンショットを取る自動化は以下です。 getWindowsWithTitle(title)の引数titleの値の文字列は大文字・小文字を区別してくれず、思ったよりたくさんのウィンドウの情報がlistに入ってくることがあるので、完全一致するものだけを得るためにif g.title==titleでフィルタしています。見つからなかったり、複数のウィンドウが見つかったりすると変数gが定まらなくなるので加減してください。g.boxはBox(left=1019, top=617, width=855, height=563)というような情報を返します。 import pyautogui as pg title = 'ごみ箱' [g] = [g for g in pg.getWindowsWithTitle(title) if g.title==title] scr = pg.screenshot(region=g.box) scr なお、デュアルディスプレイを使っている場合、1番めのスクリーンにあるウィンドウしか制御できませんでした。 pg.position() Point(x=848, y=-427) ctypes 現在のマウスの位置を取得するには、pg.position()だけでよいです。一方、マウスの左右のボタンの状態を確認する機能がありませんので、ctypesを使って以下をテストします。 # 左クリックすると終了する無限ループ while ctypes.windll.user32.GetAsyncKeyState(0x01) != 0x8000: pass # 右クリックすると終了する無限ループ while ctypes.windll.user32.GetAsyncKeyState(0x02) != 0x8000: pass ここまでを総合して、下記のテストを行いました。 # 左クリックした周辺をスクリーンショットして、カーソルを合成する while ctypes.windll.user32.GetAsyncKeyState(0x01) != 0x8000: x,y = pg.position() scr = pg.screenshot(region=(x-64,y-64,126,128)) scr.paste(cursor,(64,64),cursor) scr 上記を検知させるには、クリックが一瞬だとだめでした。たぶん0.1秒くらいはボタンをじっくり押す必要があります。 PythonによるWindowsのマウスの状態の取得についてはググって下記などにたどり着きましたのでctypesを使いました。 ものすごく詳しいページもありました。 画面を録画する ここまでの成果をもとに、特定の名前のウィンドウに対して30秒間の動画を作ってみます。 import pyautogui as pg from PIL import Image from io import BytesIO import datetime,ctypes title = 'ごみ箱' sec = 30 app = [e for e in pg.getWindowsWithTitle(title) if e.title == title][0] now = datetime.datetime.now() imgs = [] x0,y0,w,h = app.box if w%2 != 0: w+=1 if h%2 != 0: h+=1 while datetime.datetime.now() < now + datetime.timedelta(seconds=sec): x, y = pg.position() im = pg.screenshot(region=(x0,y0,w,h)).convert('RGBA') im.paste(cursor, (x-x0, y-y0), cursor) if ctypes.windll.user32.GetAsyncKeyState(0x01) == 0x8000: im.paste(lclick, (x-x0-4, y-y0+17), lclick) if ctypes.windll.user32.GetAsyncKeyState(0x02) == 0x8000: im.paste(rclick, (x-x0-4, y-y0+17), rclick) imgs.append(im) buf = BytesIO() from IPython.display import HTML import base64 buf = BytesIO() imgs[0].save(buf, format='png', save_all=True, append_images=imgs[1:], duration=100, loop=0) HTML('<img src="data:image/png;base64,'+base64.encodebytes(buf.getvalue()).decode('utf8')+'"/>') ここで、下記の部分は、動画ファイルにするために幅や高さが偶数でないといけないという決まりがあるので2で割り切れなかったら1ピクセル余分にキャプチャするようにしています。ウィンドウが画面の端にあったら調整できないので上手く動かないかも知れません。 if w%2 != 0: w+=1 if h%2 != 0: h+=1 PNG形式の画像ファイルのアニメーションは環境によっては静止画のようになってしまうかもしれません。また、いつ動画が終わったのか分からないのでやはり動画を作成して再生時間の経過が分かるようにします。 動画にするにはffmpegを使いたいので元のPNG画像をファイルも書き込まないといけません。 t = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') imgs[0].save(t+'.png', format='png', save_all=True, append_images=imgs[1:], duration=int(sec/len(imgs)*1000), loop=0) import subprocess subprocess.run('ffmpeg -i '+t+'.png -r '+str(int(len(imgs)/sec)) +' -pix_fmt yuv420p '+t+'.mp4 -y') from IPython.display import Video Video(t+'.mp4') 上記の処理で14秒くらいで下記のファイルができました。 2021-04-11_21-20-39.png 2021-04-11_21-20-39.mp4 動画はこちら。誤字っているけれどTwitterも初心者なので直し方が分からない。 コード全体 import matplotlib.pyplot as plt import numpy as np from io import BytesIO from PIL import Image # カーソルのアイコン作成 def rotate(a,theta): return np.dot(a,np.array([ ( np.cos(theta), np.sin(theta)), (-np.sin(theta), np.cos(theta))])) A = np.array([(-0.7,-3),(-0.7,-0.7),(-3,-2),(0,5.0),(3,-2),(0.7,-0.7),(0.7,-3),(-0.7,-3)]) plt.figure(figsize=(2,2),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*rotate(A,23*np.pi/180))),color='white',alpha=1) plt.plot(*list(zip(*rotate(A,23*np.pi/180))),color='black',alpha=0.8, linewidth=10) buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0) plt.close() cursor = Image.open(buf).convert('RGBA') # 左クリックのアイコン作成 M = np.array([(-1,-1),(-1,1.6),(1,1.6),(1,-1),(-1,-1)]) L = np.array([(-1,0),(-1,1.6),(0,1.6),(0,0),(-1,0)]) R = np.array([(0,0),(0,1.6),(1,1.6),(1,0),(0,0)]) plt.figure(figsize=(2,3),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*M)),color='white',alpha=0.5) plt.plot(*list(zip(*M)),color='black',alpha=0.7,linewidth=10) plt.fill(*list(zip(*L)),color='red',alpha=1) plt.plot(*list(zip(*L)),color='black',alpha=0.5,linewidth=10) plt.plot(*list(zip(*R)),color='black',alpha=0.5,linewidth=10) buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0) plt.close() lclick = Image.open(buf).convert('RGBA') # 右クリックのアイコン作成 M = np.array([(-1,-1),(-1,1.6),(1,1.6),(1,-1),(-1,-1)]) L = np.array([(-1,0),(-1,1.6),(0,1.6),(0,0),(-1,0)]) R = np.array([(0,0),(0,1.6),(1,1.6),(1,0),(0,0)]) plt.figure(figsize=(2,3),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*M)),color='white',alpha=0.5) plt.plot(*list(zip(*M)),color='black',alpha=0.7,linewidth=10) plt.fill(*list(zip(*R)),color='red',alpha=1) plt.plot(*list(zip(*R)),color='black',alpha=0.5,linewidth=10) plt.plot(*list(zip(*L)),color='black',alpha=0.5,linewidth=10) buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0) plt.close() rclick = Image.open(buf).convert('RGBA') import pyautogui as pg import datetime,ctypes # 画面録画 title = 'ごみ箱' sec = 30 app = [e for e in pg.getWindowsWithTitle(title) if e.title == title][0] now = datetime.datetime.now() imgs = [] x0,y0,w,h = app.box if w%2 != 0: w+=1 if h%2 != 0: h+=1 print('「'+title+'」というタイトルのウィンドウを対象に'+str(sec)+'秒の動画を作成') t = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') print('録画開始') while datetime.datetime.now() < now + datetime.timedelta(seconds=sec): x, y = pg.position() im = pg.screenshot(region=(x0,y0,w,h)).convert('RGBA') im.paste(cursor, (x-x0, y-y0), cursor) if ctypes.windll.user32.GetAsyncKeyState(0x01) == 0x8000: im.paste(lclick, (x-x0-4, y-y0+17), lclick) if ctypes.windll.user32.GetAsyncKeyState(0x02) == 0x8000: im.paste(rclick, (x-x0-4, y-y0+17), rclick) imgs.append(im) buf = BytesIO() print('録画終了') # PNGファイル書き出し imgs[0].save(t+'.png', format='png', save_all=True, append_images=imgs[1:], duration=int(sec/len(imgs)*1000), loop=0) print('PNG: '+t+'.png') # MP$ファイル書き出し import subprocess subprocess.run('ffmpeg -i '+t+'.png -r '+str(int(len(imgs)/sec)) +' -pix_fmt yuv420p '+t+'.mp4 -y') print('MP4: '+t+'.mp4') 「ごみ箱」というタイトルのウィンドウを対象に30秒の動画を作成 録画開始 録画終了 PNG: 2021-04-11_21-20-39.png MP4: 2021-04-11_21-20-39.mp4 みたいなのが表示され、画像と動画が得られます。 処理速度に依存すると思いますが、およそ9fpsか10fpsの動画になるので、マウスのクリックが0.1秒を目安に長めに押さないと検知が追いつかなくてクリック時のおまけの画像が描画されないと思います。30秒の動画で作成されたファイルのサイズはPNGが977KBで、MP4が250KBでした。GIFもやっては見ましたが、残念な画質だし3MBとかに肥大化したりするので微妙です。Animated PNGはTwitterに投稿すると先頭画像だけの静止画にされてしまうし表示できる環境が限られるのでMP4で配布するのが現実的かなと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windowsの画面操作を録画するPython

pyautoguiをつかって連続的にWindowsの画面キャプチャを行い、その時のマウスの状態を合成したものを連結して動画を作ります。画面操作の説明にお役立てください。ffmpegのインストールが必要です。重要そうなバージョンの情報は下記です。 Jupyterでデバッグしています。 C:\>ver Microsoft Windows [Version 10.0.19042.868] C:\>python --version Python 3.9.1 C:\>jupyter lab --version 3.0.7 C:\>pip show pyautogui Name: PyAutoGUI Version: 0.9.52 Summary: PyAutoGUI lets Python control the mouse and keyboard, and other GUI automation tasks. For Windows, macOS, and Linux, on Python 3 and 2. Home-page: https://github.com/asweigart/pyautogui (以下略) C:\>pip show pillow Name: Pillow Version: 8.1.2 Summary: Python Imaging Library (Fork) (以下略) 素材の準備 アイコン作成 フリー素材を探していたのですが、必要なものが単純すぎるのかなかなか良いものが見当たらず、自分で作ることにしました。 カーソル 上向きのやつを勘で作図し、23度傾けたらだいぶそれっぽくなりました。 import matplotlib.pyplot as plt import numpy as np A = np.array([(-0.7,-3),(-0.7,-0.7),(-3,-2),(0,5.0),(3,-2),(0.7,-0.7),(0.7,-3),(-0.7,-3)]) plt.figure(figsize=(2,2),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*A)),color='white',alpha=1) plt.plot(*list(zip(*A)),color='black',alpha=0.8,linewidth=10) plt.show() from io import BytesIO from PIL import Image def rotate(a,theta): return np.dot(a,np.array([ ( np.cos(theta), np.sin(theta)), (-np.sin(theta), np.cos(theta))])) A = np.array([(-0.7,-3),(-0.7,-0.7),(-3,-2),(0,5.0),(3,-2),(0.7,-0.7),(0.7,-3),(-0.7,-3)]) plt.figure(figsize=(2,2),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*rotate(A,23*np.pi/180))),color='white',alpha=1) plt.plot(*list(zip(*rotate(A,23*np.pi/180))),color='black',alpha=0.8, linewidth=10) buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0, transparent=True) plt.close() cursor = Image.open(buf).convert('RGBA') cursor ここで、plt.savefigのオプションには苦労しました。Jupyter上ではどうやら自動で背景が透過されるのですが、ローカルでは背景色は白くなってしまいました。transparent=Trueがあれば解決できます(意外な発見でした)。 都度画像をバッファ内に作る必要はないのでファイル保存しておいて次回以降はそこから読み込む形でももちろんよいです。 例: cursor.save('cursor.png',format='png') 次回起動時: cursor = Image.open('cursor.png').convert('RGBA') .convert('RGBA')はつけなくても透過部分を認識してくれましたが、付けたほうが無難な気がします。 左クリック M = np.array([(-1,-1),(-1,1.6),(1,1.6),(1,-1),(-1,-1)]) L = np.array([(-1,0),(-1,1.6),(0,1.6),(0,0),(-1,0)]) R = np.array([(0,0),(0,1.6),(1,1.6),(1,0),(0,0)]) plt.figure(figsize=(2,3),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*M)),color='white',alpha=0.5) plt.plot(*list(zip(*M)),color='black',alpha=0.7,linewidth=10) plt.fill(*list(zip(*L)),color='red',alpha=1) plt.plot(*list(zip(*L)),color='black',alpha=0.5,linewidth=10) plt.plot(*list(zip(*R)),color='black',alpha=0.2,linewidth=10) plt.text(-0.4,0.6,'L',fontsize=110,fontfamily='Consolas',color='white',ha='center',va='center',fontweight='bold') buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0, transparent=True) plt.close() lclick = Image.open(buf).convert('RGBA') lclick 右クリック M = np.array([(-1,-1),(-1,1.6),(1,1.6),(1,-1),(-1,-1)]) L = np.array([(-1,0),(-1,1.6),(0,1.6),(0,0),(-1,0)]) R = np.array([(0,0),(0,1.6),(1,1.6),(1,0),(0,0)]) plt.figure(figsize=(2,3),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*M)),color='white',alpha=0.5) plt.plot(*list(zip(*M)),color='black',alpha=0.7,linewidth=10) plt.fill(*list(zip(*R)),color='red',alpha=1) plt.plot(*list(zip(*R)),color='black',alpha=0.5,linewidth=10) plt.plot(*list(zip(*L)),color='black',alpha=0.2,linewidth=10) plt.text(0.4,0.6,'R',fontsize=110,fontfamily='Consolas',color='white',ha='center',va='center',fontweight='bold') buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0, transparent=True) plt.close() rclick = Image.open(buf).convert('RGBA') rclick ※クリック時のアイコンにLとRの文字は初版公開時には無かったですが、あったほうが良いと思って付け足しました。こういう融通の利くのはアイコンを自作した甲斐のあるところだと思います。 アイコンの背景が透過できているか確認 狙った座標にカーソルの矢印の先端が来ているか、方眼紙の画像に合成してテストすることにしました。 bg = Image.fromarray(np.array( [[[ 64,128, 64,255] if x%32==0 or y%32==0 else [192,255,240,255] for x in range(256) ] for y in range(256)], dtype=np.uint8), mode='RGBA') bg Jupyterを使ってファイル保存せずに画像を確認しながら作業を進めました。 from IPython.display import HTML import base64 方眼紙の罫線をなぞってみます。 imgs = [] x,y = 128,128 for _ in range(32): x += 1; img = bg.copy(); img.paste(cursor,(x,y),cursor); imgs.append(img) for _ in range(32): y -= 1; img = bg.copy(); img.paste(cursor,(x,y),cursor); imgs.append(img) for _ in range(32): x -= 1; img = bg.copy(); img.paste(cursor,(x,y),cursor); imgs.append(img) for _ in range(32): y += 1; img = bg.copy(); img.paste(cursor,(x,y),cursor); imgs.append(img) buf = BytesIO() imgs[0].save(buf, format='gif', save_all=True, append_images=imgs[1:], duration=20, loop=0) HTML('<img src="data:image/gif;base64,'+base64.encodebytes(buf.getvalue()).decode('utf8')+'"/>') いい感じにアイコンの背景が透けていますし、左上の角に変な空白もできていないようです。 これはplt.savefig(buf, bbox_inches='tight', pad_inches=0)のときのオプションのおかげです。 クリックしたときの画像の合成 img = bg.copy() x,y = 32,32 img.paste(cursor, (x,y), cursor) img.paste(lclick, (x-4,y+17), lclick) img img = bg.copy() x,y = 32,32 img.paste(cursor, (x,y), cursor) img.paste(rclick, (x-4,y+17), rclick) img 絵が下手ですが、マウスのつもりです。個人の感想ですが、使ってみると意外とわかりやすくてアリです。マウスが微妙に透けているのはplt.plotやplt.fillの引数alphaによる効果なので意図的です。 ドラッグしているときの動作を想定したアニメーションです。 imgs = [] for i in range(192): x = bg.copy() x.paste(cursor, (32+i,int(96+32*np.sin(2*np.pi*i/64))), cursor) x.paste(lclick, (32+i-4,int(96+32*np.sin(2*np.pi*i/64))+17), lclick) imgs.append(x) buf = BytesIO() imgs[0].save(buf, format='gif', save_all=True, append_images=imgs[1:], duration=20, loop=0) HTML('<img src="data:image/gif;base64,'+base64.encodebytes(buf.getvalue()).decode('utf8')+'"/>') imgs = [] for i in range(192): x = bg.copy() x.paste(cursor, (32+i,int(96+32*np.sin(2*np.pi*i/64))), cursor) x.paste(rclick, (32+i-4,int(96+32*np.sin(2*np.pi*i/64))+17), rclick) imgs.append(x) buf = BytesIO() imgs[0].save(buf, format='gif', save_all=True, append_images=imgs[1:], duration=20, loop=0) HTML('<img src="data:image/gif;base64,'+base64.encodebytes(buf.getvalue()).decode('utf8')+'"/>') 画面制御の基本 pyautogui 画面のタイトルを指定してそのスクリーンショットを取る自動化は以下です。 getWindowsWithTitle(title)の引数titleの値の文字列は大文字・小文字を区別してくれず、思ったよりたくさんのウィンドウの情報がlistに入ってくることがあるので、完全一致するものだけを得るためにif g.title==titleでフィルタしています。見つからなかったり、複数のウィンドウが見つかったりすると変数gが定まらなくなるので加減してください。g.boxはBox(left=1019, top=617, width=855, height=563)というような情報を返します。 import pyautogui as pg title = 'ごみ箱' [g] = [g for g in pg.getWindowsWithTitle(title) if g.title==title] scr = pg.screenshot(region=g.box) scr なお、デュアルディスプレイを使っている場合、1番めのスクリーンにあるウィンドウしかキャプチャできませんでした。 現在のマウスの位置を取得するには、pg.position()だけでよいです。 pg.position() Point(x=848, y=-427) ctypes 一方、マウスの左右のボタンの状態を確認する機能がありませんので、ctypesを使って以下をテストします。 # 左クリックすると終了する無限ループ while ctypes.windll.user32.GetAsyncKeyState(0x01) != 0x8000: pass # 右クリックすると終了する無限ループ while ctypes.windll.user32.GetAsyncKeyState(0x02) != 0x8000: pass ここまでを総合して、下記のテストを行いました。 # 左クリックした周辺をスクリーンショットして、カーソルを合成する while ctypes.windll.user32.GetAsyncKeyState(0x01) != 0x8000: x,y = pg.position() scr = pg.screenshot(region=(x-64,y-64,126,128)) scr.paste(cursor,(64,64),cursor) scr 上記を検知させるには、クリックが一瞬だとだめでした。たぶん0.1秒くらいはボタンをじっくり押す必要があります。 PythonによるWindowsのマウスの状態の取得についてはググって下記などにたどり着きましたのでctypesを使いました。 ものすごく詳しいページもありました。 画面を録画する ここまでの成果をもとに、特定の名前のウィンドウに対して30秒間の動画を作ってみます。 import pyautogui as pg from PIL import Image from io import BytesIO import datetime,ctypes title = 'ごみ箱' sec = 30 app = [e for e in pg.getWindowsWithTitle(title) if e.title == title][0] now = datetime.datetime.now() imgs = [] x0,y0,w,h = app.box if w%2 != 0: w+=1 if h%2 != 0: h+=1 while datetime.datetime.now() < now + datetime.timedelta(seconds=sec): x, y = pg.position() im = pg.screenshot(region=(x0,y0,w,h)).convert('RGBA') im.paste(cursor, (x-x0, y-y0), cursor) if ctypes.windll.user32.GetAsyncKeyState(0x01) == 0x8000: im.paste(lclick, (x-x0-4, y-y0+17), lclick) if ctypes.windll.user32.GetAsyncKeyState(0x02) == 0x8000: im.paste(rclick, (x-x0-4, y-y0+17), rclick) imgs.append(im) buf = BytesIO() from IPython.display import HTML import base64 buf = BytesIO() imgs[0].save(buf, format='png', save_all=True, append_images=imgs[1:], duration=100, loop=0) HTML('<img src="data:image/png;base64,'+base64.encodebytes(buf.getvalue()).decode('utf8')+'"/>') ここで、下記の部分は、動画ファイルにするために幅や高さが偶数でないといけないという決まりがあるので2で割り切れなかったら1ピクセル余分にキャプチャするようにしています。ウィンドウが画面の端にあったら調整できないので上手く動かないかも知れません。 if w%2 != 0: w+=1 if h%2 != 0: h+=1 PNG形式の画像ファイルのアニメーションは環境によっては静止画のようになってしまうかもしれません。また、いつ動画が終わったのか分からないのでやはり動画を作成して再生時間の経過が分かるようにします。 ↑ マウスのアイコンにLやRの文字がないバージョン。↓ LとRを書き足したバージョン。 私の環境のChromeでは、上記の画像は止まって見えますが、クリックして個別のタブで開くとカーソルが動き始めます。さて、いよいよ動画にするにはffmpegを使いたいので元のPNG画像をファイルも書き込まないといけません。 t = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') imgs[0].save(t+'.png', format='png', save_all=True, append_images=imgs[1:], duration=int(sec/len(imgs)*1000), loop=0) import subprocess subprocess.run('ffmpeg -i '+t+'.png -r '+str(int(len(imgs)/sec)) +' -pix_fmt yuv420p '+t+'.mp4 -y') from IPython.display import Video Video(t+'.mp4') 上記の処理で14秒くらいで下記のファイルができました。 2021-04-11_21-20-39.png 2021-04-11_21-20-39.mp4 動画はこちら。Twitterも初心者なので誤字ったときの直し方が分からない。 コード全体 import matplotlib.pyplot as plt import numpy as np from io import BytesIO from PIL import Image # カーソルのアイコン作成 def rotate(a,theta): return np.dot(a,np.array([ ( np.cos(theta), np.sin(theta)), (-np.sin(theta), np.cos(theta))])) A = np.array([(-0.7,-3),(-0.7,-0.7),(-3,-2),(0,5.0),(3,-2),(0.7,-0.7),(0.7,-3),(-0.7,-3)]) plt.figure(figsize=(2,2),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*rotate(A,23*np.pi/180))),color='white',alpha=1) plt.plot(*list(zip(*rotate(A,23*np.pi/180))),color='black',alpha=0.8, linewidth=10) buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0, transparent=True) plt.close() cursor = Image.open(buf).convert('RGBA') # 左クリックのアイコン作成 M = np.array([(-1,-1),(-1,1.6),(1,1.6),(1,-1),(-1,-1)]) L = np.array([(-1,0),(-1,1.6),(0,1.6),(0,0),(-1,0)]) R = np.array([(0,0),(0,1.6),(1,1.6),(1,0),(0,0)]) plt.figure(figsize=(2,3),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*M)),color='white',alpha=0.5) plt.plot(*list(zip(*M)),color='black',alpha=0.7,linewidth=10) plt.fill(*list(zip(*L)),color='red',alpha=1) plt.plot(*list(zip(*L)),color='black',alpha=0.5,linewidth=10) plt.plot(*list(zip(*R)),color='black',alpha=0.2,linewidth=10) plt.text(-0.4,0.6,'L',fontsize=110,fontfamily='Consolas',color='white',ha='center',va='center',fontweight='bold') buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0, transparent=True) plt.close() lclick = Image.open(buf).convert('RGBA') # 右クリックのアイコン作成 M = np.array([(-1,-1),(-1,1.6),(1,1.6),(1,-1),(-1,-1)]) L = np.array([(-1,0),(-1,1.6),(0,1.6),(0,0),(-1,0)]) R = np.array([(0,0),(0,1.6),(1,1.6),(1,0),(0,0)]) plt.figure(figsize=(2,3),dpi=8) plt.axes().set_aspect('equal') plt.axis('off') plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0) plt.fill(*list(zip(*M)),color='white',alpha=0.5) plt.plot(*list(zip(*M)),color='black',alpha=0.7,linewidth=10) plt.fill(*list(zip(*R)),color='red',alpha=1) plt.plot(*list(zip(*R)),color='black',alpha=0.5,linewidth=10) plt.plot(*list(zip(*L)),color='black',alpha=0.2,linewidth=10) plt.text(0.4,0.6,'R',fontsize=110,fontfamily='Consolas',color='white',ha='center',va='center',fontweight='bold') buf = BytesIO() plt.savefig(buf, bbox_inches='tight', pad_inches=0, transparent=True) plt.close() rclick = Image.open(buf).convert('RGBA') import pyautogui as pg import datetime,ctypes # 画面録画 title = 'ごみ箱' sec = 30 app = [e for e in pg.getWindowsWithTitle(title) if e.title == title][0] now = datetime.datetime.now() imgs = [] x0,y0,w,h = app.box if w%2 != 0: w+=1 if h%2 != 0: h+=1 print('「'+title+'」というタイトルのウィンドウを対象に'+str(sec)+'秒の動画を作成') t = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') print('録画開始') while datetime.datetime.now() < now + datetime.timedelta(seconds=sec): x, y = pg.position() im = pg.screenshot(region=(x0,y0,w,h)).convert('RGBA') im.paste(cursor, (x-x0, y-y0), cursor) if ctypes.windll.user32.GetAsyncKeyState(0x01) == 0x8000: im.paste(lclick, (x-x0-4, y-y0+17), lclick) if ctypes.windll.user32.GetAsyncKeyState(0x02) == 0x8000: im.paste(rclick, (x-x0-4, y-y0+17), rclick) imgs.append(im) print('録画終了') # PNGファイル書き出し imgs[0].save(t+'.png', format='png', save_all=True, append_images=imgs[1:], duration=int(sec/len(imgs)*1000), loop=0) print('PNG: '+t+'.png') # MP4ファイル書き出し import subprocess subprocess.run('ffmpeg -i '+t+'.png -r '+str(int(len(imgs)/sec)) +' -pix_fmt yuv420p '+t+'.mp4 -y') print('MP4: '+t+'.mp4') 上記の実行で、録画が始まるまでに1秒弱かかり、30秒間の録画後にファイルの書き出しに14秒ほどかかったので、トータルで45秒の処理時間でした。 「ごみ箱」というタイトルのウィンドウを対象に30秒の動画を作成 録画開始 録画終了 PNG: 2021-04-11_21-20-39.png MP4: 2021-04-11_21-20-39.mp4 みたいなのが表示され、画像と動画が得られます。アイコンを毎回の起動時に作図する必要はないので、アイコンの定義をファイル読み込みにすると、コードは以下のようにだいぶスッキリします。 import matplotlib.pyplot as plt import numpy as np from PIL import Image import pyautogui as pg import datetime,ctypes # アイコン定義 cursor = Image.open('cursor.png').convert('RGBA') lclick = Image.open('lclick.png').convert('RGBA') rclick = Image.open('rclick.png').convert('RGBA') # 画面録画 title = 'CLOCK' sec = 30 app = [e for e in pg.getWindowsWithTitle(title) if e.title == title][0] now = datetime.datetime.now() imgs = [] x0,y0,w,h = app.box if w%2 != 0: w+=1 if h%2 != 0: h+=1 print('「'+title+'」というタイトルのウィンドウを対象に'+str(sec)+'秒の動画を作成') t = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') print('録画開始') while datetime.datetime.now() < now + datetime.timedelta(seconds=sec): x, y = pg.position() im = pg.screenshot(region=(x0,y0,w,h)).convert('RGBA') im.paste(cursor, (x-x0, y-y0), cursor) if ctypes.windll.user32.GetAsyncKeyState(0x01) == 0x8000: im.paste(lclick, (x-x0-4, y-y0+17), lclick) if ctypes.windll.user32.GetAsyncKeyState(0x02) == 0x8000: im.paste(rclick, (x-x0-4, y-y0+17), rclick) imgs.append(im) print('録画終了') # PNGファイル書き出し imgs[0].save(t+'.png', format='png', save_all=True, append_images=imgs[1:], duration=int(sec/len(imgs)*1000), loop=0) print('PNG: '+t+'.png') # MP4ファイル書き出し import subprocess subprocess.run('ffmpeg -i '+t+'.png -r '+str(int(len(imgs)/sec)) +' -pix_fmt yuv420p '+t+'.mp4 -y') print('MP4: '+t+'.mp4') 処理速度に依存すると思いますが、およそ9fpsか10fpsの動画になるので、マウスのクリックが0.1秒を目安に長めに押さないと検知が追いつかなくてクリック時のおまけの画像が描画されないと思います。30秒の動画で作成されたファイルのサイズはPNGが977KBで、MP4が250KBでした。カラフルで変化の大きな内容だと画像は重くなります。GIFもやっては見ましたが、残念な画質だし3MBとかに肥大化したりするので微妙です。Animated PNGはTwitterに投稿すると先頭画像だけの静止画にされてしまうし表示できる環境が限られるのでMP4で配布するのが現実的かなと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】MACDをバックテストしてみた

以前,Backtesting.pyを使って2本のSMAのゴールデンクロス,デッドクロスによる売買をバックテストしてみました. 今回は,MACDによる売買をバックテストしてみます. 分からないところがあれば,以前の記事もしくは,もう少しだけ詳しく書いたこちらの記事を見てください. 株価データの取得 今回は,2018/1/1~現在までのAAPL(Apple)の株価で行いたいと思います. import pandas_datareader.data as web import datetime start = datetime.date(2018,1,1) end = datetime.date.today() data = web.DataReader('AAPL', 'yahoo', start, end) MACDの計算 TA-Libを用います.詳しくはこちらの記事をご覧ください. import talib as ta def MACD(close, n1, n2, ns): macd, macdsignal, macdhist = ta.MACD(close, fastperiod=n1, slowperiod=n2, signalperiod=ns) return macd, macdsignal, macdhist MACDによる売買 class MACDCross(Strategy): n1 = 12 #短期EMAの期間 n2 = 26 #長期EMAの期間 ns = 9 #シグナル(MACDのSMA)の期間 def init(self): self.macd, self.macdsignal, self.macdhist = self.I(MACD, self.data.Close, self.n1, self.n2, self.ns) def next(self): # チャートデータの行ごとに呼び出される if crossover(self.macd, self.macdsignal): #macdがsignalを上回った時 self.buy() # 買い elif crossover(self.macdsignal, self.macd): #signalがmacdを上回った時 self.position.close() #ポジション降りる バックテスト実行 # バックテストを設定 bt = Backtest( data, # チャートデータ MACDCross, # 売買戦略 cash=1000, # 最初の所持金 commission=0.00495, # 取引手数料 margin=1.0, # レバレッジ倍率の逆数(0.5で2倍レバレッジ) trade_on_close=True # True:現在の終値で取引,False:次の時間の始値で取引 ) output = bt.run() # バックテスト実行 print(output) # 実行結果(データ) bt.plot() # 実行結果(グラフ) 結果 Start 2018-01-02 00:00:00 End 2021-04-09 00:00:00 Duration 1193 days 00:00:00 Exposure Time [%] 55.650061 Equity Final [$] 2062.831333 Equity Peak [$] 2248.015358 Return [%] 106.283133 Buy & Hold Return [%] 208.835491 Return (Ann.) [%] 24.82104 Volatility (Ann.) [%] 25.063568 Sharpe Ratio 0.990323 Sortino Ratio 1.866684 Calmar Ratio 0.886406 Max. Drawdown [%] -28.001871 Avg. Drawdown [%] -3.602678 Max. Drawdown Duration 314 days 00:00:00 Avg. Drawdown Duration 35 days 00:00:00 # Trades 30 Win Rate [%] 50.0 Best Trade [%] 27.772298 Worst Trade [%] -9.209289 Avg. Trade [%] 2.510722 Max. Trade Duration 68 days 00:00:00 Avg. Trade Duration 21 days 00:00:00 Profit Factor 2.731656 Expectancy [%] 2.796484 SQN 1.918841 _strategy MACDCross _equity_curve ... _trades Size EntryB... 資産額1000 → 2062,リターン+106%と,良い結果がでました. しかし,Appleの株はこんなもんじゃないということで,手法を最適化してみます. 手法最適化 MACDやそのシグナルの期間を変えることで,最終資産額が大きくなるように最適化してみたいと思います. #最適化 output2=bt.optimize(n1=range(2, 50, 10),n2=range(2, 100, 20),ns=range(2, 20, 5)) print(output2) bt.plot() 結果 Start 2018-01-02 00:00:00 End 2021-04-09 00:00:00 Duration 1193 days 00:00:00 Exposure Time [%] 52.126367 Equity Final [$] 2501.078474 Equity Peak [$] 2752.971034 Return [%] 150.107847 Buy & Hold Return [%] 208.835491 Return (Ann.) [%] 32.405287 Volatility (Ann.) [%] 24.44461 Sharpe Ratio 1.325662 Sortino Ratio 2.709822 Calmar Ratio 2.088269 Max. Drawdown [%] -15.517775 Avg. Drawdown [%] -2.948361 Max. Drawdown Duration 220 days 00:00:00 Avg. Drawdown Duration 24 days 00:00:00 # Trades 13 Win Rate [%] 69.230769 Best Trade [%] 31.465604 Worst Trade [%] -3.370525 Avg. Trade [%] 7.455329 Max. Trade Duration 113 days 00:00:00 Avg. Trade Duration 47 days 00:00:00 Profit Factor 13.551977 Expectancy [%] 7.984174 SQN 2.515907 _strategy MACDCross(n1=32,... _equity_curve ... _trades Size EntryB... 資産額1000 → 2501,リターン+150%となりました.さすがApple株ですね. 最適化したMACDでは,短期EMA=32日,長期EMA=62日,シグナル=12日で,取引回数は先ほどの30回から13回に減っています. 他の銘柄でもやってみた Appleがいいのか,MACDで売買するのがいいのかイマイチ分からないので,他の銘柄でもやってみます.ただし,最適化はなしで全て以下の条件で行います(先ほどと同じです). 短期EMA:12日 長期EMA:26日 シグナル(MACDのSMA):9日 ただし,日本株の場合は最初の所持金を100倍にします(1ドル=100円と仮定). 他の条件に付いては同じです. ソースコードは省略し,結果の一部分だけ紹介します. テスラ(TSLA) Equity Final [$] 5399.971518 Equity Peak [$] 6173.316735 Return [%] 439.997152 Buy & Hold Return [%] 956.094578 Return (Ann.) [%] 67.59303 資産:1000 → 5399 めちゃめちゃ強いですね. 日経平均株価 Equity Final [$] 113300.050432 Equity Peak [$] 119929.71685 Return [%] 13.30005 Buy & Hold Return [%] 26.638486 Return (Ann.) [%] 4.03233 資産:100000 → 119929 日経も最近は強いですからね. キヤノン(7751) Equity Final [$] 54995.981025 Equity Peak [$] 101632.8796 Return [%] -45.004019 Buy & Hold Return [%] -38.666667 Return (Ann.) [%] -16.954962 資産:100000 → 54995 さすがにキヤノンはMACDでも厳しいですね. ちなみにキヤノンの株価推移は以下のようになっています. まあ,長期的に下がっていれば無理ですよね… 投資に関する免責事項 プログラムや考え方の情報の提供・作業代行を目的としており,投資勧誘を目的とするものではありません.また,この記事は投資成績を保証するものではありません.投資はあくまで自己責任でお願いします.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pyenv + PoetryでPython環境構築(Linux/Windows/Mac共通)

今まではAnacondaとかPipenvを使ってましたが、いまいち使いにくく、pyenv + Poetryを使った環境がいい感じだったのでメモ。 Linux/Windows/Mac共通で動くと思いますが、Ubuntu(WSL)でしか動作確認はしてません。以下の手順はUbuntu(WSL)環境です。 要件 Pythonのバージョン管理 仮想環境・パッケージ管理 requirements.txtは使いたくないが、出力はできるようにしておきたい (クラウド環境等で必要になることがあるため) できればLinux/Windows/Mac共通で使いたい エディタはVS Code(+ Remote WSL)を想定 データ解析用途にはあまり向かないかも 構成 WSL2: Windows上のLinux環境 anyenv: **env系コマンド管理ツール pyenv: Pythonバージョン管理ツール Poetry: 仮想環境・パッケージ管理ツール 0. WSL2を導入する(Windowsのみ) WSLのおかげでWindowsでもLinux環境が簡単に使えるようになったので、開発がかなりやりやすくなりました。手順は公式ドキュメントを参考に。 Windows Subsystem for Linux (WSL) を Windows 10 にインストールする | Microsoft Docs WSL2の注意事項 WSL2では問題点が2つあります。 スリープ時にWSL上の時刻がずれる WSL2 date incorrect after waking from sleep · Issue #5324 · microsoft/WSL WSLを起動している状態でPC自体がスリープに入るとWSL上の時刻がずれます。 時刻がずれているとaptやgitでエラーが発生するようになるので結構困ります。 一時的な対応としてsudo hwclock -sを実行することでホストの時刻に同期させることができます。 WSL環境ではUSBなどの外部デバイスが扱えない 2021年4月現在、WSL2上でUSBを使う公式の方法はありません。 WSL 2 についてよく寄せられる質問 | Microsoft Docs 現時点では、WSL 2 にはシリアル サポートや USB デバイス サポートは含まれていません。 Microsoft では、これらの機能を追加するための最適な方法を調査しています。 USB over IPという技術で対応することもできるみたいですが未検証です。 1. anyenvをインストール pyenvを含む**env系コマンドの管理ツールです。pyenvをインストールするため、事前にインストールしておきます。 Ubuntu以外の環境はREADMEを参考にしてください。 anyenv/anyenv: All in one for **env # Install git clone https://github.com/anyenv/anyenv ~/.anyenv echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bashrc # Initialize ~/.anyenv/bin/anyenv init 2. pyenvをインストール Pythonのバージョン管理ツールです。 複数のバージョンのPythonをインストールしてプロジェクトごとに切り替えることができます。 pyenvを使ってvirtualenvを管理することもできるのですが、ここではPythonのバージョン管理のみ使います。 先ほどインストールしたanyenvを使ってpyenvをインストールします。 PoetryのインストールでPythonを使用するため、最新バージョンをglobalに設定しています。 pyenv/pyenv: Simple Python version management # Install pyenv anyenv install pyenv # Install Python pyenv install 3.9.4 pyenv global 3.9.4 3. Poetryをインストール Pythonの仮想環境・パッケージ管理ツールです。 以前からあるrequirements.txtを使ったパッケージ管理を、pyproject.tomlを使って依存関係も扱えるようにした管理ツールです。 パッケージ管理と同時にvirtualenvを使った仮想環境も自動で作成、管理してくれます。 python-poetry/poetry: Python dependency management and packaging made easy. # Install curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc ここでPoetryの設定を1つだけ変更します。 poetry config virtualenvs.in-project true これを設定するとvirtualenvがプロジェクトフォルダのルートに作られ、VS Codeが自動的に認識するようになります。 これでPython開発を始める準備ができました。 4. プロジェクトを始める プロジェクトフォルダを作り、Pythonバージョンの指定、Poetryで初期化・モジュールインストールの流れでプロジェクトを始めます。 # ディレクトリを作る mkdir python-project cd python-project # pyenvでPythonのバージョン指定 pyenv install 3.8.9 pyenv local 3.8.9 # poetryで初期化 poetry init # pyproject.tomlを読み取り、インストール(virtualenvが作られる) poetry install # ライブラリ追加 poetry add <name> # 開発用ライブラリ追加 poetry add -D <name> # requirements.txtエクスポート poetry export -o requirements.txt おまけ よく使うライブラリ autopep8: PEP8スタイルガイドに準拠したコード整形ツール flake8: リンターツール flake8-commas: 末尾のカンマをチェック flake8-isort: import文の順序チェック flake8-quotes: シングル・ダブルクォーテーションの使用をチェック 最後に Node.jsのnpmと似たような構成で管理できるので気に入ってます。 npm - poetry package.json - pyproject.toml package.lock - poetry.lock node_module - .venv これがPythonのデファクトスタンダードになってくれるといいなぁ。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Colab のコード内で R・HTML・Markdown を利用する

はじめに 記事の内容は、表題の通りです。 ふと、「Colab の 1つのノートブック内で Python と R を両方実行できたりするかな」と思って調べたり試したりしたのがきっかけで、その後に「別のプログラミング言語やその他のものも使えたりするだろうか?」と思って、追加で「HTML・Markdown の利用」を試してみた時のメモです。 R・HTML・Markdown の件について、それぞれ以下を参照しました。 R Google ColabでRを使う HTML Jupyterで好きなHTMLを埋め込む - Qiita Markdown python - What is the purpose of the IPython.display.display_markdown() function? - Stack Overflow R の利用(Rpy2 を使う) 話は単純で Rpy2 を使えば良いようです。 Colab のノートブックで、まずは以下を実行します。 %load_ext rpy2.ipython あとは、R を利用したい部分で、最初の行に「%%R」を入れれば良いようです。 以下、冒頭で書いた参考ページの中の内容を、実際に Colab上で試した際の画面キャプチャです。 コードの部分はこんな感じです。 %%R curve(sin(x), -pi, pi) HTML の利用(IPython.display を使う) HTML については IPython.display を使います。 以下、実行結果の画面キャプチャと、コードの部分です。 from IPython.display import HTML HTML("<h1>Hello world!</h1>") Markdown の利用(IPython.display を使う) Markdown も HTML と同様に IPython.display を使います。 以下、実行結果の画面キャプチャと、コードの部分です。 from IPython.display import Markdown Markdown('# This is a Title') おわりに 今回使った IPython.display は、以下を見てみると他にもできることがいろいろありそうなので、試してみられればと思います。 ●Module: display — IPython 7.22.0 documentation  https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでプログラムの考え方を学ぶ 改訂版

参考書籍のchap 17のエクササイズ17_1 Think Python: How to Think Like a Computer Scientist (English Edition) https://www.amazon.co.jp/dp/B018UXJ9EQ/ref=cm_sw_r_tw_dp_H69W6TFMGB5G29K61QKN 終了条件 改訂版が付いていた時 やって見て 次に進もうと思います。 ・何回か読む ・用語を把握する。 ・問題をやってみる。 並行していくつかやらないと一気には進まないので・・・ ソリューション ・問題がわかっていれば ・整理していれば→ メソッド名/変数名/実行順番/キーワードの値の動き ・ロジックがわかっていれば ・知識があれば ・ひとつひとつ実行していれば ・アウトプットイメージがしっかりしていれば 問題 ex 17_1 この章のコードをhttp://thinkpython2.com/code/Time2.py からダウンロードします。 Timeの属性を、真夜中からの秒数を表す単一の整数に変更します。 次に、メソッド(および関数int_to_time)を変更して、 新しい実装で機能するようにします。 mainのテストコードを変更する必要はありません。 完了すると、出力は以前と同じになります。 解決策:http://thinkpython2.com/code/Time2_soln.py Downey, Allen B.. Think Python: How to Think Like a Computer Scientist (p.303). O'Reilly Media. Kindle 版. コードの整理 変数 hour, minute, second def __init__(self, hour=0, minute=0, second=0): def __str__(self): return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second) def print_time(self): def time_to_int(self): return seconds def is_after(self, other): return self.time_to_int() > other.time_to_int() def __add__(self, other): return self.add_time(other) return self.increment(other) def __radd__(self, other): return self.__add__(other) def add_time(self, other): return int_to_time(seconds) def increment(self, seconds): return int_to_time(seconds) def is_valid(self): return True def int_to_time(seconds): return time ロジック 真夜中からの秒数を表す単一の整数 → H * 60 * 60 + Minutes * 60 + Seconds 時間・分・秒に戻す場合 秒→ Seconds % 60 分数 → int(Seconds / 60 % 60) 時間→ int(Seconds / 60 /60) 処理の流れ 注 Time2.pyのdef main()内で起った処理を書いています。 Time(9, 45, 00)→ hour=9, minute=45, second = 00 start 9:45:00 print_time     str(self): increment(1337)     int_to_time → hour=10 minute=07 second = 00 end 10:07:00 is_after time_to_int → start 32445 seconds ends 36007 seconds アウトプットイメージ →必要な分だけ省略しています。 09:45:00 10:07:17 Is end after start? True Using str 09:45:00 10:07:17  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoチュートリアル⑥(自動テスト)

はじめに Djangoチュートリアル⑤(汎用ビュー)に続いて、はじめての Django アプリ作成、その 5に沿ってチュートリアルを進めていく。チュートリアルだけでは理解できないので、現場で使える Django の教科書(基礎編)も読みながら進める。 自動テストの導入 ここまでアプリケーションの動作に関することのみ実行してきたが、ここでテストを作成する。自動テストを作ることで、プログラムが正しく動作していることの確認が一瞬ででき、エラーが発生していた場合にはどこでそれが起きたのかをすばやく見極めることができる。テストを作成する時間はかかるが、テストなしで実装した場合のデバッグと比べても大きく時間を節約することができるため非常に意味がある。その他にも、ソフト自体の信頼度が上がったり複数人で開発をした場合にも他者のコードを自分が破壊したり、また自分のコードを他者が破壊することを防ぐことができる。 Django では アプリケーションのテストを tests.py というファイルに記述することで、テスト実行時に自動的にテストを見つけて実行してくれる。たとえば、polls アプリケーションのテストは polls/tests.py にテストを書く。具体的には、django.test.TestCase クラスを継承したクラスの、名前が test で始まるメソッドをテストとして実行する。チュートリアルでは、TestCase を継承した QuestionModelTests というサブクラスを作成し、未来の日付の pub_date を持つ Question のインスタンスを生成して、それに対する was_published_recently() の出力をチェックする test_was_published_recently_with_future_question() というメソッドを記述している。 polls/tests.py import datetime from django.test import TestCase from django.utils import timezone from .models import Question class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): """ was_published_recently() returns False for questions whose pub_date is in the future. """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False) コード中の assertIs(a, b) というメソッドは、「a is b」となるかどうかのテストを実行しており、was_published_recently() の出力が False となっていれば、テストは成功となる。Python では unittest という標準ライブラリがあり、ここで使用している TestCase というクラスはこれを拡張しているらしい。assertIs 以外にも便利な assert メソッドはあるのでそれらは assert メソッド一覧を参照。 テストの実行 以下コマンドで、作成したテストを polls アプリケーションのテストを実行できる。 $ python manage.py test polls テストの結果以降、コードの編集等ははじめての Django アプリ作成、その 5を参照することとし、説明はここまでとする。 テストにおいて多いことはいいことだ このようにテストを作成していくと、似たようなテストがたくさんできたり、重複があったりとなっていくことが想定される。しかし、テストにおいては多いことはいいことであり、たとえ冗長であっても一度テストを作成すればあとはそれを実行するだけで多くの恩恵を得られる。テストを整理さえすれば、テストがいくら膨大になろうと問題はないため、以下のようなルールを意識してテストを作成することが推奨されている。 モデルやビューごとに TestClass を分割する テストしたい条件の集まりのそれぞれに対して、異なるテストメソッドを作る テストメソッドの名前は、その機能を説明するようなものにする さらなるテスト チュートリアルでは、モデルの内部ロジックやビューに関するテストを作成したが、それらはほんの一部で他にも多くの便利なツールが用意されている。たとえば、Web アプリケーションの UI テストや JavaScript のテストを行える Selenium のようなフレームワークを使うことができる LiveServerTestCase がある。他にもコミット時に自動的にテストを実行することや、コードカバレッジをチェックしてテストされていないコードを発見することも重要とのこと。そのあたりは今後進めていった際に、利用して再度記事にしたい。 おわりに 初心者のため、unittest ライブラリを含めこんなに便利なツールがあることを知らなかった。テストを意識したアプリ開発をできるように慣れていこう。引き続き、はじめての Django アプリ作成、その 6を進めていく。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

yukicoder contest 290 参戦記

yukicoder contest 290 参戦記 ★1.5が3つ、★2が2つあって前回に続いて ABC っぽさを期待したが、まあいつもの星数詐欺でした. A 1469 programing 理由は知らないのですが、Python の文字列の加算は遅くないので、素直に加算すれば良い. S = input() result = S[0] for c in S[1:]: if c == result[-1]: continue result += c print(result) 文字列の加算が遅い言語は StringBuilder を使うなり、リストに追加して最後に join するなりすればいいんじゃないですかね. S = input() t = [S[0]] for c in S[1:]: if c == t[-1]: continue t.append(c) print(''.join(t)) B 1470 Mex Sum 2つの値の mex は 1, 2, 3 のどれか. 後は場合分けして計算するだけ. N, *A = map(int, open(0).read().split()) a, b, c = 0, 0, 0 for x in A: if x == 1: a += 1 elif x == 2: b += 1 elif x >= 3: c += 1 result = 0 result += a * (a - 1) // 2 * 2 # mex(1, 1) result += a * b * 3 # mex(1, 2) result += a * c * 2 # mex(1, >=3) result += b * (b - 1) // 2 * 1 # mex(2, 2) result += b * c * 1 # mex(2, >=3) result += c * (c - 1) // 2 * 1 # mex(>=3, >=3) print(result) C 1471 Sort Queries 各インデックスまでのアルファベットの累積出現数を求めておけば O(26) でクエリに回答できる. N,Q≦104 なので、時間及び空間計算量は 2.6×105となり問題ない. from sys import stdin readline = stdin.readline N, Q = map(int, readline().split()) S = readline()[:-1].encode('us-ascii') a = [0] * 26 b = [] for c in S: i = c - 97 a[i] += 1 b.append(a[:]) result = [] for _ in range(Q): L, R, X = map(int, readline().split()) L, R = L - 1, R - 1 t = b[R][:] if L != 0: u = b[L - 1] for i in range(26): t[i] -= u[i] c = 0 for i in range(26): c += t[i] if c < X: continue result.append(chr(i + 97)) break print(*result, sep='\n') E 1473 おでぶなおばけさん 到達可能な体重を最大化するように、同じ到達可能な体重であれば通る必要のある道の数を最小化するように幅優先探索するだけ. from collections import deque from sys import stdin readline = stdin.readline INF = 10 ** 18 n, m = map(int, readline().split()) links = [[] for _ in range(n)] for _ in range(m): s, t, d = map(int, readline().split()) s, t = s - 1, t - 1 links[s].append((t, d)) links[t].append((s, d)) q = deque([(0, INF)]) dp = [(-1, -1) for _ in range(n)] dp[0] = (INF, 0) while q: cp, mw = q.popleft() if dp[cp][0] > mw: continue for np, lw in links[cp]: w, s = dp[np] nw = min(mw, lw) if w >= nw: continue if w == nw: dp[np] = (nw, min(s, dp[cp][1] + 1)) else: dp[np] = (nw, dp[cp][1] + 1) q.append((np, nw)) print(dp[n - 1][0], dp[n - 1][1])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Pandas】matplotlibを使ってグラフを書く方法 ② .no.38

こんにちは、まゆみです。 Pandasについての記事をシリーズで書いています。 今回は第38回目になります。 今回は、前回の記事に引き続きmatplotlibライブラリーを使ってグラフを描く方法を書いていきます。 前回は 折れ線グラフ を描く方法について書きました。 今回は、 棒グラフと 円グラフ の描き方について書いていきます。 では早速はじめていきますね。 棒グラフ(bar chart)の描き方 棒グラフに適しているのは、『カテゴリーに分けられたグループのそれぞれのカウント数』のデータなどなので、『セントラルパークのリスに関するデータ』を使って、グラフの描き方を説明していきます。 上記のような数字だけでデータを示すのではなく このようなグラフにすると一目でデータの概要をつかむことができます。 matplotlibとPandasをインポートします(matplotlibのインポートについての注意点はこちらを参考にどうぞ) import pandas as pd import matplotlib.pyplot as plt %matplotlib inline .read_csv()でデータを読み込むと下のようになります。 『Primary Fur Color』はリスの毛の色についてのコラムなので、こちらのコラムをGray, Black, Cinnamon に分け、アイテム数を棒グラフにします。 .value_counts()でそれぞれの値の数を取り出す df["コラム名"].value_counts() で、それぞれの値がいくつあるのかを取り出すことができるので、まず.value_counts()を使い、3つのグループのそれぞれのカウント数を調べましょう この後ろに、.plot()メソッドを使うと、折れ線グラフが示されます。 .plot()のパラメーターkindに引数『bar』を渡せば、棒グラフにすることができます。 引数をbar ではなく、『barh』にすると、barh はbar Horizontal(horizontalは水平方向のという意味です。)という意味であり、下記のように横軸・縦軸が入れ替わります。 円グラフ(pie chart)の描き方 円グラフは全体のどれくらいの割合なのかを視覚で訴えたい時に便利です。 .plot()に渡す引数を"pie"にします まとめ 前回に引き続きmatplotlibを使ってグラフを描く方法を書かせていただきました。 色んなデータを使って是非、応用してみてくださいね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Equalumにkafkaを繋げてみる(実行編)

今回はいよいよEqualumに接続します 前回は、準備編としてMBP上にkafka環境を導入し、Pythonを使った検証ツールを作成し、kafka上での動作確認までを行いました。今回はいよいよ具体的にkafkaのプロデューサに送りこまれたメッセージを、JSON形式に整えてEqualumの上流側データソースとして接続してみたいと思います。 まずは、受け側の準備 Equalumを通して受け取る側のデータソースを準備したいと思います。今回はMySQLをDocker上で動かして、この環境上に事前展開したテーブルに向かって、JSON形式をEqualumが通常のRDB系カラム情報に変換し、シンプルに連続して挿入処理を行う形にします。 MySQL側のテーブルを作る 基本的には、今までのパターンを修正して「サクッと!」準備してしまいます。 # coding: utf-8 # # MySQL側のテーブル作成 # # Python 3版 # # # 初期設定 import sys stdout = sys.stdout sys.stdout = stdout import pymysql.cursors # 作成するテーブル名 Table_Name = "kafka_TGT_Table" # テーブル初期化 Table_Init = "DROP TABLE IF EXISTS " # テーブル定義 DC0 = "ts_key VARCHAR(30) PRIMARY KEY, " DC1 = "xx INT, yy INT " try: print("TGTテーブル作成処理を開始") # デモ用のテーブルの作成 Table_Create = "CREATE TABLE IF NOT EXISTS " + Table_Name + " (" + DC0 + DC1 + ")" # MySQLとの接続 db = pymysql.connect(host = 'localhost', port=xxxx, user='xxxxxx', password='xxxxxxxxx', db='xxxxx', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) with db.cursor() as cursor: # 既存テーブルの初期化 cursor.execute(Table_Init + Table_Name) db.commit() # 新規にテーブルを作成 cursor.execute(Table_Create) db.commit() except KeyboardInterrupt: print('!!!!! 割り込み発生 !!!!!') finally: # データベースコネクションを閉じる db.close() print("TGTテーブル作成処理が終了") 検証用のトピックを作る 今回の検証に使うトピックを作ります。コマンドは前回の形式(最小構成なのでパーティション等は1個で対応します)で実行し、名前はTPC4JSONとします。 % kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic TPC4JSON Equalumの設定の際に、プレビューを使って説明したいので「テストを兼ねてコンシューマ側に向かって」少しデータを作成しておきます。このテスト分はEqualumが繋がったタイミングで「初期抽出」が自動的に行われます。コンソールを見ながら10個程度有れば十分なので、適当なタイミングで切り上げればOKです。 プロデューサ側の入力 {'id': '2021/04/11-08:18:59:171423', 'x': -1399, 'y': 85} {'id': '2021/04/11-08:19:00:304312', 'x': -791, 'y': 476} {'id': '2021/04/11-08:19:01:309313', 'x': -1001, 'y': 508} ///////// 途中省略 ////////// {'id': '2021/04/11-08:19:12:382028', 'x': 544, 'y': 200} {'id': '2021/04/11-08:19:13:389613', 'x': 518, 'y': 205} コンシューマ側の出力 読み出したデータ   1個目 Data ID: 2021/04/11-08:18:59:171423 Data X: -1399 Data Y: 85 読み出したデータ   2個目 Data ID: 2021/04/11-08:19:00:304312 Data X: -791 Data Y: 476 読み出したデータ   3個目 Data ID: 2021/04/11-08:19:01:309313 Data X: -1001 Data Y: 508 //////// 途中省略 //////// 読み出したデータ   14個目 Data ID: 2021/04/11-08:19:12:382028 Data X: 544 Data Y: 200 読み出したデータ   15個目 Data ID: 2021/04/11-08:19:13:389613 Data X: 518 Data Y: 205 !!!!! 割り込み発生 !!!!! 疎通が確認出来ましたので、いよいよ検証の本番に移ります。 Equalumの設定 次にEqualumの設定を行って行きます。 kafkaの接続も、基本的には他のデータソースと同じ様にGUIベースの設定項目を埋めて行く作業になります。 (1)ダッシュボードのSOURCES項目にある+ADDボタンを選択 (2)kafkaを選択 (3)必要事項を設定 今回はオールインワンなのでBrokers Listは1個だけ「IPアドレス:ポート番号」で設定しました。 (4)設定したソースにトピックを登録 (4−1)ダッシュボードに先程のkafkaが登録されたら、そのソースを選択してトピックを登録します。 Streamsの右側にある+ADDボタンを選択して、表示されるGUI上で必要な項目を設定します。先程のソース登録が正常に行われていると、Source Topicの所でプルダウンメニューが出てきて、事前に登録されたトピックが選択可能になっています。 File format optionsのプルダウンメニューからJSONを選択します。 JSON Schema File Optionsの項目にあるSchema Fileの設定部分でGenerateを選択します。前もって正しい内容のファイルが有れば此処でアップロードする事も可能ですが、今回はEqualum任せでサクッと!前に進める事にします。 次にTag Name to Eventを設定します。今回はシンプルな形式ですので、デフォルトの/rootを選択しました。 最後にAdvanced Settingsの中にあるUser-Defined Primary Keyを設定(今回はidを選択)しておきます。 此処までの作業で、kafkaとEqualumの接続設定は終了になります。 設定後にすぐに初期抽出の処理が行われ、下流側の設定が済み次第処理が実行されます(デフォルトでは、初期抽出がONになっています)。またこの処理準備により、実際のFLOWデザインの際に実際のデータをシュミレートした作業が出来る様になりますので、途中に組み込む処理によっては、設定した処理の結果が即確認出来る等の便利機能になっています。 (5)FLOWの作成 次にFLOWの作成を行います。今回はデータ自体が超シンプルなので、間の処理は省略した形(ある意味レプリケーションになりますが)で検証を行います。Equalum的には、正常に接続されたデータは、FLOWの中においては全て操作対象になりますので、JSONの項目にルックアップ情報を埋め込み、それをFLOWの中で抽出・参照・置き換えといった作業等も他のデータソース同様に簡単に行えます。 ダッシュボードのFLOWS右側にある+ADDボタンを選択します。 (5−1)FLOWデザイン上でマウスのダブルクリックを行い、上流側のデータソースを選択します。 (5−2)同様にFLOWデザイン上でマウスのダブルクリックを行い、下流側のデータソースを選択します。 (5−3)FLOWデザイン上で両者を関連付けます。 此処で、最初に動作確認で生成したJSONデータの状況を確認するには、上流側のPreviewを選択します。 無事にカラム属性を含めて処理対象になっている事が確認出来ました。 (5−4)ターゲット側のマッピングを行います。 Equalumの柔軟性の一つに、この最終段でのカラム項目のマッピング機能が有ります。この段階で上流側から流れてくるデータを送らない設定も出来ますし、途中で派生させたカラムに対するデータ適用なども行えます(ルックアップ処理などで便利)。 Editボタンを選択して設定GUIを呼び出します。今回は必要項目だけを設定して検証作業を行います。 データベースとテーブルを選択します。 +MAPPINGボタンを選択します。 Target Mappingの部分を適切に選択・設定します。 問題がなければOKボタンを選択して設定を終了します。 この状況まで来れば、あとは以前の検証で行った作業と同じですので、FLOWをセーブして配備・実行するだけです。 これで準備が整いました。 では検証開始! 今回の検証は、画面上のマウス座標をPythonで抽出し、シンプルで小規模のJSON形式でプロデューサに送出する形で行った関係上、利き腕の労力が半端ない状況でしたので、あまり長時間のデータ生成は出来ませんでした。。。(汗) 以下は、検証作業時のEqualumダッシュボードの状況になります。 以下は、今回の検証結果データになります(左側はMySQLに格納されたデータ、右側がkafkaのプロデューサへの送出データになります) 2021/04/11-09:37:58:330350| 527|223| {'id': '2021/04/11-09:37:58:330350', 'x': 527, 'y': 223} 2021/04/11-09:37:59:335195| -993| 62| {'id': '2021/04/11-09:37:59:335195', 'x': -993, 'y': 62} 2021/04/11-09:38:00:341232| 1059| 0| {'id': '2021/04/11-09:38:00:341232', 'x': 1059, 'y': 0} 2021/04/11-09:38:01:347066| 1350| 69| {'id': '2021/04/11-09:38:01:347066', 'x': 1350, 'y': 69} /////// 途中省略 ////// 2021/04/11-09:38:11:412048| -911|229| {'id': '2021/04/11-09:38:11:412048', 'x': -911, 'y': 229} 2021/04/11-09:38:12:419794| -509|212| {'id': '2021/04/11-09:38:12:419794', 'x': -509, 'y': 212} 2021/04/11-09:38:13:425034| -272|246| {'id': '2021/04/11-09:38:13:425034', 'x': -272, 'y': 246} 2021/04/11-09:38:14:429277| -586|248| {'id': '2021/04/11-09:38:14:429277', 'x': -586, 'y': 248} /////// 途中省略 ////// 2021/04/11-09:38:30:523297|-1037|482| {'id': '2021/04/11-09:38:30:523297', 'x': -1037, 'y': 482} 2021/04/11-09:38:31:528702| -333|641| {'id': '2021/04/11-09:38:31:528702', 'x': -333, 'y': 641} 2021/04/11-09:38:32:535799|-1228|316| {'id': '2021/04/11-09:38:32:535799', 'x': -1228, 'y': 316} 2021/04/11-09:38:33:544202|-1110|690| {'id': '2021/04/11-09:38:33:544202', 'x': -1110, 'y': 690} 2021/04/11-09:38:34:550620| -990|197| {'id': '2021/04/11-09:38:34:550620', 'x': -990, 'y': 197} /////// 途中省略 ////// 2021/04/11-09:38:51:646299| -823|456| {'id': '2021/04/11-09:38:51:646299', 'x': -823, 'y': 456} 2021/04/11-09:38:52:655003|-1196|438| {'id': '2021/04/11-09:38:52:655003', 'x': -1196, 'y': 438} 2021/04/11-09:38:53:662194| -624|305| {'id': '2021/04/11-09:38:53:662194', 'x': -624, 'y': 305} 2021/04/11-09:38:54:668645| -242|568| {'id': '2021/04/11-09:38:54:668645', 'x': -242, 'y': 568} 2021/04/11-09:38:55:674050| -328|163| {'id': '2021/04/11-09:38:55:674050', 'x': -328, 'y': 163} 2021/04/11-09:38:56:678265|-1332|646| {'id': '2021/04/11-09:38:56:678265', 'x': -1332, 'y': 646} /////// 途中省略 ////// 2021/04/11-09:39:14:792023| 935| 55| {'id': '2021/04/11-09:39:14:792023', 'x': 935, 'y': 55} 2021/04/11-09:39:15:794852| 1116| 49| {'id': '2021/04/11-09:39:15:794852', 'x': 1116, 'y': 49} 2021/04/11-09:39:16:798872| 557|121| {'id': '2021/04/11-09:39:16:798872', 'x': 557, 'y': 121} 2021/04/11-09:39:17:804355| 637|199| {'id': '2021/04/11-09:39:17:804355', 'x': 637, 'y': 199} 無事にストリーミング出来た様です。 今回のまとめ 今回は、前回に引き続きEqualumのストリーミング検証として、データソースにkafkaを使った場合の検証作業を行いました。Equalumは、仕組みの総力戦で効率的且つ即時的な「Exactly Once」を実現し、CDCストリーミング対象のリレーショナル・データベース系に加えて、今回検証したkafkaにもこの機能を適用させています。これは、昨今のIoT系データ処理の仕組みを構築する際に、周辺の既存業務系やエッジ展開しているデータベース群等とも、極めて有機的で能動的なデータ連携の可能性を提供出来る事を示しています。 もちろん、データは企業・団体活動の重要なエビデンスであり、その量・質を高める作業こそが日常の業務・課題であると言えるでしょう。そしてそれらを強固に守るという事も重要なITの役割だと言えますが、変化の激しい・不確定性の高い現代社会において、常時接続時代、5Gを始めとする低遅延・高速な次世代通信、全てがネットに繋がるコネクテッドXの流れが、それらのより良いデータを創る重要な資源として、多様なデータの利活用戦略や具体的な方法論・実装を要求してきています。 その新たなデータ・セントリックな時代における、データ・オプス(DataOps)の核として、Equalumは色々なシチュエーションの中で、確実に前に進めるソリューションなのかもしれません。 データの質や量・扱う時間に関係なく 情け容赦の無い、創造的で革新的なデータ利活用し データを再生可能エネルギー(資源)として、より良い結果(データ)の創造を行う それらを支えるデータ流通革命の一端を、Equalumは十分に担えると一連の検証を通じて感じました。 最近、国内でも正式に2社の外販代理店が立ち上がるという話も聞こえておりますので、また別の機会に「小ネタ的な」検証を行って皆様と共有させて頂きたいと考えております。 謝辞 本検証は、Equalum社の最新公式バージョン(V2.24)を利用して実施しています。この貴重な機会を提供して頂いたEqualum社に対して感謝の意を表すると共に、本内容とEqualum社の公式ホームページで公開されている内容等が異なる場合は、Equalum社の情報が優先する事をご了解ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Equalumにkafkaを繋げてみる(準備編)

以前に書いたEqualum検証の続き・・・・ 前に投稿したEqualumの検証で、kafkaを上流側データソースとして使えないのか?・・・なるご質問を頂くケースが結構有りましたので、今回は簡単なkafka環境との接続検証を行ってみたいと思います。 まずはkafka環境の構築 ハードウエアを正式に用意して、保守本流・王道のkafka環境を構築する・・という方向性も有るのですが、今回は普段使いのMBPの上にサラッとkafka環境を構築して、その環境に対してPythonで作ったスクリプトでメッセージを投げ、そのメッセージをEqualumが受け取って必要な処理(今回はメッセージの分解とカラム化を行います)をFLOWで作成し、ターゲットとして同じMBP上で動いているDocker環境で稼働しているMySQLに着地させてみることにします。 事前の言い訳・・・m(_ _)m(苦笑) kafka周辺の技術情報は、既に多くの諸先輩方がQiita、その他の記事として公開されていますので、それらの記事を是非!参考にして見てください。今回の検証は、あくまでも動くかどうかを確認する・・という部分で戦略的妥協をしております(汗)。。。 また、今回は紙面の都合上・・kafka周りの準備までになります(Equalumとの連携は次回を予定)ので、その辺は何卒ご理解頂きたく・・・ まず最初にbrew環境の導入(導入済みの場合不要です) Mac環境へhomebrewをインストールします。 kafkaのインストール(zookeeperも一緒にインストールされます) % brew install kafka kafkaを起動してみます 無事にインストールが出来れば、環境を立ち上げてみましょう。 % brew services start zookeeper % brew services start kafka zookeeper は localhost:2181, kafka は localhost:9092 で起動します(標準設定) また、環境設定のファイルは/usr/local/etc/kafka/server.propertiesに有ると思いますので、取り急ぎトピックを削除出来るように設定を変更しておきます(最初、コマンドで試験的に作った幾つかのトピックを、初期化&リソースを解放しようと削除していたのですが、コマンドを使って既存リストを見ると永久不滅のトピック状態で、あれ?・・・という事で探し当てた設定になります・・・ご参考まで) delete.topic.enable=true この設定をファイルに加えてから、環境を再起動します。起動中の環境を止める場合は % brew services stop kafka % brew services stop zookeeper を行います。 本格的にkafkaを弄ってみる まずは、検証に使うトピックを作成します。 % kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic XXXXX 

 中パラメータは全て必要みたいなので、このままコピペで使って最後のトピック名だけ適宜変更します 因みに・・・・ * --create でトピックの作成 * --zookeeper localhost:2181 で zookeeper の LISTEN アドレスの指定 * --replication-factor 1 でレプリカを 1 つのみ生成 * --partitions 1 でパーティションを 1 つのみ生成 * --topic でトピック名を指定 です 今回は、MBPの資源保護の観点から最小構成で設定しました。 トピック周りの処理について 環境が起動して、取り急ぎのトピックを作成したらリストで表示してみます。 % kafka-topics --list --zookeeper localhost:2181 また、初期化等を行う場合は、kafkaの作法に従って(?)トピック自体を削除・初期化する必要が有りますので、以下のコマンドで処理を行います。(前述の設定をお忘れなく・・・) % kafka-topics --delete --zookeeper localhost:2181 --topic XXXXXXX 今回の検証では使いませんが、コマンド処理で弄る場合の参考として・・・ プロデューサーを起動する場合は、以下のコマンドで処理を行います。 % kafka-console-producer --broker-list localhost:9092 --topic XXXXXXX 同様にコンシューマの起動は、少し長めになりますが % kafka-console-consumer --bootstrap-server localhost:9092 --topic XXXXXXX --from-beginning でメッセージ待受状態になりますので、双方が起動している状態で適当な文字列を入力して、リターンキーを押す事にコンシューマ側のコンソールに入力された文字列がメッセージとして表示されると思います。 では、検証用のPythonを書きます 今回の検証では、Pythonのモジュールを使って(pyautogui)画面上のマウス座標を取得し、その情報と識別情報としてタイムスタンプ的な情報を組み合わせ、JSON形式でEqualumを通過させたいと思います。ネット上には諸先輩方の歴戦の苦労の賜物が沢山公開されておりますので、それらを参考にして頂けば、さほど難しくなく「取り敢えず動く=動きを作る(動作する)」版は作れるかと思います。今回作成して検証に使ったスクリプトは以下の通りです。 # # Equalumのkafka接続検証 # # プロデューサ側の処理(JSON形式でメッセージを送信) # from kafka import KafkaProducer import pyautogui import time import json try: # 変数の初期化 Counter = 1 TimeOut = 60 Sleep = 1 # プロデューサーとの接続処理 producer = KafkaProducer(bootstrap_servers='localhost:9092', value_serializer=lambda m: json.dumps(m).encode('utf-8'))    #安易ですが、無限ループで処理します。 while True: # 日付情報と時間情報の取得 from datetime import datetime messageId = datetime.now().strftime("%Y/%m/%d-%H:%M:%S:%f") # JSON形式にメッセージを作成 kafka_msg = {"id" : messageId, "x" : pyautogui.position().x, "y" : pyautogui.position().y} print(kafka_msg) # プロデューサーにメッセージを送る result = producer.send('JSON', kafka_msg).get(timeout=TimeOut) #print(result) time.sleep(Sleep) Counter = Counter + 1 except KeyboardInterrupt: print('!!!!! 割り込み発生 !!!!!') finally: print ("処理の終了") print(str(Counter) + "回の処理を実行しました") Equalumに接続する前に、取り急ぎMBP上で立ち上げたkafka環境を通してみたいと思います。 トピック名をJSONとして先程のコマンドで作成しておきます。 データが届いているかを確認するために、コンシューマ側の仕組みを用意する 今回は、取り急ぎの動作検証ですので、以下のスクリプトで受信状況を確認します。 # # Equalumのkafka接続検証 # # コンシューマ側の処理 # from kafka import KafkaConsumer import sys import json try: # 変数の初期化 Counter = 1 # コンシューマに接続する consumer = KafkaConsumer(bootstrap_servers='localhost:9092', auto_offset_reset='latest',value_deserializer=lambda m: json.loads(m.decode('utf-8'))) consumer.subscribe(['JSON']) for message in consumer: print("コンシューマ側に送られてきたJSONメッセージ : " + message) print("\n読み出したデータ\n") print("Data ID:",message[6]['id']) print("Data X:",message[6]['x']) print("Data Y:",message[6]['y']) Counter = Counter + 1 # Terminate the script sys.exit() except KeyboardInterrupt: print('!!!!! 割り込み発生 !!!!!') finally: print ("処理の終了") print(str(Counter) + "回の処理を実行しました") 準備が出来たら動作確認します 両方のスクリプトを起動して、画面上でマウスを「心のままに」動かすと、1秒間隔でデータを取り込んでくれます。動作状況は双方のコンソールに出力されるようになっていますので、その結果を確認して問題が無い様で有れば、いよいよEqualumを経由させてMySQLに着地させる事になります。 コンシューマ側の出力 コンシューマ側に送られてきたメッセージ :  ConsumerRecord(topic='JSON', partition=0, offset=137, timestamp=1618055496288, timestamp_type=0, key=None, value={'id': '2021/04/10-20:51:36:287301', 'x': 910, 'y': 454}, headers=[], checksum=None, serialized_key_size=-1, serialized_value_size=56, serialized_header_size=-1) 読み出したデータ Data ID: 2021/04/10-20:51:36:287301 Data X: 910 Data Y: 454 ////// 途中省略 ////////// コンシューマ側に送られてきたメッセージ :  ConsumerRecord(topic='JSON', partition=0, offset=146, timestamp=1618055505336, timestamp_type=0, key=None, value={'id': '2021/04/10-20:51:45:335406', 'x': 701, 'y': 253}, headers=[], checksum=None, serialized_key_size=-1, serialized_value_size=56, serialized_header_size=-1) 読み出したデータ Data ID: 2021/04/10-20:51:45:335406 Data X: 701 Data Y: 253 !!!!! 割り込み発生 !!!!! 処理の終了 11回の処理を実行しました プロデューサ側の入力 {'id': '2021/04/10-20:51:36:287301', 'x': 910, 'y': 454} {'id': '2021/04/10-20:51:37:293747', 'x': 905, 'y': 458} //////// 途中省略 ///////// {'id': '2021/04/10-20:51:44:327730', 'x': 605, 'y': 254} {'id': '2021/04/10-20:51:45:335406', 'x': 701, 'y': 253} 今回のまとめ 今回は、Equalumの上流側データソースとして、kafkaを繋げるための前準備を行いました。Equalum社の製品・開発のVP、CTOとやりとりをさせてもらった際に、RDB系のCDC版Exactly Onceだけではなくて、kafkaを上流に設定した場合も、Equalum以降では、Exactly Onceを同じ様にサポート出来る・・との事でした。また、幾つかのパターンに分けてこの可能性を考えてみると、 技術的なユースケースの観点から: Kafka-> Equalum-> RDBMS このパターンは通常、アプリケーション統合等に使用されるパターンで、アプリケーションの特定のイベントがKafkaへの書き込みをトリガーし、Equalumがこのイベントを読み取って必要な変換/エンリッチメントなどを行い、そのデータを2番目のデータベースに速やか且つ確実プッシュする事が出来ます。 Kafka-> Equalum-> Kafka このパターンも前述同様に、かなりの頻度でアプリケーション統合に使用されており、アプリケーションの特定のイベントがKafkaへの書き込みをトリガーし、Equalumはこのイベントを読み取って、前述同様に必要な変換/エンリッチメントなどを実行(この部分は、EqualumのGUIで行いますので、皆様が既に暗記されている「Noコード」の恩恵を受ける事が可能です)し、変換されたイベントを別のKafkaトピックにプッシュして、他のアプリケーションでさらに処理を続行する事になります。(戦略的にkafkaの途中処理に柔軟な介入を可能とする事で、新しいkafkaの可能性が出てくる使い方になるかと) RDBMS-> Equalum-> Kafka このパターンは、「イベント生成」または「イベントソーシング」と呼ばれる使い方で、EqualumはデータベースからCDCイベントを読み取り、必要に応じてデータを変換し、誰でも簡単に利用できるように前準備を提供してから、当該イベントをKafkaトピックに公開し、処理を継続していきます。既存のデータベース情報を速やか&確実にkafkaのパイプに提供出来ますので(FLOWの部分は前述同様の「Noコード」で)面白い使い方が可能になるかと思います。 等が出てきますので、最近良く話を聞く「xxOps」的なシステム活用を「データ流通」の側面で大きくサポート出来る可能性が有ると思います。 次回は・・・ 次回は、今回の環境を活用していよいよEqualumを間に入れて、ターゲット・データソースとしてMySQLに「Exactly Once」で着地させてみたいと思います。 謝辞 本検証は、Equalum社の最新公式バージョン(V2.24)を利用して実施しています。この貴重な機会を提供して頂いたEqualum社に対して感謝の意を表すると共に、本内容とEqualum社の公式ホームページで公開されている内容等が異なる場合は、Equalum社の情報が優先する事をご了解ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Skypeの履歴 'messages.json' をPythonでいい感じに出力する

Skypeの履歴を出力したいと思いましたが、 アプリからまとめてコピペができなかったので こちらのページから履歴をダウンロードしました。 ファイルとチャット履歴のコピーの要求 ダウンロードできたのは'messages.json' エクスポート用のメッセージ ビューアーで見るといいらしいのですが ダウンロードできませんでした。macには対応していないのかな? そこで、pythonで読みやすく出力してみました。 skype2txt.py import json # jsonファイル読み込み json_open = open('messages.json', 'r') json_load = json.load(json_open) #ざっくり確認 #print(json_load) # conversationsから印刷したいスレッドを探す df = pd.json_normalize(json_load,record_path='conversations') # 印刷したいスレッドを指定 df1 = df.iloc[1] df2 = pd.json_normalize(df1['MessageList']) #df2.head(1) # idがUNIX時間なので、ソートして変換 df2 = df2.sort_values(by="id") df2['timestamp'] = df2['id'].astype(int)/1000 df2['timestamp'] = pd.to_datetime(df2['timestamp'].astype(int), unit='s') 画面で確認する skype2txt.py for index, row in df2.iterrows(): print() print(row['timestamp']) print(row['displayName']) print(row['content']) 印刷して確認する f = open('skype.txt', 'w', encoding='UTF-8') for index, row in df2.iterrows(): f.write('') f.write(row['timestamp']) f.write(row['displayName']) f.write(row['content']) f.close() 残念、印刷すると改行がうまく反映されない。 とりあえず、ざっくり見渡すのはこれでよさそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Pandas】matplotlibを使ってグラフを書く方法 ① .no.37

こんにちは、まゆみです。 Pandasについての記事をシリーズで書いています。 今回の記事は第37回目になります。 このシリーズも終盤に近付いてきました。 40記事前後の記事でPandasの全体の概要はつかめると思うので、是非頑張っていきましょう! ちなみにPandasの人気に関するグラフを見つけたので、貼っておきますね 引用元:The rise in popularity of Pandas そして今回の記事のテーマは『グラフを描く』ことになります。 数字のデータをグラフ化すると、そのトレンドが一目で分かったりと大変便利です。 (上のグラフのデータも数字だけで書かれては、急上昇しているトレンドが一目では分からないですよね。) matplotlibというライブラリーを使えば、 折れ線グラフや 棒グラフや 円グラフ を描くことができるようになります。 ではさっそく始めていきますね matplotlibのインポート matplotlibは『マットプロットリブ』と発音します。 matplotlib は慣習的にエイリアスは『plt』です import matplotlib.pyplot as plt %matplotlib inline 『%matplotlib inline』はjupyter notebook 内に結果を表示させるために必要だから書けと言われたのですが、書かなくてもjupyter notebookに表示される時があります。 こちらのページに『%matplotlib inline』は書くべきか、書かなくてもいいのか?の詳しい説明が載っていましたので貼っておきますね。 また、matplotlibをうまくインポートできない人は、Anacondaにmatplotlibをインストールしていますか? 「インストールしていないかも」っていう人はこちらを参考にして、インストールしてから再度インポートしてみてくださいね。 今回使うデータ 今回は、株価情報をグラフ化したいので、『pandas_datareader』を使ってyahoo!finance から情報を取ってきます。 (株価情報を取ってくる方法はこちらの記事に詳しく書いています。) 今回は、2020/4/9~2021/4/9までのAmazonの株価情報を取り出しました。 グラフを描く ではmatplotlib のplot()メソッドを使うとどのようなグラフが描けるか見てみましょう DataFrameに.plot()メソッドを使うと、全ての数字の値(整数、少数)でできたコラムを使ったグラフが作られます。 今回のデータですと、6つのコラムの値は全て数字なので全てのコラムの値からグラフが作られます。 ただ、Volumeの値が他の値に比べてとびぬけて大きいので、表示されているけど見えていない状態です。 また、DataFrameのインデックスはx軸に、DataFrameのコラムはy軸でグラフが作られます。 ではコラムのなかの『Close』のみをy軸にとってグラフを作ってみましょう。.plot()メソッドのパラメーターyの引数に使いたいコラムを引数として渡します。 もしくは、DataFrameから、グラフを作りたいコラムのみを取り出した上でグラフを作っても同じ結果になります。 2つ以上のコラムを使う 先ほどのように、Volumeが他のコラムの値に比べてとびぬけて大きい値である時以外は、2つ以上のコラムの値を同時にグラフに表示させることもできます。 使いたいコラムをリストにします。 最初に使いたいコラムのみを抜き出してグラフを作るか、パラメーターy の値を使いたいコラム名にするかどちらの方法にせよ、コラム名をリスト型にして渡します。 グラフの色を変える plt.style.available と書くと、色々なテーマのテンプレートが詰まったリストを返してくれます。 ではこのテーマを使ってグラフの色を変えてみましょう plt.style.use("使いたいテーマ") と書いた後、.plot()メソッドを使ってグラフを描くと色々なテーマが楽しめます。 まとめ 今回の記事では、matplotlibというライブラリーを使って、折れ線グラフを描く方法を書かせていただきました。 次回の記事では、棒グラフや円チャートを描く方法を書いていこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む