物理の駅 Physics station by 現役研究者

テクノロジーは共有されてこそ栄える

Google Apps Script(GAS): Google Drive上のテキストファイルを読み取り、上書きする

Google Apps Scriptで、Google Drive上のテキストファイル(TXTやJSON)を読み取り、上書きしてみよう。

ファイルIDは、Google Drive上のファイルを表示させて、「共有」から「リンクをコピー」したときの https://drive.google.com/file/d/この部分/view?usp=sharing のことである。

読み取り

  var FILE_ID = "set file id here";
  var file = DriveApp.getFileById(FILE_ID);
  console.log(file.getBlob().getDataAsString());  

書き込み(上書き)

  var FILE_ID = "set file id here";
  var file = DriveApp.getFileById(FILE_ID);
  file.setContent("file content");

Google Apps Script(GAS): SpotifyのAPIでプレイリストを取得する

Google Apps Script経由で、SpotifyのAPIを使って、プレイリストを取得してみよう

function myFunction() {
  var clientId = "set your clientId here";
  var clientSecret = "set your client secret here";

  var url = "https://accounts.spotify.com/api/token";
  var params = {
    method: "post",
    headers: { "Authorization": "Basic " + Utilities.base64Encode(clientId + ":" + clientSecret) },
    payload: { grant_type: "client_credentials" },
  };
  var response = UrlFetchApp.fetch(url, params);
  var access_token = JSON.parse(response.getContentText()).access_token;

  var playlists = ["4tY0lHoV8IemMBp4iTnKnl", "799Fo9to2TK0ahGEJsvcm2"];
  for (var i = 0; i < playlists.length; i++) {
    var url = "https://api.spotify.com/v1/playlists/" + playlists[i];
    var params = {
      method: 'get',
      headers: { 'Authorization': 'Bearer ' + access_token }
    };

    var response = UrlFetchApp.fetch(url, params);
    var data = JSON.parse(response.getContentText());
    Logger.log(data.name + " " + data.tracks.items.length)
  }
}

VScode: 独自の syntax highlighter 拡張機能を作る

シンタックス(構文) ハイライター用の拡張機能(Extension)を作る。拡張機能として公開することは目指さない。

インストールしていない場合は以下をインストールする。

VS Codeの雛形インストール

npm install -g yo generator-code
yo code

色々聞かれる。今回はLISE++ファイル(*.lpp)を読む拡張機能を作るので以下の設定とした。

 New Language Support を選ぶ
URL or file to import, or none for new: → `Enter`
What's the name of your extension? → `LISE-Reader`
What's the identifier of your extension? → `Enter`
What's the description of your extension? → `Enter`
 Language id: → `lise`
Language name: →`LISE`
File extensions: → `.lpp`
Scope names: → `lise-file.lpp`
Initialize a git repository? → Y
Open with code を選択

lise.tmLanguage.json を見ると

{
    "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
    "name": "LISE",
    "patterns": [
        {
            "include": "#keywords"
        },
        {
            "include": "#strings"
        }
    ],
    "repository": {
        "keywords": {
            "patterns": [{
                "name": "keyword.control.lise",
                "match": "\\b(if|while|for|return)\\b"
            }]
        },
        "strings": {
            "name": "string.quoted.double.lise",
            "begin": "\"",
            "end": "\"",
            "patterns": [
                {
                    "name": "constant.character.escape.lise",
                    "match": "\\\\."
                }
            ]
        }
    },
    "scopeName": "lise-file.lpp"
}

これが意味するところは、

  • if while for return が完全一致でキーワードに
  • " で囲まれた文字は文字列扱いに。その中で \ 直後の1文字はエスケープ文字

である。実際に試してみよう。F5 でデバッグ開始すると、新しいvscode のウィンドウが立ち上がる。右下の言語モードの選択から LISE(lise) を選べるようになる。

確かにハイライトが機能しているようだ。

ここまでは簡単だが、各自の言語の構文を一つ一つ実装していく必要がある。

LISE++ファイルの構文を実装してみたので、気になる人はGIthub のリポジトリを参考にしてほしい。

github.com

拡張機能として公開したい人はこのブログを参照されたし。

qiita.com

Python+matplotlib: ログスケールでの目盛り、サブ目盛りを設定する

十分な広さのfigsizeにグラフを書くと、軸はそれなりに表示される

import matplotlib.pyplot as plt

fig, axes = plt.subplots(ncols=2, nrows=2)
for ax in axes.flat:
    ax.plot([1,2], [0.00001,1])
    ax.set_yscale("log")
plt.tight_layout()
plt.show()

グラフに使える面積が小さくなると、目盛りやラベルを勝手に省略してしまうことがある。例えば、4x4のsubplotsの場合は目盛りがほとんど表示されなくなる。

import matplotlib.pyplot as plt

fig, axes = plt.subplots(ncols=4, nrows=4)
for ax in axes.flat:
    ax.plot([1,2], [0.00001,1])
    ax.set_yscale("log")
plt.tight_layout()
plt.show()

対処療法として、文字サイズを小さくするか、figsizeを大きくする方法があるものの、根本解決ではない。

1つ目の対応策は、set_xticks() set_yticks() で、目盛り=ticksを明示的に設定し、必要なticksを表示させる。

import matplotlib.pyplot as plt

fig, axes = plt.subplots(ncols=4, nrows=4)
for ax in axes.flat:
    ax.plot([1,2], [0.00001,1])
    ax.set_xticks([1.0,1.5,2.0])
    ax.set_yscale("log")
    ax.set_yticks([1e-5,1e-4,1e-3,1e-2,1e-1,1e-0])
plt.tight_layout()
plt.show()

2つ目の対応策は、locator を使う方法である。set_ticks では設定できない minor ticksの設定が可能になる。また、描画範囲が変わった時でもコードの修正を必要としない。

import matplotlib.pyplot as plt
from matplotlib.ticker import (MultipleLocator, LogLocator)

fig, axes = plt.subplots(ncols=4, nrows=4)
for ax in axes.flat:
    ax.plot([1,2], [0.00001,1])
    ax.set_yscale("log")
    ax.xaxis.set_major_locator(MultipleLocator(0.5)) #major ticksは0.5刻み
    ax.xaxis.set_minor_locator(MultipleLocator(0.1)) #minor ticksは0.1刻み

    ax.yaxis.set_major_locator(LogLocator(base=100)) #基数 100で描画
    ax.yaxis.set_minor_locator(LogLocator(base=100,subs=[10])) #基数 100、その 10倍 も描画
    ax.yaxis.set_minor_formatter("") #minor ticksのlabelは表示されるので非表示に

plt.tight_layout()
plt.show()

Python awkward: parquet形式で1億イベントのtreeのIO(入出力)速度を圧縮形式で比較する

この記事の Awkward のバージョンは1.X.Xです

Python の awkward パッケージに実装されている parquet ファイル形式を入出力するときの処理時間を、圧縮形式ごとに比較してみる。配列の型は awkward.highlevel.Array と呼ばれる形式である。

8バイト(int64_t)✕8メンバー✕1億エントリーなので、圧縮なしの場合は6.4GBのデータサイズとなる。

圧縮形式
Compression algorithm
ファイルサイズ
File size
出力時間
Write time
読み込み時間
Read time
理想IO時間
Ideal time
NONE(無圧縮) 6.40 GB 13.8 sec 16.5 sec 12.8 sec
SNAPPY(デフォルト) 3.21 GB 15.5 sec 18.4 sec 6.42 sec
LZ4 3.21 GB 11.0 sec 13.1 sec 6.42 sec
ZSTD 0.82 GB 19.0 sec 13.7 sec 1.64 sec
参考: ROOT LZ4 3.22 GB 37 sec 16 sec 6.44 sec
参考: ROOT ZSTD 0.56 GB 39 sec 25 sec 1.12 sec

理想IO時間は、SSDの読み書き速度を500MB/sとしたときのIO時間である。理想IO時間に、圧縮展開の時間、他の処理の時間を加えると、出力時間、読み込み時間になる。

NONEは、無圧縮なので6.4GBもの容量を使ってしまうものの、理想IO時間との差は当然ながら少なかった。デフォルト SNAPPYLZ4 は 圧縮率は同じだが、IO速度はLZ4の圧勝だった。ZSTDは圧縮率は高いもののLZ4より遅い。LZ4ZSTDは、圧縮率を取るか、IO速度を取るかで選べばよさそうだ。

なお、C++でコンパイルしたROOTのTFileのIO速度と同じアルゴリズムで比べると(圧縮率の差はあるが)Pythonの方が2倍早い。Treeをシンプルな処理で使う分にはPythonで十分であることが分かるだろう。

Python awkward の parquet ファイルのIO速度を検証したコード。ディスクキャッシュを防ぐため、数20GBのディスク容量を使う。

import awkward as ak
assert(ak.__version__.split(".")[0]=="1")
import numpy as np
import time
import os

comps = ["NONE", "SNAPPY", "LZ4", "ZSTD"] #"GZIP", "BROTLI"は遅すぎるので除外
for comp in comps:
    for i in range(3):
        start = time.time()

        N = 1 * 10000 * 10000
        tree = ak.Array({})
        tree["x"] = np.arange(N, dtype=np.int64)
        tree["y"] = np.arange(N, dtype=np.int64)
        tree["ax"] = np.arange(N, dtype=np.int64)
        tree["ay"] = np.arange(N, dtype=np.int64)
        tree["ax0"] = np.arange(N, dtype=np.int64)
        tree["ay0"] = np.arange(N, dtype=np.int64)
        tree["ax1"] = np.arange(N, dtype=np.int64)
        tree["ay1"] = np.arange(N, dtype=np.int64)
        
        filename = f"tree{i}_{comp}.parquet"
        ak.to_parquet(tree, filename, compression=comp)

        end = time.time()
        print(f"WRITE {i} {comp} {os.path.getsize(filename)/1e9:.2f} GB {(end-start):.1f} sec")

        del tree
import awkward as ak
import numpy as np
import time

comps = ["NONE", "SNAPPY", "LZ4", "ZSTD"] #"GZIP", "BROTLI"は遅すぎるので除外

for comp in comps:
    for i in range(3):
        start = time.time()
        filename = f"tree{i}_{comp}.parquet"
        tree = ak.from_parquet(filename, lazy=True)
        for key in ["x","y","ax","ay","ax0","ay0","ax1","ay1"]:
            print(np.max(tree[key]),end=" ")
        end = time.time()
        print(f"READ {i} {comp} {(end-start):.1f} sec")
        
        del tree

Awkward 2.X.Xを使う場合は、おそらく以下のように書き換えるとよい

import awkward as ak
assert(ak.__version__.split(".")[0]=="2")
import numpy as np
import time
import os

comps = ["NONE", "SNAPPY", "LZ4", "ZSTD"] #"GZIP", "BROTLI"は遅すぎるので除外
for comp in comps:
    for i in range(3):
        start = time.time()

        N = 1 * 10000 * 10000
        tree = ak.Array({"x":np.arange(N, dtype=np.int64)})
        for key in ["y","ax","ay","ax0","ay0","ax1","ay1"]:
            tree[key] = np.arange(N, dtype=np.int64)
        
        filename = f"tree{i}_{comp}.parquet"
        ak.to_parquet(tree, filename, compression=comp)

        end = time.time()
        print(f"WRITE {i} {comp} {os.path.getsize(filename)/1e9:.2f} GB {(end-start):.1f} sec")

        del tree
import dask_awkward as dak
import awkward as ak
import time

comps = ["NONE", "SNAPPY", "LZ4", "ZSTD"] #"GZIP", "BROTLI"は遅すぎるので除外

for comp in comps:
    for i in range(3):
        start = time.time()
        filename = f"tree{i}_{comp}.parquet"
        tree = dak.from_parquet(filename)
        for key in ["x","y","ax","ay","ax0","ay0","ax1","ay1"]:
            print(ak.max(tree[key]).compute(),end=" ")
        end = time.time()
        print(f"READ {i} {comp} {(end-start):.1f} sec")
        
        del tree