RとGephiによるTwitterダイナミクスの理解:テキスト分析と中心性
公開: 2022-07-22この記事では、ソーシャルネットワーク分析シリーズの最初の記事で紹介した分析を拡張して深めます。 最初の記事で構築したものと同じTwitterデータセットとインタラクションネットワークを使用します。 ただし、今回のアイデアは、主要なアクターをより適切に推測し、ディスカッショントピックを特定し、これらのトピックがどのように広がるかを理解することです。
ソーシャルネットワークの中心性
目標を達成するには、まず中心性の概念を導入する必要があります。 ネットワーク科学では、中心性とはネットワークに強い影響を与えるノードを指します。 影響力はあいまいな概念です。 それは多くの方法で理解することができます。 エッジが多いノードは、エッジが少ないが「重要な」ノードよりも影響力がありますか? ソーシャルネットワークの重要なエッジを構成するものは何ですか?
これらのあいまいさに対処するために、ネットワーク科学者は中心性の多くの尺度を開発しました。 ここでは、他にも多くの方法がありますが、一般的に使用される4つの方法について説明します。
程度
最も一般的で直感的な尺度は、次数の中心性です。 次数の中心性の背後にある考え方は単純です。ノードの次数による影響を測定します。 グラフが指示されている場合は、バリアントを持つことができます。 その場合、インディグリーとアウトディグリーを測定できます。最初のスコアはハブスコア、2番目のスコアはオーソリティスコアと呼ばれます。
このシリーズの最初の記事では、無向アプローチを使用しました。 今回は、インディグリーアプローチに焦点を当てます。 これにより、単に頻繁にリツイートするユーザーよりも、他のユーザーによってリツイートされるユーザーを強調することで、より正確な分析が可能になります。
固有ベクトル
固有ベクトル測度は、次数の中心性に基づいて構築されます。 影響力のあるノードが特定のノードを指すほど、そのスコアは高くなります。 行と列がノードを表す隣接行列から始め、1または0を使用して、特定の行と列の対応するノードが接続されているかどうかを示します。 主な計算では、行列の固有ベクトルを推定します。 主要な固有ベクトルには、必要な中心性の尺度が含まれます。ここで、位置iはノードiの中心性スコアを保持します。
PageRank
PageRankは、Googleの中核となる固有ベクトルメジャーのバリエーションです。 Googleが使用する正確な方法は不明ですが、一般的な考え方は、各ノードが1のスコアで始まり、そのスコアを各エッジに均等に分配するというものです。 たとえば、ノードに3つのエッジが伸びている場合、ノードはスコアの3分の1を各エッジに「送信」します。 同時に、ノードはそれを指すエッジによってより重要になります。 これにより、 N個の未知数を持つN個の方程式の解けるシステムが得られます。
間
4番目の尺度であるbetweennessは、非常に異なるアプローチを使用します。 ここで、ノードが他のノード間の多くの短いパスに含まれている場合、そのノードは影響力があると言われます。 つまり、他の多くのノードと通信し、「異なる世界」を接続する役割を果たします。
たとえば、ソーシャルネットワーク分析では、これらの種類のノードは、他の人が新しい仕事を見つけたり、新しいつながりを作ったりするのを助けるタイプの人々として理解できます。これらは、これまで知られていなかったソーシャルサークルへの扉です。
どちらを使うべきですか?
適切な中心性の尺度は、分析の目的によって異なります。 どのユーザーが他のユーザーから量的に頻繁に選ばれているのか知りたいですか? 次数の中心性がおそらく最良の選択肢でしょう。 それとも、品質を考慮した中心性の尺度を好みますか? その場合、固有ベクトルまたはPageRankの方が良い結果が得られます。 どのユーザーが異なるコミュニティ間のブリッジとして最も効果的に機能するかを知りたい場合は、中間性が最善の選択肢です。
eigenvectorやPageRankなど、複数の同様のメジャーを使用する場合、それらすべてを推定して、同等のランキングが得られるかどうかを確認できます。 そうでない場合は、差異の分析を深めるか、スコアを組み合わせて新しいメジャーを生成できます。
別のアプローチでは、主成分分析を使用して、ネットワーク上のノードの実際の影響に関する詳細情報を提供するメジャーを推定します。
実践的な中心性の計算
RとRStudioを使用してこれらのメジャーを計算する方法を見てみましょう。 (Gephiを使用して実行することもできます。)
まず、この記事全体で使用するすべてのライブラリをロードする必要があります。
library("plyr") library(igraph) library(tidyverse) library(NLP) library("tm") library(RColorBrewer) library(wordcloud) library(topicmodels) library(SnowballC) library("textmineR")
次に、以前に使用したデータから分離されたノードを削除します。これらはこの分析には役立たないためです。 次に、 igraph
関数betweenness
、 centr_eigen
、 page_rank
、およびdegree
を使用して、中心性の尺度を推定します。 最後に、スコアをigraph
オブジェクトとデータフレームに保存して、どのユーザーが最も中心的であったかを確認します。
load("art1_tweets.RData") Isolated = which(degree(net)==0) net_clean = delete.vertices(net, Isolated) cent<-data.frame(bet=betweenness(net_clean),eig=centr_eigen(net_clean)$vector,prank=(page_rank(net_clean)$vector),degr=degree(net_clean, mode="in")) cent <- cbind(account = rownames(cent), cent)
これで、各メジャーで最も中心的な10人のユーザーを確認できます。
程度 |
|
固有ベクトル |
|
PageRank | |
間 |
|
結果:
程度 | 固有ベクトル | PageRank | 間 | ||||
---|---|---|---|---|---|---|---|
ESPNFC | 5892 | PSG_inside | 1 | ムンドダボラ | 0.037 | viewsdey | 77704 |
トロールサッカー | 5755 | CrewsMat19 | 0.51 | AleLiparoti | 0.026 | エドマンドオリス | 76425 |
PSG_inside | 5194 | eh01195991 | 0.4 | PSG_inside | 0.017 | ba ***** lla | 63799 |
CrewsMat19 | 4344 | mohammad135680 | 0.37 | RoyNemer | 0.016 | FranciscoGaius | 63081 |
brfootball | 4054 | ActuFoot_ | 0.34 | トロールサッカー | 0.013 | イエミハザン | 62534 |
PSG_espanol | 3616 | marttvall | 0.34 | ESPNFC | 0.01 | hashtag2weet | 61123 |
IbaiOut | 3258 | ESPNFC | 0.3 | PSG_espanol | 0.007 | Angela_FCB | 60991 |
ActuFoot_ | 3175 | brfootball | 0.25 | lnstantFoot | 0.007 | Zyyon_ | 57269 |
FootyHumour | 2976 | SaylorMoonArmy | 0.22 | IbaiOut | 0.006 | CrewsMat19 | 53758 |
ムンドダボラ | 2778 | JohnsvillPat | 0.2 | 2010MisterChip | 0.006 | MdeenOlawale | 49572 |
最初の3つのメジャーは、PSG_inside、ESPNFC、CrewsMat19、TrollFootballなどの多くのユーザーを共有していることがわかります。 彼らが議論に強い影響を与えたと推測できます。 中間性は、中心性を測定するための異なるアプローチを持っているため、他の手法との重複はあまり見られません。
注:この記事で言及されているTwitterアカウントによって表現されたビューは、Toptalまたは作成者のビューを反映していません。
次の画像では、2つのユーザーラベルオーバーレイを使用した元の色付きネットワークグラフを確認できます。 最初のノードはPageRankスコアで強調表示され、2番目のノードは中間スコアで強調表示されます。
Gephiを使用して、これらの画像を再現できます。 統計パネルの[ネットワーク直径]ボタンを使用して、中間スコアまたはPageRankスコアを推定できます。 次に、このシリーズの最初の記事で示したように、属性を使用してノード名を表示できます。
テキスト分析:RおよびLDA
また、ソーシャルネットワークのディスカッションを分析して、ユーザーが何について話しているかを特定することもできます。 これにアプローチする方法は複数あります。 教師なし機械学習手法である潜在的ディリクレ割り当て(LDA)を使用してトピックモデリングを行います。これにより、どの単語セットが一緒に出現する傾向があるかを推定できます。 次に、その一連の単語を通じて、議論されているトピックを推測できます。
最初のステップは、テキストをサニタイズすることです。 そのために、次の関数を定義します。
# This function normalizes text by removing Twitter-related terms and noisy characters sanitize_text <- function(text) { # Convert to ASCII to remove accented characters: text <- iconv(text, to = "ASCII", sub = " ") # Move to lower case and delete RT word (this is added by Twitter) text <- gsub("rt", " ", tolower(text)) # Delete links and user names: text <- gsub("@\\w+", " ", gsub("http.+ |http.+$", " ", text)) # Delete tabs and punctuation: text <- gsub("[ |\t]{2,}", " ", gsub("[[:punct:]]", " ", text)) text <- gsub("amp", " ", text) # Remove HTML special character # Delete leading and lagging blanks: text <- gsub("^ ", "", gsub(" $", "", text)) text <- gsub(" +", " ", text) # Delete extra spaces return(text) }
また、ストップワード、重複、および空のエントリを削除する必要があります。 次に、テキストをLDAで処理するドキュメント用語マトリックスに変換する必要があります。
このデータセットには、多くの言語(英語、スペイン語、フランス語など)で話すユーザーがいます。 LDAは、単一の言語に焦点を当てた場合に最適に機能します。 このシリーズの最初の記事で検出された最大のコミュニティのユーザーに適用します。このコミュニティは、主に英語を話すユーザーのアカウントで構成されています。
# Detect communities: my.com.fast <-cluster_louvain(as.undirected(simplify(net))) largestCommunities <- order(sizes(my.com.fast), decreasing=TRUE)[1:3] # Save the usernames of the biggest community: community1 <- names(which(membership(my.com.fast) == largestCommunities[1])) # Sanitize the text of the users of the biggest community: text <- unique(sanitize_text(tweets.df[which(tweets.df$screen_name %in% community1),]$text)) text = text[text!=''] # Delete empty entries stopwords_regex = paste(stopwords('es'), collapse = '\\b|\\b') stopwords_regex = paste0('\\b', stopwords_regex, '\\b') # Remove English stopwords: text = stringr::str_replace_all(text, stopwords_regex, '') # Create the document term matrix: dtm <- CreateDtm(text, doc_names = seq(1:length(text)), ngram_window = c(1, 2))
トピック数とコヒーレンススコア
LDAで定義する必要がある主なハイパーパラメータは、推定するトピックの数(k)です。 しかし、どうすれば事前にそれを知ることができますか? 一般的なアプローチの1つは、さまざまなk値でLDAモデルをトレーニングし、それぞれのコヒーレンスを測定することです。 私の経験では、この範囲外の値はチェックする価値がないため、3から20までのk値に対してこれを行います。
tf <- TermDocFreq(dtm = dtm) # Remove infrequent words: tf_trimmed = tf$term[ tf$term_freq > 1 & tf$doc_freq < nrow(dtm) / 2 ] # Create a folder to store trained models: model_dir <- paste0("models_", digest::digest(tf_trimmed, algo = "sha1")) if (!dir.exists(model_dir)) dir.create(model_dir) # Define a function to infer LDA topics: train_lda_model <- function(number_of_topics){ filename = file.path(model_dir, paste0(number_of_topics, "_topics.rda")) # Check if the model already exists: if (!file.exists(filename)) { # To get exactly the same output on each run, use a constant seed: set.seed(12345) lda_model = FitLdaModel(dtm = dtm, k = number_of_topics, iterations = 500) lda_model$k = number_of_topics lda_model$coherence = CalcProbCoherence(phi = lda_model$phi, dtm = dtm, M = 5) save(lda_model, file = filename) } else { load(filename) } lda_model } # The number of topics that we are going to infer in each LDA training run: topic_count = seq(3, 20, by = 1) # Train through the TmParallelApply function models = TmParallelApply(X = topic_count, FUN = train_lda_model, export = c("dtm", "model_dir"))
次に、それぞれのコヒーレンス値をグラフ化します。
coherence_by_topics_quantity = data.frame( topic_number = sapply(models, function(model_instance) nrow(model_instance$phi)), score_coherence = sapply(models, function(model_instance) mean(model_instance$coherence)), stringsAsFactors = FALSE) ggplot(coherence_by_topics_quantity, aes(x = topic_number, y = score_coherence)) + geom_point() + geom_line(group = 1) + ggtitle("Coherence by Topic") + theme_minimal() + scale_x_continuous(breaks = seq(1,20,1)) + ylab("Coherence Score") + xlab("Number of topics")
コヒーレンス値が高いと、テキストがトピックに適切に分割されていることを示します。
k = 13でピークコヒーレンススコアに到達するため、13のトピックでトレーニングされたLDAモデルを使用します。 GetTopTerms関数を使用すると、各トピックの10の主要な単語を確認し、それらを介してトピックのセマンティックを推定できます。
best_model <- models[which.max(coherence_by_topics_quantity$score_coherence)][[ 1 ]] # Most important terms by topic: best_model$top_terms <- GetTopTerms(phi = best_model$phi, M = 20) top10 <- as.data.frame(best_model$top_terms) top10
次の表に、検出された5つの最も重要なトピックと、それらを例示する10の主要な単語の詳細を示します。
t_1 | t_2 | t_3 | t_4 | t_5 | |
---|---|---|---|---|---|
1 | メッシ | メッシ | メッシ | メッシ | メッシ |
2 | ライオネル | インスタグラム | 同盟 | EST(東部基準時 | psg |
3 | ライオネル・メッシ | 役職 | 勝つ | il | レオ |
4 | psg | 100万 | 目標 | au | leo_messi |
5 | マドリッド | 好き | ch | 注ぐ | アホラ |
6 | 本物 | spo | イオン | pas | コンパ |
7 | バルセロナ | ヤギ | ch_ions | avec | va |
8 | パリ | psg | ucl | デュ | ser |
9 | レアル·マドリード | バー | 風船 | qui | ジュガドール |
10 | mbapp | より大きい | 世界 | je | mejor |
このコミュニティのほとんどのユーザーは英語を話しますが、フランス語とスペイン語を話す人はまだたくさんいます(表のt_4とt_5)。 最初のトピックはメッシの前のチーム(FCバルセロナ)に関連し、2番目のトピックはメッシのInstagramへの投稿に関するものであり、3番目のトピックはメッシの業績に焦点を当てていると推測できます。
トピックができたので、どれが最も議論されたかを予測できます。 そのために、最初にユーザーによるツイートを連結します(ここでも最大のコミュニティから)。
tweets.df.com1 = tweets.df[which(tweets.df$screen_name %in% community1),] users_text <- ddply(tweets.df.com1, ~screen_name, summarise, text = paste(text, collapse = " "))
次に、前と同じようにテキストをサニタイズし、DTMを作成します。 その後、LDAモデルとDTMを引数として使用してpredict
関数を呼び出します。 また、分析するテキストがたくさんあるため、計算時間を改善するためにメソッドをGibbsに設定しました。
users_text$text <- sanitize_text(users_text$text) # Get rid of duplicates stopwords_regex = paste(stopwords('en'), collapse = '\\b|\\b') stopwords_regex = paste0('\\b', stopwords_regex, '\\b') users_text$text = stringr::str_replace_all(users_text$text, stopwords_regex, '') dtm.users.com1 <- CreateDtm(users_text$text, doc_names = users_text$screen_name, ngram_window = c(1, 2)) com1.users.topics = predict(best_model, dtm.users.com1, method="gibbs", iterations=100)
これで、 com1.users.topics
データフレームで、各ユーザーが各トピックについてどれだけ話したかがわかります。
アカウント | t_1 | t_2 | t_3 | t_4 | t_5 | […] |
---|---|---|---|---|---|---|
___99日 | 0.02716049 | 0.86666666 | 0.00246913 | 0.00246913 | 0.00246913 | |
ボス__ | 0.05185185 | 0.84197530 | 0.00246913 | 0.00246913 | 0.00246913 | |
メンフィス | 0.00327868 | 0.00327868 | 0.03606557 | 0.00327868 | 0.00327868 | |
___Alex1 | 0.00952380 | 0.00952380 | 0.00952380 | 0.00952380 | 0.00952380 | |
[…] |
最後に、この情報を使用して、ノードグラフに新しい属性を作成し、どのトピックがどのユーザーによって最も話題にされたかを定義できます。 次に、Gephiで視覚化するための新しいGMLファイルを作成できます。
# Get the subgraph of the first community: net.com1 = induced_subgraph(net,community1) # Estimate the topic with the max score for each user: com1.users.maxtopic = cbind(users_text$screen_name, colnames(com1.users.topics)[apply(com1.users.topics, 1, which.max)]) # Order the users topic data frame by the users' order in the graph: com1.users.maxtopic = com1.users.maxtopic[match(V(net.com1)$name, com1.users.maxtopic[,1]),] # Create a new attr of the graph by the topic most discussed by each user: V(net.com1)$topic = com1.users.maxtopic[,2] # Create a new graph: write_graph(simplify(net.com1), "messi_graph_topics.gml", format = "gml")
重要なトピックを推測し、ソーシャルネットワークの中心性を適用する
このシリーズの最初の記事では、Twitterからデータを取得し、インタラクショングラフを作成し、Gephiを介してプロットし、コミュニティと重要なユーザーを検出する方法を学びました。 今回の記事では、影響力のあるユーザーを検出するための追加の基準の使用を示すことにより、この分析を拡張しました。 また、ユーザーが話していることを検出して推測し、それをネットワークにプロットする方法も示しました。
次の記事では、ユーザーがソーシャルメディアの惨劇(スパムボットとトロール)を検出する方法を示すことで、この分析をさらに深めていきます。
Toptal Engineeringブログでさらに読む:
- Power BIおよびRを使用したソーシャルネットワーク分析:カスタムビジュアルガイド