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

職場のパソコンでマウス操作やキーボード操作を自動化する

はじめに 職場で使っている業務システムの入力を高速で行うためにマウス操作やキーボード操作を自動化したいと思い、色々と試行錯誤した過程を書き記しました。同じようなことをやりたいと考えている方の参考になればと思います。 背景 毎年、年度末にひたすらシステムに入力を行う作業が発生し、長時間の超過勤務をしていました。全く同じマウス操作およびキーボード操作をずっと繰り返す作業なので、自動化できれば作業時間を大幅に短縮できると考えました。ただし、職場のパソコンなので、ネットから自動化ツール等をダウンロードしてきてインストールするのは極力避けたかったです。 最終的にしたこと WindowsPowerShellでマウス操作およびキーボード操作を所定の順番で行うスクリプトを作成し、これまで手で行っていた操作をほぼ全て自動化することができました。 結果 1か月の超過勤務時間を前年より27時間削減できました。 職場のパソコンの環境 Windows10 自動化への道のり 最終的にはWindowsPowerShellを使って自動化したのですが、そこに至るまでに色々と試行錯誤があったので、その過程を書き記します。 Pythonで自動化 そもそも自動化をやろうと思ったきっかけが「退屈なことはPythonにやらせよう」という本を本屋で見かけたからだったので、最初はPythonでやってみようと思っていました。実際、調べてみたところPythonを用いた自動化は簡単に行えそうでした。下記の記事が大変参考になりました。 https://qiita.com/konitech913/items/301bb63c8a69c3fcb1bd 例えば、下記はメモ帳を自動で開いて、文字入力を行うスクリプトです。 # PyAutoGUIライブラリのインポート import pyautogui # timeモジュールのsleep関数をインポート from time import sleep # Windowsボタン押下 pyautogui.press('win') # 検索窓に「notepad」と入力 pyautogui.write('notepad') # Enterボタン押下 pyautogui.press('enter') # メモ帳が起動するまで待機(待機時間は適宜調整) sleep(1) # 文字を入力 pyautogui.write('abcdefghi') また、下記はペイントを開いて四角を描くスクリプトです。 # PyAutoGUIライブラリのインポート import pyautogui # timeモジュールのsleep関数をインポート from time import sleep # Windowsボタン押下 pyautogui.press('win') # 検索窓に「paint」と入力 pyautogui.write('paint') # Enterボタン押下 pyautogui.press('enter') # ペイントが起動するまで待機(待機時間は適宜調整) sleep(1) # マウスを描き始めの場所まで移動(座標値は適宜調整) pyautogui.moveTo(200,200) # 各頂点へマウスをドラッグして四角を描く(座標値は適宜調整) pyautogui.dragTo(200,600) pyautogui.dragTo(800,600) pyautogui.dragTo(800,200) pyautogui.dragTo(200,200) 各関数の意味等は他の記事を参照してください。これらのスクリプトを実行すると、自動でメモ帳なりペイントなりが開いて操作が行われます。これを応用して組み合わせていけば、マウスとキーボードの操作の組み合わせで行う大抵の操作は自動化できるかと思います。 しかし問題点として、実行環境にPythonがインストールされていなければこのスクリプトの実行はできません。職場のパソコンに何かをインストールするのは色々と面倒なので、結局Pythonを使うのは微妙だなということになりました。 C言語で自動化 次に考えたのは、C言語で操作を自動で実行するプログラムを作成する方法です。あらかじめ作成したプログラムをコンパイルし、実行ファイルを職場のパソコンに持っていって実行させれば、特に何かをインストールしなくても自動操作が実現できるはずです。外部から職場のパソコンにファイルを持ってくるのは、何かをインストールするよりはハードルが低そうです。C言語を選択したのは、唯一経験のあるプログラミング言語だったからです。 調べてみたところ、WindowsAPIなるものを呼び出すプログラムを作成すれば、自動操作が実現できるようです。下記のサイトが参考になりました。 http://www.sm.rim.or.jp/~shishido/inputevent.html http://kaitei.net/winapi/ https://kagasan.hatenablog.com/entry/2016/11/27/175657 https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes 例えば先ほどのペイントで自動で四角を描くスクリプトは、C言語で書くと下記のようになります。 #include <stdio.h> #include <windows.h> /* windows.hを読み込み */ int main(){ /* Windowsボタン押下 */ keybd_event(VK_LWIN,0,KEYEVENTF_EXTENDEDKEY|0,0); Sleep(10); keybd_event(VK_LWIN,0,KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP,0); /* 待機(待機時間は適宜調整、以下同様) */ Sleep(500); /* 検索窓に「paint」と入力 */ keybd_event(0x50,0,KEYEVENTF_EXTENDEDKEY|0,0); Sleep(10); keybd_event(0x50,0,KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP,0); Sleep(10); keybd_event(0x41,0,KEYEVENTF_EXTENDEDKEY|0,0); Sleep(10); keybd_event(0x41,0,KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP,0); Sleep(10); keybd_event(0x49,0,KEYEVENTF_EXTENDEDKEY|0,0); Sleep(10); keybd_event(0x49,0,KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP,0); Sleep(10); keybd_event(0x4E,0,KEYEVENTF_EXTENDEDKEY|0,0); Sleep(10); keybd_event(0x4E,0,KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP,0); Sleep(10); keybd_event(0x54,0,KEYEVENTF_EXTENDEDKEY|0,0); Sleep(10); keybd_event(0x54,0,KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP,0); /* 待機 */ Sleep(500); /* Enterボタン押下 */ keybd_event(VK_RETURN,0,KEYEVENTF_EXTENDEDKEY|0,0); Sleep(10); keybd_event(VK_RETURN,0,KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP,0); /* ペイントが起動するまで待機 */ Sleep(1000); /* マウスを描き始めの場所まで移動 */ SetCursorPos(200,200); Sleep(100); /* 各頂点へマウスをドラッグして四角を描く(座標値は適宜調整) */ mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0); Sleep(100); SetCursorPos(200,600); Sleep(100); SetCursorPos(800,600); Sleep(100); SetCursorPos(800,200); Sleep(100); SetCursorPos(200,200); Sleep(100); mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0); return 0; } これをコンパイルして実行すれば、自動でペイントを起動して四角が描かれます。検索窓にpaintと入力するところは、「p」「a」「i」「n」「t」をそれぞれkeybd_eventで順番にキー入力するという力技です。Pythonのpyautogui.writeのようにテキストを直接入力できるような方法があればよかったのですが、調べてもよくわかりませんでした。 WindowsAPIについて詳しく分かっているわけではないので、各関数の詳細やそもそもWindowsAPIとは何かという説明については他の記事や文献をあたってください。私にとっては、こう書けばこう動くということが分かれば十分でした。 見ての通り、Pythonほど簡潔に書くことはできませんし、ネットで調べても何やら難解な記事ばかりで私のような素人レベルで参考にできる情報の量はあまり多くなかったですが、それでもマウス操作やキーボード操作はできているので、一応自動化は実現出来そうでした。あとは、必要な操作を順番に行うプログラムを自宅で書いてコンパイルし、実行ファイルを職場に持っていくだけです。 が、ここまで来て、もっといい方法がありそうなことに気づきました。それが、以下に記すWindowsPowerShellを使う方法です。 WindowsPowerShellで自動化 ここまで書いてきたことを調べる過程で、どうやらWindowsのパソコンにはWindowsPowerShellなるものがデフォルトで備わっていて、これまでやろうとしていたことは全てWindowsPowerShellでもできるらしいということを知りました。恥ずかしながら、WindowsPowerShellというものの存在もここで初めて知りました。Windowsにデフォルトで入っているなら当然職場のパソコンにも入っているはずですし、実行ファイルを職場に持っていくというようなことをせずとも、職場でスクリプトの作成や編集をすることが可能です。というわけで、現時点で最もハードルの低い方法に思えたので、WindowsPowerShellを使う方針に切り替えました。 スクリプトの書き方は下記のページを参考にしました。 https://tarenagashi.hatenablog.jp/entry/2020/12/07/213259 https://mitosuya.net/automation-input-keyboard https://qiita.com/nimzo6689/items/488467dbe0c4e5645745 https://note.com/mirupiso/n/n1190de896767 例えば、上記のペイントに四角を描くプログラムは、WindowsPowerShellだと下記のようになります。 #System.Windows.Formsなどのインポート Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing # Windows APIの宣言 $signature=@' [DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)] public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo); '@ $SendMouseClick = Add-Type -memberDefinition $signature -name "Win32MouseEventNew" -namespace Win32Functions -passThru # Windowsキー(Ctrl+Escキー)押下 [System.Windows.Forms.SendKeys]::SendWait("^{ESC}") Start-Sleep -m 100 # 検索窓に「paint」と入力 [System.Windows.Forms.SendKeys]::SendWait("paint") Start-Sleep -m 100 # Enterボタン押下 [System.Windows.Forms.SendKeys]::SendWait("{ENTER}") # ペイントが起動するまで待機(待機時間は適宜調整) Start-Sleep -m 1000 # マウスを描き始めの場所まで移動(座標値は適宜調整、以下同様) [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(200, 200) Start-Sleep -m 100 # 各頂点へマウスをドラッグして四角を描く $SendMouseClick::mouse_event(0x00000002, 0, 0, 0, 0) Start-Sleep -m 100 [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(200, 600) Start-Sleep -m 100 [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(800, 600) Start-Sleep -m 100 [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(800, 200) Start-Sleep -m 100 [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(200, 200) Start-Sleep -m 100 $SendMouseClick::mouse_event(0x00000004, 0, 0, 0, 0) 各処理の詳細はやはり他の記事等を参照してください。Windowsキー押下については、下記のページの「Ctrl+EscキーでWindowsキー押下になる」というテクを参考にしました。 https://qiita.com/kiduki/items/51301918c4b415255890 画面上のある場所をクリックしたいという場合は、先述の記事のやり方を参考にしました。 [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") Start-Sleep -s 3 $X = [System.Windows.Forms.Cursor]::Position.X $Y = [System.Windows.Forms.Cursor]::Position.Y Write-Output "X: $X | Y: $Y" Read-Host このスクリプトを実行すると、3秒後にマウスカーソルの座標値が表示されます。これでクリックしたいボタン等の座標を取得し、以下のx,yに代入すれば任意の場所がクリックできます。 # マウスカーソル移動 [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(x, y) # クリックイベント生成 $SendMouseClick::mouse_event(0x0002, 0, 0, 0, 0); $SendMouseClick::mouse_event(0x0004, 0, 0, 0, 0); また、繰り返し処理については、色々なやり方があるかと思いますが、下記の記事の方法がシンプルだったので参考にしました。 https://mk-55.hatenablog.com/entry/2016/12/17/115632 1..n | %{ # 繰り返したい処理 } これにより、繰り返したい処理をn回繰り返すことができます。スクリプトの一部で繰り返し処理を行ったり、あるいはスクリプト全体を繰り返し処理したりできます。 これらの要素を組み合わせて、システムへの入力操作の手順を1個1個再現していくことで、入力作業を完全に自動化することに成功しました。これまで夜遅くまでヒイヒイ言いながらやっていた作業が、繰り返し処理を設定してスクリプトを実行することで、トイレに行っている間に終わっているのは中々に爽快でした。 課題 操作自動化により業務時間を大幅に削減することに成功しましたが、いくつか課題も残っています。 システムのユーザーインターフェースが変わると使えなくなる 今回作成した自動化スクリプトでは、システム上のボタン等の座標を事前に取得し、その数値をスクリプト上にベタ打ちすることでボタンのクリック操作を実現しました。従って、システムのバージョンアップなどによりボタンの配置が変わったりしたら再度座標の取得し直しになります。また、操作方法が根本的に変更になった場合、一から作り直しになります。 別のパソコンで使えない 一般的に、別のパソコンでは画面上のボタン等の座標値が違います。従って、別のパソコンで使いたい場合はそのパソコンで改めて座標の取得し直しになります。 変化に弱い 毎回全く同じ操作をずっと行うスクリプトなので、例外的な処理を行う案件などが一つでも混ざっていると対応できません。また、いつもより読み込みに時間がかかったりして待機時間をはみ出してしまうとハマります。待機時間はなるべく長めに取っておいたほうがスピードは落ちますが、安定性は増します。条件分岐等を駆使すれば、もっと汎用性を持たせられるかもしれません。 何が起こるかわからない 今回のスクリプトは「こう書けばこう動く」程度の理解で作ったもので、WindowsPowerShellやWindowsAPIなどについて深く理解しているわけではないので、何か変なことが起こる可能性を否定できません。半年ほど使ってみていますが、今のところ特に変なことは何も起きていません。 まとめ 職場のパソコンで操作を自動化するためにあれこれ考えた結果、WindowsPowerShellで自動化させることに成功しました。今回自動化の対象にしたのはとても単調な繰り返し作業だったため自動化する価値がありましたが、他の業務にも応用できないか試してみたところ、似たような業務なら応用できる場合もありましたが、「これの自動化スクリプト書くくらいなら手でやったほうが早いな」という場合が多かったです。自動化できるかどうかとはまた別の問題で、自動化したほうが本当に早いかどうかは冷静に見極める必要があると思います。 今回取り組んだ問題を簡潔に言い表すと「何もインストールしない素のWindowsパソコンで操作を自動化する最も簡単な方法は?」という問いになるかと思います。私は最終的にWindowsPowerShellを用いるという結論になりましたが、別の方法もあるかと思います。 操作自動化の一般論や善し悪しについては下記の記事が詳しいです。 https://qiita.com/mima_ita/items/4149a4cdb9a33084258b https://qiita.com/mima_ita/items/453bb6c313e459c44689
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2021.10.11】学び日記_勉強方法の検討/Python/SQL

やったこと 初期のレッスンをプロゲートかドットインストールのどちらで進めるか検討 どちらも月額1,000円くらいだが、集中度的にも、一旦はどちらか片方から始めた方が良さそう 結論としては、最初はプロゲートでやってみる   プロゲート   〇:環境構築が不要で、かつ過去に有料コースで利用したことがあり、馴染みがある。   △:実際の開発環境との乖離が大きい?気がする(素人感覚…)   ⇒初心者に優しく、コードを覚えることに集中できて楽しい!   ドットインストール   〇:実際の開発環境に近そう(素人感覚…)、かつ1動画が短くて集中が切れない   △:分からない単語がポンポン出てきて難しさを感じる   ✕:環境構築しないと実践できない(AWS Cloud9を使って、と案内がある)   ⇒慣れてきたらこっちかな、という感覚   ⇒プロゲートの学習コース終了後に環境構築する手順があるため、それからやってみようと判断 Pythonのレッスンを受講 有料コースに登録して早速Python2を進め始めた まだまだ壁には当たらず楽しいだけの状態 SQLを朝に少し 過去受講したコースを少し復習してみた 学んだこと ・Pythonでリストを更新する際、上からコードが実行されるため、printの位置を間違えると更新前の値が出力されてしまうので注意 ・「for 変数名 in リスト:」を使うとprintの中身が繰り返し処理される  →動きが不思議すぎるw  →どんな場面で必要なのかイメージが付かないw ・"を以下のように使えばコメントを複数行残せる    """    Coment    Coment    """   #で残せることは知っていたが上記のやり方は知らなかった ・AWSは登録後、1年間は無料利用枠を貰える   →しかしうっかり有料枠を使ってしまうこともあるらしい   →請求アラートとか立てられるらしい 残ってる疑問 ・「:」や「;」や「[]」や「{}」の使い分けがよく分からない  →必要だったり不要だったり、リストと辞書で括弧が違ったり ・リストや辞書の更新ってすごくミスの温床のような印象。  →根本的な更新ができないor今回だけこうしたい、というような場面で使うのだろうか これまで修了したコース Progate Python 1 Progate SQL 1~4 Progate HTML&CSS 初級編
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ABC140 C - Maximal Value を解いた

問題文の通りに書いてみた MaximalValue.py N = int(input()) A = [0]*N B = list(map(int,input().split())) for i in range(N-2,-1,-1): if i == N-2: A[i],A[i+1] = B[i],B[i] else: A[i] = B[i] if B[i] <= A[i+1]: A[i+1] = B[i] print(sum(A))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ABC139 C - Lower を解いた

もっと上手な書き方はあると思う。 Lower.py N = int(input()) H = list(map(int,input().split())) ref = H[0] cnt = 0 ans = 0 for i in range(1,N): if H[i] <= ref: cnt += 1 ref = H[i] ans = max(ans, cnt) else: ref = H[i] ans = max(ans, cnt) cnt = 0 print(ans)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

犬・猫画像識別アプリ作成

犬・猫画像識別アプリ作成 はじめに 初めまして!python初心者のgenkiです。 学習内容のアウトプットを目的としてブログ制作しました。 https://dog-cat1234.herokuapp.com/ アドバイス等あればご指導頂ければ幸いです。 目次 学習データの準備 モデルの定義 モデルの学習 モデルの評価 アプリの実装 動作確認 1.学習データの準備 学習に使用する画像は下記から引用しています。 https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz こちらの画像は犬、猫が混同していた為手動で振り分けました。 2.モデルの定義 KerasのSequentialモデルによるモデルの定義を以下に示します。 # モデルにvggを使います input_tensor = Input(shape=(50, 50, 3)) vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor) # vggのoutputを受け取り、2クラス分類する層を定義します # その際中間層を下のようにいくつか入れると精度が上がります top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(256, activation='relu')) top_model.add(Dropout(0.5)) top_model.add(Dense(2, activation='softmax')) # vggと、top_modelを連結します model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) # vggの層の重みを変更不能にします for layer in model.layers[:19]: layer.trainable = False # コンパイルします model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), metrics=['accuracy']) 3.モデルの学習 model.fit(X_train, y_train, batch_size=100, epochs=10, validation_data=(X_test, y_test)) Epoch 1/10 39/39 [==============================] - 36s 139ms/step - loss: 8.8243 - accuracy: 0.6019 - val_loss: 3.7073 - val_accuracy: 0.6907 Epoch 2/10 39/39 [==============================] - 3s 84ms/step - loss: 4.9566 - accuracy: 0.6544 - val_loss: 2.4863 - val_accuracy: 0.7022 Epoch 3/10 39/39 [==============================] - 3s 84ms/step - loss: 3.4556 - accuracy: 0.6664 - val_loss: 1.8680 - val_accuracy: 0.7095 Epoch 4/10 39/39 [==============================] - 3s 84ms/step - loss: 2.5457 - accuracy: 0.6742 - val_loss: 1.5202 - val_accuracy: 0.7095 Epoch 5/10 39/39 [==============================] - 3s 84ms/step - loss: 1.9700 - accuracy: 0.6951 - val_loss: 1.2439 - val_accuracy: 0.6980 Epoch 6/10 39/39 [==============================] - 3s 85ms/step - loss: 1.5720 - accuracy: 0.7027 - val_loss: 1.0528 - val_accuracy: 0.7053 Epoch 7/10 39/39 [==============================] - 3s 85ms/step - loss: 1.3735 - accuracy: 0.6954 - val_loss: 0.9597 - val_accuracy: 0.7053 Epoch 8/10 39/39 [==============================] - 3s 85ms/step - loss: 1.1443 - accuracy: 0.6959 - val_loss: 0.8448 - val_accuracy: 0.6991 Epoch 9/10 39/39 [==============================] - 3s 85ms/step - loss: 0.9943 - accuracy: 0.7014 - val_loss: 0.7856 - val_accuracy: 0.7022 Epoch 10/10 39/39 [==============================] - 3s 86ms/step - loss: 0.9087 - accuracy: 0.6978 - val_loss: 0.7203 - val_accuracy: 0.7074 上記の通り「loss: 0.9087:損失」は減少し、「accuracy: 0.6978:正解率」は上昇している事がわかります。 4.モデルの評価 # 画像を一枚受け取り、犬か猫かを判定する関数 def pred_gender(img): img = cv2.resize(img, (50, 50)) print(np.array([img]).shape) pred = np.argmax(model.predict(np.array([img]))) if pred == 0: return 'dog' else: return 'cat' # pred_gender関数に画像を渡して犬か猫を予測します img = cv2.imread('./dog/' + path_dog[0]) b,g,r = cv2.split(img) img = cv2.merge([r,g,b]) plt.imshow(img) plt.show() print(pred_gender(img)) (1, 50, 50, 3) cat 今回は残念ながら不正解となりました。 5.アプリの実装 import os from flask import Flask, request, redirect, render_template, flash from werkzeug.utils import secure_filename from tensorflow.keras.models import Sequential, load_model from tensorflow.keras.preprocessing import image import numpy as np classes = ["犬","猫"] image_size = 50 UPLOAD_FOLDER = "uploads" ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) app = Flask(__name__) def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS model = load_model('./model.h5')#学習済みモデルをロード @app.route('/', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': if 'file' not in request.files: flash('ファイルがありません') return redirect(request.url) file = request.files['file'] if file.filename == '': flash('ファイルがありません') return redirect(request.url) if file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(UPLOAD_FOLDER, filename)) filepath = os.path.join(UPLOAD_FOLDER, filename) #受け取った画像を読み込み、np形式に変換 img = image.load_img(filepath, target_size=(image_size,image_size)) img = image.img_to_array(img) data = np.array([img]) #変換したデータをモデルに渡して予測する result = model.predict(data) predicted = result.argmax() pred_answer = "これは " + classes[predicted] + " です" return render_template("index.html",answer=pred_answer) return render_template("index.html",answer="") #if __name__ == "__main__": # #app.run() # app.run(debug=True) if __name__ == "__main__": port = int(os.environ.get('PORT', 8080)) app.run(host ='0.0.0.0',port = port) 6.動作確認 下記の通り問題なく動作しました。 下記画像を識別 結果
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

# 使い終わった壁紙を自動で削除したい!

使い終わった壁紙を自動で削除したい! 「東京ディズニーリゾートの壁紙を毎月自動でダウンロード・設定するプログラムを作った!」と友人に話をしたところ、「昔の壁紙は消えないの??」と聞かれました。 私は何かと記念に残しがちですが、よくよく考えれば確かに要らない気がするので、ダウンロードするタイミングで削除しちゃおうと思います。 前回の記事はこちら↓ ざっくりフローチャート 前回の記事で、「今月の壁紙が指定フォルダに格納されていない場合、壁紙をダウンロードする。」という条件分岐を追加したので、壁紙をダウンロードする処理の前に、古い壁紙を削除する処理(オレンジ部分)を追加します。 削除対象ファイルのリストアップ まずは削除対象のファイルをリストアップします。 wallpaper_yyyymm_1.jpgという名前のファイルを検索します。 今月の壁紙をダウンロードする前なので、対象のファイルを一度すべて消しちゃいます。 「先月の画像を削除する」という方法も考えましたが、1カ月以上パソコンをインターネット環境に接続しない場合に昔の壁紙が残ってしまうため、前の月以前の壁紙はすべて削除することにしました。 import glob rmpic = glob.glob(r'C:\Users\ayk_f\wallpaper\wallpaper_*_1.jpg') print(rmpic) glog.glog(<full_path>)で正規表現を利用し対象ファイルを指定します。 glogとは python のモジュールの一つで、特定のパターンにマッチするファイルを取得することができます。 今回のように、wallpaper_*_1.jpgに当てはまるファイルを抽出する、といった場合に使えるみたいです。 $ ls code/ wallpaper_202012_1.jpg wallpaper_202108_1.jpg wallpaper_202109_1.jpg フォルダの中身はこんな感じです。 wallpaper_202012_1.jpg, wallpaper_202108_1.jpg, wallpaper_202109_1.jpg の3つが抽出できればOKです。 $ python wallpaper_v3.0.py ['C:\\Users\\ayk_f\\wallpaper\\wallpaper_202012_1.jpg', 'C:\\Users\\ayk_f\\wallpaper\\wallpaper_202108_1.jpg', 'C:\\Users\\ayk_f\\wallpaper\\wallpaper_202109_1.jpg'] 対象の3つのファイルがフルパスで抽出できました。 抽出した画像の削除 対象ファイルの抽出ができたので、ファイルを削除します。 for文を用いて対象ファイル分だけ削除を実施する、という動きになります。 for t in rmpic: print("remove;{0}".format(t)) os.remove(t) おそらくですが、リストの0つ目、1つ目、2つ目・・・のように削除するみたいです。 ファイル名に整数が入っている必要はないみたいです。 ※私はファイル名が wallpaper_1.jpg, wallpaper_2.jpg, wallpaper_3.jpg のようなファイルにしておかないといけないと勘違いしていたので、結構混乱しました・・・。 $ python wallpaper_v3.0.py ['C:\\Users\\ayk_f\\wallpaper\\wallpaper_202012_1.jpg', 'C:\\Users\\ayk_f\\wallpaper\\wallpaper_202108_1.jpg', 'C:\\Users\\ayk_f\\wallpaper\\wallpaper_202109_1.jpg'] remove;C:\Users\ayk_f\wallpaper\wallpaper_202012_1.jpg remove;C:\Users\ayk_f\wallpaper\wallpaper_202108_1.jpg remove;C:\Users\ayk_f\wallpaper\wallpaper_202109_1.jpg remove;<full_path> で削除したファイルが出力されました。 $ ls code/ 削除も無事にできています! これで今回の任務は完了です。 さいごに 今回もプログラムの改版でした。 日曜大工的な感覚でやってますが、思った通りに動き出すと楽しいですね。 いつかは他の人でも使えるように、画像の格納先を最初に登録するような挙動も入れたいな・・・・と思ったり。 人に「こんなの作ったよ!」って話すことで、思ってもなかった意見がもらえるのでそれも楽しかったです。 参考リンク My Drive上にある画像(*.jpg)を一括削除したい。 - teratail Pythonでファイル・ディレクトリを削除するos.remove, shutil.rmtreeなど - note.nkmk.me 【初心者向け】Pythonのglobを徹底解説!正規表現の書き方も説明 - Tech Teacher Blog
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ソースコードからのCanteraのビルド (調査中)

環境とバージョン Windows 10 Home Cantera 2.5.1 Boost 1.77.0 Visual Studio 2019 事前準備 Anaconda環境を新しく作成してインストールする場合を想定します. Boostのインストール Boost公式:https://www.boost.org/ インストール方法を日本語で解説しているサイト(https://www.kkaneko.jp/tools/win/boost.html )の「システム環境変数 CPATH の設定」までを実行. ビルドには結構時間がかかります. SConsのインストール ビルドに使用するツールをインストールします conda install -c anaconda scons ruamel.yamlのインストール A YAML package for Python. It is a derivative of Kirill Simonov's PyYAML 3.11 which supports YAML1.1 Python用のYAMLパッケージらしいです. conda install -c conda-forge ruamel.yaml Canteraのビルド 公式のマニュアル: https://cantera.org/compiling/installation-reqs.html ローカル環境にCanteraのパッケージをクローンする git clone --recursive https://github.com/Cantera/cantera.git cd cantera sconsを使用したビルドコマンドを実行する.boost_ind_dirには上で実行したBoostのインストールフォルダを指定. scons build boost_inc_dir='C:\Program Files\boost\boost_1_77_0' 以下に始まるエラーが発生 C:\Users\sakir\SandBox\cantera_syoukera\include\cantera\ext\fmt\core.h(327): warning C4566: ユニバーサル文字名 '\u00B5' によって表示されている文字は、現在のコード ページ (932) で表示できません src\base\Units.cpp(29): error C2001: 定数が 2 行目に続いています。 エラーが発生している行は以下のように記述されており,この行を削除もしくはコメントアウトすると解消する. {"Å", Units(1e-10, 0, 1, 0)}, 無事にビルドが完了すると以下のような出力が得られてる. ******************************************************* Compilation completed successfully. - To run the test suite, type 'scons test'. - To list available tests, type 'scons test-help'. - To install, type 'scons install'. - To create a Windows MSI installer, type 'scons msi'. ******************************************************* scons: done building targets. ビルドが完了したので管理者権限でAnacondaのプロンプトを開き以下を実行してインストール. scons install 無事にインストールが完了すると以下のようなメッセージが表示される. Cantera has been successfully installed. File locations: applications C:\Program Files\Cantera\bin library files C:\Program Files\Cantera\lib C++ headers C:\Program Files\Cantera\include samples C:\Program Files\Cantera\samples data files C:\Program Files\Cantera\data minimal Python module scons: done building targets. と思いきやPythonでimport canteraしようとしても動かない.ビルドはC++の内容だけで,Cythonで提供されているPythonのインターフェースは別途設定が必要.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ディープラーニングは作画監督を見分けられるのか? 〜涼宮ハルヒの憂鬱[エンドレスエイト」より〜

ー はじめに ー もし、この記事を読む読者の方がアニメ好きならば、「涼宮ハルヒの憂鬱」の名を知らぬ人はいないでしょう。 今回は後世に名を残した「エンドレスエイト」より、絵コンテ・演出家、作画監督をディープラーニングで認識することができるのかを検証していきたいと思う。 ☆「涼宮ハルヒの憂鬱」を知らない方へ 涼宮ハルヒの憂鬱とは原作者:谷川 流氏、角川スニーカー文庫から刊行されているSF系学園ストーリー(筆者談、諸説あり) 2009年4月から放映されたアニメ2期「涼宮ハルヒの憂鬱」にて、世間を騒がす大事件が起きました。 「エンドレスエイト」 原作である「涼宮ハルヒの暴走」の名が指し示す通り、アニメ涼宮ハルヒの憂鬱が暴走を起こし、全く同じ内容を8週にかけて放映しました。 今回はその8回分の同じ内容のアニメを活用し、ディープラーニングの能力を検証していきたいと思います。 蛇足にはなりますが「涼宮ハルヒの憂鬱」の名誉の為、書いておきます。 エンドレスエイトの各8話は8回制作され、8回収録されたものであり、決して使い回しではありません。賛否両論ありますが、映像や声優の演技は違うので、色々な楽しみ方ができます。 興味を持った方は是非、アニメ「涼宮ハルヒの憂鬱」をご視聴下さい! さらに詳しく知りたい方は原作「涼宮ハルヒシリーズ」を講読下さい! また、今回は検証を行う上である程度のネタバレが含まれます。 純粋に「涼宮ハルヒの憂鬱」を楽しみたい方は2期の全話28話(2期は1期の14話を含む。ハイビジョン版のなので絵が綺麗)の視聴をオススメします。 ー 検証の動機 ー 前回「Deep Learningで長門有希を画像認識させたい」を作成する為、改めて「涼宮ハルヒの憂鬱」の情報を集めたのですが、おもしろい事実を発見しました。 エンドレスエイトの各話情報 エンドレスエイトⅠ 作画監督: 米田光良 絵コンテ・演出家:高橋真梨子 エンドレスエイトⅡ 作画監督: 荒谷朋恵 絵コンテ・演出家:西屋太志 エンドレスエイトⅢ 作画監督: 三好一郎 絵コンテ・演出家:高橋博行 エンドレスエイトⅣ 作画監督: 高雄統子 絵コンテ・演出家:植野千世子 エンドレスエイトⅤ 作画監督: 荒石原立也 絵コンテ・演出家:池田和美 エンドレスエイトⅥ 作画監督: 北之原孝将 絵コンテ・演出家:門脇未来 エンドレスエイトⅦ 作画監督: 石立太一 絵コンテ・演出家:秋竹斉一 エンドレスエイトⅧ 作画監督: 米田光良 絵コンテ・演出家:高橋真梨子 つまり! 1話〜7話の絵コンテ・演出家、作画監督は違う。 1話と8話は同じ絵コンテ・演出家、作画監督である。 1話〜8話の内容の構成はほぼ同じ。 ということが分かりました。 この事実を知って筆者は思いました。 1〜7話を判別するモデルを作って、8話目を認識させるとどうなるんだろう? 普通の人にエンドレスエイトの1〜7話を見せ、その後に8話目を流し「さて、今回の話と同じ作画監督、絵コンテ・演出家が作ったのは何話目でしょう?」と質問したとしても、運以外で正解できる人はまずいないと思います。 しかし、人間が見つけられない特徴を見つけ、学習することができるディープラーニングであれば、正解することが出来るのではないか。 同じキャラクター、同じストーリー、同じ構成の作画から、各絵コンテ・演出家、各作画監督の構図の癖や描き方の特徴を見出すことができるのではないか。 筆者は機械学習に片足のつま先だけを浸した程度の知識しかないので、画像認識による分類を使い検証します。 この疑問を払拭するために、検証を行っていこうと思います。 ー 検証目標 ー ①エンドレスエイト1〜7話の画像を学習させ、各話に分類するモデルを作成する。 ②エンドレスエイト8話目の画像をモデルに与え、その結果、何話目に分類されるのか検証する。 ー 検証環境 ー macOS Big Sur 11.5.2 Python 3.9.7 Keras 2.6.0 tensorflow 2.6.0 VSCode ー 検証手順 ー 1、エンドレスエイト8話分の画像をとにかく集める。 2、1〜7話目の画像を水増しし、ラベル付けし、学習モデルを作成する。 3、エンドレスエイト8話目の画像をモデルに与えて結果を見る。 4、検証結果に一喜一憂する。 それでは早速、検証開始! 1、エンドレスエイト8話分の画像をとにかく集める。 以前モデル作成をした際はスクリーンショットで集めましたが、流石に精神が崩壊しそうだったので以前に使ったコードを使います。 screenshot.py import os import pyautogui import time start = time.time() for l in range(1,8): for i in range(494): im = pyautogui.screenshot('./test3/' + str(l) +'_'+ str(i) + '.png', region=(499,600,1300,742)) time.sleep(3) end = time.time() print('result time is :', end - start) ちなみにこのコード、実際やると分かりますが、コード実行からの動画スタートタイミングまでの時間誤差などの影響で、実は綺麗に各話数ごとにファイル名が付かなかったりします。 なので、range数を多くして実行し、動画が終わったらで手動でコードを止めた方が確実に楽です。 筆者は心配性なので、各話終わる毎に止めてファイルにまとめてました。 詳しくはこちらの記事を参照してください。 こんな感じに切り抜きます。 2、1〜7話目の画像を水増しし、ラベル付けし、学習モデルを作成する。 データの水増し、ラベル付け、学習モデル作成までは前回作った時の方法をそのまま使いました。詳しくはDeep Learningで長門有希を画像認識させたい。の記事でご確認ください。 完成したモデルの中で一番精度の良かったものがこちら Test loss: 0.12673038244247437 Test accuracy: 0.9659311175346375 3、エンドレスエイト8話目の画像をモデルに与えて結果を見る。 ここから、作品のネタバレを含みますので、ご注意ください。 エンドレスエイト8話目の画像をモデルに与える前に全話の簡単な流れを確認し、全話共通のシーンを抜き出します。 各話の流れ一覧はこちら エンドレスエイト1話 ハルヒから電話→プール→喫茶店で夏休みの計画立てる→盆踊り→河川敷で花火→セミとり→バイト→天体観測→バッティングセンター→花火大会を見にいく→ハゼ釣り→肝試し→映画館→海水浴→ボーリング→カラオケ→夏休みの反省会→キョン帰宅→就寝 エンドレスエイト2話 ハルヒから電話→プール→喫茶店で夏休みの計画立てる→解散後、キョン、長門を呼び止める→盆踊り→河川敷で花火→セミとり→バイト→古泉からの呼び出し→長門、ループを説明(15,498回目)→天体観測→バッティングセンター→肝試し→ボーリング→カラオケ→夏休みの反省会→デジャブ→キョン就寝 エンドレスエイト3話 ハルヒから電話→プール→喫茶店で夏休みの計画立てる→盆踊り→河川敷で花火→セミとり→バイト→古泉からの呼び出し→長門、ループを説明(15,499回目)]→天体観測→バッティングセンター→花火大会を見に行く→ハゼ釣り→肝試し→映画→海水浴→ボーリング→カラオケ→夏休みの反省会→デジャブ→キョン就寝 エンドレスエイト4話 ハルヒから電話→プール→喫茶店で夏休みの計画立てる→解散後、キョン長門を呼び止める→盆踊り→河川敷で花火→セミとり→バイト→古泉からの呼び出し→長門、ループを説明(15,513回目)→天体観測→バッセン→映画→夏休みの反省会→デジャブ→キョン就寝 エンドレスエイト5話 ハルヒから電話→プール→喫茶店で夏休みの計画立てる→解散後、キョン長門を呼び止める→盆踊り→河川敷で花火→セミとり→バイト→古泉からの呼び出し→長門、ループを説明(15,521回目)→肝試し→雨宿り→公園で遊ぶ→夏休みの反省会→デジャブ→キョン就寝 エンドレスエイト6話 ハルヒから電話→プール→喫茶店で夏休みの計画立てる→解散後、キョン長門を呼び止める→盆踊り→河川敷で花火→セミとり→バイト→古泉からの呼び出し→長門、ループを説明(15,524回目)→天体観測→バッティングセンター→花火大会→釣り→肝試し→海→映画→ボーリング→カラオケ→夏休みの反省会→デジャブ→キョン就寝 エンドレスエイト7話 ハルヒから電話→プール→喫茶店で夏休みの計画立てる→解散後、キョン長門を呼び止める→盆踊り→河川敷で花火→セミとり→バイト→古泉からの呼び出し→長門、ループを説明(15,527回目)→天体観測→バッティングセンター→夏休みの反省会→デジャブ→キョン就寝 エンドレスエイト8話 ハルヒから電話→プール→喫茶店で夏休みの計画立てる→解散後、キョン長門を呼び止める→盆踊り→河川敷で花火→セミとり→バイト→古泉からの呼び出し→長門、ループを説明(15,532回目)→バッティングセンター→雨の描写→夏休みの反省会→キョン起床→学校で小泉とポーカー 全話共通のシーン ハルヒから電話 プール 喫茶店で夏休みの計画立てる 盆踊り 河川敷で花火 セミとり バイト 夏休みの反省会 以上8シーンを抜き出し、学習済みモデルに与えてみます。 該当画像は検証1の手順で198枚用意する事ができました。 それでは、学習済みモデルにぶち込みましょう。 今回は数が多いので、数値で表示する様にします。 評価を行うためのコード evaluation.py import cv2 from keras.models import load_model import numpy as np model = load_model("./endless_model.h5") # 画像を一枚受け取り、何話目に近いかを判定する def pred_gender(img): img = cv2.resize(img,(64,64)) pred = np.argmax(model.predict(img.reshape(1,64,64,3))) if pred ==0: return "roop1" elif pred == 1 : return "roop2" elif pred == 2 : return "roop3" elif pred == 3 : return "roop4" elif pred == 4 : return "roop5" elif pred == 5 : return "roop6" elif pred == 6 : return "roop7" # 用意した枚数分の名前リストを作る listr = list(range(1,199)) listb = [] for a in listr: listb.append("./loop8/x" +str(a)+".png") #結果を受け取るリストを作成 listc=[] for i in listb: img = cv2.imread(i,1) img = cv2.resize(img,(64,64)) pred = np.argmax(model.predict(img.reshape(1,64,64,3))) listc.append(pred) #各話の一致枚数を表示 for a in range(7): print("エンドレスエイト{}話".format(a+1)) print(listc.count(a)) print() #結果一覧 print(listc) 評価結果は、 、 、 、 、 、 、 、 、 、 、 、 エンドレスエイト1話 48 エンドレスエイト2話 30 エンドレスエイト3話 33 エンドレスエイト4話 26 エンドレスエイト5話 20 エンドレスエイト6話 25 エンドレスエイト7話 16 [1, 2, 1, 0, 6, 1, 5, 0, 2, 5, 2, 2, 2, 2, 2, 2, 2, 0, 2, 5, 5, 1, 5, 0, 5, 2, 3, 3, 0, 0, 0, 0, 0, 3, 0, 5, 5, 2, 4, 2, 6, 0, 3, 4, 4, 3, 3, 6, 1, 0, 0, 0, 1, 4, 2, 0, 5, 6, 6, 5, 0, 2, 2, 0, 5, 5, 1, 1, 0, 4, 1, 1, 1, 1, 1, 4, 2, 0, 3, 3, 0, 5, 5, 3, 3, 3, 3, 2, 3, 3, 0, 5, 5, 3, 1, 0, 2, 2, 4, 2, 3, 3, 5, 3, 5, 3, 1, 0, 4, 1, 1, 0, 0, 0, 0, 5, 5, 3, 3, 0, 0, 1, 6, 6, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 2, 2, 2, 2, 6, 3, 5, 6, 4, 4, 2, 2, 1, 5, 4, 6, 2, 4, 1, 6, 3, 0, 5, 2, 3, 3, 3, 4, 6, 1, 0, 0, 2, 1, 4, 2, 0, 0, 0, 4, 4, 1, 1, 0, 0, 0, 4, 1, 0, 2, 5, 0, 2, 3, 5, 4, 5, 0, 6, 6, 4, 2, 3, 3, 1, 6, 6, 4, 6, 1, 4] 一応、エンドレスエイト1話と判定された画像が多い結果になりました。 なんとも言えない結果になってしまったので、0(1話目)と判定されていないシーンの塊を確認してみたところ、凡ミスが発覚しました。 同じシーンでも1話目になかったデジャブのシーンであったり、 語り合いのシーンであったり、 同じシーンだとしても1話目にはなかった展開(物語上の繰り返しによって変化した日常シーン)の画像があったので、これらを省き、再度検証してみました。 結果はこちら エンドレスエイト1話 45 エンドレスエイト2話 19 エンドレスエイト3話 28 エンドレスエイト4話 23 エンドレスエイト5話 14 エンドレスエイト6話 22 エンドレスエイト7話 11 1話目になかったシーン部分の多くが他の話に振り分けられていたようです。 このくらい差が出てくると達成感に近いものが出てきました。 検証結果に対する考察に関しては最後にまとめる事にして、このモデルでいくつか追加で検証を試してみます。  ー 追加検証 ー ンドレスエイト8話目の全ての画像で判定させてみる。 エンドレスエイト1話 72(+27) エンドレスエイト2話 54(+35) エンドレスエイト3話 77(+49) エンドレスエイト4話 39(+16) エンドレスエイト5話 47(+33) エンドレスエイト6話 31(+9) エンドレスエイト7話 36(+25) 同じ絵コンテ・演出家、作画監督のエンドレスエイト以外の話を判定させてみる。 涼宮ハルヒの溜息Ⅲ(絵コンテ・演出家:石原立也  作画監督:池田和美) エンドレスエイト1話 41 エンドレスエイト2話 52 エンドレスエイト3話 42 エンドレスエイト4話 64 エンドレスエイト5話(絵コンテ・演出家:石原立也  作画監督:池田和美) 28 エンドレスエイト6話 79 エンドレスエイト7話 19 涼宮ハルヒの溜息Ⅳ(絵コンテ・演出家:北之原孝将  作画監督:門脇未来) エンドレスエイト1話 54 エンドレスエイト2話 35 エンドレスエイト3話 40 エンドレスエイト4話 57 エンドレスエイト5話 38 エンドレスエイト6話(絵コンテ・演出家:北之原孝将  作画監督:門脇未来) 94 エンドレスエイト7話 41 1期 涼宮ハルヒの憂鬱Ⅵ(絵コンテ・演出家:石原立也  作画監督:池田和美) エンドレスエイト1話 69 エンドレスエイト2話 73 エンドレスエイト3話 63 エンドレスエイト4話 62 エンドレスエイト5話(絵コンテ・演出家:石原立也  作画監督:池田和美) 34 エンドレスエイト6話 31 エンドレスエイト7話 34 ー 検証結果、考察 ー 今回の検証をまとめていきます。 ①絵コンテ・演出家、作画監督ごとに同じ場面を描いてもらった場合は、識別することができる。 ②正解の訓練データに該当のシーンが無い、かつ、正解以外の訓練データにシーンがある場合は正しく識別できない。 ③訓練データと全く異なる画像データを与えた場合、正しく判定出来る場合と出来ない場合がある。 ①について、 本筋で検証を行った通りエンドレスエイト8話目を1話目と識別することが出来ました。 検証を行う上で、全ての話の訓練データに存在するシーンに絞り込むことで、より精度が上がった為、絵コンテ・演出家、作画監督の特徴を掴んでいると言えると思います。 ②について、 追加検証にてエンドレスエイト8話目の全シーンの検証を行いました。その結果、1話目と3話目に判定された数が多かったのですが、①の検証結果からの増加量で見ると1話目(+27)3話目(+49)と大きく差が出ました。内容を確認したところ、やはり1話目にはなかった展開やシーンで他の話と判定される数が増加していました。特に、この夏が繰り返し行われている事を説明をするシーンが3話目に判定されることが多かったです。しかし、どの話にも無い、8話目のみにあるシーンで1話目が増加量を増やしていたことから「正解の訓練データに該当のシーンが無い、かつ、正解以外の訓練データにシーンがある場合は正しく識別できない」と推論を立てました。よくよく考えると、当然の結果ですね。 ③について、 追加検証にて涼宮ハルヒの溜息Ⅲ、Ⅳ、また1期放映分の涼宮ハルヒの憂鬱Ⅵに関しても同じ絵コンテ・演出家、作画監督だったので判定してみましたが、判定結果はできる場合と出来ない場合に分かれました。こちらに関しても、判定された内容を確認してみたのですが、なぜこのような結果になったのか確定的な事がわかりませんでした。真正面から顔を捕らえ、尚且つ普通の表情をしているシーンに関しては正しく判定される事が多いような気がしましたが、正直、確証は得られません。 例えば、涼宮ハルヒの溜息Ⅳより ↑これはエンドレスエイト6話目と正しい絵コンテ・演出家、作画監督を判定しますが、 ↑これはエンドレスエイト7話と判定します。 このようにキョンの目が糸のようになっているシーンでの誤判断が多いかと思いきや、 ↑これはエンドレスエイト6話目と正しい絵コンテ・演出家、作画監督を判定しますが、 ↑これはエンドレスエイト7話と判定するなど、違いがよくわかりません。 人間には理解できない特徴を掴んでいるのは間違いないですね。 ー まとめ ー 結論として、元々の検証動機へのアンサーとしては「同じシーンを書いたものであれば可能」という結論が出ました。当初から、完成したモデルでその他の追加検証をしようと思っていたので、今回は一話丸ごとを訓練データとして使いましたが、各話の同じシーンだけを訓練データにしてモデルを作ればさらに精度は良くなるのでは無いかと思います。 追加検証で行ったエンドレスエイト以外の画像分類結果を改めて考えてみたのですが、一つの仮説が浮かび上がりました。そもそも今回は絵コンテ・演出家、作画監督を判別するという、言うなれば「技術力」を判別する事を目標にしていました。しかし、「技術力」というものは、それ自体が曖昧であり、簡単に数値化できる代物ではありません。特に芸術という分野においてはなおさらでしょう。その上、今回の場合、各絵コンテ・演出家、作画監督は同じ制作会社ですし、そもそも作画に関しては彼等だけではなく大勢のスタッフと共に作り上げているものです。制作現場を見たわけではありませんが、もしかするとクレジット以外のスタッフの手伝いなんかもあったのかもしれません。 唯一、正しく判定できた数が多かった「絵コンテ・演出家:北之原孝将  作画監督:門脇未来」ペアに関して調べてみると、北之原孝将氏は社内の「プロ養成塾」の講師をされている程の実力者であったり、門脇未来氏はエンドレスエイト6話が初作画監督作品であったりと、他のペアと比べて特徴量が多かったのかも知れません。 エンドレスエイト8話目を丸ごと判定させた際に数が多かったエンドレスエイト3話目の絵コンテ・演出家、三好一郎氏(本名:木上 益治)はアニメーター界のレジェンドのような方でしたので、他の方々にも大きな影響を与えていたのでは無いかと思います。それによって類似点が多かったりしたのかも知れません。全ては想像の域を出ませんが、、、、、、 今回はディープラーニングで作画監督を見分けられるのかを検証しました。 結果としては「条件が合えば、見分ける事ができる」という事がわかりました。 色々と思う所や、さらに検証を進めたい部分はありますが、今回はここまでにしたいと思います。 もっと深い部分に関しては知識をもっと付けてから、再度取り組みたいと思います。 ご拝読、ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Plotly/Dash チートシート

Plotly&Dash を使う上でよく使うテクニックをまとめました。 メインPythonで少しCSS&JavaScriptです。 ※内容は随時更新していきます 散布図のカラーテーマを変更する fig = px.scatter( df, x="sepal_width", y="sepal_length", color_discrete_sequence=px.colors.sequential.Viridis, // ここでテーマ設定 ) #使用できるテーマは下記で確認可能 #colorscales = px.colors.named_colorscales() #print(colorscales) Plotly: Built-in Color Display Mode Bar非表示 Plotlyでグラフを表示したとき表示される、右上のアイコンバーを非表示にする。 Plotly (CSSで変更する場合) .chart .modebar-container{ display: none; } DashのFigureで変更する場合 dcc.Graph( id='graph', config={ 'displayModeBar':False, }, figure = figure, // 各自で用意 ), JavaScriptで編集する場合 Plotly.newPlot('myDiv', data, layout, {displayModeBar: false}); Configuration Options in JavaScript Map 右下企業情報 非表示 .chart .mapboxgl-control-container{ display: none; } グラフ周りのカラーを変更 外周エリアの白色部分とセンターのグレーカラーを透過させる。 fig.update_layout( paper_bgcolor='rgba(0,0,0,0)', # legend and axis transparent plot_bgcolor='rgba(0,0,0,0)', # graph area transparent ) グラフのMargin, Padding調整 下記はMarginとPaddingを0にするコード。 fig.update_layout( margin = dict(l=0, r=0, b=0, t=0, pad=0), ) Plotly レスポンシブ対応 Responsive / Fluid Layouts in JavaScript
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python (miniforge) from 最初に入れるやつら2021

Summary 最初に入れるやつら2021に付け足すpython (miniforge)のパートが長いので分離。 MacではZshを、WindowsではPowerShell。 Python管理は、Macではpyenv、Windowsではpyenv-win。 Macではエスケープキーが\だが、Windowsでは`。 ここでは\に統一するので、Windowsの場合は適宜置換する。 python (miniforge) pyenv install miniforge3 Package conda install \ astropy \ autopep8 \ conda-build \ docopt \ dropbox \ ipympl \ ipython \ jupyter \ jupyterlab \ matplotlib \ mpl_interactions \ nidaqmx-python \ numpy \ pandas \ pdf2image \ pre-commit \ pycodestyle \ pydocstyle \ pyperclip \ pyvisa-py \ scipy \ timeout-decorator \ tqdm Packageがcondaに無い時は、 pip install hoge --upgrade-strategy only-if-needed とかでお茶を濁す。 ただし、hogeはPackage名。 Jupyter Lab jupyter labextension install \ @jupyter-widgets/jupyterlab-manager jupyter-matplotlib Jupyter nbextensions - Qiita Jupyter Notebook with Mathematica Wolfram Engine を導入して Jupyter-notebook で動かすまで - Qiita Wolfram 言語が使えるようになった!(mac 編, ビッグバネイト) - Qiita 実質無料で Wolfram 言語(Mathematica の中身)が使えるようになったらしい - Qiita Jupyter Notebook Viewer Launcher for Jupyter Notebook CUI nbopenが良さそう。 GUI Make Jupyter Launcher.app with Automater. Run zsh script with pass input as arguments. nbopen_path=`which nbope` if [[ -s "$jupyter_launcher_path" ]]; then $nbopen_path "$@" fi References Jupyter Notebook (*.ipynb)をダブルクリックで開くための Mac アプリケーション - Qiita tmux 自動化の Python スクリプトを作成した - Qiita Python の subprocess - Qiita Tmux Cheat Sheet & Quick Reference pyenv-win/pyenv-win: pyenv for Windows. pyenv is a simple python version management tool. It lets you easily switch between multiple versions of Python. It's simple, unobtrusive, and follows the UNIX tradition of single-purpose tools that do one thing well.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AtCoder解説】PythonでABC222のA,B,C,D問題を制する!

ABC222のA,B,C,D問題を、Python3でなるべく丁寧に解説していきます。 ただ解けるだけの方法ではなく、次の3つのポイントを満たす解法を解説することを目指しています。 シンプル:余計なことを考えずに済む 実装が楽:ミスやバグが減ってうれしい 時間がかからない:パフォが上がって、後の問題に残せる時間が増える ご質問・ご指摘はコメントかツイッター、マシュマロまでどうぞ! Twitter: u2dayo マシュマロ: https://marshmallow-qa.com/u2dayo ほしいものリスト: https://www.amazon.jp/hz/wishlist/ls/2T9IQ8IK9ID19?ref_=wl_share よかったらLGTMや拡散していただけると喜びます! 目次 ABC222 まとめ A問題『Four Digits』 B問題『Failing Grade』 C問題『Swiss-System Tournament』 D問題『Between Two Arrays』 アプリ AtCoderFacts を開発しています コンテストの統計データを見られるアプリ『AtCoderFacts』を作りました。 現在のところ、次の3つのデータを見ることができます。 レート別問題正解率 パフォーマンス目安 早解きで上昇するパフォーマンス 今後も機能を追加していく予定です。使ってくれると喜びます。 ABC222 まとめ 全提出人数: 7180人 パフォーマンス パフォ AC 点数 時間 順位(Rated内) 200 AB------ 300 9分 5404(5187)位 400 AB------ 300 3分 4466(4249)位 600 ABC----- 600 65分 3699(3484)位 800 ABC----- 600 37分 2918(2703)位 1000 ABCD---- 1000 87分 2198(1985)位 1200 ABCD---- 1000 54分 1601(1390)位 1400 ABCD---- 1000 30分 1136(931)位 1600 ABCDE--- 1500 83分 779(590)位 1800 ABCDE--- 1500 54分 518(347)位 2000 ABCDEF-- 2000 93分 319(188)位 2200 ABCDEF-- 2000 61分 196(94)位 2400 ABCDE-G- 2100 64分 111(44)位 色別の正解率 色 人数 A B C D E F G H 灰 3052 98.8 % 97.2 % 30.5 % 5.1 % 0.6 % 0.2 % 0.2 % 0.0 % 茶 1322 99.4 % 99.5 % 75.1 % 27.2 % 1.6 % 0.6 % 0.8 % 0.0 % 緑 1024 99.8 % 99.6 % 94.1 % 75.4 % 10.7 % 2.6 % 1.7 % 0.0 % 水 651 99.4 % 99.4 % 98.2 % 94.6 % 51.0 % 12.1 % 4.3 % 0.0 % 青 366 99.2 % 99.2 % 98.9 % 98.6 % 84.4 % 40.7 % 11.5 % 0.0 % 黄 164 93.9 % 93.3 % 91.5 % 93.3 % 84.8 % 54.3 % 26.2 % 0.0 % 橙 32 96.9 % 96.9 % 96.9 % 96.9 % 93.8 % 81.2 % 62.5 % 0.0 % 赤 27 100.0 % 100.0 % 100.0 % 100.0 % 92.6 % 88.9 % 96.3 % 18.5 % ※表示レート、灰に初参加者は含めず A問題『Four Digits』 問題ページ:A - Four Digits 灰コーダー正解率:98.8 % 茶コーダー正解率:99.4 % 緑コーダー正解率:99.8 % 入力 $N$ : 整数 実装 色々な実装がありますが、$N$ を文字列で受け取り、N.zfill(4)で $4$ 桁のゼロ埋めされた文字列を出力するのが楽です。 コード N = input() print(N.zfill(4)) B問題『Failing Grade』 問題ページ:B - Failing Grade 灰コーダー正解率:97.2 % 茶コーダー正解率:99.5 % 緑コーダー正解率:99.6 % 入力 *$N$ : 人数 $P$ : 合格点 $a_i$ : 学生 $i$ の点数 * 考察 $a_i < P$ の要素がいくつあるか数える問題です。 実装 forループでひとつずつ確認すれば良いです。 コード N, P = map(int, input().split()) A = list(map(int, input().split())) ans = 0 for x in A: if x < P: ans += 1 print(ans) sum関数と内包表記を使う方法(おまけ) わかるならこのように書いたほうが短くて楽かもしれません。 N, P = map(int, input().split()) A = list(map(int, input().split())) print(sum(x < P for x in A)) # x < P なら Trueになります。Trueは1, Falseは0と同じものとして扱われるので、sum関数で足し合わせればいいです C問題『Swiss-System Tournament』 問題ページ:C - Swiss-System Tournament 灰コーダー正解率:30.5 % 茶コーダー正解率:75.1 % 緑コーダー正解率:94.1 % 入力 $N$ : 参加人数 $M$ : じゃんけん大会のラウンド数 $A_{i,j}$ : 選手番号 $i$ の人が、ラウンド $j$ で出す手(G、C、Pのどれか) 考察 制約が小さいので、シミュレーションすることを考えます。 インデックスに合わせるため、一番上の順位を $1$ 位ではなく $0$ 位とします。ラウンドごとに、$0\le{i}\lt{N}$ について、$2i$ 位の人と $2i + 1$ 位の人をじゃんけんさせて、勝利数を更新します。 ラウンドが終了するごとに、勝利数が多い順、勝利数が同じなら選手の番号が小さい順にソートして、各プレイヤーの順位を求めます。 $M$ ラウンド終了したら、順位が上の順に選手番号を出力します。 実装 rankという配列を作ります。各要素は、$(勝利数, 選手の番号)$ のリストです。手前から2人ずつ取り出して、じゃんけんで勝ったほうの勝利数を増やします。あいこならどちらの勝利数も変更しません。 ラウンドがすべて終わったら、配列をソートして順位ごとに並び替えます。 ここで、勝った人に $+1$ ポイントするかわりに、 $-1$ ポイントすることにすると、普通にソートするだけで順位通りに並び替えてくれます。$+1$ ポイントにすると、選手の番号が小さい順にソートして、その後勝利数が大きい順にソートする必要があります。 コード N, M = map(int, input().split()) hands = [] for _ in range(2 * N): s = input() hands.append(s) rank = [[0, i] for i in range(2 * N)] # D[h1][h2]: じゃんけんでh1, h2を出して、h1が勝つなら1, h2が勝つなら2 D = {"G": {"C": 1, "P": 2}, "C": {"P": 1, "G": 2}, "P": {"G": 1, "C": 2}} for j in range(M): for i in range(N): p1 = rank[2 * i][1] # 2*i位の選手番号 p2 = rank[2 * i + 1][1] # 2*i+1位の選手番号 h1 = hands[p1][j] # p1がjラウンド目に出す手 h2 = hands[p2][j] # p2がjラウンド目に出す手 if h1 == h2: # あいこです continue else: winner = D[h1][h2] if winner == 1: rank[2 * i][0] -= 1 # ここで勝ったほうの勝利カウントを-1すると、ソートが楽です else: rank[2 * i + 1][0] -= 1 rank.sort() # 勝利カウントが小さい順(=勝利数が多い順)に並び替える、同じなら選手番号が小さい順が上 for i in range(2 * N): print(rank[i][1] + 1) 勝利数カウントを+1にした場合 基本的には同じですが、ソート部分がやや面倒になります。 # 上は省略 if h1 == h2: # あいこです continue else: winner = D[h1][h2] if winner == 1: rank[2 * i][0] += 1 else: rank[2 * i + 1][0] += 1 rank.sort(key=lambda x: x[1]) # まず選手番号が小さい順に並び替える rank.sort(key=lambda x: x[0], reverse=True) # その後、勝利数が大きい順に並び替える D問題『Between Two Arrays』 問題ページ:D - Between Two Arrays 灰コーダー正解率:5.1 % 茶コーダー正解率:27.2 % 緑コーダー正解率:75.4 % 入力 $N$ : 数列の長さ $a_i$ : 広義単調増加な長さ $N$ の数列 $A$ の、$i$ 番目の要素 $b_i$ : 広義単調増加な長さ $N$ の数列 $B$ の、$i$ 番目の要素($a_i\le{b_i}$) 考察 動的計画法で解きます。 状態 $dp[i][c]$ : $i$ 番目まで決めて、最後が $c$ の数列の数 初期状態 $dp[0][0]=1$ $dp[i][c]=0$(それ以外) 遷移 $dp[i+1][c] = dp[i][0] + dp[i][1] + \dots + dp[i][c-1]+dp[i][c]$ ($a_{i+1}\le{c}\le{b_{i+1}}$) $dp[i+1][c] = 0$ (それ以外の $c$ すべて) 長さ $i$ の数列に $c$ を付け加えて、長さ $i+1$ で最後が $c$ の数列にします。問題文の条件より、$a_{i+1}\le{c\le{b_{i+1}}}$ かつ $c_i\le{c_{i+1}}$ という条件を満たす数列が可能です。 実装 Pythonのコードで遷移を素直に実装すると、以下のコードになります。 dp[i+1][c] = sum(dp[i][:c+1]) % 998244353 しかし、この方法で動的計画法を実装すると、毎回sum(dp[i][:c+1]) を計算する部分の計算量が $O(C)$ ($C = 3000$)となるので、全体の計算量が $O(NC^2)$となり、TLEになります。 そこで、累積和の考えを使ってDPを高速化します。s = sum(dp[i][:c])が既に求まっていれば、sum(dp[i][:c+1]) = s + dp[i][c] という関係を使って、$O(1)$ で求めることができます。こうすることで、全体の計算量が $O(NC)$ となり間に合います。 コード Pythonだと実行時間制限がギリギリ($1800$ ms程度)なので、PyPy($250$ ms程度)で出したほうがいいです。 def main(): MOD = 998244353 N = int(input()) A = list(map(int, input().split())) B = list(map(int, input().split())) CONST = 3001 dp = [[0] * CONST for _ in range(N + 1)] dp[0][0] = 1 for i in range(N): a = A[i] b = B[i] s = sum(dp[i][:a]) % MOD # dp[i]のa未満の総和です for c in range(a, b + 1): s += dp[i][c] # sum(dp[i][:c+1])を毎回計算する代わりに、既に求まっているsum(dp[i][:c])にdp[i][c]を足します s %= MOD dp[i + 1][c] = s print(sum(dp[N]) % MOD) if __name__ == '__main__': main() 1次元配列2つで高速化 $dp[i+1][c]$ を求めるのに必要なのは $dp[i]$ だけなので、二次元配列の代わりに一次元配列 $2$ つを使うことで少し高速になります。(Pythonで $1100$ ms程度) def main(): MOD = 998244353 N = int(input()) A = list(map(int, input().split())) B = list(map(int, input().split())) CONST = 3001 prev = [0] * CONST prev[0] = 1 for i in range(N): curr = [0] * CONST a = A[i] b = B[i] s = sum(prev[:a]) % MOD for c in range(a, b + 1): s += prev[c] s %= MOD curr[c] = s prev = curr print(sum(curr) % MOD) if __name__ == '__main__': main()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[○○分で読める] 食べログとSNSの評価を比較してみた~ショート編~

本記事に関して 本記事は以下、4つの記事を短く1つにまとめたものとなります。 お時間があります際に、以下の記事もお読み頂けると幸いです!!! コードなどの情報もこちらの記事には記載しておりません。 はじめに Python初学者のまさやです! 皆様は、カフェやレストランの情報を何から取得していますでしょうか? 最近は、食べログなどのサイトだけではなく、SNSで確認される方もいるのではないでしょうか? 私は前職、中小企業のPRのお手伝いを行っていました。 お客様にはSNSでの情報発信の必要性も訴えてきましたが、それは本当に必要だったのでしょうか!? 今回はPythonの練習としてTwitterAPIにてツイートの取得。 食べログのスクレイピングを行い、評価の比較を行ってみました!!! 簡単なあらすじ(元記事リンク!) (その1)ツイートの取得(SNSの調査)/カフェの選定 TwitterAPIを活用し、東京23区内の人気カフェ/スイーツ店のツイートを取得 取得した店舗のうち、総ツイート数が200件を超えている店舗10店舗を選定。 (その2)ツイートの前処理 ツイートには取得したい感想(評価)の他に、公式アカウントや引用ツイート、 お土産ランキングなど不要なツイートを削除。 (その3)感情分析にてツイートを得点化 「日本語評価極性辞書」と「単語感情極性値対応表」2つの辞書を活用し、得点の比較。 食べログの評価幅が[0~5]のため、正規化にて幅を[0~5]合わせる。 (その4)食べログ評価を取得/比較 食べログの評価を取得し、評価の分布をグラフ化。 先ほど取得したツイートの点数との比較を行う。 (その1)ツイートの取得(SNSの調査)/カフェの選定 ツイート取得 TwitterAPIを利用し、各店舗の1週間分(2021年09月20日~27日)を取得。 様々な情報を取得できるが、今回は以下の7情報を取得。 表1_取得情報一覧 取得情報 用途 ツイート文書(text) 感情分析のため ユーザーID(id) 重複ツイート検出のため ユーザー名(name) 重複ツイート検出のため ツイート日(created_at) 正しく1週間分取得できているか確認するため いいね数(favorite_count) 今後の展望 リツイート数(retweet_count) 今後の展望 フォロワー数(followers_count) 今後の展望 写真1_ツイート取得イメージ 取得した感想 textにはSNSならではの顔文字や文脈が多く、正しく感情分析が可能か不安。 「-RT」でリツイートを除外することはできたが、引用ツイートが意外と多かった。 今後、ツイートした人だけではなく、「フォロワー」と「いいね数」、「リツイート」などインフルエンサーポイントを追加し、ツイートに反応した人の点数も加点してオリジナルランキングも作成してみたい。 カフェ選定 カフェの選定は、以下のサイト内にあるカフェから お店ごとにツイートを取得し、ツイート数が多かった10店舗を採用する。 △注意点 ツイートを検索する際、お店名はフルネームではなくツイートしやすい言葉で検証。 例) スターバックス コーヒー ジャパン: Starbucks Coffee Japan →「スタバ」 や 「スターバックス」などのツイート数が多いワードを採用  ノミネート10店舗 上記にてツイート数が多かった10店舗は以下になります! 表2_ノミネート店舗と取得ツイート数 店名 検索名 取得ツイート数 キル フェ ボン キルフェボン 2385 ピエール・エルメ ピエールエルメ 996 ブルーボトルコーヒー ブルーボトル 731 ケンズカフェ東京 ケンズカフェ 647 マリアージュフレール マリアージュフレール 527 ひみつ堂 ひみつ堂 419 資生堂パーラー 資生堂パーラー 395 和栗や 和栗や 326 アンリ・シャルパンティエ アンリ・シャルパンティエ 292 銀座ウエスト 銀座ウエスト 268 ツイート数が予想より少なく、200件を超える店舗がほぼなかった。やはり、カフェなどの情報をSNSにアップする際、Twitterではなく、Instagramが選ばれる傾向がある予想。 店舗名を決める際、「パティスリー」などの文言は、他店舗と混合するためSNSには不向き。また、英語表記の店舗名は外国人の名前やツイートと混合してしまい、こちらも不向きだと思った。 ~季節に関して~ 「ひみつ堂」は人気かき氷屋。夏には3時間以上並ぶことも。まだまだ、残暑が厳しく10月を前にしてもツイートは多かった。 和栗やは栗の季節を目前に「食べたい」など期待に対するツイート多数。栗が収穫できない季節には店を閉めるほど徹底された品質管理にも"限定感"が相まってツイート数が多かったと推測。 詳細は以下に記載あり!!! [その1] 食べログとSNSの評価を比較してみた [その1]~ツイート取得・店舗選別~ (その2)ツイートの前処理 ツイート日を確認しやすいよう、日にちのみに変更。 感情分析にて不要なメンション(@)、URL(http)から始まる文字列の消去。 同様に顔文字も消去を行った。 表3_text処理 変更前    変更後       Sun Sep 26 12:53:51 +0000 2021 26 ????ブルーボトルコーヒー?#ブルーボトルコーヒー食べた物→ @0117ushi ?その他→ @ushi0117 ?カフェ活→ #?カフェ ?https://t.co/cw4LE8PRIt https://t.co/2BK0Uwi1vb ブルーボトルコーヒー#ブルーボトルコーヒー食べた物→ その他→ カフェ活→ #カフェ 重複ツイート/アカウントの消去 公式アカウントのツイートや引用ツイートを削除するため ['text']['name']それぞれの重複があるものを消去。 △懸念点 「○○行きたい」や「美味しい」などシンプルなツイートを消去してしまう可能性あり。 また、複数回コメントしているアカウントも消去してしまう。 表4_処理後のツイート数 店名 処理前 処理後 キル フェ ボン 2385 1914 ピエール・エルメ 996 786 ブルーボトルコーヒー 731 565 ケンズカフェ東京 647 567 マリアージュフレール 527 429 ひみつ堂 419 303 資生堂パーラー 395 308 和栗や 326 265 アンリ・シャルパンティエ 292 226 銀座ウエスト 268 216 消去されたツイートには「公式アカウント」や「スイーツランキング」の引用ツイートなどが見られた SNSでの情報発信には「いいね」が一定数見られた、SNSの活用はマストか… ~今後の展望として~ ツイート数の多い店舗のツイート内容を確認し、どんな内容に関してはツイート数が増加するのか確認してみたい! 例えば、テイクアウト、お土産、フェア、新商品など 詳細は以下に記載あり!!! [その2] 食べログとSNSの評価を比較してみた [その2]~ツイート前処理~ (その3)ツイートデータの感情分析 「単語感情極性値対応表」「日本語評価極性辞書」それぞれの辞書を使用しツイートを感情分析を行った。 加工せず、最初に得ることができたPN値を["pn"] "pn"を標準化しデータの平均を限りなく0に近づけたものを["pn_st"] "pn_st"を正規化し評価の幅を[0~5]に変更したものを["pn_sc"]と記載。 それぞれの結果は以下の通りになった。 写真2_「銀座ウエスト」単語感情極性値対応表 単語感情極性値対応表はポジ:ネガ=1:9でネガティブに著しい偏りがあるとのこと。 全体的に低い値をとり、店舗ごとの差が生まれなかった。 「'秋'を'PN': -0.9467959999999999」とするように、今回は正常に判別できないと判断。 写真3_「銀座ウエスト」日本語評価極性辞書 先ほどよりは幅広く値が取れている。 調査したところ、グラフ中央の「pn_sc = 2.5」の部分は「notfound」=認識できないものが集まっていた。 平均値に影響が大きいと考え、消去した結果が以下に。 表5_評価の比較 店名 ツイート数 処理前_SC 処理後_SC 単語感情極性値 後 - 前 キル フェ ボン 1914 2.95 3.60 1.59 0.65 ピエール・エルメ 786 2.97 3.60 1.52 0.63 ブルーボトルコーヒー 565 3.10 3.78 1.53 0.68 ケンズカフェ東京 567 2.99 3.41 1.53 0.42 マリアージュフレール 429 3.16 3.71 1.55 0.55 ひみつ堂 303 2.86 3.65 1.62 0.79 資生堂パーラー 308 3.00 3.60 1.62 0.60 和栗や 265 3.16 3.61 1.68 0.45 アンリ・シャルパンティエ 226 3.14 3.90 1.62 0.76 銀座ウエスト 216 3.10 3.73 2.28 0.63 ※処理後/前は「日本語評価極性辞書」の値 全数値「notfound処理後」数値上昇したことにより「notfound」によって低い評価に影響されていた ツイート数と「notfound」の関係はなし 単語感情極性値対応表より店舗ごとの評価に差ができた グラフから分かるように「日本語評価極性辞書」の方が明らかに「notfound」が多い。これは適当に評価を下していないこととともに、認識できる名詞が少ないことが言える。 [その3] 食べログとSNSの評価を比較してみた [その3]~感情分析~ (その4)食べログ評価を取得/比較 食べログから取得する情報は以下 - id(名前のようなもの) - rate(評価) - frequency(来店回数) それぞれをグラフにし分布を可視化 (グラフ左)が「訪問回数と評価」を可視化。 x軸にrate(評価) y軸にfrequency(訪問回数) 丸が大きいほど同じ評価が多いことを表し、来店回数における評価の変化を可視化。 x軸が上昇するほど、再来店(リピーター)の評価となる。 (グラフ右)が「評価の分布」を可視化 x軸にrate(評価) y軸にcount(同じ評価を付けた人の数) 棒グラフにより、どのあたりの評価が多いのかを可視化。 それぞれ、棒グラフの数(ビン数)を30に設定。 「青棒」 = 食べログでの評価 「オレンジ棒」= Twitterでの評価 ひみつ堂 「食べログ」= 3.76 / 940件 「Twitter」= 3.65 / 864件 (9) ※()内はツイートを複製し棒グラフを伸ばした回数 = 数を合わせるため 写真4_評価の分布 その他の店舗はこちらをチェック [その4] 食べログとSNSの評価を比較してみた [その4]~食べログ取得・比較~ 考察 左)散布図(食べログ)に関して 来店回数が増えれば増えるほど、5ではなく4または4.5の評価が集まる(常連は5をつけない) 初来店(1回目)の評価は3 ~ 4.5の間が多い 0点の評価はなし 両端(1と5)では1 < 5の評価が多く見られる 右)ヒストグラムに関して 食べログは4周辺の評価が多いが、Twitterでは0または5の両極端も目立つ Twitterでは喜びや期待または、怒りのどちらかでしか感想をツイートしないと予測 結果として似た総合評価を得ているが、分布は全く異なる 比較 以下に食べログの評価とTwitterの分析を比較してみました。 写真6_ツイートと食べログ比較 店名 食べログ ツイート 食べログ - ツイート キル フェ ボン 3.66 3.60 0.06 ピエール・エルメ 3.89 3.60 0.29 ブルーボトルコーヒー 3.32 3.78 -0.46 ケンズカフェ東京 3.92 3.41 0.51 マリアージュフレール 3.62 3.71 -0.09 ひみつ堂 3.76 3.65 0.11 資生堂パーラー 3.81 3.60 0.21 和栗や 3.73 3.61 0.12 アンリ・シャルパンティエ 3.71 3.90 -0.19 銀座ウエスト 3.68 3.73 -0.05 食べログ平均点 = 3.71 ツイート平均点 = 3.66 どちらにもそれほど大きな差はなく、面白い結果となった 分布が全く異なるのにも関わらず、総合評価はあまり変わらないのは面白い結果となった 終わりに 今回の結果より、SNSの情報は良い意見、厳しい意見ともに多かった。 SNSは自分の意見をつぶやくため、指定(店の人)が見てない想定で本音を記入するからだろうか? ともかく、最終評価はあまり変わらなかったが、それぞれの評価の分布が可視化でき、 SNSの一面を可視化することができたのではないかと思いました。 そのため、利用方法としては、食べログで自分が行きたいお店を探し、 来店して後悔がないよう、SNSで本音を確認してお店を決めると失敗は少ないかもしれません。 今回は好きなカフェを調べてみました。 しかし、カフェの情報はInstagramに写真付きで発信する人が多いのかなと思いました。 私自身もカフェ情報はInstagramから調べます。 Instagramはスクレイピングが禁止されているため難しいですが、できたら面白いのではないでしょうか? Pythonにて、さまざまな情報を取得し可視化を行い一番思ったのは 前職では広告の効果分析のためExcelに視聴率や離脱率、クリック数など手入力を行っていました。 その時できなかったことを短時間で可能とできるPythonは便利でもっと学んでみたいと感じました。 誤っていることも多々あるかと思いますが、ここまでお読みいただきありがとうございました。 [その4] 食べログとSNSの評価を比較してみた [その4]~食べログ取得・比較~
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[3分で読める] 食べログとSNSの評価を比較してみた~ショート編~

本記事に関して 本記事は以下、4つの記事を短く1つにまとめたものとなります。 お時間があります際に、以下の記事もお読み頂けると幸いです!!! コードなどの情報もこちらの記事には記載しておりません。 はじめに Python初学者のまさやです! 皆様は、カフェやレストランの情報を何から取得していますでしょうか? 最近は、食べログなどのサイトだけではなく、SNSで確認される方もいるのではないでしょうか? 私は前職、中小企業のPRのお手伝いを行っていました。 お客様にはSNSでの情報発信の必要性も訴えてきましたが、それは本当に必要だったのでしょうか!? 今回はPythonの練習としてTwitterAPIにてツイートの取得。 食べログのスクレイピングを行い、評価の比較を行ってみました!!! 簡単なあらすじ(元記事リンク!) (その1)ツイートの取得(SNSの調査)/カフェの選定 TwitterAPIを活用し、東京23区内の人気カフェ/スイーツ店のツイートを取得 取得した店舗のうち、総ツイート数が200件を超えている店舗10店舗を選定。 (その2)ツイートの前処理 ツイートには取得したい感想(評価)の他に、公式アカウントや引用ツイート、 お土産ランキングなど不要なツイートを削除。 (その3)感情分析にてツイートを得点化 「日本語評価極性辞書」と「単語感情極性値対応表」2つの辞書を活用し、得点の比較。 食べログの評価幅が[0~5]のため、正規化にて幅を[0~5]合わせる。 (その4)食べログ評価を取得/比較 食べログの評価を取得し、評価の分布をグラフ化。 先ほど取得したツイートの点数との比較を行う。 (その1)ツイートの取得(SNSの調査)/カフェの選定 ツイート取得 TwitterAPIを利用し、各店舗の1週間分(2021年09月20日~27日)を取得。 様々な情報を取得できるが、今回は以下の7情報を取得。 表1_取得情報一覧 取得情報 用途 ツイート文書(text) 感情分析のため ユーザーID(id) 重複ツイート検出のため ユーザー名(name) 重複ツイート検出のため ツイート日(created_at) 正しく1週間分取得できているか確認するため いいね数(favorite_count) 今後の展望 リツイート数(retweet_count) 今後の展望 フォロワー数(followers_count) 今後の展望 写真1_ツイート取得イメージ 取得した感想 textにはSNSならではの顔文字や文脈が多く、正しく感情分析が可能か不安。 「-RT」でリツイートを除外することはできたが、引用ツイートが意外と多かった。 今後、ツイートした人だけではなく、「フォロワー」と「いいね数」、「リツイート」などインフルエンサーポイントを追加し、ツイートに反応した人の点数も加点してオリジナルランキングも作成してみたい。 カフェ選定 カフェの選定は、以下のサイト内にあるカフェから お店ごとにツイートを取得し、ツイート数が多かった10店舗を採用する。 △注意点 ツイートを検索する際、お店名はフルネームではなくツイートしやすい言葉で検証。 例) スターバックス コーヒー ジャパン: Starbucks Coffee Japan →「スタバ」 や 「スターバックス」などのツイート数が多いワードを採用  ノミネート10店舗 上記にてツイート数が多かった10店舗は以下になります! 表2_ノミネート店舗と取得ツイート数 店名 検索名 取得ツイート数 キル フェ ボン キルフェボン 2385 ピエール・エルメ ピエールエルメ 996 ブルーボトルコーヒー ブルーボトル 731 ケンズカフェ東京 ケンズカフェ 647 マリアージュフレール マリアージュフレール 527 ひみつ堂 ひみつ堂 419 資生堂パーラー 資生堂パーラー 395 和栗や 和栗や 326 アンリ・シャルパンティエ アンリ・シャルパンティエ 292 銀座ウエスト 銀座ウエスト 268 ツイート数が予想より少なく、200件を超える店舗がほぼなかった。やはり、カフェなどの情報をSNSにアップする際、Twitterではなく、Instagramが選ばれる傾向がある予想。 店舗名を決める際、「パティスリー」などの文言は、他店舗と混合するためSNSには不向き。また、英語表記の店舗名は外国人の名前やツイートと混合してしまい、こちらも不向きだと思った。 ~季節に関して~ 「ひみつ堂」は人気かき氷屋。夏には3時間以上並ぶことも。まだまだ、残暑が厳しく10月を前にしてもツイートは多かった。 和栗やは栗の季節を目前に「食べたい」など期待に対するツイート多数。栗が収穫できない季節には店を閉めるほど徹底された品質管理にも"限定感"が相まってツイート数が多かったと推測。 詳細は以下に記載あり!!! [その1] 食べログとSNSの評価を比較してみた [その1]~ツイート取得・店舗選別~ (その2)ツイートの前処理 ツイート日を確認しやすいよう、日にちのみに変更。 感情分析にて不要なメンション(@)、URL(http)から始まる文字列の消去。 同様に顔文字も消去を行った。 表3_text処理 変更前    変更後       Sun Sep 26 12:53:51 +0000 2021 26 ????ブルーボトルコーヒー?#ブルーボトルコーヒー食べた物→ @0117ushi ?その他→ @ushi0117 ?カフェ活→ #?カフェ ?https://t.co/cw4LE8PRIt https://t.co/2BK0Uwi1vb ブルーボトルコーヒー#ブルーボトルコーヒー食べた物→ その他→ カフェ活→ #カフェ 重複ツイート/アカウントの消去 公式アカウントのツイートや引用ツイートを削除するため ['text']['name']それぞれの重複があるものを消去。 △懸念点 「○○行きたい」や「美味しい」などシンプルなツイートを消去してしまう可能性あり。 また、複数回コメントしているアカウントも消去してしまう。 表4_処理後のツイート数 店名 処理前 処理後 キル フェ ボン 2385 1914 ピエール・エルメ 996 786 ブルーボトルコーヒー 731 565 ケンズカフェ東京 647 567 マリアージュフレール 527 429 ひみつ堂 419 303 資生堂パーラー 395 308 和栗や 326 265 アンリ・シャルパンティエ 292 226 銀座ウエスト 268 216 消去されたツイートには「公式アカウント」や「スイーツランキング」の引用ツイートなどが見られた SNSでの情報発信には「いいね」が一定数見られた、SNSの活用はマストか… ~今後の展望として~ ツイート数の多い店舗のツイート内容を確認し、どんな内容に関してはツイート数が増加するのか確認してみたい! 例えば、テイクアウト、お土産、フェア、新商品など 詳細は以下に記載あり!!! [その2] 食べログとSNSの評価を比較してみた [その2]~ツイート前処理~ (その3)ツイートデータの感情分析 「単語感情極性値対応表」「日本語評価極性辞書」それぞれの辞書を使用しツイートを感情分析を行った。 加工せず、最初に得ることができたPN値を["pn"] "pn"を標準化しデータの平均を限りなく0に近づけたものを["pn_st"] "pn_st"を正規化し評価の幅を[0~5]に変更したものを["pn_sc"]と記載。 それぞれの結果は以下の通りになった。 写真2_「銀座ウエスト」単語感情極性値対応表 単語感情極性値対応表はポジ:ネガ=1:9でネガティブに著しい偏りがあるとのこと。 全体的に低い値をとり、店舗ごとの差が生まれなかった。 「'秋'を'PN': -0.9467959999999999」とするように、今回は正常に判別できないと判断。 写真3_「銀座ウエスト」日本語評価極性辞書 先ほどよりは幅広く値が取れている。 調査したところ、グラフ中央の「pn_sc = 2.5」の部分は「notfound」=認識できないものが集まっていた。 平均値に影響が大きいと考え、消去した結果が以下に。 表5_評価の比較 店名 ツイート数 処理前_SC 処理後_SC 単語感情極性値 後 - 前 キル フェ ボン 1914 2.95 3.60 1.59 0.65 ピエール・エルメ 786 2.97 3.60 1.52 0.63 ブルーボトルコーヒー 565 3.10 3.78 1.53 0.68 ケンズカフェ東京 567 2.99 3.41 1.53 0.42 マリアージュフレール 429 3.16 3.71 1.55 0.55 ひみつ堂 303 2.86 3.65 1.62 0.79 資生堂パーラー 308 3.00 3.60 1.62 0.60 和栗や 265 3.16 3.61 1.68 0.45 アンリ・シャルパンティエ 226 3.14 3.90 1.62 0.76 銀座ウエスト 216 3.10 3.73 2.28 0.63 ※処理後/前は「日本語評価極性辞書」の値 全数値「notfound処理後」数値上昇したことにより「notfound」によって低い評価に影響されていた ツイート数と「notfound」の関係はなし 単語感情極性値対応表より店舗ごとの評価に差ができた グラフから分かるように「日本語評価極性辞書」の方が明らかに「notfound」が多い。これは適当に評価を下していないこととともに、認識できる名詞が少ないことが言える。 [その3] 食べログとSNSの評価を比較してみた [その3]~感情分析~ (その4)食べログ評価を取得/比較 食べログから取得する情報は以下 - id(名前のようなもの) - rate(評価) - frequency(来店回数) それぞれをグラフにし分布を可視化 (グラフ左)が「訪問回数と評価」を可視化。 x軸にrate(評価) y軸にfrequency(訪問回数) 丸が大きいほど同じ評価が多いことを表し、来店回数における評価の変化を可視化。 x軸が上昇するほど、再来店(リピーター)の評価となる。 (グラフ右)が「評価の分布」を可視化 x軸にrate(評価) y軸にcount(同じ評価を付けた人の数) 棒グラフにより、どのあたりの評価が多いのかを可視化。 それぞれ、棒グラフの数(ビン数)を30に設定。 「青棒」 = 食べログでの評価 「オレンジ棒」= Twitterでの評価 ひみつ堂 「食べログ」= 3.76 / 940件 「Twitter」= 3.65 / 864件 (9) ※()内はツイートを複製し棒グラフを伸ばした回数 = 数を合わせるため 写真4_評価の分布 その他の店舗はこちらをチェック [その4] 食べログとSNSの評価を比較してみた [その4]~食べログ取得・比較~ 考察 左)散布図(食べログ)に関して 来店回数が増えれば増えるほど、5ではなく4または4.5の評価が集まる(常連は5をつけない) 初来店(1回目)の評価は3 ~ 4.5の間が多い 0点の評価はなし 両端(1と5)では1 < 5の評価が多く見られる 右)ヒストグラムに関して 食べログは4周辺の評価が多いが、Twitterでは0または5の両極端も目立つ Twitterでは喜びや期待または、怒りのどちらかでしか感想をツイートしないと予測 結果として似た総合評価を得ているが、分布は全く異なる 比較 以下に食べログの評価とTwitterの分析を比較してみました。 写真6_ツイートと食べログ比較 店名 食べログ ツイート 食べログ - ツイート キル フェ ボン 3.66 3.60 0.06 ピエール・エルメ 3.89 3.60 0.29 ブルーボトルコーヒー 3.32 3.78 -0.46 ケンズカフェ東京 3.92 3.41 0.51 マリアージュフレール 3.62 3.71 -0.09 ひみつ堂 3.76 3.65 0.11 資生堂パーラー 3.81 3.60 0.21 和栗や 3.73 3.61 0.12 アンリ・シャルパンティエ 3.71 3.90 -0.19 銀座ウエスト 3.68 3.73 -0.05 食べログ平均点 = 3.71 ツイート平均点 = 3.66 どちらにもそれほど大きな差はなく、面白い結果となった 分布が全く異なるのにも関わらず、総合評価はあまり変わらないのは面白い結果となった 終わりに 今回の結果より、SNSの情報は良い意見、厳しい意見ともに多かった。 SNSは自分の意見をつぶやくため、指定(店の人)が見てない想定で本音を記入するからだろうか? ともかく、最終評価はあまり変わらなかったが、それぞれの評価の分布が可視化でき、 SNSの一面を可視化することができたのではないかと思いました。 そのため、利用方法としては、食べログで自分が行きたいお店を探し、 来店して後悔がないよう、SNSで本音を確認してお店を決めると失敗は少ないかもしれません。 今回は好きなカフェを調べてみました。 しかし、カフェの情報はInstagramに写真付きで発信する人が多いのかなと思いました。 私自身もカフェ情報はInstagramから調べます。 Instagramはスクレイピングが禁止されているため難しいですが、できたら面白いのではないでしょうか? Pythonにて、さまざまな情報を取得し可視化を行い一番思ったのは 前職では広告の効果分析のためExcelに視聴率や離脱率、クリック数など手入力を行っていました。 その時できなかったことを短時間で可能とできるPythonは便利でもっと学んでみたいと感じました。 誤っていることも多々あるかと思いますが、ここまでお読みいただきありがとうございました。 [その4] 食べログとSNSの評価を比較してみた [その4]~食べログ取得・比較~
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python 空のDataFrame 宣言

空のデータフレームを宣言したいときに使用します。 以外に情報ないので、備忘として。 Test.py import pandas as pd df = pd.DataFrame({}) # 空のデータフレームを定義 # 列の追加 xという名前の列を追加 df['x'] = [1,2,3] print(df) メモ書きです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで生存時間解析(人工データを使った実験)

生存時間解析といえば、Kaplan-Meier とか Cox比例ハザードモデル が有名ですね。それらの理論や、テストデータを用いた解析実例などはインターネット上の様々な場所で説明が見られます。それらは大変勉強になりますが、今いちピンと来ない部分があったので、人工データを使った実験を行ってみました。 lifelinesパッケージのインストール Python では次のようにして、生存時間解析を行うlifelinesパッケージのインストールができます。 !pip install lifelines Collecting lifelines Downloading lifelines-0.26.3-py3-none-any.whl (348 kB) [K |████████████████████████████████| 348 kB 4.1 MB/s [?25hRequirement already satisfied: autograd>=1.3 in /usr/local/lib/python3.7/dist-packages (from lifelines) (1.3) Requirement already satisfied: scipy>=1.2.0 in /usr/local/lib/python3.7/dist-packages (from lifelines) (1.4.1) Collecting formulaic<0.3,>=0.2.2 Downloading formulaic-0.2.4-py3-none-any.whl (55 kB) [K |████████████████████████████████| 55 kB 4.0 MB/s [?25hCollecting autograd-gamma>=0.3 Downloading autograd-gamma-0.5.0.tar.gz (4.0 kB) Requirement already satisfied: pandas>=0.23.0 in /usr/local/lib/python3.7/dist-packages (from lifelines) (1.1.5) Requirement already satisfied: matplotlib>=3.0 in /usr/local/lib/python3.7/dist-packages (from lifelines) (3.2.2) Requirement already satisfied: numpy>=1.14.0 in /usr/local/lib/python3.7/dist-packages (from lifelines) (1.19.5) Requirement already satisfied: future>=0.15.2 in /usr/local/lib/python3.7/dist-packages (from autograd>=1.3->lifelines) (0.16.0) Collecting interface-meta>=1.2 Downloading interface_meta-1.2.4-py2.py3-none-any.whl (14 kB) Requirement already satisfied: wrapt in /usr/local/lib/python3.7/dist-packages (from formulaic<0.3,>=0.2.2->lifelines) (1.12.1) Requirement already satisfied: astor in /usr/local/lib/python3.7/dist-packages (from formulaic<0.3,>=0.2.2->lifelines) (0.8.1) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.0->lifelines) (0.10.0) Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.0->lifelines) (2.8.2) Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.0->lifelines) (1.3.2) Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.0->lifelines) (2.4.7) Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from cycler>=0.10->matplotlib>=3.0->lifelines) (1.15.0) Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas>=0.23.0->lifelines) (2018.9) Building wheels for collected packages: autograd-gamma Building wheel for autograd-gamma (setup.py) ... [?25l[?25hdone Created wheel for autograd-gamma: filename=autograd_gamma-0.5.0-py3-none-any.whl size=4049 sha256=cdfec914daf278a4ce9515aa47adb1700592a8026837a6a428275825812fa37b Stored in directory: /root/.cache/pip/wheels/9f/01/ee/1331593abb5725ff7d8c1333aee93a50a1c29d6ddda9665c9f Successfully built autograd-gamma Installing collected packages: interface-meta, formulaic, autograd-gamma, lifelines Successfully installed autograd-gamma-0.5.0 formulaic-0.2.4 interface-meta-1.2.4 lifelines-0.26.3 生存曲線のモデルを作成 今回は、生存曲線のモデルとして $ S(t) = e^{-(w_1 x_1 + w_2 x_2 + b) t} $ (式1) という式を用います。ここで $S(t)$ は、時刻 $t$ において生き残っている者の割合です。 $x = [x_1, x_2]$ と $w = [w_1, w_2]$ はそれぞれ、各個人が持つ性質と、それが生存に及ぼす影響です。 $b$ は適当なバイアス項です。 式1の逆関数を求めると $ t = - ln S(t) / (w_1 x_1 + w_2 x_2 + b)$ (式2) となります。これらを Python で書き下すと、 import numpy as np class SurvivalProbability: def __init__(self, w): self.w = np.array(w) self.b = 2 def __call__(self, t, x): # 式1 f = (self.w * x).sum() + self.b return np.exp(-f * t) def reverse(self, y, x): # 式2(逆関数) f = (self.w * x).sum() + self.b return -np.log(y) / f この式に、 テキトーな $w$ を代入して、それを今回用いる生存曲線とします。 w = np.array([4, -1]) survival = SurvivalProbability(w) $x$ の値を色々変えて、生存曲線を描いてみましょう。 import matplotlib.pyplot as plt t_series = np.linspace(0, 2, 11) plt.plot(t_series, survival(t_series, [0, 0]), label="x = [0, 0]", marker="o") plt.plot(t_series, survival(t_series, [1, 0]), label="x = [1, 0]", marker="<") plt.plot(t_series, survival(t_series, [0, 1]), label="x = [0, 1]", marker=">") plt.xlabel("Time") plt.ylabel("Survival rate") plt.legend() plt.xticks(t_series) plt.yticks([x / 10 for x in range(0, 11)]) plt.grid() plt.show() $ x = [0, 0]$ のとき、生存時間の中央値は約 0.35 付近であることが分かります。 また、$x_1 = 1$ になると生存時間の中央値が半分ほどになり、$x_2 = 1$ になると生存時間の中央値が2倍近くになること、などが分かります。 生存曲線モデルに基づいた実験データ作成 以上の生存曲線モデルに基づいて、実験用データを作成しましょう。生存曲線の逆関数を用いて、生存率 $S(t)$ を一定間隔でサンプリングすれば、擬似的な実験データが得られます。 # 生存率から一定間隔でサンプリング y_series = np.linspace(0.01, 1, 100) # 今回は100人分のデータを生成する # 死亡時刻を得る d_series = survival.reverse(y_series, [0, 0]) # 今回は x = [0, 0] とする。 plt.plot(y_series, d_series, label="0000", marker="o") plt.ylabel("Time of death") plt.xlabel("Individuals") plt.yticks(t_series) plt.xticks([x / 10 for x in range(0, 11)]) plt.grid() 上図は、生存曲線の縦横をひっくり返した図と同じですが、解釈としては、横軸が「それぞれの個人をあらわすID」縦軸が「死亡時刻」という意味になります。以上のようにして得られた変数 d_series は、死亡時刻が入ったデータになります。 KaplanMeierFitter ここまで準備して、ようやくlifelinesパッケージの登場です。まずは KaplanMeierFitterを用います。 from lifelines import KaplanMeierFitter # Kaplan Meier 法による生存曲線の推定と描画 kmf = KaplanMeierFitter() kmf.fit(d_series) kmf.survival_function_.plot() # 理論曲線 plt.plot(t_series, survival(t_series, [0, 0]), label="x = [0, 0]", marker="o") plt.xlabel("Time") plt.ylabel("Survival rate") plt.legend() plt.xticks(t_series) plt.yticks([x / 10 for x in range(0, 11)]) plt.grid() plt.xlim([0, 2.1]) plt.show() 完全に一致したように見えますね。今度は、サンプリング数を減らしてみましょう。 from lifelines import KaplanMeierFitter # 生存率から一定間隔でサンプリング y_series = np.linspace(0.01, 1, 10) # 今回は10人分のデータを生成する # 死亡時刻を得る d_series = survival.reverse(y_series, [0, 0]) # 今回は x = [0, 0] とする。 # Kaplan Meier 法による生存曲線の推定と描画 kmf = KaplanMeierFitter() kmf.fit(d_series) kmf.survival_function_.plot() # 理論曲線 plt.plot(t_series, survival(t_series, [0, 0]), label="x = [0, 0]", marker="o") plt.xlabel("Time") plt.ylabel("Survival rate") plt.legend() plt.xticks(t_series) plt.yticks([x / 10 for x in range(0, 11)]) plt.xlim([0, 2.1]) plt.grid() plt.show() なるほど、ここまでサンプル数が少ないと理論曲線からいくらかズレてきます。 Cox回帰分析 以上のKaplanMeierFitterでは共変数は使いませんでしたが、今度は共変数を使ったCox回帰分析をしてみましょう。 まずは人工データの作成です。 import pandas as pd y_series = np.linspace(0.01, 1, 100) # # 今回は10人分のデータを生成する data = [] for i, y in enumerate(y_series): if (i%3) == 0: x = [0, 0] elif (i%3) == 1: x = [1, 0] else: x = [0, 1] dat = survival.reverse(y, x) # 死亡時刻を得る data.append([dat, x[0], x[1]]) df = pd.DataFrame(data) df.columns = ["Time", "x1", "x2"] df Time x1 x2 0 2.302585 0 0 1 0.652004 1 0 2 3.506558 0 1 3 1.609438 0 0 4 0.499289 1 0 ... ... ... ... 95 0.040822 0 1 96 0.015230 0 0 97 0.003367 1 0 98 0.010050 0 1 99 -0.000000 0 0 100 rows × 3 columns 比較のため、再びKaplanMeierFitterを使ってみます。先程は理論曲線とほぼ一致しましたが、今回は共変数も動かしているため、理論曲線と一致しません。 from lifelines import KaplanMeierFitter kmf = KaplanMeierFitter() kmf.fit(df["Time"]) kmf.survival_function_.plot() plt.plot(t_series, survival(t_series, [0, 0]), label="x = [0, 0]", marker="o") plt.plot(t_series, survival(t_series, [1, 0]), label="x = [1, 0]", marker="<") plt.plot(t_series, survival(t_series, [0, 1]), label="x = [0, 1]", marker=">") plt.xticks(t_series) plt.yticks([x / 10 for x in range(0, 11)]) plt.xlim([0, 2.1]) plt.grid() plt.legend() plt.show() さて、いよいよCox回帰を行うCoxPHFitterの登場です。次のようにしてfittingを行います。 from lifelines import CoxPHFitter cph = CoxPHFitter() cph.fit(df, duration_col='Time') <lifelines.CoxPHFitter: fitted with 100 total observations, 0 right-censored observations> 次のようにして結果のサマリーが得られます。 coef というのが共変数の重要度に相当します。 cph.print_summary() model lifelines.CoxPHFitter duration col 'Time' baseline estimation breslow number of observations 100 number of events observed 100 partial log-likelihood -345.81 time fit was run 2021-10-11 05:42:39 UTC coef exp(coef) se(coef) coef lower 95% coef upper 95% exp(coef) lower 95% exp(coef) upper 95% z p -log2(p) x1 1.12 3.05 0.27 0.59 1.64 1.81 5.16 4.17 <0.005 15.01 x2 -0.65 0.52 0.26 -1.15 -0.14 0.32 0.87 -2.52 0.01 6.42 Concordance 0.67 Partial AIC 695.63 log-likelihood ratio test 35.85 on 2 df -log2(p) of ll-ratio test 25.86 今回、人工的に与えた $w = [4, -1]$ と、正負は一致しますが値は一致しませんね。この意味を理解するには理論をもうちょっと深掘りする必要がありそうです。 cph.plot() <matplotlib.axes._subplots.AxesSubplot at 0x7fcb6468f510> 生存曲線の予測 今回は、各個人は全員既に死亡したものとしてそのデータをフィッティングしましたが、各個人(と同じ共変数を持つ個人)に対する生存曲線を予測する機能もあるようです。 result = cph.predict_survival_function(df) plt.plot(result.iloc[:, 0].index, result.iloc[:, 0], label=1) plt.plot(result.iloc[:, 1].index, result.iloc[:, 1], label=2) plt.plot(result.iloc[:, 2].index, result.iloc[:, 2], label=3) plt.legend() plt.grid() また、次のように、共変数を変化させたときに生存曲線がどのように変化するか予測する機能もあるようです。元データには存在しなかった範囲に外挿することもできるようですね。精度がどうなのかは分かりませんが。 cph.fit(df, duration_col='Time') cph.plot_partial_effects_on_outcome(covariates='x1', values=[-2, -1, 0, 1, 2], cmap='coolwarm') plt.grid() cph.fit(df, duration_col='Time') cph.plot_partial_effects_on_outcome(covariates='x2', values=[-2, -1, 0, 1, 2], cmap='coolwarm') plt.grid() 気になったので外挿性について少し実験してみました。予想通り、元データの範囲内においてはけっこう正しいようですが、範囲外に外挿するときはけっこう外れるようですね。 cph.plot_partial_effects_on_outcome(covariates='x1', values=[-1, 0, 1, 2], cmap='coolwarm') plt.scatter(t_series, survival(t_series, [0, 0]), label="x = [0, 0]", marker="o") plt.scatter(t_series, survival(t_series, [1, 0]), label="x = [1, 0]", marker="o") plt.scatter(t_series, survival(t_series, [2, 0]), label="x = [2, 0]", marker="o") plt.legend() <matplotlib.legend.Legend at 0x7fcb621fd410> cph.plot_partial_effects_on_outcome(covariates='x2', values=[-2, -1, 0, 1, 2], cmap='coolwarm') plt.scatter(t_series, survival(t_series, [0, 1]), label="x = [0, 1]", marker="o") plt.scatter(t_series, survival(t_series, [0, 0]), label="x = [0, 0]", marker="o") plt.scatter(t_series, survival(t_series, [0, -1]), label="x = [0, -1]", marker="o") plt.legend() <matplotlib.legend.Legend at 0x7fcb621c2dd0> 今回の記事はここでおしまいです。気になることがあったら皆様も色々実験してみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超解像タスクで精度を上げる方法

はじめに 超解像タスクにメインで取り組んでいるので、色々な論文などを参考に精度向上のため、色々試してみた結果の自分なりのまとめです。 変更前と比べてどれだけ伸びたとか細かく分析できてません。モデルによっては真逆の結果になるかもですが、別の画像や、別のモデルに変えた時にとりあえず試してみるメモになれば、と思ってます。 前提 モデルはEDSRモデルで固定。学習環境はGoogle Colabです。言語はpython、フレームワークはTensorFlow(keras)。 EDSRモデルの実装はコチラを参考にしてます。 EDSRモデルの公式論文はコチラ。 今回は4倍に拡大する設定になります。実装コードのscaleの値が4ということです。 また試したことを種類別にすると ・画像の前処理 ・モデルの調整 ・学習時のパラメータ ・推論の仕方 に分けられるかなと思うので、この順番でまとめていきます。 画像の前処理 画像データについて 超解像するにしても扱う画像が衛星画像だったり、顔画像だったり、動物だったり色々あります。当たり前ですが、超解像したい画像データを訓練データとして用いる方がいいです。 ただコチラの記事によると衛星画像だけよりもDIV2Kの画像(一般画像)を混ぜた方が精度が上がったとのことなので、一種類にするのも良くないのかなと。確かに汎化性能を上げるなら、いろんなタイプの画像を訓練データに用いる方が良さそうな感じがします。 また基本的には計算コストを減らすためにも、255で割ったり、RGB平均値を引くなどして正規化した方がいいです。 画像サイズについて 後述する水増し手法であるcutblurを使用する関係上、低解像度の画像(以下LR)と高解像度の画像(以下HR)は同じサイズでないといけないです。また画像の回転なども行いたいので、基本的には縦と横が同じ長さ(正方形の画像)が望ましいです。長方形だと例えば low_imgs = [] for image_path in image_path_list: img = cv2.imread(image_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) test_low_img = np.array(img_rgb).astype('float32') test_low_img = cv2.resize(test_low_img, (500, 400), interpolation=cv2.INTER_CUBIC) test_low_img_raw = np.reshape(test_low_img, (1,500,400,3)) # 元の画像 low_flip_img = tf.image.flip_left_right(test_low_img_raw).numpy() # 左右反転 rotate_90_img = tf.image.rot90(test_low_img_raw, k=1).numpy() # 元画像の90度回転 rotate_180_img = tf.image.rot90(test_low_img_raw, k=2).numpy() # 元画像の180度回転 rotate_270_img = tf.image.rot90(test_low_img_raw, k=3).numpy() # 元画像の270度回転 rotate_90_flip_img = tf.image.rot90(low_flip_img, k=1).numpy() # 反転画像の90度回転 rotate_180_flip_img = tf.image.rot90(low_flip_img, k=2).numpy() # 反転画像の180度回転 rotate_270_flip_img = tf.image.rot90(low_flip_img, k=3).numpy() # 反転画像の270度回転 low_imgs.append(test_low_img_raw) low_imgs.append(low_flip_img) .... # 以下270度回転させた画像などをlow_imgsに追加していく low_imgs = np.array(low_imgs) 上記コードのように画像パスを読み込んで左右反転と回転を行った水増し画像を作成し、low_imgsというリストに格納していき、最終的にndarray形式にする時に問題になります。 というのも例えば長方形画像を90度回転させると縦と横の長さが通常の画像と入れ替わります。そのため画像サイズがバラバラになってるので最終行でnp.array(low_imgs)としても(データ数、縦、横、チャネル)の4次元テンソルの形になりません。そのため、基本的にはいくら回転させても画像サイズが変化しない正方形が望ましいです。 まぁそもそも画像データを全てndarray形式でメモリの読み込ませるのは効率が悪いのでTensorFlowであればImageDataGeneratorなどバッチごとに読み込む形式が望ましいです。 じゃあ長方形画像しか手元にない時に正方形にするにはどうすればいいかというと、画像をパッチ化します。そもそも論文で紹介されるようなモデルは学習にDIV2Kという超解像用のデータセットを用いていることが多く、これは元データは正方形でもないですし、画像サイズもバラバラです。そのため論文ではこれらの画像からランダムに任意のサイズの正方形にクロップして訓練データとしています。 論文の訓練データの作り方をまとめると 1. DIV2KなどのHR画像からランダムに任意のサイズの正方形としてクロップ(これが訓練データのHR画像、すなわち教師データになる) 2. 上記HR画像に対応するLR画像を作成するため、HR画像をリサイズして縮小する(この時の補完アルゴリズムとしてはbicubicが使われることが多い)。縮小サイズは4倍拡大のモデルにしたいなら1/4、2倍拡大のモデルにしたいなら1/2にする。 3. 2で作成した縮小画像をLR画像、1で作成したクロップ画像をHR画像として訓練データとする。モデルに突っ込むのはLR画像で教師データはHR画像 となります。ただこれだとLR画像とHR画像のサイズが違うのでcutblurが使えません。そのためLR画像をもう一回bicubic補完でHR画像と同じサイズに拡大します。 画像の水増しについて 画像データの水増しでよく言われる手法って大体が画像分類について書かれているものが多いです。 超解像タスクでも通用するものもありますが、例えばcutoutとかは画像分類においては一部をマスクすることで特定領域を根拠に判断することを防ぐ効果がありますが、超解像タスクにおいては、細かい特徴を捉えることの妨げになります。タスクに応じて最適に水増し手法を行うべきです。 超解像分野の多くの論文では左右反転や90度回転だけを行っていることが多いです。ただそうした幾何変換でない水増し手法として2020年に発表されたばかりのコチラの論文は、超解像タスクにおける最適な水増し手法について書かれています。それがcutblurと呼ばれるものです。実装コードはコチラ。 以下は論文内で述べられている水増し手法のイメージ図です。 日本語で論文内容を説明してくれている記事もあるので、詳細はコチラに任せることにします。 一応簡単にcutblurについてに説明すると、画像の一部分を高画質化したり、低画質化するものになります。 LR画像の一部分を正方形で切り抜き、その部分を対応するHR画像と入れ替えるイメージです。 これにより、「どう」超解像したらいいかだけでなく「どこを」超解像したらいいかまで学習できるとのことです。 基本的に画像の水増しにおいては、これらの手法を全てランダムに行うと良いと思います。 Cutblur以外のそれぞれの水増し手法について論文では細かく述べられているので、簡単にどういうものかまとめておきます。 Blend ランダムな色を任意の割合で混ぜるものになります。 公式実装では(縦、横、チャネル数)のゼロ行列を作成してから、0~255までの値でランダムに置換することでランダムな1色の画像を作成して元の画像と混ぜ合わせています。 RGB permute 画像の色素の順番を入れ替えるものです。基本的にカラー画像はRGBの順番ですが、これを例えばGBRとか、BGRとかにするイメージ。 Cutout 上の図では任意の部分を正方形でくり抜くものになっていますが、これだと実は精度が下がることが論文内で述べられています。それは先ほども述べたように細かい特徴量が欠落するからです。そのために論文内で効果があるCutoutのやり方として述べられているのは、画像のピクセルを一定確率で消すという手法です。 要は1ピクセルごとにランダムに黒色にしていくものになっています。この確率は論文では0.1%と非常に小さいものになっています。ただ効果があると言ってもほんの少しPSNRの値が伸びるだけなので、超解像タスクにおいてはCutoutはあまりやらない方がいいと思います。 MixUp 画像と画像をランダムな割合で混ぜ合わせるものになります。この割合は任意のパラメータのベータ分布から取ってきているようです。 CutMix Cutoutは任意の部分を黒色にするものでしたが、Cutmixは別の画像にします。別の画像を貼り付けるイメージ。 CutMixUp CutMixとMixUpを混ぜたものになります。CutMixで貼り付ける画像をMixUpしたものにするイメージ。 これらの変換はどれか1種類だけを適用させ、左右反転や回転といった幾何変換と組み合わせるといいのではと思います。 モデル調整 EDSRモデルにて行ったものになるので、他のモデルではもしかしたら当てはまらないかも。 活性化関数 ReLU関連が多いです。EDSRモデルはReLUです。LeakyReLUやPReLUといったReLUの亜種が使われていることも論文では多いです。EDSRモデルで試してみましたが、あまり変化はなさげ。ReLU関連ならどれも良さげ。 初期値 活性化関数にReLUを使う関係上、Heの初期化が良さそうです。バイアスはゼロで初期化でいいと思われる。 層の深さ EDSRモデルはフィルター数64, 残差ブロック16のベースラインモデルと、フィルター数256、残差ブロック64のモデルが論文では取り上げられています。もちろん層が深い方が精度は僅かにいいですが、Google Colabで動かすのは少々しんどいです。 画像サイズによりますが、バッチサイズ1とか2とかじゃないとメモリオーバーしてしまいました。フィルター数64、残差ブロック16でも精度は出せそうなので、手軽に動かすならこっちでも良さそう。 ダウンサンプリング 論文のEDSRモデルはLR画像を直接モデルに入れて拡大した超解像画像を出力するものになっているので、LR画像とHR画像のサイズが違う状態です。ただこれだとCutblurが使えないので、自分はEDSRモデルにダウンサンプリング層を追加してみました。単純に縮小するものでもいいのですが、ピクセルシャッフルの方が良さげです。TensorFlow(keras)ならtf.nn.space_to_depthで実装できます。 ピクセルシャッフルに関してはコチラ 学習時のパラメータ 損失関数 メジャーなのはMSEかMAEです。EDSRモデルはMAEなのでMAEのままです。 オプティマイザ Adamが使われることが多いです。。MADGRADなど新しいものも出てきていますが、論文ではAdamが多いです(論文自体が古いからだろうけど) Adamのbeta1は0.9, beta2は0.999、εは1e-8や1e-6で設定されてることが多いです。 EDSRモデルはbeta1=0.9, beta2=0.999, ε=1e-8なのでそれを使ってます。 学習率 論文では初期値は1e-4で200000ステップたったら半分(5e-5)にしてます。 ただ自分のデータだと100000ステップ以降学習が進まなかったので、もっと早い段階で半分になるようにしました。 コサインカーブなども試してみましたが途中で勾配爆発したりしてうまくいかなかったです。 ただ何かしらの学習率減衰処理は必要だと思います。 バッチサイズ 正直モデルサイズや画像サイズによって変わります。自分は16を使うことが多いです。 結局のところメモリオーバーにならない最大値でいいのかなと思います。 推論の仕方 Geometric Self-ensemble 一番大事だと思います。論文でもこれを行った時の指標とそうでない指標で分けて表記されるくらいです。それくらいPSNRやSSIMの値が変わります。 EDSRモデル論文の4.3節に書かれていますが、簡単に説明するとTTA(Test Time Augmentation)の一種で超解像したい元画像に対して ①左右反転 ②90度回転 ③180度回転 ④270度回転 ⑤左右反転+90度回転 ⑥左右反転+180度回転 ⑦左右反転+270度回転 の水増しを行って元画像も合わせて合計8種類の画像を用意して、8種類それぞれに対して超解像を行い、水増し手法と逆の操作(270度回転であれば-270度回転、左右反転+90度回転なら左右反転して-90度回転)を行って、それらを均等な割合で全て組み合わせて1枚の超解像画像を得るものです。 どれか一種類でうまく超解像できなくても、他の部分でできてれば補えるというイメージですかね。 実装に関してはcv2.AddWeightedで画像の合成はいけるはずです。 パッチ推論 自分でつけた名前なのでもしかしたら正式名称があるのかもしれません。 要は超解像したい画像をいくつかのパッチに分けてそれぞれに対して超解像を行って、最後にまた1枚にするものになります。 500×500サイズの画像を直接モデルに入れて超解像するのではなく 100×100のパッチ25個に分けて25個を1個ずつ超解像するようなイメージです。 画像サイズは小さい方がメモリオーバーになることもないし、より細かい部分まで着目できるかなと思ったので採用してみたのですが、逆に精度が下がる結果となりました、、、 モデルや画像によってはうまくいくのかな。 まとめ 現時点で試してみたものとしては以上になります。その他モデルアンサンブルなども良さそうかなと思うので、また試してみて効果があったものはまとめていこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

部分和問題を総当たりで解く (Python)

N個の整数の部分和を総当たりで求める求める方法 動的計画法の練習(?)をする中で、総当たりして結果を確認したいけど総当たりのいい感じのコードがなかなか見付からなかったのでメモしておく。 大まかな手順 整数の組み合わせは配列Aで与える 2^N 個のビットマスクを行列で作成する [0,0,0,...] [0,1,0,...] 各ビットマスクの行列 * Aを計算する partialsuum_bruteforce.py A = (1,2,3) N=len(A) # ビットマスク作成 bitmask = [list(map(int, list(('0'*N + (bin(i))[2:])[-N:]))) for i in range(2 ** N)] # ビットマスクごとに部分和を計算 for mask in bitmask: print(mask,sum( [a*b for a,b in zip(mask,A)] ))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

forループ (paizaランク C 相当)

目次 問題文 私の解答 解答例 学んだこと 振り返り 問題文 詳細はコチラ 問題文の要約 数当てゲームを行う。 1行目のinputで、ゲームの参加者数、一人が書き出す数字の個数、ターゲットの値が与えられる。 2行目以降で各参加者が書き出した値が与えられ、それらとターゲットの値が合致した数をカウントしていく。 入力例2 4 5 2 1 3 4 4 5 2 2 2 2 2 1 2 3 4 5 1 1 1 1 1 出力例2 0 5 1 0 私の解答 main.py [count, num, point] = input().split() for i in range(int(count)): ans = input().split() a = 0 for j in ans: if j == point: a += 1 print(a) 概ね、今までの学習の成果通りという印象。 課題は変数名です。 解答例 main.py N, M, K = map(int, input().split()) for i in range(N): a = [int(j) for j in input().split()] ans = 0 for j in range(M): if a[j] == K: ans += 1 print(ans) 後半部分には大きな違いがありません。 一番気になったのはmap関数です。コチラでは下記のような説明がされています。 Pythonのmap()を使うと、イテラブル(リストやタプルなど)のすべての要素に組み込み関数やlambda(ラムダ式、無名関数)、defで定義した関数などを適用できる。 splitでリスト化した値、N,M,Kをstr型ではなくint型で取り出したいためにmapを使っているようです。 ちなみに先ほどのサイトを確認してみると、 map()の処理はリスト内包表記やジェネレータ式で代用可能であり、多くの場合、リスト内包表記・ジェネレータ式を使用するほうが好ましいとされている。 との記載もあるので、書き換えてみます。 main.py N, M, K = [int(s) for s in input().split()] # リスト内包表記 N, M, K = map(int, input().split()) # map関数 # 以下省略 この程度ならmap関数の方が見やすいように思います。 もう少し複雑な処理をする際にはリスト内包表記の方が優れるのかもしれません。 学んだこと map関数 リスト内包表記
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでエラーが出た時、まずやること

はじめに プログラミングにおいて、エラーとの戦いは付き物です。特に最初のうちは、基本的な構文エラーで止まってしまうことが多くあります。このようなエラーを自分で解決できるようになるために、エラーが起きた時にすることをまとめてみました。 想定読者 Pythonで初めてプログラミングに触れる方 すでに環境は整っているものとします。 環境 VisualStudioCode Python3.9 JupyterNotebook(VSCode版) 1. エラーメッセージを読む ほとんどのプログラミング言語では、実行時に想定と異なる状況が起きた時エラーが出るようになっています。 (C言語などではその一段階前のコンパイル時にエラーが出る場合もあります) Pythonでは読みやすいエラーメッセージが出てくるので、読んでみましょう。 よくあるエラー IndexError リストなどのインデックスがリストの長さを超えてしまった時に出るエラーです。 リストの長さが10のとき、インデックスは0~9の範囲でなければならないことに注意してください。 points = [90, 80, 50] for i in range(4): print(points[i]) #----- 出力 ----- 90 80 50 --------------------------------------------------------------------------- IndexError Traceback (most recent call last) /var/folders/mk/4l2s4ftj18sgrl6l79zdx1p40000gn/T/ipykernel_8747/2790366349.py in <module> 2 3 for i in range(4): ----> 4 print(points[i]) # <- エラーが出た場所を詳しく教えてくれます IndexError: list index out of range KeyError IndexErrorと同じように、あるディクショナリに、存在しないキーでアクセスした時に起きるエラーです。 points = {"Alice": 90, "Bob": 85} print(points["Carol"]) #----- 出力 ----- --------------------------------------------------------------------------- KeyError Traceback (most recent call last) /var/folders/mk/4l2s4ftj18sgrl6l79zdx1p40000gn/T/ipykernel_8747/2731939624.py in <module> 1 points = {"Alice": 90, "Bob": 85} 2 ----> 3 print(points["Carol"]) KeyError: 'Carol' NameError 変数名が存在しない場合のエラーです。タイプミスや、ローカル変数にアクセスしようとした時などに発生します。 ローカル変数とグローバル変数について詳しく def add(a, b): result = a + b add(1, 2) print(result) #----- 出力 ----- --------------------------------------------------------------------------- NameError Traceback (most recent call last) /var/folders/mk/4l2s4ftj18sgrl6l79zdx1p40000gn/T/ipykernel_8747/2211533763.py in <module> 3 4 add(1, 2) ----> 5 print(result) NameError: name 'result' is not defined 2. 検索する これがメインと言っても過言では無いです。最強のプログラミングスキルだと思います。 エラーメッセージで検索する 検索するとき、Python リスト エラーとPython IndexError: list index out of range とでは、得られる情報の質が全く違います。なるべく具体的になるようにキーワードを絞りましょう。 基本的には、エラーメッセージをコピペで大丈夫です。 print = 30 print("上書きしました") #----- 出力 ----- --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /var/folders/mk/4l2s4ftj18sgrl6l79zdx1p40000gn/T/ipykernel_8747/328976122.py in <module> 1 print = 30 2 ----> 3 print("上書きしました") TypeError: 'int' object is not callable # <- この行を検索しよう たとえばこのようなエラーが出た場合、最後の行を検索してみましょう。上の方にこのようなサイトが出てくるはずです。Pythonのエラーは最後の行に情報が端的にまとまっているので、この部分を検索するのが一番効率がいい気がします。 場合によっては、複数の言語で似たようなエラーが出る場合があるのでキーワードにPythonと入れておくと安心です。 抽象度を高める さっきと矛盾しているようですが、時にはより抽象的に検索をする必要もあります。 例えば先ほどの例で、 TypeError Traceback (most recent call last) /var/folders/mk/4l2s4ftj18sgrl6l79zdx1p40000gn/T/ipykernel_8747/328976122.py in <module> この部分を検索した場合どうなるでしょうか。おそらく具体的な解決法は得られないと思います。 /var/folders/mk/4l2s4ftj18sgrl6l79zdx1p40000gn/T/ipykernel_8747/328976122.py この部分は実行しているPythonのパスであるので、実行する人によって異なります。このように環境に依存する文字列を含んだ検索は避けた方がいいでしょう。 英語に臆さない 基本的には英語の方が実例が多いので頑張って読みましょう! 検索について詳しくはこれらのリンクを参考にしてください。 検索力を上げて技術力を鍛える Pythonの基本的なエラー一覧とその原因の確認方法 3. 人に聞く 最終手段です。ほとんどの場合解決すると思われます。 $\downarrow$こちらを参考にしてください。15分は自分で調べてみましょう。 Googleの人工開発チームが提唱した「15分ルール」を解説過去に出てたりするネタですが概要だけだと実際に何するのか分かりにくいと思ったので、経験を踏まえたアクションポイント入れてます。参考程度に。#駆け出しエンジニアと繋がりたい#プログラミング初心者 pic.twitter.com/Q8Rw6WdxOq— たみおん|Web制作脱初心者の学びを発信 (@baka_engineer) October 3, 2021 直接人に聞く 身近に強い人がいたら積極的に聞いていきましょう。 Webサイトで聞く 身近にそのような人がいない場合、web上の質問サービスで聞くのも一つです。Yahoo知恵袋などでも良いですが、プログラミングに特化したサイトもあります。 Teratail Qiita 質問機能 StackOverflow $\leftarrow$あまり初心者向きでは無いかもしれません 個人情報を出してしまわないように気をつけましょう。特にPathを貼り付ける時には自分の本名がついていることがあります。 終わりに よいプログラミングライフを。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データ分析ではまりがちなポイント

データ分析はプログラミングの知識だけでなく、統計や機械学習の知識、マネタイズにつなげるためのビジネス知識など幅広いスキルが求められます。こちらの記事では業務を通してハマったポイントを一覧にしてまとめています。 プロジェクトにアサインされた時や、既存のソースコードを読むときなど、プログラムを解読することは多々発生します。プログラムを解読するためのプロセスについて述べた数少ない良記事です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsのAnaconda環境にMeCabをインストール_備忘録

Anaconda環境にインストールしたMeCabでchasenが動かない(Windows) Jupyter labで実行するとエラーが下記の様なエラー出てしまいます。 何とか動くようになったので、備忘録として残します。 Failed initializing MeCab. Please see the README for possible solutions: https://github.com/SamuraiT/mecab-python3#common-issues<br> If you are still having trouble, please file an issue here, and include the ERROR DETAILS below: https://github.com/SamuraiT/mecab-python3/issues issueを英語で書く必要はありません。 ------------------- ERROR DETAILS ------------------------ arguments: -Ochasen [!tmp.empty()] unknown format type [chasen] ---------------------------------------------------------- やってみたこと 検索し、インストールの記事をみた。  ⇒手順はあっていそう エラーについて検索してみた。⇒パスを通してみたが、何か違うパスが出た。 一旦、アンインストール⇒冷静になって考えよう。 参考にさせていただいた記事 [PythonとMeCabで形態素解析(on Windows)](https://qiita.com/menon/items/f041b7c46543f38f78f7) @menonさんありがとうございます。 基本的手順はオリジナルを参照ください 必要なもの Anaconda3 64bit MeCab 0.996 64bit ipykernel と mecab-python-windows 1のAnaconda3については問題ないと思います。 2のMecabについては0.996ですが、 ikegami-yukinoさんGitHubからダウンロードしました。 [MeCabインストーラー0.996.2.exe] (https://github.com/ikegami-yukino/mecab/releases) (旧)のものもありますが、こちらの新しい方がこの後便利です 3のmecab-python-windowsは2021年現在メンテされてないようなので、 mecab-python3 からダウンロードpipコマンドでインストールしました。 動作確認 さてさて、Jupyter labで動作するでしょうか? test.py import sys import MeCab m = MeCab.Tagger ("-Ochasen") print(m.parse ("これはものづくりなのか分からない人")) - これでWindows上でも無事に形態素解析できるようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

凝集度 (Cohesion)

Cohesion を python で使う場合 python で計測したい時はここにインストール方法や利用方法が書いてあるので参考にしてほしい。 Cohesion の概要 色々な数式で表現されるが、Cohesion はクラス設計で活躍するメトリクスなので、オブジェクト指向プログラミングがわからない!という人は是非使ってみてほしいメトリクスである。 ざっくりとした説明をすると、このメトリクスによってクラス変数がクラス関数でどの程度使われているかを数値で表現できるため、設計の指針として活用できるというものである。 Cohesion で Why & What & How を考える (Why) なぜこのメトリクスを使うのか? この数値を利用することで以下の判断を客観的に行う材料として使えるため。 (What) 使うことで何がわかるのか?何に使えるのか? 数値を確認することで、 クラス全体の設計が適切かを判断できるようになる。 -> クラス関数内でのクラス変数のアクセス頻度を総合的に確認できるため、クラス変数をまったく使用していないクラス関数の存在の是非を問える。 クラス変数がクラスにとって必要/不要のジャッジの判断材料に使える。 -> クラス関数内で全く参照されていない(使われていない)クラス変数を発見できる。 -> クラス関数を別クラスに分割すべきか、もしくはモジュール関数のように外に取り出すべきか、といったジャッジに使える。 (How & Why & What) 数値を確認した後、どのように作業を進める必要があるか? (How) どのように作業を進めるのか? -> 数値を確認し、不要/必要、分離する/しない、といった作業を理由をつけながら繰り返す。 (Why) なぜ繰り返す必要があるのか? -> 精錬されたクラス設計へと向かっていけるため。 -> その結果、何も考えずに作ったプログラムではなく、あなたの哲学がプログラムに宿る。 (What) 何か良いことがあるのか? -> 可読性/再利用性が向上する。 (What) 何に使えるのか? -> 他人に説明するときの材料となる。 ->(あなたがいる/いないに関わらず)他人が見た時の理解速度を向上させる。 (What) 何をする力がつくのか? -> オブジェクト思考におけるクラスの概念やクラス設計の最適化する力がつく。 ちなみに、Why -> What -> How -> Why -> What とループしている現状にお気づきだろうか。 ここまで述べていなかったが、5W1H は何度もループさせることが重要で(させすぎ注意でもあるが...)、真のターゲットに向かっていける場合が多いので余裕のある限り繰り返してみてほしい。 では、実際にこの python ツールを使って計測してみよう。 Cohesion の例 以下のように、非常にやる気のない3つのクラスを作ったとしよう。 e.py class Caliculate: a = 0 b = 0 c = 0 d = 0 def f(x,y,z): return x+y*z class ExampleClass1(object): def func1(self): self.instance_variable1 = 7 class ExampleClass2(object): class_variable1 = 5 class_variable2 = 6 def func1(self): self.instance_variable = 6 local_variable = self.class_variable1 return local_variable def func2(self): print(self.class_variable2) def func3(self): print(self.class_variable1) print(self.class_variable2) def func4(self): self.func3() print(self.instance_variable) def funcX(variable): return variable + 7 実際にこのの python ツールを使ってみると、 cohesion --files e.py 以下のような結果が返ってくる。 File: e.py Class: Caliculate (14:0) Function: f 0/4 0.0% Total: 0.0% Class: ExampleClass1 (22:0) Function: func1 1/1 100.00% Total: 100.0% Class: ExampleClass2 (26:0) Function: func1 2/3 66.67% Function: func2 1/3 33.33% Function: func3 2/3 66.67% Function: func4 1/3 33.33% Function: funcX 0/3 0.0% Total: 40.0% 1つ目のクラス わざわざクラス変数を4つも宣言しておきながら、唯一のクラス関数 f(x,y,x) ではまったく参照されていない。 xxx.py class Caliculate: a = 0 # クラス変数1 b = 0 # クラス変数2 c = 0 # クラス変数3 d = 0 # クラス変数4 # クラス関数1 def f(x,y,z): return x+y*z そのため、計測結果は 0/4(0%) になっている。これはクラス設計が不適であることが明白である。思い浮かぶことは、これはクラスである必要があるのか?クラス変数を削除すれば良いのでは?といったことだろう。(他にも計算をするクラス関数を追加すれば、もしかしたらクラス変数が威力を発揮するようになるかもしれない...?) #ここで、直感的に$\frac{(クラス関数がクラス変数を参照している数)}{(クラス変数の数)}$ということがわかるだろう。 Class: Caliculate (14:0) Function: f 0/4 0.0% Total: 0.0% 2つ目のクラス 1つ宣言したクラス変数を唯一のクラス関数 func1 で参照しているため、 xxx.py class ExampleClass1(object): # クラス関数1 def func1(self): self.instance_variable1 = 7 # クラス変数1 計測結果は 1/1(100%) になっている。これはクラス設計が適切であることが明白である。 #ここで、100%に近いほど、クラス設計が適切であることがわかるだろう。このクラスはとても最適だとは思わないけれど... Class: ExampleClass1 (22:0) Function: func1 1/1 100.00% Total: 100.0% 3つ目のクラス ここまでの経過をまとめると、  計算式は $\frac{(クラス関数がクラス変数を参照している数)}{(クラス変数の数)}$ ぽい  計算結果が 1 に近いほどクラス設計は適切、0 に近いほど不適切 ということがわかったので、発展形に取り組んでみよう。 クラス変数が3つ、クラス関数が5つの場合である。 xxx.py class ExampleClass2(object): class_variable1 = 5 # クラス変数1 class_variable2 = 6 # クラス変数2 # クラス関数1 def func1(self): self.instance_variable = 6 # クラス変数3 local_variable = self.class_variable1 # クラス変数1 return local_variable # クラス関数2 def func2(self): print(self.class_variable2) # クラス変数2 # クラス関数3 def func3(self): print(self.class_variable1) # クラス変数1 print(self.class_variable2) # クラス変数2 # クラス関数4 def func4(self): self.func3() print(self.instance_variable) # クラス変数3 # クラス関数5 def funcX(variable): return variable + 7 計測結果は何やらクラス関数毎に出力されており、Total が 40% になっている。数値としては非常に微妙であることがわかる。 Class: ExampleClass2 (26:0) Function: func1 2/3 66.67% Function: func2 1/3 33.33% Function: func3 2/3 66.67% Function: func4 1/3 33.33% Function: funcX 0/3 0.0% Total: 40.0% そして、funcX がどうやらクラス変数をまったく使用していないので、クラス関数としては不適切だとわかるだろう。 加えて、計算式は $\frac{(クラス関数がクラス変数を参照している数)}{(クラス変数の数)}$ ではなさそうなこともわかったので、末尾の問題で、いったいこの数値は LCOM のどれを使っているのか を考えてみよう。 #亜流の数値になっていることに気がつけば... なお、Total は以下の式で計算されている。 ※クラス変数が3つ、クラス関数が5つ \frac{\frac{2}{3}+\frac{1}{3}+\frac{2}{3}+\frac{1}{3}+\frac{0}{3}}{5}=0.4\\ =>\frac{2+1+2+1+0}{3 \times 5}=0.4 おまけとして、funcX を取り除けば Total が 50% となり、クラスとしてはまあまあの状態にアップグレードする。 ここからどうするかは美意識の問題も絡んでくる可能性が高いため、ここで止めておく。 Class: ExampleClass2 (26:0) Function: func1 2/3 66.67% Function: func2 1/3 33.33% Function: func3 2/3 66.67% Function: func4 1/3 33.33% Total: 50.0% なお、Total は以下の式で計算されている。 ※クラス変数が3つ、クラス関数が4つ \frac{\frac{2}{3}+\frac{1}{3}+\frac{2}{3}+\frac{1}{3}}{4}=0.5\\ =>\frac{2+1+2+1+0}{3 \times 4}=0.5 問題 問題1 3つ目のクラスにおいて、Cohesion の計算式は $\frac{(クラス関数がクラス変数を参照している数)}{(クラス変数の数)}$ ではなさそうなことがわかった。 この数値は LCOM2 の亜種となっているが、元の数式と比較して何がどれに該当するか考えてみよう。 なお、Total は以下の式で計算されている。 ※クラス変数が3つ、クラス関数が5つ \frac{\frac{2}{3}+\frac{1}{3}+\frac{2}{3}+\frac{1}{3}+\frac{0}{3}}{5}=0.4\\ =>\frac{2+1+2+1+0}{3 \times 5}=0.4 問題2 以下のプログラムを実行し、この pkg や同じディレクトリ格納されている pkg の Cohesion を計測してみよう。 #エラーになるなら別の pkg を計測しよう。 #余裕があれば MCC も計測してみよう。 xxx.py import datetime print(datetime.__file__)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPadのPython(Jypyter notebook)実行環境であるJunoを使い始める

はじめに 2021年10月10日、iPadのPython (Jupyter notebook) 実行環境である Juno を購入した。¥1840也。iPad 用のPython実行環境としては、以前からPythonista 3 を持っていたのだが、Pythonista 3 で使用可能なライブラリは、numpy と matplotlib だけであり、作成できるプログラムが限られてくるため、numpy, matplotlib, scipy, pandas, sklearn が使える Juno を購入したわけだ。同じように scipy, pandas, sklearn が使えるiPad用Python 環境として、 Pyto というものもあるが、App store で評価を見ると、『挙動が不安定でよく落ちる』というものがあり、これが気になったため、比較的安定していると思われる Juno を選択した。 なお、無料のPython環境で、Carnets-Jupiter (with scipy) というものもあり試してみたが、外付けキーボードからのリターンキーによる改行を受け付けなかった為、これは早々に諦めた。 余談だが、最近 M1 iPad Pro 12.9-inch を購入している。先般、長期出張でホテルでの PC 作業を長期間行なったのだが、メインマシンである M1 MacBook Airのお供にiPad Air 4を持って行き、Sidecarを使い2画面で作業を行ったのだが、サブディスプレイとしてもう少し大きな画面が欲しいなあと思ったのが iPad Pro 12.9-inch 購入のきっかけである。自宅では M1 MacBook Air を 27-inch4k モニターに接続して使っているので、比較的快適であるのだが、出張となると大画面モニターを持っていくわけにもいかず、MaxBook Air と iPad Airの組み合わせで作業する事になっていたものを、今後は MacBook Air + iPad Pro 12.9-inch の組み合わせで使うことが出来るので、嬉しい。しかしながら、M1 iPad Pro 12.9-inch は私のメインマシンである M1 MacBook Air よりも高価であるため、MacBook 以上の働きをしてもらわなければならない。Juno を入れたことにより Python 環境が、MacBook にかなり近づいたことになり、iPad Pro の今後の活躍に期待しようというわけである。 インストール Juno をダウンロード・インストールすると、Files アプリiの中に Juno というフォルダができるので、ここを使っていくことになるが、必ずしもここでなくても、Files アプリで見ることができるファイルならどこでも Juno を実行することはできるようである。Juno は Jupyter notebook のアプリであるため、ipynb ファイルがあれば、それをタップすることにより Juno を実行することができる。 少し使ってみる ライブラリのインポート よく使うライブラリをインポートしてみる。numpy, scipy, matplotlib, pandas, sklearn, PIL (pillow)はプレインストールされている模様。 Seabornは入っていないので新規にインストールする必要がある。新規のライブラリインストールは、ファイル名をタップすることにより表示されるメニューのInstall Python Packageから行う。インストールされたライブラリは、Junoフォルダの下にあるSite-packagesに格納され使用可能になる。 簡単なグラフを書いてみる 私の場合、iPadはダークモードで使っている。ライトモードでは問題ないのであるが、ダークモードでグラフを描画すると、下の写真のようになってしまう。figureでfacecolor=‘white’を指定しても効果なし。 seabornの使用なし 解決策としてはSeabornを噛ませること。seabornをインポートし、set(style=‘white’) を入れることにより解決できた。 Sseaborn使用 Junoにはよく使うライブラリが含まれており、またライブラリの新規インストールも可能(全てではないらしい)なので、だいぶMacの環境に近付いた気がする。 これからは、iPad Proでどこまでできるか、色々試していこうと思う。とは言いながらiPad Pro 12.9-inch はこれまで使ってきたiPad Airと比較してかなり重く、取り回ししづらい。慣れるのに時間がかかりそうである。 以 上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Athenaで基礎からしっかり入門 分析SQL(Pythonコード付き) #5

今まで複雑なデータ操作・分析などはPythonでやっており、SQLは普通のアプリ開発程度のライトなものしか触って来なかったのですが、やはり分析用の長いSQLなども書けた方がやりとり等で便利・・・という印象なので、復習も兼ねて記事にしておきます。 また、SQLに加えて検算も兼ねてPythonやPandasなどを使ったコードもSQLと併記していきます(Pythonで書くとどういった記述が該当するのかの比較用として使います)。 ※長くなるのでいくつかの記事に分割します。本記事は5記事目となります。 他のシリーズ記事 ※過去の記事で既に触れたものは本記事では触れません。 #1: 用語の説明・SELECT、WHERE、ORDER BY、LIMIT、AS、DISTINCT、基本的な集計関係(COUNTやAVGなど)、Athenaのパーティション、型、CAST、JOIN、UNION(INTERSECTなど含む)など。 #2: GROUP BY・HAVING・サブクエリ・CASE・COALESCE・NULLIF・LEAST・GREATEST・四則演算などの基本的な計算・日付と日時の各操作など。 #3: 文字列操作全般・正規表現関係など。 #4: コメント関係・配列操作全般・ラムダ式など。 この記事で触れること 辞書関係全般(STRUCT、MAP、ROW、JSON) 環境の準備 以下の#1の記事でS3へのAthena用のデータの配置やテーブルのCREATE文などのGitHubに公開しているものに関しての情報を記載していますのでそちらをご参照ください。 特記事項 AthenaのクエリエディタのUIが新しいバージョンのものにアクセスができるようになったため、本記事からはお試しで新しいUIのものを使っていっています。 辞書の操作 以降の節では辞書について全体的に色々と触れていきます。 STRUCT型とMAP型とROW型とJSON型 まず型についてですが、辞書に近い挙動をするものも含めるとAthena(Presto)上にはSTRUCTとMAPとROWという型があります。 STRUCT型はキー名と値の型が固定された辞書となります。例えばSTRUCTでxというキーを整数、yというキーを文字列を定義したとすると{"x": 10, "y": "allegro"}といったような辞書の値を扱えるようになります。ただし定義されていないキーを使用することはできません。例えば{"x": 10, "y": "allegro", "z": true}といったように定義されていないzというキーは扱うことができません。 MAP型はキーの型と値の型が固定されます。型は固定されるものの、キー名自体は固定されません。例えばキーに武器のマスタID、値に攻撃力を持つ辞書・・・といったように、キー名がログの行ごとに様々なようなケースで使えます。例えば{"10": 1200, "13": 2500}みたいなログに向いています。 ROW型は辞書のようにキーと値のペアを持つ型となりますが、STRUCTやMAP型などとはCASTの方法や値への参照の書き方などが変わってきます。テーブル自体の定義では基本的にSTRUCTかMAPを使い、ROW型はAthena(Presto)の関数の返却値などで出てきます。例えば前回の配列関係の記事で触れたZIP関数などが該当します。 STRUCTなどのカラムに対してSELECTで表示した場合そのカラムの型はROWとなっています(ROW関係の制御が指定できます。この辺りは後々の節で触れます)。 また、JSON型というデータも存在します。こちらはJSON形式の文字列が特定のカラムに含まれている・・・といった場合に変換を挟んで利用したりします。 新しく扱う辞書用のテーブルについて 本記事から辞書関係のデータを扱っていく上で以下のテーブルが追加になっています。それぞれSTRUCT(キーが固定な辞書)用、MAP(キーが様々な辞書)用、STRUCTを格納したARRAY(辞書を格納した配列)用として設定しています。 user_pvp_status テーブル user_pvp_status テーブルはユーザーのPVPごとのステータス情報を格納するテーブルです。基本的なカラムに加えて自身のPVP時点でのステータスのカラム(self_status)と相手のPVP時点でのステータスのカラム(enemy_status)を持っています。ステータスのカラムはSTRUCT型で、user_id, attack, defence, speed, luckというキーを持っています。 SELECT * FROM athena_workshop.user_pvp_status LIMIT 10; user_materials_count テーブル user_materials_count テーブルはユーザーの日次の所持している素材アイテム(合成素材的なものをイメージしてください)の所持数のデータを格納するテーブルです。基本的なカラムに加えてキーに対象のアイテムのマスタID、値に所持数を格納した辞書のカラム(materials_count)を持っています。対象のカラムはMAP型でキーも値も整数で設定してあります。 SELECT * FROM athena_workshop.user_materials_count LIMIT 10; user_weapon テーブル user_weapon テーブルはユーザーの所持している武器データの配列をする格納するテーブルです。基本的なカラムに加えてweaponsというカラム名で武器データの辞書を格納した配列が設定されています。辞書には武器のマスタIDのmst_idキー、攻撃力のattackキー、レベルのlevelキーが設定されています。 SELECT * FROM athena_workshop.user_weapon LIMIT 10; user_weapon_json_str テーブル user_weapon_json_str テーブルは内容はほぼ user_weapon テーブルと同じですが、武器のデータのカラムのみ配列ではなくJSONの文字列になっています。後の節でJSON変換などの説明のために使っていきます。 SELECT * FROM athena_workshop.user_weapon_json_str LIMIT 10; STRUCTのカラムの値はSELECTで表示するとROW型となる STRUCTのカラムの値に関してですが、SELECTで値を表示すると結果はROW型として表示されます。UI上の表示としては{user_id=3642, attack=905, ...}といったように辞書のような表示となり、ただしキー名(フィールド名)と値の間がイコールで繋がれる形となります。また、対象のカラムの型を表示するTYPEOF関数を使うとROW型として表示されることが分かります(row(user_id bigint, attack bigint, ...)といったような表示になります)。 SELECT TYPEOF(self_status) AS self_status_type, self_status FROM athena_workshop.user_pvp_status LIMIT 10; STRUCTのカラム内の特定の値にアクセスする STRUCTのカラムの値はSELECTするとROW型となるため、ROW型と同じようにカラム内の特定の値にアクセスすることができます。 ROW型は<対象のカラム名>.<フィールド名>といった形でドット区切りで指定することで特定の値にアクセスすることができます。なおカラム名はSTRUCT内のフィールド名が自動で設定されるようです。ASとかを指定しなくても該当の名前が表示されています。 SELECT self_status.user_id, self_status.attack, self_status FROM athena_workshop.user_pvp_status LIMIT 10; MAPのカラムの値はSELECTで表示してもMAPのままとなる STRUCT型と異なりMAP型の方はSELECTで表示してもそのままMAP型で扱われます。TYPEOF関数で表示してみるとmap(bigint, bigint)といったように表示されることが確認できます。値の表示はROW型と同じように、{{16=37, 18=24, 10=13}}といったようにキーと値の間がイコールで繋がれる形となります。 SELECT TYPEOF(materials_count) AS map_type, materials_count FROM athena_workshop.user_materials_count LIMIT 10; MAPのカラムの特定のキーの値にアクセスする MAP型はSTRUCT型やROW型のデータとは特定のキー(フィールド)へのアクセスの仕方が変わり、<カラム名>[<キーの値>]といったように[]の括弧を使ってアクセスしていきます。 MAP型はSTRUCTとは異なりキーの値が固定されない(行ごとに様々)ため、指定したキーが存在しない行に関しては表示が-といったようにハイフンになります。 以下のSQLでは[15]といったように指定してキーが15の値を表示しています。 SELECT materials_count[15] AS material_id_15_count, materials_count FROM athena_workshop.user_materials_count WHERE dt = '2021-01-01' LIMIT 10; ROW型の値の固定値の作り方 ROW型の固定値は()の括弧内にコンマ区切りで値を指定することで作ることができます。 SELECT ('a', 1) AS result_row これだけだとフィールド名(キー名)がfield0, field1, ...と割り振られます。もしフィールド名の指定をしたい場合には以下のようにROW関数とCASTによる変換処理を指定することで対応ができます。 SELECT CAST(ROW('a', 2) AS ROW(name VARCHAR, age BIGINT)) AS result_row MAP型のデータの様々な関数 MAP型に関しては追加でいくつかの関数が用意されています。以降の節ではそれらの各関数について触れていきます。 2つの配列からMAPの値を生成する: MAP MAP関数では2つの配列を渡すことで、それぞれがキーと値として使われる形でMAPの値を取得することができます。 SELECT MAP(ARRAY['a', 'b', 'c'], ARRAY[1, 2, 3]) AS result_map Pythonでの書き方 ビルトインの辞書の生成処理などはごく基礎的なのでスキップします。 Pandasのシリーズであれば色々書き方はありますが一例として以下のようにしてキーと値のセットを作ることができます。 import pandas as pd sr: pd.Series = pd.Series(data={'a': 1, 'b': 2, 'c': 3}) print(sr) a 1 b 2 c 3 dtype: int64 ROW型を格納した配列からMAPの値を生成する: MAP_FROM_ENTRIES MAP_FROM_ENTRIES関数ではROW型の値を格納した配列からMAPの値を生成します。ROW型の値部分には最初のフィールドにはキー名、2番目の値には値の2件のみを格納した形式が必要になります。 SELECT MAP_FROM_ENTRIES(ARRAY[('a', 1), ('b', 2), ('c', 3)]) AS result_map 値が配列となるMAPの値を生成する: MULTIMAP_FROM_ENTRIES MULTIMAP_FROM_ENTRIES関数はMAP_FROM_ENTRIES関数と似ていますが、MAPの値が配列になって返ってきます。また、同名のキー名が複数ある場合にも対応しています。 以下のSQLではMAPのaのキーが複数存在する条件を指定しています。 SELECT MULTIMAP_FROM_ENTRIES(ARRAY[('a', 1), ('b', 2), ('a', 3)]) AS result_map MAPからROW型の値を格納した配列を取得する: MAP_ENTRIES MAP_ENTRIESではMAP_FROM_ENTRIES関数などとは逆にMAPの値からROWの値を格納した配列を取得します。ROWのフィールド名は順番にfield0, field1, ...と設定されていきます。 SELECT MAP_ENTRIES(materials_count) AS materials_count_rows, materials_count FROM athena_workshop.user_materials_count LIMIT 10 キーと値のペアの値を作る: MAP_AGG MAP_AGG関数では第一引数にキー、第二引数に値を指定することで単一の値のMAPの値を取得することができます。 SELECT MAP_AGG('a', 1) AS result_map なお、このMAP_AGG関数ですがデータの縦持ち・横持ちの変換に使われるケースもあります。そちらに関してはリンクを貼っておきます。 特定のキーの値を取得する: ELEMENT_AT []による添え字でのアクセスとほぼ同じような挙動をしますが、ELEMENT_AT関数でも特定のキー名の部分のMAPの値を取得することができます。第一引数には対象のMAPのカラムもしくはMAPの固定値、第二引数にはキーを指定します。 SELECT ELEMENT_AT(materials_count, 2) AS element_2, materials_count FROM athena_workshop.user_materials_count LIMIT 10 Pythonでの書き方 Pythonのビルトインの辞書では[]の括弧を使うか、もしくは存在しないキーでもエラーにせずにNoneを返却するようにしたければgetメソッドなどを使います。 from typing import Dict, Optional dict_val: Dict[str, int] = {'a': 1, 'b': 2, 'c': 3} value: Optional[int] = dict_val.get('b') print(value) 2 Pandasのシリーズであれば[]の括弧を使います。 import pandas as pd sr: pd.Series = pd.Series(data={'a': 1, 'b': 2, 'c': 3}) print(sr['b']) 2 データフレームであれば[]の括弧を2個繋げたりilocやloc、atなど色々書き方があります。atを使うと以下のような書き方になります。 import pandas as pd df: pd.DataFrame = pd.DataFrame(data=[{'a': 1, 'b': 2}, {'a': 3, 'b': 4}]) print(df.at[0, 'a']) 1 MAPのキーの配列を取得する: MAP_KEYS MAP_KEYS関数ではMAPの値のキーの値の配列を返却します。なお、元のMAPの値の順番は担保されないようなので注意が必要です。Python3.6や3.7辺りから標準の挙動となった順序付きの辞書的な挙動はしません。 SELECT MAP_KEYS(materials_count) AS keys, materials_count FROM athena_workshop.user_materials_count LIMIT 10 Pythonでの書き方 ビルトインの辞書であればkeysメソッドが用意されています。値がリストで欲しい場合はlist関数でキャストします。 from typing import Dict dict_val: Dict[str, int] = {'a': 1, 'b': 2, 'c': 3} print(list(dict_val.keys())) ['a', 'b', 'c'] Pandasのシリーズもしくはデータフレームであればindex属性でアクセスができます。 import pandas as pd sr: pd.Series = pd.Series(data={'a': 1, 'b': 2, 'c': 3}) print(sr.index) Index(['a', 'b', 'c'], dtype='object') リストが欲しい場合にはtolistメソッドで変換が効きます。 import pandas as pd sr: pd.Series = pd.Series(data={'a': 1, 'b': 2, 'c': 3}) print(sr.index.tolist()) ['a', 'b', 'c'] MAPの値の配列を取得する: MAP_VALUES MAP_VALUES関数ではMAPの値の配列を取得することができます。こちらもMAP_KEYS関数と同様に値の順番は担保されない(元のMAPの値と同じにはならない)ので注意が必要です。 SELECT MAP_VALUES(materials_count) AS keys, materials_count FROM athena_workshop.user_materials_count LIMIT 10 Pythonでの書き方 ビルトインの辞書の場合はvaluesメソッドで値が取れます。こちらもリストが欲しい場合にはlist関数でキャストする必要があります。 from typing import Dict dict_val: Dict[str, int] = {'a': 1, 'b': 2, 'c': 3} print(list(dict_val.values())) [1, 2, 3] PandasのシリーズやNumPyのndarrayなどであればvalues属性で取れます。リストが欲しい場合にはtolistメソッドが必要になります。 import pandas as pd sr: pd.Series = pd.Series(data={'a': 1, 'b': 2, 'c': 3}) print(sr.values.tolist()) [1, 2, 3] 複数のMAPを統合する: MAP_CONCAT MAP_CONCAT関数は複数のMAPの値を連結します。各引数には各MAPのカラムもしくはMAPの固定値を指定します。 以下のSQLでは第一引数にMAP関数から生成したMAPの固定値(キーに100と101、値に1と2という設定をしてあります)、第二引数にMAPのカラムを指定しています。結果のMAPのキーに100や101が含まれていることが確認できます。 SELECT MAP_CONCAT(MAP(ARRAY[100, 101], ARRAY[1, 2]), materials_count) AS result_map FROM athena_workshop.user_materials_count LIMIT 10 この関数の引数は可変長なので、第三引数、第四引数・・・と必要に応じてMAPの引数を増やしていくこともできます。 注意点として、以下のようなものがあります。 キーの順番は担保されません。第一引数のMAPの値が結果のMAPの先に来たりはしません。 各引数でキーが被った場合は、後の引数の値によって上書きされます。 各MAPのキーと値の型は一致していないとエラーになります。 キーが被るケースを少し試してみます。以下のSQLでは第一引数のMAPの固定値のキーを1と2、値を3と4としています。 SELECT MAP_CONCAT(MAP(ARRAY[1, 2], ARRAY[3, 4]), materials_count) AS result_map FROM athena_workshop.user_materials_count LIMIT 10 1と2というキーは第二引数のカラムでも持っていることが多いので、1=97とか1=75といったように第二引数の値で上書きされて第一引数の値は消えていることが確認できます。 また、各引数でMAPの型が一致していないケースを試してみます。materials_countカラムはmap<bigint, bigint>の型を持つため、第一引数はmap<string, bigint>の型で試してみます。 SELECT MAP_CONCAT(MAP(ARRAY['a', 'b'], ARRAY[3, 4]), materials_count) AS result_map FROM athena_workshop.user_materials_count LIMIT 10 実行してみると以下のようにエラーとなります。 Pythonでの書き方 ビルトインの辞書であればupdateメソッドで別の辞書の値を加えることができます。こちらもキーが重複している場合には引数で指定された辞書の方の値が優先されます。 from typing import Dict dict_val_1: Dict[str, int] = {'a': 1, 'b': 2, 'c': 3} dict_val_2: Dict[str, int] = {'c': 4, 'd': 5} dict_val_1.update(dict_val_2) print(dict_val_1) {'a': 1, 'b': 2, 'c': 4, 'd': 5} Pandasのシリーズなどであればconcat関数などで複数のシリーズ(やデータフレーム)を統合することができます。ちなみにシリーズやデータフレームは同じインデックスを複数持つことができるので、Athena(Presto)やビルトインの辞書と異なりインデックスが被った場合でもそれぞれの値が残ります(以下のコードではcというキーの値が2つ残っています)。 import pandas as pd sr_1: pd.Series = pd.Series(data={'a': 1, 'b': 2, 'c': 3}) sr_2: pd.Series = pd.Series(data={'c': 4, 'd': 5}) result_sr: pd.Series = pd.concat([sr_1, sr_2]) print(result_sr) a 1 b 2 c 3 c 4 d 5 dtype: int64 MAPの値を特定条件でフィルタリングする: MAP_FILTER MAP_FILTER関数はMAPのキーと値をラムダ式で参照して、条件を満たすもののに結果のMAPに残すという挙動をします。 ラムダ式に関しては以前の記事で触れたので詳しい書き方などはそちらをご確認ください。 ラムダ式の引数にはキーと値の2つが渡されます。この記事ではキーの引数名をk、値の引数名をvとして扱っていきます。 返却値は真偽値で設定し、trueであれば結果に残りfalseであれば結果から除外されます。 以下のSQLでは1, -2, 5という3つの値を持っているMAPに対して、値が0以上(v >= 0)の値のみ結果のMAPに残しています。 SELECT MAP_FILTER(MAP(ARRAY['a', 'b', 'c'], ARRAY[1, -2, 5]), (k, v) -> v >= 0) AS result_map Pythonでの書き方 ビルトインの辞書であればループを回すか、filter関数とラムダ式などを組み合わせるか、もしくは内包記法などを使うかといった方法があります。 filter関数は以下のような内容になっています。 filterは第1引数に与えられた関数を第2引数で与えられたシーケンス1つ1つに適応し、Trueになったシーケンスのみを返す関数です。 【Python】dictをfilterする from typing import Dict dict_val: Dict[str, int] = {'a': 1, 'b': -2, 'c': 5} dict_val = dict(filter(lambda item: item[1] >= 0, dict_val.items())) print(dict_val) {'a': 1, 'c': 5} キーに調整を加える: TRANSFORM_KEYS TRANSFORM_KEYS関数はラムダ式を使って各キーを調整したMAPのデータを取得することができます。第一引数にはMAPのカラムもしくはMAPの固定値、第二引数にはラムダ式を指定します。 ラムダ式の引数にはキー(k)と値(v)の2つの引数を受け付け、返却値にはキーの値の1つの値が必要になります。 以下のSQLではキー名をキー_値といったようにアンダースコアを挟んでキーと値を連結しています(結果のキーはa_1とかc_1などになります)。 SELECT TRANSFORM_KEYS(MAP(ARRAY['a', 'b', 'c'], ARRAY[1, 2, 1]), (k, v) -> k || '_' || CAST(v AS VARCHAR)) AS result_map Pythonでの書き方 色々書き方があると思いますが、ビルトインの辞書であれば内包記法とかがシンプルかもしれません。 from typing import Dict dict_val: Dict[str, int] = {'a': 1, 'b': 2, 'c': 1} renamed_dict_val: Dict[str, int] = { f'{k}_{v}': v for k, v in dict_val.items()} print(renamed_dict_val) {'a_1': 1, 'b_2': 2, 'c_1': 1} ※f-stringsとかも使っています。 値に調整を加える: TRANSFORM_VALUES TRANSFORM_VALUES関数はTRANSFORM_KEYS関数の値版です。値に対してラムダ式を反映したMAPを取得します。 以下のSQLはTRANSFORM_KEYSの時とほぼ同じ内容ですが、結果は値の方がa_1とかになっていることを確認できます。 SELECT TRANSFORM_VALUES(MAP(ARRAY['a', 'b', 'c'], ARRAY[1, 2, 1]), (k, v) -> k || '_' || CAST(v AS VARCHAR)) AS result_map Pythonでの書き方 キーの時とほぼ同じで内包記法で対応しています。 from typing import Dict dict_val: Dict[str, int] = {'a': 1, 'b': 2, 'c': 1} result_dict_val: Dict[str, str] = { k: f'{k}_{v}' for k, v in dict_val.items()} print(result_dict_val) {'a': 'a_1', 'b': 'b_2', 'c': 'c_1'} 2つのMAPに対して同じキーの値同士で計算を行う: MAP_ZIP_WITH MAP_ZIP_WITH関数は2つのMAPの値に対して、同じキーを持つ値同士で処理を行うことができます。結果もMAPの値となります。 第一引数には1つ目のMAPのカラムもしくはMAPの固定値、第二引数には2つ目のMAPのカラムもしくはMAPの固定値、第三引数にはラムダ式が必要になります。 ラムダ式にはキー名(k)、1つ目のMAPの該当のキーの値(v1)、2つ目のMAPの該当のキーの値(v2)が渡され、返却値としてはMAPの値として1つの値が必要になります。 以下のSQLでは2つのMAPの値に対して、各値を合算したMAPをMAP_ZIP_WITH関数を使って取得しています。 SELECT MAP_ZIP_WITH( MAP(ARRAY['a', 'b', 'c'], ARRAY[1, 2, 3]), MAP(ARRAY['a', 'b', 'c'], ARRAY[4, 5, 6]), (k, v1, v2) -> v1 + v2 ) AS result_map なお、片方にしか無いキーが存在するとそちらの値はNULLになるので注意が必要です(欠損値の制御が必要になります)。 Pythonでの書き方 色々書き方がありますが、ビルトインの辞書であれば一例として以下のコードでは一旦辞書のkeysメソッドやset関数などを使って2つの辞書の一意なキーの配列を取得して、それを使って内包記法(もしくは普通のループを回す形でも良いと思います)で処理をしています。 from typing import Dict, List dict_val_1: Dict[str, int] = {'a': 1, 'b': 2, 'c': 3} dict_val_2: Dict[str, int] = {'a': 4, 'b': 5, 'c': 6} keys: List[str] = list(set([*dict_val_1.keys(), *dict_val_2.keys()])) result_dict: Dict[str, int] = {k: dict_val_1[k] + dict_val_2[k] for k in keys} print(result_dict) {'a': 5, 'b': 7, 'c': 9} MAPのデータのカーディナリティのサイズを取得する: CARDINALITY CARDINALITY関数ではカーディナリティのサイズを取得できます。カーディナリティってなんだ・・・という感じですが、MAP内で異なる値が多い(バリエーションが豊富)な行ほどサイズが大きくなる・・・という値のようです。 カーディナリティとは、数学で基数あるいは濃度という意味の用語。ITの分野では、リレーショナルデータベースにおいてあるテーブルの同一の列(カラム)に含まれる異なる値の数(バリエーション)のことを指すことが多い。 カーディナリティ 【cardinality】 引数は第一引数のMAPのカラムもしくはMAPの固定値のみです。 SELECT CARDINALITY(materials_count) AS cardinality_size, materials_count FROM athena_workshop.user_materials_count LIMIT 10 MAPの値を行列に変換する MAPの値をキーと値で各列に展開し、行列として扱うと便利なことがあります。そういった場合はUNNESTを使うと行列に変換できます。 以前の配列の以下の記事で詳しく触れましたが、書き方としてはFROM <対象テーブル>, UNNEST(対象テーブルのMAPのカラム) AS <展開後のテーブル名>(展開後のキー名, 展開後のカラム名)となります。 配列の時と比べてASの後の()の括弧内がキーと値で2つ必要になっている点には注意が必要です。また、配列の記事で書きましたがその後のデータでさらに連結など色々と操作をする場合CROSS JOINを一緒に使ってUNNESTする必要があるケースもあります。詳しくはリンク先をご確認ください。 以下のSQLではMAPの値を行列に展開しています。キーはmaterials_id, 値はcountという名前にしています。 SELECT user_id, materials_id, count, materials_count FROM athena_workshop.user_materials_count, UNNEST(athena_workshop.user_materials_count.materials_count) AS unnested_table(materials_id, count) LIMIT 100 STUCTの値を行列に変換する 1次元のSTRUCTの場合はSELECT結果はROW型となるため、そのままダイレクトに使うキーの値をドットで繋いで展開すれば行列として扱えます。 SELECT self_status.user_id AS self_user_id, self_status.attack AS self_attack, self_status.defence AS self_defence FROM athena_workshop.user_pvp_status LIMIT 100 STRUCTを格納した配列を行列に変換する STRUCT型の辞書を格納した配列といった具合に2次元でデータが格納されているケースもよくあります。そういったデータを行列に変換してみます。 まず配列に対しては以下の過去の記事で触れたようにUNNESTで展開できます。 SELECT user_id, weapon, weapons FROM athena_workshop.user_weapon, UNNEST(athena_workshop.user_weapon.weapons) AS unnested_weapons(weapon) LIMIT 100; 展開後はROW型で{mst_id=360, attack=1092, level=9}といったような値で1次元になっていることが確認できます。 あとはSTRUCTの行列変換の節で触れたような形で必要な各キーをSELECT句の部分に指定すれば行列に展開できます。 SELECT user_id, weapon.mst_id, weapon.attack, weapon.level, weapons FROM athena_workshop.user_weapon, UNNEST(athena_workshop.user_weapon.weapons) AS unnested_weapons(weapon) LIMIT 100; Pythonでの書き方 他の行列変換の処理も同様なのですが、この手の変換は以前記事を書いたのでそちらをご確認ください。 JSONのデータを扱う ログによっては配列などではなくJSON形式の文字列で入っている・・・といったケースなどがあります。この節以降ではそういったJSONデータに対して触れていきます。 値をJSONへ変換する JSONへは真偽値・整数値・文字列(VARCHAR)・配列・辞書(MAPもしくはROW)や欠損値(NULL)の値を変換できます。配列などで入れ子にする場合には内部に格納する値もこれらの値である必要があります。 また、MAPの辞書などであればキーは文字列になっている必要があります(整数とかになっていると文字列に変換されます)。配列に関しても複数の型を格納していると変換がうまくいきません(例えば文字列と整数が混在している配列など)。それらは事前に型変換などをかけておく必要があります。 変換処理はCAST関数で行うことができます。CASTのAS部分にはJSONと指定します。表示上はTYPEOFなどで型を表示しない限り違いが分かりづらいですが、TYPEOF関数などで表示される型はJSONとなります。 整数をJSONに変換する例 SELECT CAST(10 AS JSON) AS json_value, TYPEOF(CAST(10 AS JSON)) AS type 配列をJSONに変換する例 SELECT CAST(ARRAY[10, 20, 30] AS JSON) AS json_value, TYPEOF(CAST(ARRAY[10, 20, 30] AS JSON)) AS type キーが整数のMAPを指定してみると、キーにダブルクォーテーションが追加されていることから分かるようにキーが文字列に変換されることが分かります(エラーにはなりません)。 キーが整数のMAPをJSONに変換する例 SELECT CAST(MAP(ARRAY[10], ARRAY[20]) AS JSON) AS json_value 配列に複数の型が含まれている場合、例えば整数と文字列が含まれている場合などには変換しようとしてもエラーになります。 複数の型を含む配列をJSONに変換するとエラーになる例 SELECT CAST(ARRAY[10, 'cat'] AS JSON) AS json_value ※JSON形式の文字列をJSONに変換する・・・という場合はCASTによる変換ではなく後述するJSON_PARSEを使う必要があります。CASTでそういった文字列に対して処理を反映すると単純にその文字列のJSONの単一値となり、階層構造などは展開されません。 JSONから別の型に値を変換する こちらもCAST関数で任意のJSONからの変換がサポートされている型であれば変換ができます(CAST(<JSONの値> AS <変換したい型>)といった具合に指定します)。 JSONの値から整数に変換する例 SELECT CAST(CAST(10 AS JSON) AS BIGINT) AS result, TYPEOF(CAST(CAST(10 AS JSON) AS BIGINT)) AS type JSONの文字列をJSON型に変換する: JSON_PARSE JSONの文字列をJSON型に変換したい場合JSON_PARSEを使います。第一引数に対象のJSONの文字列のカラムを指定します。 SELECT JSON_PARSE(weapons) AS weapons FROM athena_workshop.user_weapon_json_str LIMIT 10; 結果はSTRUCTなどと異なりROW型のような表記にはならずにJSONの表示そのままになります。 TYPEOFで型を見てみると確かにVARCHARなどではなくJSONの型になっています。 SELECT TYPEOF(JSON_PARSE(weapons)) AS weapons FROM athena_workshop.user_weapon_json_str LIMIT 10; Pythonでの書き方 jsonモジュールのloads関数でJSON形式の文字列をPythonのデータ(辞書など)に変換できます。 import json from typing import Dict dict_val: Dict[str, int] = json.loads('{"a": 10, "b": 20}') print(dict_val) {'a': 10, 'b': 20} PandasなどでそういったJSON形式の文字列のが含まれている場合には、データフレームなどでのapplyメソッドでloads関数を指定すれば対応ができます。 import json import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_weapon_json_str/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, orient='records', compression='gzip') df['weapons'] = df['weapons'].apply(json.loads) print(df.head()) print('type:', type(df.at[0, 'weapons'])) user_id date device_type weapons 0 23475 2021-01-01 2 [{'mst_id': 1700, 'attack': 300, 'level': 2}, ... 1 23826 2021-01-01 2 [{'mst_id': 1672, 'attack': 1709, 'level': 15}... 2 2496 2021-01-01 2 [{'mst_id': 655, 'attack': 1238, 'level': 12},... 3 1192 2021-01-01 1 [{'mst_id': 56, 'attack': 871, 'level': 6}, {'... 4 26195 2021-01-01 1 [{'mst_id': 1427, 'attack': 836, 'level': 9}, ... type: <class 'list'> JSONの値をJSONの文字列に変換する: JSON_FORMAT JSON_FORMAT関数はJSONの値をJSON形式の文字列へと変換します。JSON_PARSE関数と逆の挙動をします。 SELECT JSON_FORMAT(CAST(weapons AS JSON)) AS json_str, TYPEOF(JSON_FORMAT(CAST(weapons AS JSON))) AS type FROM athena_workshop.user_weapon LIMIT 10; 実行結果の型がVARCHARになっていることが確認できます。 ※CASTで文字列に変換しただけだと正確なJSONのフォーマットにならないケースが結構ある(JSONに合わせたフォーマットではなくSQLに合わせたフォーマットになったりする)ため、その辺りでエラーなどを避けるためにもCASTではなくJSON_FORMAT関数の利用が推奨されます。 JSONの値を配列に変換する JSON_PARSEでJSON型にした値は前節までで触れたようにCAST関数で他の型に変換することができます。今回のJSONは配列になっているため配列にキャストしてみます。 辞書を含んだ配列・・・といったデータであれば、CAST(<JSONの値> AS ARRAY(ROW(<辞書のフォーマット>)))といったように、ARRAY関数にROW関数を入れ子にすることで表現できます。ROW関数はROW(<キー名> 値の型, <キー名> 値の型)といったように書きます(例 : ROW(a BIGINT, b VARCHAR))。 SELECT CAST(JSON_PARSE(weapons) AS ARRAY(ROW(mst_id VARCHAR, attack BIGINT, level BIGINT))) AS weapons FROM athena_workshop.user_weapon_json_str LIMIT 10; これでARRAYとSTRUCTを扱った時と同じような値となるので、STRUCT関係の節で触れたような必要な行列変換などを入れれば色々操作が行えます。 もしくはキーが多すぎて個別に型を定義するのが難しい・・・といった場合には、JSONの型を含んだ配列(ARRAY(JSON))といったように指定するのが楽なケースもあります。こちらの場合は行列変換などはJSONのフォーマットによる参照方法などが必要になってきます(後の節で触れます)。 SELECT CAST(JSON_PARSE(weapons) AS ARRAY(JSON)) AS weapons FROM athena_workshop.user_weapon_json_str LIMIT 10; 特定の位置のJSONの値を抽出する: JSON_EXTRACT と JSON_EXTRACT_SCALAR JSONの値の特定の位置の値を抽出するにはJSON_EXTRACT関数を使います。第一引数には対象のJSONのカラムもしくは固定値、第二引数には対象の位置の指定用の文字列を設定します。 第二引数の位置指定用の文字列はJSONPath - XPath for JSONの資料に書かれているものが使えるようです。全ては触れませんが良く使いそうなものをこの節で触れていきます。 まずは$の記述です。これはJSONのルート(一番上の階層)を示します。$を単体で指定した場合は一番上のフォルダから中身の全てのファイルが参照されるように、JSONのデータ全体の参照となるためJSON_EXTRACTを使わない場合と同じ結果になります。 SELECT JSON_EXTRACT(JSON_PARSE(weapons), '$') AS result FROM athena_workshop.user_weapon_json_str WHERE dt = '2021-01-01' LIMIT 10; JSON内の特定の位置のデータを抽出したい場合ルートのデータから子の要素にアクセスしていく必要があります。そういった場合には.のドット記号かもしくは[]の記号を使ってインデックスの指定をする形で参照します。 例えば今回扱っているデータだとルート部分が配列になっているので、配列の先頭の要素にアクセスしたい場合ドット記号を使うと.0といったように書きます。ルートの位置からその配列の先頭のデータ・・・とするため、ルートの$と合わせて$.0といったように指定します。ちなみにAthena(Presto)のSQL上だと最初のインデックスは1から始まるケースが大半ですが、JSONの場合0から始まります。最初のデータが0となり1ではないため注意が必要です。 SELECT JSON_EXTRACT(JSON_PARSE(weapons), '$.0') AS result FROM athena_workshop.user_weapon_json_str WHERE dt = '2021-01-01' LIMIT 10; JSONの配列の先頭のデータのみを抽出することができました。ドット記号を使うのと同じように[]の括弧を使って参照することもできます。例えば配列の先頭のインデックスにアクセスしたい場合には[0]といった書き方でもアクセスすることができます。 SELECT JSON_EXTRACT(JSON_PARSE(weapons), '$[0]') AS result FROM athena_workshop.user_weapon_json_str WHERE dt = '2021-01-01' LIMIT 10; 配列の先頭のデータの辞書部分でさらに特定のキーにアクセスしたいとします。例えばattackキーのデータだけ欲しい・・・といった場合を考えます。 そういった場合は子の要素には整数だけでなくキー名なども指定できるため、ドットを使う場合は$.0.attackといったように書くことで子要素にアクセスすることができます。 SELECT JSON_EXTRACT(JSON_PARSE(weapons), '$.0.attack') AS result FROM athena_workshop.user_weapon_json_str WHERE dt = '2021-01-01' LIMIT 10; []の括弧を使うこともできます。キー名で[]の括弧を使う場合にはダブルクォーテーションで囲んで書いてもダブルクォーテーションを省略してもどちらでも動くようです(JSONだとダブルクォーテーションで囲む方が自然ですが、特に必要というわけではないようです)。 SELECT JSON_EXTRACT(JSON_PARSE(weapons), '$[0][attack]') AS result FROM athena_workshop.user_weapon_json_str WHERE dt = '2021-01-01' LIMIT 10; その他にもリンク先の資料だと全てを表すアスタリスクや複数条件の[]の括弧内のコンマの記述やスライス記法なども書かれていますが、なんだかAthena上ではエラーになる?ようで、Athenaではまだ使えないのかもしれません。 JSON内の配列の全データを展開したい・・・といった場合はこの関数ではなくUNNESTなどで各行を辞書などで行列変換し、その後JSON_EXTRACT関数で各キーの値を展開する・・・とすると良いかもしれません。 なお、JSON_EXTRACT関数では抽出結果の値は全てJSONの型となります。試しに以下のようにルートの配列部分や配列内の辞書、さらにその辞書の特定のキーの値の型をそれぞれ表示してみても全てjsonと表示されます。 SELECT TYPEOF(JSON_EXTRACT(JSON_PARSE(weapons), '$')) AS array_type, TYPEOF(JSON_EXTRACT(JSON_PARSE(weapons), '$[0]')) AS dict_type, TYPEOF(JSON_EXTRACT(JSON_PARSE(weapons), '$[0][attack]')) AS scalar_type FROM athena_workshop.user_weapon_json_str WHERE dt = '2021-01-01' LIMIT 10; 一方でJSON_EXTRACT_SCALAR関数は文字列で値が返ってくるようです。数値などの他の型であってもVARCHARに結果はなります(文字列以外はどのみちCASTが必要になりそうなので、あまり使い道は無い・・・?かもしれません)。 SELECT JSON_EXTRACT_SCALAR(JSON_PARSE(weapons), '$[0][attack]') AS attack, TYPEOF(JSON_EXTRACT_SCALAR(JSON_PARSE(weapons), '$[0][attack]')) AS attack_type FROM athena_workshop.user_weapon_json_str WHERE dt = '2021-01-01' LIMIT 10; Pythonでの書き方 json.lodasで文字列からリストに変換した後に、追加で特定の位置の値を抽出する関数(今回はextract_attack_from_weaponsという関数名にしてあります)を設けてそちらもapplyメソッドで反映すれば抽出できます。 import json from typing import Any, Dict, List import pandas as pd def extract_attack_from_weapons(weapons: List[Dict[str, Any]]) -> int: """ 指定された武器データの配列から、先頭の武器の攻撃力の値を抽出する。 Parameters ---------- weapons : list of dict 武器データを格納した配列。 Returns ------- attack : int 抽出された攻撃力。空のリストの場合には0が設定される。 """ if not weapons: return 0 return weapons[0]['attack'] df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_weapon_json_str/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, orient='records', compression='gzip') df['weapons'] = df['weapons'].apply(json.loads) df['attack'] = df['weapons'].apply(extract_attack_from_weapons) print(df[['attack', 'weapons']].head()) attack weapons 0 300 [{'mst_id': 1700, 'attack': 300, 'level': 2}, ... 1 1709 [{'mst_id': 1672, 'attack': 1709, 'level': 15}... 2 1238 [{'mst_id': 655, 'attack': 1238, 'level': 12},... 3 871 [{'mst_id': 56, 'attack': 871, 'level': 6}, {'... 4 836 [{'mst_id': 1427, 'attack': 836, 'level': 9}, ... 値がJSONで扱えるスカラー値かどうかの真偽値を取得する: IS_JSON_SCALAR IS_JSON_SCALAR関数では特定の値がJSONで扱える単一のスカラー値(単一の数値、文字列、真偽値、NULLなど)かどうかの真偽値を返します。単一の数値などであればtrue、そうではなく配列などの複数の値を含むデータなどであれはfalseとなります。 以下のSQLでは整数・文字列・真偽値・配列の固定値に対してIS_JSON_SCALAR関数を実行していますが、配列のみfalseになっていることが確認できます。 SELECT IS_JSON_SCALAR(CAST(10 AS JSON)) AS int_is_json_scalar, IS_JSON_SCALAR(CAST('Hello' AS JSON)) AS string_is_json_scalar, IS_JSON_SCALAR(CAST(true AS JSON)) AS bool_is_json_scalar, IS_JSON_SCALAR(CAST(ARRAY[1, 2] AS JSON)) AS array_is_json_scalar JSONの配列内に特定の値がふくまれているかの真偽値を取得する: JSON_ARRAY_CONTAINS JSON_ARRAY_CONTAINS関数はJSONの配列内に特定の値が含まれているかどうかの真偽値を取得することができます。存在すればtrue、存在しなければfalseとなります。通常の配列でもこの辺の関数はありますが、JSONのまま扱いたい場合などに使用します。 第一引数には対象のJSONの配列、第二引数には検索対象の値を指定します。 SELECT JSON_ARRAY_CONTAINS(CAST(ARRAY[1, 2, 3] AS JSON), 2) AS result ちなみにですが、JSONの配列の形式の文字列であればキャストやJSON_PARSE関数などを通さなくても文字列のまま処理が流せるようです。例えば以下のように文字列を直接指定してもtrueが返ります。 SELECT JSON_ARRAY_CONTAINS('[1, 2, 3]', 2) AS result Pythonでの書き方 正直この辺はJSON変換が必要になる以外は配列関係に触れた以前の記事のままなのでリンクだけ貼って割愛します。inキーワードで対応ができます。 JSONの配列の長さを取得する: JSON_ARRAY_LENGTH JSON_ARRAY_LENGTH関数ではJSONの配列の長さ(値の件数)を取得することができます。 以下のSQLでは3件の値の配列なので3が返っています。 SELECT JSON_ARRAY_LENGTH(CAST(ARRAY[1, 2, 3] AS JSON)) AS length こちらもJSON形式の文字列であればキャストやJSON_PARSE関数などを省いても動くようです。 SELECT JSON_ARRAY_LENGTH('[1, 2, 3]') AS length Pythonでの書き方 json.loads関数でJSONをリストに変換した後に、len関数でリストの値の件数が取れるのでそちらをapplyメソッドで反映すれば取れます。 import json from typing import Any, Dict, List import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_weapon_json_str/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, orient='records', compression='gzip') df['weapons'] = df['weapons'].apply(json.loads) df['length'] = df['weapons'].apply(len) print(df[['length', 'weapons']].head()) length weapons 0 61 [{'mst_id': 1700, 'attack': 300, 'level': 2}, ... 1 44 [{'mst_id': 1672, 'attack': 1709, 'level': 15}... 2 60 [{'mst_id': 655, 'attack': 1238, 'level': 12},... 3 26 [{'mst_id': 56, 'attack': 871, 'level': 6}, {'... 4 77 [{'mst_id': 1427, 'attack': 836, 'level': 9}, ... 特定位置のJSONの要素の数を取得する: JSON_SIZE JSON_SIZE関数は特定の位置のJSONの要素の数を取得します。JSON_ARRAY_LENGTH関数とは異なり第二引数に位置の指定が必要になります。第二引数の位置の指定にはJSON_EXTRACT関数の節で触れたようなJSONPathの記法の文字列が必要になります。 対象は配列もしくは辞書の位置でのみ正常な値が取れます。それ以外のスカラー値の位置では返却値は0となります。 以下のSQLでは2次元配列で配列の先頭の位置($.0)の配列の値([1, 2, 3])の件数を取得しています。 SELECT JSON_SIZE('[[1, 2, 3], [4]]', '$.0') AS length 結果は3となります。 参考文献・参考サイトまとめ The Applied SQL Data Analytics Workshop: Develop your practical skills and prepare to become a professional data analyst, 2nd Edition Data Types Map Functions and Operators 【Python】dictをfilterする カーディナリティ 【cardinality】 SQL で縦横変換まとめ(pivot と unpivot) PrestoのArray, Mapの使い方調査 [Amazon Athena]一見json配列に見えるvarcharのデータをパースして集計できる形式に変換する JSON Functions and Operators JSONPath - XPath for JSON
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GCP AI Platform NotebookからCSVをDLする際にデータが途切れることを回避する方法

問題 GCP AI Platform Notebookでpythonを用いて計算を行った後、CSVを出力したとします。これをそのままDLしたら、テーブルの途中までしか入っていませんでした。 解決方法 zipに変換してからDLすることで回避できました。 import zipfile with zipfile.ZipFile('./output/new_comp.zip', 'w', compression=zipfile.ZIP_DEFLATED) as new_zip: new_zip.write('../output/test.csv', arcname='test.csv')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データ収集作業を自動化しよう

はじめに 最近Lancersというアウトソージングサイトを見ていたら、データ入力や調査系の仕事が2円/件くらいでたくさんあることに気が付きました。しかも仕事依頼者は手作業で進めることを前提としており、応募している方々も皆そのように進めようとしていました。 これGoogle先生のAPI使えば無双できるんじゃね。ってのがこの記事を書いたモチベです。 Qiitaに集いしエンジニアの方々からすれば大したことないコードだと思いますが、このようなデータ収集等をいまだに手作業で進めている人類の方は多いと思いますので、そのような方でかつプログラミングに興味のある方を対象とした記事です。 デジタル庁も発足し、ITに疎いお父さんの口からもDXという言葉がもれる時代ですから、是非この機会に自動化をはじめてみませんか。 技術 Python Google Places API 作るもの 今回はLancersに落ちていた、「場所+施設」で検索をかけて、その施設や店舗の名前・HPのURL・電話番号をエクセルに入力する仕事を自動化します。PythonとGoogle APIで情報を集めてCSVに出力しましょう。CSVにすればGoogleスプレッドシートやエクセルにもインポートできるので便利です。 コード解説 Google API まずコードを書き始める前にGoogle Cloud Platformにプロジェクトを作ったりAPI認証用のキーを取得したりしないといけないので、「Google Mpas API はじめかた」や「Google Places API はじめかた」などと調べてAPI KEYの取得まで完了させてください。 また今回はPlaces APIの中でもPlaces Search APIとPlaces Detail APIを使います。Places Search APIはさらに検索の仕方で3種類に分かれていますが、Text Search APIを使います。 Text Search API:https://developers.google.com/maps/documentation/places/web-service/search-text Places Detail API:https://developers.google.com/maps/documentation/places/web-service/details Pythonライブラリ 主要な役割をするのはrequestsとcsvというライブラリです。csvは標準ライブラリですが、requestsは違うのでpip等でインストールしてください。 簡単に解説 Text Search APIでキーワードを検索し、結果をsearch_resultに格納。ちなみにレスポンスデータが長い場合は複数ページ(API?)に分割され、next_page_tokenというのも返されるのでそれをもとに次のAPIを再帰的に呼び出しています。(access_textsearch関数) Text Search APIで得たPlaceデータの配列でループをかけて、place_idを元にPlace Detail APIを呼び出す。websiteとformatted_phone_numberを各Placeデータに追加する。これらがない場合は空文字を入れる。(access_placedetail関数) 「場所 施設.csv」という名前のCSVファイルを作成し、search_resultでループをかけて、各行にPlaceデータを出力していく。 (write_csv関数) import requests import time import csv SEARCH_QUERY = '三宮 ジム' API_KEY = '自分のAPI KEYを入れてください' search_result = [] textsearch_url = 'https://maps.googleapis.com/maps/api/place/textsearch/json' textsearch_q = {'query': SEARCH_QUERY, 'language': 'ja', 'key': API_KEY} FIELDS = 'website,formatted_phone_number' plcadetail_url = 'https://maps.googleapis.com/maps/api/place/details/json' plcadetail_q = {'key': API_KEY, 'fields': FIELDS} def main(): access_textsearch(textsearch_url, textsearch_q) for p in search_result: time.sleep(2) website, phone_number = access_placedetail(plcadetail_url, plcadetail_q, p['place_id']) p['website'] = website p['phone_number'] = phone_number write_csv() def access_textsearch(url , q, nexttoken=0): if nexttoken: q['pagetoken'] = nexttoken s = requests.Session() r = s.get(url, params=q) json_o = r.json() status = check_status(json_o['status']) if status != 'OK': print(status) exit() for r in json_o['results']: search_result.append({'place_id': r['place_id'], 'name': r['name']}) if 'next_page_token' in json_o: time.sleep(2) access_textsearch(url, q, json_o['next_page_token']) def access_placedetail(url, q, place_id): q['place_id'] = place_id s = requests.Session() r = s.get(url, params=q) json_o = r.json() status = check_status(json_o['status']) if status != 'OK': print(status) exit() if 'website' in json_o['result']: website = json_o['result']['website'] else: website = '' if 'formatted_phone_number' in json_o['result']: phone_number = json_o['result']['formatted_phone_number'] else: phone_number = '' return website, phone_number def write_csv(): with open(f'data/{SEARCH_QUERY}.csv', 'w', newline='') as f: writer = csv.writer(f) writer.writerow(["名前", "URL", "電話番号"]) for p in search_result: writer.writerow([p['name'], p['website'], p['phone_number']]) def check_status(status): if status == 'ZERO_RESULTS': return '検索結果が0件です' elif status == 'INVALID_REQUEST': return '無効な(奇形な)リクエストです' elif status == 'OVER_QUERY_LIMIT': return 'リクエストの利用制限回数を超えました' elif status == 'REQUEST_DENIED': return 'リクエストが拒否されました(KEYやクエリに間違いがある)' elif status == 'UNKNOWN_ERROR': return '予期せぬエラーが発生しました' else: return 'OK' if __name__ == "__main__": main() 結果 ちなみにこんな感じのCSVが完成します。たったの1分足らずです。 csv 名前,URL,電話番号 東急スポーツオアシス三宮24Plus,https://www.sportsoasis.co.jp/sh13/?utm_source=google&utm_medium=mybusiness,078-333-8109 コナミスポーツクラブ 三宮,https://information.konamisportsclub.jp/ksc/004090/,078-291-6641 エニタイムフィットネス 三宮旭通店,http://www.anytimefitness.co.jp/sannomiyaa/,078-200-5701 ジムスタイルプラス三宮,http://www.gymstyle24.com/plus/,078-599-8981 ジョイフィット三宮,https://joyfit.jp/sannomiya/,078-381-8730 【神戸初!女性専用】24時間フィットネスジムVNSCOA( ヴィーナスコア),https://vnscoa.com/,078-331-9555 FEELCYCLE 三ノ宮,https://www.feelcycle.com/,0570-055-319 GRANDSTONE神戸三宮店,http://grandstone.jp/,078-232-7086 エクササイズコーチ三宮店,https://exercisecoach.co.jp/store/kinki/sannomiya/,078-381-9700 jump one Sannomiya トランポリン×暗闇フィットネス,https://www.jumpone.jp/,078-599-8062 24/7ワークアウト 三宮店,https://247-workout.jp/gym/h_sannomiya.html?acckey=gym0-googmybu-0000-2470-glpforms-00000000&utm_source=google&utm_medium=gmb&utm_campaign=h_sannomiya,0120-501-247 Flexible Style神戸三宮店 ダイエットと姿勢改善のための女性専用パーソナルトレーニングジム,http://www.flexible-style.com/,050-5328-3808 UNDEUX(アンドゥ)神戸 パーソナルトレーニングジム,https://www.diet-undeux.jp/?utm_source=google&utm_medium=maps&utm_campaign=map_kobe,078-855-3898 ゴールドジム 神戸元町,https://www.goldsgym.jp/shop/28110,078-334-3434 テトラフィット ホワイト 神戸三宮店,https://tetra.fit/studio/kobesannomiya,078-862-3857 女性専用フィットネスミットネス 神戸店,https://mittness.jp/shop-kobe/?utm_source=google&utm_medium=map&utm_campaign=kobe,078-391-0787 三宮 パーソナルジム|BEZELオアシス神戸三宮店,https://www.kaatsu-studio.net/studio/studio-kobe-sannomiya/,078-392-7370 フロージム 神戸三宮店|パーソナルトレーニング,https://flow-gym.net/,080-6172-9604 アライブジム,http://alive-gym.com/,090-9881-5548 身体工房LIBERTEX/リベルテクス 神戸三宮店,https://www.libertex.training/,078-599-6204 DoDoPep,https://dodopep-diet.com/,090-7095-4594 Re:ly Fitness(リリーフィットネス) 三宮店,https://relyfitness.com/,078-891-7715 Surf Fit Studio(サーフフィット)神戸三宮店,https://surffit.jp/studio/kobe-sannomiya/?utm_source=Google_mybusiness&utm_medium=link&utm_campaign=kobe,0570-055-302 Revive〈リバイブ〉パーソナルジム神戸三宮,https://revivegym.web.fc2.com/,090-5155-2234 Private gym CONCIERGE,,090-1593-1112 Trust(トラスト) | プライベートジム神戸三宮,https://trust-gym.com/,070-8334-5752 アガーラ 三宮メディカルフィットネス,https://www.aga-la.com/,078-251-4444 JOYFIT24新神戸,https://joyfit.jp/shinkobe/,078-862-1576 タンニングセンター,, Y-STYLE,http://yskobe.jp/,090-6988-8781 美脚専門プライベートジム IVY,https://www.ivy-gym.com/,078-855-8667 キックボクシングスタジオ リフィナス神戸三宮,http://refinas.jp/,06-6641-1588 フリーラウンド,https://www.freeround.jp/,078-335-5610 Re:ly Fitness(リリーフィットネス) 元町店,https://relyfitness.com/,078-325-1616 KARADA BESTA 神戸三宮店,https://besta.tokyo/, Mahalo Training Gym,,078-381-7924 BEFORT,https://befort.jp/,078-381-9373 BUKATSU,https://bukatsu-gym.com/,078-806-8693 fullness,http://waistshapeup.com/,080-4390-9334 FiTNESS UNIVERSITY 神戸元町,https://www.fituni.jp/,080-3276-2596 personal training gym ONE,https://personalgymone.com/,090-4284-8286 iPTOxGym,http://iptoxgym.com/,078-862-5772 シェイプスガール(ShapesGirl)神戸三宮店,https://www.shapesgirl.com/kobesannomiya.html,078-335-5577 リスタイリッシュ神戸店,https://restylish.net/,078-599-6575 TopWorks-Body神戸元町店,https://topworks-body.com/kobemotomachi/?utm_source=mybusiness&utm_medium=referral&utm_campaign=mybusiness_kobemotomachi,078-599-9720 富永パーク,, グラビティリサーチ ミント神戸,http://blog2.kojitusanso.jp/grmintkobe/,080-9706-3216 Lifelong(ライフロング),https://www.lifelong-conditioning.com/,080-9746-8156 オージースポーツ神戸福祉スポーツセンター,https://www.fukushi-sc.net/,078-271-5332 神戸三宮パーソナルトレーニング加圧筋調整,https://trainingconditioningkobe.wordpress.com/, プロジム 神戸校,, カーブス 神戸元町,https://www.curves.co.jp/gmb/?s_id=91024,078-325-5557 corpus(コアパス),,090-8501-0691 Joy Fit 24,http://www.joyfit.jp/, BoostGym,https://boostgym.business.site/?utm_source=gmb&utm_medium=referral,090-4293-1399 FITNESS GYM WEST,https://fitnessgym-west.com/,078-381-9336 Blue Ocean Gym 神戸三宮店,https://www.blueocean-gym.com/, RICHBODY,https://tatezaki-rb.com/,070-2288-4408 BRAVE FITNESS - ブレイブフィットネス,https://bravefitness.jp/,078-515-6185 UNIVERSAL JUMPING(ユニバーサルジャンピング),https://universal-jumping.com/,078-855-7598 自動化で生産性を高めていきましょ♪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ChromebookのランチャーからJupyter Notebookを起動できるようにする方法

はじめに  ChromebookでLinuxモードを有効にした場合、Linux内の /usr/share/applications/ にデスクトップエントリファイル(.desktop)を保存すれば、ホスト側(ChromeOS)のランチャーにLinuxアプリを表示できるように Garcon daemon が動作しています。  ところが、Anaconda3 の jupyter-notebook のデスクトップエントリ(notebook.desktop)を登録して、ランチャーから実行するとシェルフに表示されたアイコンが永遠とローディング状態となり一度本体をシャットダウンしないと2回目の起動ができないという問題が発生しました。 実行環境 機種 Chromebook 3100 2-in-1 CPU Intel(R) Celeron(R) N4020 CPU @ 1.1 GHz x 2 MemTotal 3,897,296 kB ChromeOS Google Chrome OS ver.93.0.4577.107(64 bit) LinuxOS Debian GNU/Linux 10.11 Anaconda3 ver. 2021.05-Linux-x86_64 対処方法  今回は xfce4-terminal を経由することで問題を回避しました。Anaconda3 を /opt 以下にインストールした場合の notebook.desktop の修正例を下に示します。これをデスクトップエントリとして登録すると、ランチャーから xfce4-terminal を立ち上げると同時に jupyter-notebook を起動します。Notebook を終了すれば xfce4-terminal も自動的に終了し、逆に xfce4-terminal から Notebook を終了することも可能です。どちらにしても正常に起動・終了するようになりました。  なお、xfce4-terminal のデスクトップエントリが存在すると正常に動作しないため、 /usr/local/share/applications/xfce4-terminal.desktop を削除するか末尾に hidden=true を追記して更新する必要があります。 /usr/share/applications/notebook.desktop [Desktop Entry] Version=1.0 Name=Jupyter Notebook Comment=The Jupyter HTML Notebook. - Exec=/opt/anaconda3/bin/jupyter-notebook + Exec=xfce4-terminal -e "/opt/anaconda3/bin/jupyter-notebook" Icon=/usr/share/icons/hicolor/256x256/apps/notebook.png Terminal=false Type=Application ちなみに、Linuxモードの標準ターミナルから新規ターミナルを立ち上げると同時に jupyter-notebook を起動するという方法でもローディングが永遠と続くため、標準ターミナルとは別の xfce4-terminal のようなGUIターミナルは必要と考えられます。 おまけ  Anaconda3のインストールから jupyter-notebook のデスクトップエントリの登録までをスクリプトにしてみたので参考にして下さい。 setupAnaconda.sh #更新・インストール sudo apt update -y sudo apt upgrade -y sudo apt install -y wget xfce4-terminal # #install Anaconda3 ANACONDA_URL="https://repo.anaconda.com/archive/Anaconda3-2021.05-Linux-x86_64.sh" wget ${ANACONDA_URL} -O Anaconda.sh sh Anaconda.sh -b -p /opt/anaconda3 rm Anaconda.sh # #PATHを通す echo ' # set PATH of Anaconda3 if [ -d "/opt/anaconda3/bin" ] ; then export "PATH=${PATH}:/opt/anaconda3/bin" fi' >> .profile # #JupyterNotebookのデスクトップエントリの登録 NOTEBOOK_ICONURL="https://icon-icons.com/downloadimage.php?id=161280&root=2667/PNG/256/&file=jupyter_app_icon_161280.png" sudo wget ${NOTEBOOK_ICONURL} -O /usr/share/icons/hicolor/256x256/apps/notebook.png echo "\ [Desktop Entry] Version=1.0 Name=Jupyter Notebook Comment=The Jupyter HTML Notebook. Exec=xfce4-terminal -e \"/opt/anaconda3/bin/jupyter-notebook\" Icon=/usr/share/icons/hicolor/256x256/apps/notebook.png Terminal=false Type=Application" | \ sudo tee /usr/share/applications/notebook.desktop # #xfce4-terminalのデスクトップエントリの無効化 echo "Hidden=true" | sudo tee -a /usr/share/applications/xfce4-terminal.desktop # #デスクトップエントリの更新 sudo update-desktop-database # #終了 echo " =-------------= | F I N I S H | =-------------=" rm setupAnaconda.sh
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

matplotlibのplot_surfaceで100万個ほどのデータなのにメモリエラーが出たので解決策

matplotlibで3Dのグラフを描いてみよう matplotlibで3次元のグラフを描くのは、とりあえず from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D などを使えば、出来るとされています 特にAxes3Dクラスのメソッドであるplot_surfaceを使えば簡単に描けるというので、 早速描いてみましょう [Pythonによる科学・技術計算] 3次元曲面の描画,サーフェス,ワイヤーフレーム,可視化,matplotlib を真似てみて from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt import numpy as np fig = plt.figure() #プロット領域の作成 ax = fig.gca(projection='3d') #プロット中の軸の取得。gca は"Get Current Axes" の略。 x = np.arange(-2, 2, 0.05) # x点として[-2, 2]まで0.05刻みでサンプル y = np.arange(-2, 2, 0.05) # y点として[-2, 2]まで0.05刻みでサンプル x, y = np.meshgrid(x, y) # 上述のサンプリング点(x,y)を使ったメッシュ生成 z = np.exp(-(x**2 - 0.4*(y**2))) #exp(-(x^2-0.4*y^2)) を計算してzz座標へ格納する。 ax.plot_surface(x, y, z, rstride=1, cstride=1, cmap='hsv', linewidth=0.3) # 曲面のプロット。rstrideとcstrideはステップサイズ,cmapは彩色,linewidthは曲面のメッシュの線の太さ,をそれぞれ表す。 plt.show() # 絵の出力。 (現時点でHaru_M D氏の許可を得ていないのですが、これでいいのでしょうか?) 他の人がどう思うかはともかく、個人的には馬の鞍型の綺麗な形ができました。 ではzの値が数式でなく数値で与えられていたらどうするか 私は ・xとyが1次元のリスト ・zが2次元のリスト、len(x)×len(y)個のデータおよびそういう構造 の3次元の点列を図にしてみようと考えました それで、 x=[-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375] y=[-3.6, -2.8, -2.0, -1.2, -0.4, 0.4, 1.2, 2.0, 2.8, 3.6] というx,y座標に対して、randomにz座標を割り振ってみたらどうなるかなと思い、 Haru_M D氏の例に倣って、x,yをmeshgridで処理して何か描けばいいのかな? と思いました from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt import numpy as np import random import time tx=[-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375] ty=[-3.6, -2.8, -2.0, -1.2, -0.4, 0.4, 1.2, 2.0, 2.8, 3.6] X = np.array(tx)#numpy型に変換します Y = np.array(ty) print(str(len(X)) + ','+str(len(Y)))#8×10の配列であると確認しております Z = [] tz=[] for i in range(10): Z.append(tz) for j in range(8): tz.append(random.uniform(0,3)) tz=[] #ここを内包表記でスマートに書くと私はミス連発になるので泥臭くやっています print(print([len(v) for v in Z])#これで配列が何×何なのか分かり易い、と聞いております print(Z) z_new= np.array(Z,dtype=np.float)#念のため浮動小数点にしておきます print(z_new) # 時間計測開始 time_sta = time.time()#何故か時間を測ります X, Y = np.meshgrid(X, Y)#Haru_M D氏に倣って、meshgridというもので処理します fig = plt.figure() ax = fig.gca(projection='3d') surf = ax.plot_surface(X, Y, z_new, rstride=1, cstride=1, cmap=cm.coolwarm) #plot_surface関数の使い方はこんなもので良いのですかねえ? plt.show() # 時間計測終了 time_end = time.time() # 経過時間(秒) tim = time_end- time_sta print(tim)#core i3 6100TのPCでも0.2秒で出力されます (zの値がランダムなので、もちろん毎回形は違います) これに関しては上手く行きました 大失敗した例 (・・・普通、3次元っていったら(x,y,z)で固めとくもんだな) (・・・それも1つ1つの座標で提示するもんだな) (・・・なら、xもyもzと同じlen(x)×len(y)、つまりこの場合8×10にしとかないといけないのかな?) と考えてしまいまして、 x=[[-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375]] y=[[-3.6,-3.6,-3.6,-3.6,-3.6,-3.6,-3.6,-3.6], [-2.8,-2.8,-2.8,-2.8,-2.8,-2.8,-2.8,-2.8], [-2.0,-2.0,-2.0,-2.0,-2.0,-2.0,-2.0,-2.0,], [-1.2,-1.2,-1.2,-1.2,-1.2,-1.2,-1.2,-1.2], [-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4], [0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4], [1.2,1.2,1.2,1.2,1.2,1.2,1.2,1.2], [2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0], [2.8,2.8,2.8,2.8,2.8,2.8,2.8,2.8], [3.6,3.6,3.6,3.6,3.6,3.6,3.6,3.6]] と、予め8×10の配列にして作ったらどうなるの?と思いました from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt import numpy as np from scipy.interpolate import griddata import random import time tX=np.arange(-5, 5, 1.25) X=[] for i in range(len(tX)): X.append(tX[i] + 0.625)#わざわざ0を中心にするためにずらしました tY=[-3.6,-2.8,-2.0,-1.2,-0.4,0.4,1.2,2.0,2.8,3.6] Y = np.array(tY) print(str(len(X)) + ','+str(len(Y)))#長さは見ておきましょう print(X) print(Y) Z=[] for i in range(80): Z.append(random.uniform(0,3)) print(Z) fP=[]#まさに馬鹿正直に[[x0,y0,z0],[x1,y1,z1]・・・]と詰め込むための配列です tf=[] for j in range(10): for i in range(8): tf.append(X[i]) tf.append(Y[j]) tf.append(Z[10*i+j]) fP.append(tf) tf=[] print(print([len(v) for v in fP])) print("使いたい配列の全体は") print(fP) print(fP[0][0]) print(fP[0][1]) print(fP[1][0])#苦労して配列詰め直してますが、うまく詰め込めたみたいですね x_new=[] y_new=[] z_new=[] for i in range(len(fP)):#ここでまた何故か1次元配列に並べ直しました x_new.append(fP[i][0]) y_new.append(fP[i][1]) z_new.append(fP[i][2]) print("x_newは") print(x_new) print(len(x_new)) print("y_newは") print(y_new) print(len(y_new)) print("z_newは") print(z_new) print(len(z_new)) x_np=np.array(x_new,dtype=np.float64) y_np=np.array(y_new,dtype=np.float64) z_np=np.array(z_new,dtype=np.float64) X_mesh, Y_mesh = np.meshgrid(x_np, y_np)#何かmeshgridがいいみたいなんでそうします print("meshgridを経たX_meshの形は") print(print([len(v) for v in X_mesh])) #meshgridを経たX_meshの形は #[80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80]とか書いてあります print("meshgridを経たY_meshの形は") print(print([len(v) for v in Y_mesh])) #meshgridを経たY_meshの形は #[80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80]とか書いてあります # 時間計測開始 time_sta = time.time() fig = plt.figure() ax = fig.gca(projection='3d') Z_mesh = griddata((x_np, y_np),z_np,(X_mesh,Y_mesh))#後述するリンク先の方により、griddataというのがいいみたいですね? print("griddataを経たZ_meshの形は") print(print([len(v) for v in Z_mesh])) #griddataを経たZ_meshの形は #[80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80]とか書いてあります surf = ax.plot_surface(X_mesh, Y_mesh, Z_mesh, rstride=1, cstride=1, cmap=cm.coolwarm) plt.show() # 時間計測終了 time_end = time.time() # 経過時間(秒) tim = time_end- time_sta print(str(tim)+"秒です") 何か、全然違う形になってしまいましたよ? 時間も0.8秒くらいと、4倍も掛かっています どこが間違っているのか そもそも X_mesh, Y_mesh = np.meshgrid(x_np, y_np) という関数で何をしているかというと、 [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375] という横の配列を [[-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375], [-4.375, -3.125, -1.875, -0.625, 0.625, 1.875, 3.125, 4.375]] と、段数をyの要素数だけ増やし [-3.6, -2.8, -2.0, -1.2, -0.4, 0.4, 1.2, 2.0, 2.8, 3.6] という縦の配列を [[-3.6,-3.6,-3.6,-3.6,-3.6,-3.6,-3.6,-3.6], [-2.8,-2.8,-2.8,-2.8,-2.8,-2.8,-2.8,-2.8], [-2.0,-2.0,-2.0,-2.0,-2.0,-2.0,-2.0,-2.0,], [-1.2,-1.2,-1.2,-1.2,-1.2,-1.2,-1.2,-1.2], [-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4], [0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4], [1.2,1.2,1.2,1.2,1.2,1.2,1.2,1.2], [2.0,2.0,2.0,2.0,2.0,2.0,2.0,2.0], [2.8,2.8,2.8,2.8,2.8,2.8,2.8,2.8,2.8], [3.6,3.6,3.6,3.6,3.6,3.6,3.6,3.6,3.6]] と、列方向にxの数だけ増やしている、そういう役割があるのです つまり、わざわざ[[x0,y0,z0],[x1,y1,z1],・・・]なんて形にしているのだったら、 その段階でxもyも、meshgridと似たようなことをやっただけ!という事になります そしてこの時に、meshgridされたx,yと[[x0,y0,z0],[x1,y1,z1],・・・]から取り出した [x0,x1・・・],[y0,y1・・・]で何が違うかというと、meshgridされたものはlen(x)行len(y)列にまとまっていますが、[x0,x1・・・]は1次元に並んでいるだけなのです。 私はそれをわざわざ、np.reshape()で纏めて再び素早く描画する、というプログラムも一応書いたのですが、一体何度手間なんだと自分でもあきれ返ったので、もうそれを載せる気はありません またzに対してgriddataという関数でまた改造していますが、これは 【Python】matplotlib の「TypeError: Input z must be a 2D array.」でお困りの方へ【3次元プロット】 という、@kzm4269氏の記事をそのまま猿真似したのが原因でした。 (現時点で@kzm4269氏の許可を得ていないのですが、これでいいのでしょうか?) meshgridとgriddataが何をしているかを、もっとしっかり調べる必要があったのです。 実際に私がやらかした大失態 ・4桁オーダー×4桁オーダーのx,yに対するz、で3次元図形を描こうとしましたが、 表題にある通り、本来は100万個ほどの座標での処理だったのが 100万×100万=1兆(実際に10兆)個での計算になり、メモリエラーの表示が出た ・試しに40×40とか60×60で試したら、cmap=cm.coolwarmを使っていたせいもあって描画に数十分掛かり、幾らなんでもこれはおかしいと考えた ・さらにそれはサーバーにアップロードする必要があるらしかったので、こんなものを動かせないだろうとサーバー担当者との話し合いまでする事になってしまった 大恥をかきました まとめ ・ax.plot_surfaceは3次元図形を描画するうえで便利な関数です ・でもその引数の形を間違えると酷い目に遭います ・xとyは1次元のものをmeshgridで並べた変数, zはlen(x) × len(y)の形の変数で素直に ax.plot_surface(X, Y, z_new) と入れれば、動きます ・meshgridでも、あるいは間違って使ってしまったgriddataでも その関数を使って、変数(特に配列変数)がどんな型、どんな成分数になったのかを調べながら使いましょう 他にこんな目に遭った方はいるのでしょうか 海外の掲示板を見ると、 Surface Plot in Python - MemoryError というところで、2次元で密度推定をしようとして失敗した!という方がおられるようですね 10000×10000でエラーになった、と書いておられます でも、私みたいに何も考えずにメモリオーバーになった人は、いないみたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ABC221 C - Swiss-System Tournament から学んだ

わからん。回答を見た。 これは思いつかない。勉強になった。 SwissSystemTournament.py N,M = map(int,input().split()) S = [input() for _ in range(2*N)] rank = [[0,Y] for Y in range(2*N)] #rank[i] = [X,Y] ->i 位なのは X 勝の 人Y #[順位,人] を 1 セルとした配列を作るメリット ###順位が入れ替わった後、配列内の順位を sort により整理できるため、 ###for 文を使って簡潔に順位順に処理が出来る #####※辞書で実現できないか検討したが、結局、ソートを使うので記述が複雑化し、訳が分からなくなる(今の自分では) # 勝ち:0 , 負け:1, 引き分け:-1 def judge(a,b): if a==b:return -1 if a=="G" and b=="P":return 1 if a=="C" and b=="G":return 1 if a=="P" and b=="C":return 1 return 0 for j in range(M): for i in range(N): player1 = rank[2*i][1] player2 = rank[2*i+1][1] result = judge(S[player1][j],S[player2][j]) #↓↓補足 #a が勝者(return 0)の場合、a の rank[2*i + 0] 勝数を -1. #b が勝者(return 1)の場合、b の rank[2*i + 1] 勝数を -1. #2人ずつ勝負して、小数をメモ if result != -1: rank[2*i+result][0] -=1 #sort して 勝数が小さい順に並べることで、勝者をグループ化できる rank.sort() for _,i in rank:print(i+1) 正直、思いつかないし、読んでスグにはピンとこなかった。 中々自分には難しかった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでヒストグラムをビンまとめ(rebin)する方法

背景 python で既に構築されているヒストグラムを後からビンまとめしたい場合に、2つのやり方を紹介する。例えば、root を用いて一次元ヒストグラムを生成していて、 uproot でヒストグラムを読んだ場合など、python でビンまとめ(俗称rebin)したい場合に便利かと思います。 googleユーザーで、コードを見ればわかる人は、google colab のサンプルページ を見てください。 具体的な方法 方法1(ビンまとめしたい数で割り切れる場合) 一次元のヒストグラムで、要素数がN個で、これをkビンにまとめたい場合、N/kが割り切れる場合に限定しても構わないことが多いはず。この場合は、numpy.splitで要素数を分割して、その要素毎に平均を計算すればよい。 rebin1.py import matplotlib.pyplot as plt import numpy as np # 正規分布の乱数を2種類生成する。 mean=30; std=6; n=5000 n1 = np.random.normal(mean,std,n) mean=50; std=6; n=10000 n2 = np.random.normal(mean,std,n) n12 = np.concatenate([n1, n2]) y, xbin = np.histogram(n12, range=(0,80), bins=100) x = 0.5*(xbin[1:] + xbin[:-1]) # ビンまとめする関数。rebin にはビンまとめするビン数を入れる。 def rebin(x, y, rebin=2, renorm=False): if not len(x) % rebin == 0: print("ERROR : len(x)/rebin was not an integer."); return -1, -1 ndiv = int(len(x)/rebin) xdiv = np.split(x, ndiv) ydiv = np.split(y, ndiv) xd = np.mean(xdiv, axis=1) # mean of bins yd = np.sum(ydiv, axis=1) # sum of entries if renorm: yd = yd/rebin return xd, yd xd, yd = rebin(x, y, rebin=4, renorm=True) fig, ax = plt.subplots(figsize=(10.0, 6.0)) plt.subplot(121) plt.plot(x,y+1,"ko-", label="no rebin") plt.plot(xd, yd+1, "ro-", label="rebin 4, renorm=True") print(xd.shape) plt.yscale("linear") plt.ylabel("counts/bin + 1") plt.xlabel("xvalue") plt.legend() # plt.subplot(122) plt.plot(x,y+1,"ko-", label="no rebin") plt.plot(xd, yd+1, "ro-", label="rebin 4, renorm=True") print(xd.shape) plt.yscale("log") plt.ylabel("counts/bin + 1") plt.xlabel("xvalue") plt.legend() plt.show() 2つのガウス分布を足し込みで、サンプルデータを生成しています。 np.splitで分割して、X軸方向はmeanで、縦軸方向はsumで計算 renormフラグのON/OFFで、rebin数で割るか、割らないかを調節できる。(全面積を保存したいか、縦軸をcounts/binにして、bin幅を広くしても、縦軸が同じ場所に来るように表示したいかどうか、という違いです。) (左)縦軸がリニア、(右)縦軸がログで、ログ表示するために縦軸方向に1を足して表示してます。 方法2(ビンまとめしたい数で割り切れない場合でも可) 2つ目の方法は、np.convolveで、全要素1のベクトルとの畳み込みを計算する方法である。 rebin2.py import matplotlib.pyplot as plt import numpy as np # 正規分布の乱数を2種類生成する。 mean=30; std=6; n=5000 n1 = np.random.normal(mean,std,n) mean=50; std=6; n=10000 n2 = np.random.normal(mean,std,n) n12 = np.concatenate([n1, n2]) y, xbin = np.histogram(n12, range=(0,80), bins=100) x = 0.5*(xbin[1:] + xbin[:-1]) # ビンまとめする関数 (np.convolve編) def rebinc(x, y, rebin=2, renorm=False, cut=True): if not len(x) % rebin == 0: print("Warning : len(x)/rebin was not an integer.") v = np.ones(rebin) if renorm: v = v/rebin xd = np.convolve(x,v, mode='valid') yd = np.convolve(y,v, mode='valid') if cut: xd = xd[::rebin]; yd = yd[::rebin]; return xd, yd xd1, yd1 = rebinc(x, y, rebin=4, renorm=True, cut=True) fig, ax = plt.subplots(figsize=(10.0, 6.0)) plt.subplot(121) plt.plot(x,y+1,"ko-", label="no rebin", alpha=0.5) plt.plot(xd1, yd1+1, "ro-", label="rebin 4, cut = True", alpha=0.8) print(xd1.shape) xd2, yd2 = rebinc(x, y, rebin=4, renorm=True, cut=False) print(xd2.shape) plt.plot(xd2, yd2+1, "b.-", label="rebin 4, cut = False",alpha=0.6) plt.yscale("linear") plt.ylabel("counts/bin + 1") plt.xlabel("xvalue") plt.legend() # plt.subplot(122) plt.plot(x,y+1,"ko-", label="no rebin", alpha=0.5) plt.plot(xd1, yd1+1, "ro-", label="rebin 4, cut = True", alpha=0.8) print(xd1.shape) xd2, yd2 = rebinc(x, y, rebin=4, renorm=True, cut=False) print(xd2.shape) plt.plot(xd2, yd2+1, "b.-", label="rebin 4, cut = False", alpha=0.6) plt.yscale("log") plt.ylabel("counts/bin + 1") plt.xlabel("xvalue") plt.legend() plt.show() サンプルデータはガウス分布を2つ足したもの np.convolveで、要素1のベクトルをx, y 軸それぞれで畳み込む。 renorm フラグで、rebin数で割るかどうかを変更できる。 cut フラグで、要素数をrebin数で間引くか、間引かない(running averegeに相当)か、変更できる。 どれも概形は変化せず、平滑化が行われていることがわかる。縦軸をログ表示して、イベント数が少ない部分で計算が問題ないことを示すために、縦軸に1を加えて表示している。 結論 pythonで、1次元ヒストグラムをrebinしたい場合は、要素数を割り切れる数でsplitして平均するか、np.convolveで畳み込みで平滑化するか、2つの方法があって、好きな方法で間違えないように使いましょう。特に、全面積が保存することが大事な場合は、rebin前後での全積分の値をチェックすると大間違いは避けられると思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む