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関数betweennesscentr_eigenpage_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人のユーザーを確認できます。

程度
top_n(cent, 10 ,degr)%>% arrange(desc(degr))%>% select(degr)
固有ベクトル
top_n(cent, 10 ,eig)%>% arrange(desc(eig))%>% select(eig)
PageRank
 top_n(cent, 10 ,prank)%>% arrange(desc(prank))%>% select(prank)
top_n(cent, 10 ,bet)%>% arrange(desc(bet))%>% select(bet)

結果:

程度固有ベクトル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番目のノードは中間スコアで強調表示されます。

上位10人のユーザーとそのネットワークが強調表示された色付きのPageRankプロットを示す画像。 3つの最大のユーザーは、PSG_inside、TrollFootball、およびESPNFCです。 ESPNFCはプロットの左側にあり、紫色になっています。PSG_insideはプロットの右側にあり、赤色になっています。 TrollFootballは、緑色、青色、オレンジ色のユーザーの間で、それらの上部と右側に配置されています。
強調表示された上位10人のPageRankユーザーとのメッシディスカッション

上位10人のユーザーとそのネットワークにラベルを付けて強調表示した、色付きの中間プロットを示す画像。前の画像よりもサイズが似ている上位10人のユーザーはすべて、紫色で表示されている画像の左下隅に配置されています。それらは緊密にグループ化されています。
メッシの議論とトップ10の中間ユーザーが強調表示

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")

コヒーレンス値が高いと、テキストがトピックに適切に分割されていることを示します。

さまざまなトピックのコヒーレンススコアを示すグラフ。コヒーレンススコアは、6〜7つのトピックで0.05をわずかに超える範囲で変化し、3〜12のトピックはすべて0.065未満のスコアを持ちます。スコアは、13のトピックで約0.105で突然ピークに達します。次に、17トピックで0.06を下回り、19トピックで0.09近くまで下がり、20トピックで0.07をわずかに上回ります。

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") 

Gephiを使用して生成された色付きのノードグラフ。PageRankの中心性によってESPNFCが最高ランクのユーザーであることを示しています。 ESPNFCは画像の下部近くにあり、その下に多くの紫色のノードがグループ化されています。
トピックごとに色分けされ、PageRankの中心性によってユーザーが強調されたメッシディスカッションの最大のコミュニティ

グラフで使用されている各色で強調表示されているユーザーの割合を示す画像。紫色の「t6」が最も使用されている色(グラフの全ユーザーの40.53%)であり、11.02の緑色の「t13」がそれに続きます。 %、および9.68%の青/シアン「t10」。この11のリストの最後から2番目の位置にある灰色の「NA」は2.25%を占めています。
グラフで使用されている各色のトピックラベルとユーザーの割合

重要なトピックを推測し、ソーシャルネットワークの中心性を適用する

このシリーズの最初の記事では、Twitterからデータを取得し、インタラクショングラフを作成し、Gephiを介してプロットし、コミュニティと重要なユーザーを検出する方法を学びました。 今回の記事では、影響力のあるユーザーを検出するための追加の基準の使用を示すことにより、この分析を拡張しました。 また、ユーザーが話していることを検出して推測し、それをネットワークにプロットする方法も示しました。

次の記事では、ユーザーがソーシャルメディアの惨劇(スパムボットとトロール)を検出する方法を示すことで、この分析をさらに深めていきます。

Toptal Engineeringブログでさらに読む:

  • Power BIおよびRを使用したソーシャルネットワーク分析:カスタムビジュアルガイド