京橋のバイオインフォマティシャンの日常

まずは、データ分析、コマンドラインのメモとして

R/Slack APIの諸設定、slackrの使い方、及びGoogle scholarで検索された新着論文情報を知らせるTips

RのSlack APIであるslackrパッケージの使い方について、いろいろとまとめてみた*1

APIの諸設定、基本的なslackrの使い方に加えて、新着論文情報をRからチャネルに送信するプログラムも実装してみた。

まずは、Salck API設定の手順からはじめよう。

Salck API設定の手順

1. Slackのワークスペースに、任意のチャネルを作る。(必要なら)
2. Slack アプリを新規作成する

Rからは以下の関数実行で、Slack APIのサイトに飛べる。

browseURL("https://api.slack.com/apps/new")

f:id:skume:20210322221559p:plain:w400

ここで、任意のApp Nameを記入して、Development Slack Workspaceを選択する*2

3. Incomming WebhooksをActiveにする

はじめに、Incomming Webhooks の設定に進む。

f:id:skume:20210322221719p:plain:w400

デフォルトでは、その設定はOFFになっている。

f:id:skume:20210322221748p:plain:w400

その設定を、ONにする。

f:id:skume:20210322221812p:plain:w400

次に、Add New Webhook to Workspaceをクリックする

f:id:skume:20210322221853p:plain:w400

次の画面で、対象となるチャネルを選択して、許可する。

そうすると、Webhook URLが作成される。

f:id:skume:20210322221914p:plain:w400

ただ、この後のBot Token Scopesを設定して、reinstallすると、新たなWebhook URLが作成されるっぽいので、ひとまずはスルーしておく。

4. Bot User OAuth Tokenの取得、OAuth & Permissionsの設定

次に、サイドバーで、OAuth & Permissionsをクリックする。

f:id:skume:20210322222246p:plain:w200

ここで、上部に表示される、Bot User OAuth Token は後ほど使用するので、コピーして、保存しておく。

f:id:skume:20210322222303p:plain:w400

続いて、Bot Token Scopesを設定する。

f:id:skume:20210322222321p:plain:w400

ここには、incoming-webhookのみが追加されているので、「Add an OAuth Scope」でScopeを追加する。

まずは、以下の項目あたりを追加して、様子をみるのが良い。

  • channels:read

  • chat:write

  • chat:write.customize

  • chat:write.public

  • users:read

  • files:read

  • files:write

  • groups:read

  • groups:write

  • im:read

  • im:write

  • channels:history

上記が選択できたら、reinstallして、次の画面で、対象となるチャネルを選択して、許可する。

f:id:skume:20210322222502p:plain:w400

そうしたら、Incomming Webhooksの設定に戻って、新しく生成されたWebhook URLを取得しておく。

f:id:skume:20210322222530p:plain:w400

Sample curl requestのコードの最後部分が最新のWebhook URLなので、それをコピーしておく。

5. Slack アプリをチャネルに招待する【これが重要!】

どうも、以下の設定は必須みたい。

Known Issues

Depending on your scopes, slackr could quietly fail (i.e. not throw an error, but also not post anything to your channel). If this happens, try explicitly adding the app you’re trying to have slackr post as to the channel you want in your Slack workspace with /invite @your_app_name or make sure you have chat:write.public enabled.

日本語への機械翻訳: 既知の問題

スコープによっては、slackrが静かに失敗することがあります(エラーをスローせず、チャンネルに何もポストしないなど)。この問題が発生した場合は、Slackrに投稿させようとしているアプリをSlackのワークスペースに/invite @your_app_nameで明示的にチャンネルに追加するか、chat:write.publicを有効にしてください。

これは、chat:write.publicを有効にするだけでは解決しない。

やることは、対象チャネルで、「/invite @App Name」を入力して、作成したSlack アプリをチャネルに加える。

以下は、「@r-slack」を追加したときの例である。

f:id:skume:20210322222709p:plain:w400

送信すれば、「@r-slack」がチャネルに招待される。

6. RでのSlack APIの設定

ようやく、slackr側の設定を行う。

Rを起動して、まずは関連パッケージをインストール・ロードする。

#パッケージのインストール
library(devtools)
devtools::install_github("hrbrmstr/slackr", force = TRUE)
library(slackr)
7. slackr config ファイルの設定

ここでは、Rからホームディレクトリに、~/.slackrを作成する。

上記で、コピー・保存しておいたOAuth TokenやWebhook URLを使用する。

.slackrの基本フォーマットは以下の通りである。

#基本的なフォーマット
bot_user_oauth_token: Bot User OAuth Token
channel: #チャネル名
username: 上記のApp Name
incoming_webhook_url: https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX

以下、R上から~/.slackrの作成を行う。tokenやチャネル名は、適時変更のこと。

また、~/.slackrを設定さえすれば、次回使用時からはslackr_setup()だけで完結する。

#Token & チャネル名 & Username の設定
OAuth_token <- "xoxb-XXXXXXX-XXXXXXX-XXXXXXX"
WebURL <- "https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX"
Channel <- "チャネル名"
#チャネル名は、"Channel ID"でも可 
Username <- "slack-r"
#Usernameは任意でOK

a <- paste0("
bot_user_oauth_token: ", OAuth_token, "
channel: ", Channel, "
username: ", Username, "
incoming_webhook_url: ", WebURL)

#作成
system(paste0("echo \"", a, "\" > ~/.slackr"))

#確認
system("cat ~/.slackr")

#削除 (必要なら)
#system("rm -rf ~/.slackr")

#slackr_setupの実行
slackr_setup()
#[1] "Successfully connected to Slack"

あと、オプション的な内容である。

#authentication & identity のチェック
auth_test()

#Slack usersの表示
slackr_users()

#Slack channelsの表示
slackr_channels()

#設定状況の確認
Sys.getenv("SLACK_CHANNEL")
Sys.getenv("SLACK_BOT_USER_OAUTH_TOKEN")
8. 簡単な投稿実行

次に、実際に、Slackrパッケージを使って見ていこう。

#メッセージ送信
slackr_msg("Test message")

#メッセージをR表現の結果 「```形式」 として送信
slackr_bot('Test message')

上記の出力結果

f:id:skume:20210322224230p:plain:w500

#Rスクリプトの実行結果の送信
slackr(summary(UKgas))

#Plot結果を送信する
plot(UKgas)
slackr_dev(channels="#test")
#OR
ggslackr(plot(UKgas))

#画像のアップロード: 任意の画像で試してください。
slackr_upload("./01.png")

#チャネルを指定して送信する場合
library(maps)
map('county')
slackr_dev(channels = "#test")

#その他
#チャネルからメッセージを削除する
#slackr_delete(count=1)

上記の出力結果(一部)

f:id:skume:20210322224255p:plain:w500

R/Slackrで、Google scholarで検索された新着論文情報を知らせる

応用例として、Google scholarで検索された論文情報を新着順に10件取得して、Slackのチャネルに投げるということをやってみる。

Rを用いたWebスクレイピングの詳細については、以下の記事を参考のこと。

skume.net

また、googleScholarSearchNewest関数については、下記の補足資料を参照のこと。

#パッケージとかの準備
if(!require("rvest")){install.packages("rvest")}; library(rvest)
if(!require("xml2")){install.packages("xml2")}; library(xml2)
if(!require("magrittr")){install.packages("magrittr")}; library(magrittr)
source("https://gist.githubusercontent.com/kumeS/ef4d750a191b847174b3b93a4b8391c9/raw/9281e95919903fa66f2c43b71b9ae2340404d8a8/googleSearch_function.R")

#検索語の準備 ex. electron microscopy
Query <- c("electron microscopy")

#Google Scholar 検索実行
d <- googleScholarSearchNewest(Query=Query)

#結果ベクトルの取得
res <- d$`electron microscopy`

res
#[1] "https://www.nature.com/articles/s41467-021-21709-z"                          
# [2] "https://pubs.acs.org/doi/abs/10.1021/acsmacrolett.1c00032"                   
# [3] "https://academic.oup.com/cdn/advance-article/doi/10.1093/cdn/nzab025/6174670"
# [4] "https://pubs.acs.org/doi/full/10.1021/acsnano.0c10065"                       
# [5] "https://pubs.acs.org/doi/abs/10.1021/acsnano.0c10065"                        
# [6] "https://www.sciencedirect.com/science/article/abs/pii/S2213138821001284"     
# [7] "https://link.springer.com/article/10.1140/epjp/s13360-021-01291-5"           
# [8] "https://pubs.acs.org/doi/abs/10.1021/acs.energyfuels.1c00167"                
# [9] "https://journals.sagepub.com/doi/abs/10.1177/0095244321996396"               
#[10] "https://www.sciencedirect.com/science/article/pii/S0269749121005315"  

#Slackへの出力
for(n in 1:length(res)){
slackr_msg(paste0("Search results No. ", n))
slackr_msg(res[n])
}

Slack上に、こんな感じで出力される

f:id:skume:20210322224630p:plain:w500

まとめ

ファイルのアップロードとか、スクリプトの情報共有とかは楽なのかもしれない。

今のところ、メッセージ送信とかレスポンスとかを、Rからすべてやるのはちょっとやり過ぎかもという所感である。

補足資料

googleScholarSearchNewest関数について

gist.github.com

参考資料

qiita.com

github.com

*1:Web上の日本語資料の2021版アップデート

*2:Slackにログインしておくとよいかも

【R・ビッグデータ解析の処方箋】Rで、10万ノードを超える大きなネットワーク図を描画するTips 〜 igraph::plot.igraphは使い物にならない件 〜

Rでのネットワーク図の作成では、igraph packageがよく使われる。

ただ、igraphによるネットワーク図の描写は、1万ノードを超えたあたりから、結構な時間がかかる。

そのため、10万ノードを超えるような、大規模なネットワーク図の描画には、ちょっとしたコツがある。

今回、そんな大規模なネットワークの作成方法を取り上げ、実際の実行時間を検証してみた。

結論的には、igraph::plot.igraphを使うよりも、graphics::plot.defaultで描画する方が、10倍以上早くなる。

graphics::plot.defaultによるネットワーク図の作成例

まずは、Barabasi-Albertモデルで、サンプルのグラフ構造を作成する。

#パッケージ準備
library(igraph)

#サンプル・グラフの作成
#sample_pa: Barabasi-Albertモデルによるスケールフリー・グラフの生成関数
#BAモデルは、グラフを構築するためのシンプルな確率的アルゴリズムである。
pa <- igraph::sample_pa(n=10, power=1, m=1, directed=F)

pa
#IGRAPH fe31e44 U--- 10 9 -- Barabasi graph
#+ attr: name (g/c), power (g/n), m (g/n), zero.appeal (g/n),
#| algorithm (g/c)
#+ edges from fe31e44:
#[1] 1-- 2 2-- 3 2-- 4 2-- 5 1-- 6 3-- 7 1-- 8 5-- 9 7--10

#graphics::plot.defaultによる作図
system.time(graphics::plot.default(layout_with_fr(pa), pch=20, cex=2.5,
                       axes = F,  type = "p", xlab = NA, ylab = NA))
#quartz.save(file = paste0("./Graph_net_00.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321181207p:plain:w400

うーん、この図だと、ネットワークのエッジがない。

そこで、線分を追加するsegments関数で、エッジの描画を行う。

関数として組むと、こんな感じである。

gist.github.com

このNetwork_plot関数で、pa を描画してみる。

#パッケージの準備 & 簡単な実行例
library(data.table)
library(magrittr)
source("https://gist.githubusercontent.com/kumeS/2c8204b1c8a78a16d00ec2eaed5a7ca5/raw/e67a5760d7fcfc3a9c8f62f729e407b2722f5bf2/Network_plot.R")

pa <- igraph::sample_pa(n=10, power=1, m=1, directed=F)
Network_plot(pa, Cex=2.5)
#quartz.save(file = paste0("./Graph_net_01.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321181341p:plain:w400

見慣れた、ネットワーク図が得られる。

グラフのレイアウトは、layout_with_fr *1 を使用している。

グラフ・ノード数による描画時間の比較

それでは、次に、グラフのノード数を増やしていって、igraph::plot.igraphとgraphics::plot.defaultを使う場合とでの実行時間を比較してみる。

検証したスクリプトは、以下の通りである。

#####################
#10 nodes
#####################
#plot.igraphでの実行
pa <- sample_pa(n=10, power=1, m=1, directed=F)
system.time(plot.igraph(pa, vertex.size=10, vertex.label=NA))
#   ユーザ   システム       経過  
#     0.014      0.002      0.019 
#quartz.save(file = paste0("./Graph_01.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=2.5))
#   ユーザ   システム       経過  
#     0.016      0.002      0.018 
#quartz.save(file = paste0("./Graph_02.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321181600p:plain:w400
10 nodes by plot.igraph

f:id:skume:20210321181646p:plain:w400
10 nodes by Network_plot

#####################
#100 nodes
#####################
#plot.igraphでの実行
pa <- sample_pa(n=100, power=1, m=1, directed=F)
system.time(plot.igraph(pa, vertex.size=4, vertex.label=NA))
#   ユーザ   システム       経過  
#     0.023      0.003      0.026
#quartz.save(file = paste0("./Graph_03.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=1.5))
#   ユーザ   システム       経過  
#     0.029      0.003      0.031
#quartz.save(file = paste0("./Graph_04.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321181834p:plain:w400
100 nodes by plot.igraph

f:id:skume:20210321181857p:plain:w400
100 nodes by Network_plot

#####################
#1000 nodes
#####################
pa <- sample_pa(n=1000, power=1, m=1, directed=F)
system.time(plot(pa, vertex.size=2, vertex.label=NA))
#   ユーザ   システム       経過  
#     1.033      0.017      1.053 
#quartz.save(file = paste0("./Graph_05.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=0.5))
#   ユーザ   システム       経過  
#     0.771      0.006      0.781 
#quartz.save(file = paste0("./Graph_06.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321181931p:plain:w400
1000 nodes by plot.igraph

f:id:skume:20210321181954p:plain:w400
1000 nodes by Network_plot

#####################
#10000 nodes: 1万ノード
#####################
pa <- sample_pa(n=10000, power=1, m=1, directed=F)
system.time(plot(pa, vertex.size=1, vertex.label=NA))
#   ユーザ   システム       経過  
#    11.014      0.035     11.061
#quartz.save(file = paste0("./Graph_07.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=0.2))
#   ユーザ   システム       経過  
#     0.873      0.005      0.881
#quartz.save(file = paste0("./Graph_08.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321182442p:plain:w400
10000 nodes by plot.igraph

f:id:skume:20210321182446p:plain:w400
10000 nodes by Network_plot

#####################
#100000 nodes: 10万ノード
#####################
pa <- sample_pa(n=100000, power=1, m=1, directed=F)
system.time(plot(pa, vertex.size=0.5, vertex.label=NA))
#   ユーザ   システム       経過  
#   140.286      0.545    141.231
#quartz.save(file = paste0("./Graph_09.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=0.03))
#   ユーザ   システム       経過  
#     9.381      0.021      9.423
#quartz.save(file = paste0("./Graph_10.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321182545p:plain:w400
100000 nodes by plot.igraph

f:id:skume:20210321182604p:plain:w400
100000 nodes by Network_plot

#####################
#200000 nodes: 20万ノード
#####################
pa <- sample_pa(n=200000, power=1, m=1, directed=F)
system.time(plot(pa, vertex.size=0.5, vertex.label=NA))
#   ユーザ   システム       経過  
#   278.272      0.539    279.129
#quartz.save(file = paste0("./Graph_11.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=0.01))
#   ユーザ   システム       経過  
#    20.613      0.037     20.700 
#quartz.save(file = paste0("./Graph_12.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321182634p:plain:w400
200000 nodes by plot.igraph

f:id:skume:20210321182653p:plain:w400
200000 nodes by Network_plot

実行時間の可視化

実行時間を表で比較すると、1000や10000あたりから、すでに差が出てくる。

ノード数 igraph::plot.igraphでの実行時間 Network_plotでの実行時間
10 0.019 0.018
100 0.026 0.031
1000 1.053 0.781
10000 11.061 0.881
100000 141.231 9.423
200000 279.129 20.700

図としてプロットすると、こんな感じ。

tm <- data.frame(a=c(10, 100, 1000, 10000, 100000, 200000),
                 b=c(0.019,0.026,1.053,11.061,141.231,279.129),
                 c=c(0.018,0.031,0.781,0.881,9.423,20.700))
par(family="HiraKakuProN-W3", mgp=c(2.5, 1, 0), mai=c(0.75, 0.75, 0.5, 0.25))
plot(tm[,1], tm[,2], type="n", log="x", xlab="nodes", ylab="Time")
points(tm[,1], tm[,2], type="b", pch=16)
points(tm[,1], tm[,3], type="b", pch=21)
legend("topleft", legend=c("igraph::plot.igraphでの実行時間","Network_plotでの実行時間"), pch=c(16, 21), cex=1)
#quartz.save(file = paste0("./Graph_13.png"), type = "png", dpi = 150); dev.off()

f:id:skume:20210321183127p:plain:w400

まとめ

描画速度が歴然と違う、、、igraphオブジェクトは1万ノードくらいを扱うのが良いところかな。

大規模なネットワークを扱いたい人には、graphics::plot.defaultが基本型だろう。

参考資料

stackoverflow.com

*1:Fruchterman and ReingoldによるForce-Directed Layoutアルゴリズムを用いて、平面上に頂点を配置するレイアウト。

【Rのジミ〜な小技シリーズ】変数で、データフレームに「任意の列名」を追加するTips

pasteなどで連結した文字列を、データフレームの列名にしたい。 そういうときがよくある。

食わず嫌い的に試して無かったけど、「えっ、これできるの?!」という感じである・・・。 文字列の変数で、データフレームの列名を追加できることがわかったので、そのメモである。 R歴 約10年、まだまだ奥が深い。

test <- data.frame(a=1:3, b=4:6, c="abc")
test
#  a b   c
#1 1 4 abc
#2 2 5 abc
#3 3 6 abc

#よくやる列追加のやり方
test$d <- 7:9
test
#  a b   c d
#1 1 4 abc 7
#2 2 5 abc 8
#3 3 6 abc 9

次に、文字列の変数で、データフレームの列名を追加してみる。

#任意の文字列を作成して、列名にする
col01 <- paste0("X", "01")
#[1] "X01"
  
test[,col01] <- NA
test
#  a b   c X01
#1 1 4 abc  NA
#2 2 5 abc  NA
#3 3 6 abc  NA

#複数の任意の文字列を作成して、列名にする
col02 <- paste0("X", c("02", "03", "04"))
col02
#[1] "X02" "X03" "X04"

test[,col02] <- NA
test
#  a b   c d X01 X02 X03 X04
#1 1 4 abc 7  NA  NA  NA  NA
#2 2 5 abc 8  NA  NA  NA  NA
#3 3 6 abc 9  NA  NA  NA  NA